应用监控CAT之cat-home源码阅读(三)
上两章从点到点讲了,cat-client 到 cat-consumer 的请求处理过程,但是怎么样让我们监控给人看到呢?那么就需要一个展示的后台了,也就是本章要讲的 cat-home 模块 ! 带你一起走进cat-home。
作为观察监控的平台,为所需要的人提供着可视化的稳健服务!那是必须的!
作为web展现层,在java中,自然是以servlet为接收方法了。
以tomcat作为web容器,进行运行cat-home服务。
servlet 以处理 uri 为基础,因此,让我们先看一下都有些什么样的路由。也就是说总体服务能力就是这些。
@OutboundActionMeta(name = "home") @OutboundActionMeta(name = "app") @OutboundActionMeta(name = "cdn") @OutboundActionMeta(name = "top") @OutboundActionMeta(name = "web") @OutboundActionMeta(name = "home") @OutboundActionMeta(name = "alert") @OutboundActionMeta(name = "cache") @OutboundActionMeta(name = CrossAnalyzer.ID) @OutboundActionMeta(name = "e") @OutboundActionMeta(name = "model") @OutboundActionMeta(name = StateAnalyzer.ID) @OutboundActionMeta(name = MatrixAnalyzer.ID) @OutboundActionMeta(name = MetricAnalyzer.ID) @OutboundActionMeta(name = "system") @OutboundActionMeta(name = "m") @OutboundActionMeta(name = "monitor") @OutboundActionMeta(name = "network") @OutboundActionMeta(name = "p") @OutboundActionMeta(name = "storage") @OutboundActionMeta(name = "activity") @OutboundActionMeta(name = "database") @OutboundActionMeta(name = "overload") @OutboundActionMeta(name = "dashboard") @OutboundActionMeta(name = "h") @OutboundActionMeta(name = "alteration") @OutboundActionMeta(name = DependencyAnalyzer.ID) @OutboundActionMeta(name = "statistics") @OutboundActionMeta(name = "t") @OutboundActionMeta(name = "login") @OutboundActionMeta(name = "config") @OutboundActionMeta(name = "plugin") @OutboundActionMeta(name = "router")
目录结构为 xxx/Handler.java,也算是比较难以理解的结构了。不过学习还是可以的!!
既然是web服务,第一个自然要看一下 web.xml 了:
<?xml version="1.0" encoding="UTF-8"?> <web-app xmlns="http://java.sun.com/xml/ns/javaee" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd" version="2.5"> <!-- 初始化一些cat需要的参数 --> <filter> <filter-name>cat-filter</filter-name> <filter-class>com.dianping.cat.servlet.CatFilter</filter-class> </filter> <!-- 设置响应用的cookie信息 --> <filter> <filter-name>domain-filter</filter-name> <filter-class>com.dianping.cat.report.view.DomainFilter</filter-class> </filter> <!-- 以 cat-servlet 作为第一个启动的servlet --> <servlet> <servlet-name>cat-servlet</servlet-name> <servlet-class>com.dianping.cat.servlet.CatServlet</servlet-class> <load-on-startup>1</load-on-startup> </servlet> <!-- 以 mvc-servlet 作为第二个启动的servlet --> <servlet> <servlet-name>mvc-servlet</servlet-name> <servlet-class>org.unidal.web.MVC</servlet-class> <init-param> <param-name>cat-client-xml</param-name> <param-value>client.xml</param-value> </init-param> <init-param> <param-name>init-modules</param-name> <param-value>false</param-value> </init-param> <load-on-startup>2</load-on-startup> </servlet> <!-- 定义过滤器的使用场景: REQUEST --> <filter-mapping> <filter-name>cat-filter</filter-name> <url-pattern>/r/*</url-pattern> <dispatcher>REQUEST</dispatcher> </filter-mapping> <filter-mapping> <filter-name>domain-filter</filter-name> <url-pattern>/r/*</url-pattern> <dispatcher>REQUEST</dispatcher> </filter-mapping> <filter-mapping> <filter-name>cat-filter</filter-name> <url-pattern>/s/*</url-pattern> <dispatcher>REQUEST</dispatcher> </filter-mapping> <filter-mapping> <filter-name>cat-filter</filter-name> <url-pattern>/jsp/*</url-pattern> <dispatcher>FORWARD</dispatcher> </filter-mapping> <!-- servlet 请求映射,主要转给 mvc-servlet,如果匹配不到再交给 --> <servlet-mapping> <servlet-name>mvc-servlet</servlet-name> <url-pattern>/r/*</url-pattern> </servlet-mapping> <servlet-mapping> <servlet-name>mvc-servlet</servlet-name> <url-pattern>/s/*</url-pattern> </servlet-mapping> <jsp-config> <taglib> <taglib-uri>/WEB-INF/app.tld</taglib-uri> <taglib-location>/WEB-INF/app.tld</taglib-location> </taglib> </jsp-config> </web-app>
// 其中 Cat-Servlet 主要用于初始化cat相关的程序,不作具体的请求接收功能
// 主要为调用如下 initComponents() 方法 @Override protected void initComponents(ServletConfig servletConfig) throws ServletException { try { ModuleContext ctx = new DefaultModuleContext(getContainer()); ModuleInitializer initializer = ctx.lookup(ModuleInitializer.class); File clientXmlFile = getConfigFile(servletConfig, "cat-client-xml", "client.xml"); File serverXmlFile = getConfigFile(servletConfig, "cat-server-xml", "server.xml"); ctx.setAttribute("cat-client-config-file", clientXmlFile); ctx.setAttribute("cat-server-config-file", serverXmlFile); initializer.execute(ctx); } catch (Exception e) { m_exception = e; System.err.println(e); throw new ServletException(e); } } // DefaultModuleInitializer.execute() 运行, public class DefaultModuleInitializer implements ModuleInitializer { @Inject private ModuleManager m_manager; @InjectAttribute private boolean m_verbose; private int m_index = 1; // 调入,获取topModules,进行加载 @Override public void execute(ModuleContext ctx) { Module[] modules = m_manager.getTopLevelModules(); execute(ctx, modules); } @Override public void execute(ModuleContext ctx, Module... modules) { Set<Module> all = new LinkedHashSet<Module>(); info(ctx, "Initializing top level modules:"); for (Module module : modules) { info(ctx, " " + module.getClass().getName()); } try { // 先调用 setup() 方法 expandAll(ctx, modules, all); for (Module module : all) { if (!module.isInitialized()) { // 初始化具体的类的初始化方法 executeModule(ctx, module, m_index++); } } } catch (Exception e) { throw new RuntimeException("Error when initializing modules! Exception: " + e, e); } } private synchronized void executeModule(ModuleContext ctx, Module module, int index) throws Exception { long start = System.currentTimeMillis(); // set flat to avoid re-entrance module.setInitialized(true); info(ctx, index + " ------ " + module.getClass().getName()); // execute itself after its dependencies module.initialize(ctx); long end = System.currentTimeMillis(); info(ctx, index + " ------ " + module.getClass().getName() + " DONE in " + (end - start) + " ms."); } private void expandAll(ModuleContext ctx, Module[] modules, Set<Module> all) throws Exception { if (modules != null) { for (Module module : modules) { expandAll(ctx, module.getDependencies(ctx), all); if (!all.contains(module)) { if (module instanceof AbstractModule) { ((AbstractModule) module).setup(ctx); } all.add(module); } } } } }
// 主要接收页面请求的mvc-servlet,
// 初始化mvc @Override protected void initComponents(ServletConfig config) throws Exception { String contextPath = config.getServletContext().getContextPath(); String path = contextPath == null || contextPath.length() == 0 ? "/" : contextPath; getLogger().info("MVC is starting at " + path); // 初始化cat initializeCat(config); // 初始化模块 initializeModules(config); // 获取所有role为mvc的handler,如: r:t, r:home, r:model m_handler = lookup(RequestLifecycle.class, "mvc"); m_handler.setServletContext(config.getServletContext()); config.getServletContext().setAttribute(ID, this); getLogger().info("MVC started at " + path); } // MVC, 接收请求,交由handler处理 @Override protected void service(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { if (request.getCharacterEncoding() == null) { request.setCharacterEncoding("UTF-8"); } response.setContentType("text/html;charset=UTF-8"); try { // 转到handler处理 m_handler.handle(request, response); } catch (Throwable t) { String message = "Error occured when handling uri: " + request.getRequestURI(); getLogger().error(message, t); if (!response.isCommitted()) { response.sendError(500, message); } } } // DefaultRequestLifecycle.handle(), 接管请求第一棒 public void handle(final HttpServletRequest request, final HttpServletResponse response) throws IOException { RequestContext context = m_builder.build(request); try { handleRequest(request, response, context); } finally { m_builder.reset(context); } } // DefaultRequestContextBuilder.build(), 构建请求数据, 分解 module,action , 找到对应的处理handler @Override public RequestContext build(HttpServletRequest request) { ParameterProvider provider = buildParameterProvider(request); String requestModuleName = provider.getModuleName(); ActionResolver actionResolver = (ActionResolver) m_modelManager.getActionResolver(requestModuleName); if (actionResolver == null) { return null; } UrlMapping urlMapping = actionResolver.parseUrl(provider); String action = urlMapping.getAction(); // 获取 action InboundActionModel inboundAction = m_modelManager.getInboundAction(requestModuleName, action); if (inboundAction == null) { return null; } RequestContext context = new RequestContext(); // 获取 module ModuleModel module = m_modelManager.getModule(requestModuleName, action); urlMapping.setModule(module.getModuleName()); // 设置配置参数及请求参数到上下文 context.setActionResolver(actionResolver); context.setParameterProvider(provider); context.setUrlMapping(urlMapping); context.setModule(module); context.setInboundAction(inboundAction); context.setTransition(module.findTransition(inboundAction.getTransitionName())); context.setError(module.findError(inboundAction.getErrorActionName())); return context; } // 处理请求,先处理入站,再处理出站 private void handleRequest(final HttpServletRequest request, final HttpServletResponse response, RequestContext requestContext) throws IOException { if (requestContext == null) { showPageNotFound(request, response); return; } ModuleModel module = requestContext.getModule(); InboundActionModel inboundAction = requestContext.getInboundAction(); ActionContext<?> actionContext = createActionContext(request, response, requestContext, inboundAction); Transaction t = Cat.getManager().getPeekTransaction(); if (t == null) { // in case of no CatFilter is configured t = NullMessage.TRANSACTION; } request.setAttribute(CatConstants.CAT_PAGE_URI, actionContext.getRequestContext().getActionUri(inboundAction.getActionName())); try { InboundActionHandler handler = m_actionHandlerManager.getInboundActionHandler(module, inboundAction); // 调用前置方法,为类似于拦截器一类的方法生效 handler.preparePayload(actionContext); if (!handlePreActions(request, response, module, requestContext, inboundAction, actionContext)) { return; } // 正式开始处理进入请求 handleInboundAction(module, actionContext); t.addData("module", module.getModuleName()); t.addData("in", actionContext.getInboundAction()); if (actionContext.isProcessStopped()) { t.addData("processStopped=true"); return; } handleTransition(module, actionContext); t.addData("out", actionContext.getOutboundAction()); // 开始处理出站操作 handleOutboundAction(module, actionContext); } catch (Throwable e) { handleException(request, e, actionContext); } } // 正式处理入站请求 private void handleInboundAction(ModuleModel module, ActionContext<?> actionContext) throws ActionException { InboundActionModel inboundAction = actionContext.getRequestContext().getInboundAction(); InboundActionHandler inboundActionHandler = m_actionHandlerManager.getInboundActionHandler(module, inboundAction); // 调用获取到的handler的handle方法完成 inboundActionHandler.handle(actionContext); } // DefaultActionHandlerManager.getInboundActionHandler(), 获取入站处理handler, 以 r:home 样例为格式获取 public InboundActionHandler getInboundActionHandler(ModuleModel module, InboundActionModel inboundAction) { String key = module.getModuleName() + ":" + inboundAction.getActionName(); InboundActionHandler actionHandler = m_inboundActionHandlers.get(key); // 双重锁的应用 if (actionHandler == null) { synchronized (m_inboundActionHandlers) { actionHandler = m_inboundActionHandlers.get(key); if (actionHandler == null) { actionHandler = lookup(InboundActionHandler.class); actionHandler.initialize(inboundAction); m_inboundActionHandlers.put(key, actionHandler); } } } return actionHandler; } // DefaultInboundActionHandler.handle(), 类似于 around 式的切面 public void handle(ActionContext ctx) throws ActionException { Transaction t = m_cat.newTransaction("MVC", "InboundPhase"); try { for (Validator<ActionContext<?>> validator : m_preValidators) { validator.validate(ctx); } if (ctx.getPayload() == null) { preparePayload(ctx); } for (Validator<ActionContext<?>> validator : m_validators) { validator.validate(ctx); } // 静态调用 ReflectUtils.invokeMethod(), 进行最后的也是最开始的业务逻辑处理 invokeMethod(m_inboundAction.getActionMethod(), m_inboundAction.getModuleInstance(), ctx); for (Validator<ActionContext<?>> validator : m_postValidators) { validator.validate(ctx); } t.setStatus(Transaction.SUCCESS); } catch (Exception e) { String actionName = m_inboundAction.getActionName(); m_cat.logError(e); t.setStatus(e); throw new ActionException("Error occured during handling inbound action(" + actionName + ")!", e); } finally { t.complete(); } }
// 以上,就是整个的入站调用过程
// 接下来,就是出站的调用过程了
// t.addData("out", actionContext.getOutboundAction()); handleOutboundAction(module, actionContext); 进入出站处理 private void handleOutboundAction(ModuleModel module, ActionContext<?> actionContext) throws ActionException { String outboundActionName = actionContext.getOutboundAction(); OutboundActionModel outboundAction = module.getOutbounds().get(outboundActionName); if (outboundAction == null) { throw new ActionException("No method annotated by @" + OutboundActionMeta.class.getSimpleName() + "(" + outboundActionName + ") found in " + module.getModuleClass()); } else { OutboundActionHandler outboundActionHandler = m_actionHandlerManager.getOutboundActionHandler(module, outboundAction); outboundActionHandler.handle(actionContext); } } // 获取出站路由信息 public OutboundActionHandler getOutboundActionHandler(ModuleModel module, OutboundActionModel outboundAction) { String key = module.getModuleName() + ":" + outboundAction.getActionName(); OutboundActionHandler actionHandler = m_outboundActionHandlers.get(key); if (actionHandler == null) { synchronized (m_outboundActionHandlers) { actionHandler = m_outboundActionHandlers.get(key); if (actionHandler == null) { actionHandler = lookup(OutboundActionHandler.class); actionHandler.initialize(outboundAction); m_outboundActionHandlers.put(key, actionHandler); } } } return actionHandler; } // DefaultOutboundActionHandler.handle(), 记录操作,调用action public void handle(ActionContext<?> context) throws ActionException { Transaction t = m_cat.newTransaction("MVC", "OutboundPhase"); try { invokeMethod(m_outboundAction.getMethod(), m_outboundAction.getModuleInstance(), context); t.setStatus(Transaction.SUCCESS); } catch (RuntimeException e) { String actionName = m_outboundAction.getActionName(); m_cat.logError(e); t.setStatus(e); throw new ActionException("Error occured during handling outbound action(" + actionName + ")", e); } finally { t.complete(); } }
// 以上,出入站的流程就讲完了
// 接下来,看一下具体的几个处理讲求的实例
// home/Handler (由 /cat 或 /cat/r 转入), 首页展现逻辑最简单,默认无数据操作 // 入站不处理 @Override @PayloadMeta(Payload.class) @InboundActionMeta(name = "home") public void handleInbound(Context ctx) throws ServletException, IOException { } // 出站显示首页页面 @Override @OutboundActionMeta(name = "home") public void handleOutbound(Context ctx) throws ServletException, IOException { Model model = new Model(ctx); Payload payload = ctx.getPayload(); switch (payload.getAction()) { case THREAD_DUMP: showThreadDump(model, payload); break; case VIEW: break; case CHECKPOINT: m_receiver.destory(); m_realtimeConsumer.doCheckpoint(); break; default: break; } model.setAction(payload.getAction()); model.setPage(ReportPage.HOME); model.setDomain(payload.getDomain()); model.setDate(payload.getDate()); m_jspViewer.view(ctx, model); } // transaction/Handler, transaction 页面呈现 // 入站不处理 @Override @PayloadMeta(Payload.class) @InboundActionMeta(name = "t") public void handleInbound(Context ctx) throws ServletException, IOException { // display only, no action here } // 出站进行数据复合 @Override @OutboundActionMeta(name = "t") public void handleOutbound(Context ctx) throws ServletException, IOException { Model model = new Model(ctx); Payload payload = ctx.getPayload(); normalize(model, payload); String domain = payload.getDomain(); Action action = payload.getAction(); String ipAddress = payload.getIpAddress(); String group = payload.getGroup(); String type = payload.getType(); String name = payload.getName(); String ip = payload.getIpAddress(); if (StringUtils.isEmpty(group)) { group = m_configManager.queryDefaultGroup(domain); payload.setGroup(group); } model.setGroupIps(m_configManager.queryIpByDomainAndGroup(domain, group)); model.setGroups(m_configManager.queryDomainGroup(payload.getDomain())); switch (action) { case HOURLY_REPORT: // 当前小时数据 TransactionReport report = getHourlyReport(payload); if (report != null) { report = m_mergeHelper.mergeAllMachines(report, ipAddress); model.setReport(report); buildTransactionMetaInfo(model, payload, report); } break; case HISTORY_REPORT: report = m_reportService.queryReport(domain, payload.getHistoryStartDate(), payload.getHistoryEndDate()); if (report != null) { model.setReport(report); buildTransactionMetaInfo(model, payload, report); } break; case HISTORY_GRAPH: if (Constants.ALL.equalsIgnoreCase(ipAddress)) { report = m_reportService.queryReport(domain, payload.getHistoryStartDate(), payload.getHistoryEndDate()); buildDistributionInfo(model, type, name, report); } m_historyGraph.buildTrendGraph(model, payload); break; case GRAPHS: report = getHourlyGraphReport(model, payload); if (Constants.ALL.equalsIgnoreCase(ipAddress)) { buildDistributionInfo(model, type, name, report); } if (name == null || name.length() == 0) { name = Constants.ALL; } report = m_mergeHelper.mergeAllNames(report, ip, name); model.setReport(report); buildTransactionNameGraph(model, report, type, name, ip); break; case HOURLY_GROUP_REPORT: report = getHourlyReport(payload); report = filterReportByGroup(report, domain, group); report = m_mergeHelper.mergeAllMachines(report, ipAddress); if (report != null) { model.setReport(report); buildTransactionMetaInfo(model, payload, report); } break; case HISTORY_GROUP_REPORT: report = m_reportService.queryReport(domain, payload.getHistoryStartDate(), payload.getHistoryEndDate()); report = filterReportByGroup(report, domain, group); report = m_mergeHelper.mergeAllMachines(report, ipAddress); if (report != null) { model.setReport(report); buildTransactionMetaInfo(model, payload, report); } break; case GROUP_GRAPHS: report = getHourlyGraphReport(model, payload); report = filterReportByGroup(report, domain, group); buildDistributionInfo(model, type, name, report); if (name == null || name.length() == 0) { name = Constants.ALL; } report = m_mergeHelper.mergeAllNames(report, ip, name); model.setReport(report); buildTransactionNameGraph(model, report, type, name, ip); break; case HISTORY_GROUP_GRAPH: report = m_reportService.queryReport(domain, payload.getHistoryStartDate(), payload.getHistoryEndDate()); report = filterReportByGroup(report, domain, group); buildDistributionInfo(model, type, name, report); List<String> ips = m_configManager.queryIpByDomainAndGroup(domain, group); m_historyGraph.buildGroupTrendGraph(model, payload, ips); break; } if (payload.isXml()) { m_xmlViewer.view(ctx, model); } else { m_jspViewer.view(ctx, model); } } // 获取小时报告实例 private TransactionReport getHourlyReport(Payload payload) { String domain = payload.getDomain(); String ipAddress = payload.getIpAddress(); ModelRequest request = new ModelRequest(domain, payload.getDate()).setProperty("type", payload.getType()) .setProperty("ip", ipAddress); if (m_service.isEligable(request)) { // invoke service ModelResponse<TransactionReport> response = m_service.invoke(request); TransactionReport report = response.getModel(); return report; } else { throw new RuntimeException("Internal error: no eligable transaction service registered for " + request + "!"); } } // BaseCompersiteModelService.invoke(), @Override public ModelResponse<T> invoke(final ModelRequest request) { int requireSize = 0; final List<ModelResponse<T>> responses = Collections.synchronizedList(new ArrayList<ModelResponse<T>>()); // 使用信号量进行加锁 final Semaphore semaphore = new Semaphore(0); final Transaction t = Cat.getProducer().newTransaction("ModelService", getClass().getSimpleName()); int count = 0; t.setStatus(Message.SUCCESS); t.addData("request", request); t.addData("thread", Thread.currentThread()); for (final ModelService<T> service : m_allServices) { if (!service.isEligable(request)) { continue; } // save current transaction so that child thread can access it if (service instanceof ModelServiceWithCalSupport) { ((ModelServiceWithCalSupport) service).setParentTransaction(t); } requireSize++; s_threadPool.submit(new Runnable() { @Override public void run() { try { ModelResponse<T> response = service.invoke(request); if (response.getException() != null) { logError(response.getException()); } if (response != null && response.getModel() != null) { responses.add(response); } } catch (Exception e) { logError(e); t.setStatus(e); } finally { semaphore.release(); } } }); count++; } try { semaphore.tryAcquire(count, 10000, TimeUnit.MILLISECONDS); // 10 seconds timeout } catch (InterruptedException e) { // ignore it t.setStatus(e); } finally { t.complete(); } String requireAll = request.getProperty("requireAll"); if (requireAll != null && responses.size() != requireSize) { String data = "require:" + requireSize + " actual:" + responses.size(); Cat.logEvent("FetchReportError:" + this.getClass().getSimpleName(), request.getDomain(), Event.SUCCESS, data); return null; } ModelResponse<T> aggregated = new ModelResponse<T>(); T report = merge(request, responses); aggregated.setModel(report); return aggregated; } // TransactionMergeHelper public class TransactionMergeHelper { public TransactionReport mergeAllMachines(TransactionReport report, String ipAddress) { if (StringUtils.isEmpty(ipAddress) || Constants.ALL.equalsIgnoreCase(ipAddress)) { AllMachineMerger all = new AllMachineMerger(); all.visitTransactionReport(report); report = all.getReport(); } return report; } public TransactionReport mergeAllNames(TransactionReport report, String allName) { if (StringUtils.isEmpty(allName) || Constants.ALL.equalsIgnoreCase(allName)) { AllNameMerger all = new AllNameMerger(); all.visitTransactionReport(report); report = all.getReport(); } return report; } public TransactionReport mergeAllNames(TransactionReport report, String ipAddress, String allName) { TransactionReport temp = mergeAllMachines(report, ipAddress); return mergeAllNames(temp, allName); } } // AllMerchineMerger.visitTransactionReport() @Override public void visitTransactionReport(TransactionReport transactionReport) { m_report = new TransactionReport(transactionReport.getDomain()); m_report.setStartTime(transactionReport.getStartTime()); m_report.setEndTime(transactionReport.getEndTime()); m_report.getDomainNames().addAll(transactionReport.getDomainNames()); m_report.getIps().addAll(transactionReport.getIps()); super.visitTransactionReport(transactionReport); } // 调用父类的方法 BaseVisitor.visitTransactionReport() 进行循环调用集群机器小时数据 @Override public void visitTransactionReport(TransactionReport transactionReport) { for (Machine machine : transactionReport.getMachines().values()) { visitMachine(machine); } } @Override public void visitMachine(Machine machine) { m_report.findOrCreateMachine(Constants.ALL); super.visitMachine(machine); } @Override public void visitType(TransactionType type) { m_currentType = type.getId(); TransactionType temp = m_report.findOrCreateMachine(Constants.ALL).findOrCreateType(m_currentType); m_merger.mergeType(temp, type); super.visitType(type); } // TransactionReportMerger.mergeType(), 进行数据合并 @Override public void mergeType(TransactionType old, TransactionType other) { long totalCountSum = old.getTotalCount() + other.getTotalCount(); if (totalCountSum > 0) { // 95、99线相加/总数 double line95Values = old.getLine95Value() * old.getTotalCount() + other.getLine95Value() * other.getTotalCount(); double line99Values = old.getLine99Value() * old.getTotalCount() + other.getLine99Value() * other.getTotalCount(); old.setLine95Value(line95Values / totalCountSum); old.setLine99Value(line99Values / totalCountSum); } // 取总数,取最大最小值 old.setTotalCount(totalCountSum); old.setFailCount(old.getFailCount() + other.getFailCount()); old.setTps(old.getTps() + other.getTps()); if (other.getMin() < old.getMin()) { old.setMin(other.getMin()); } if (other.getMax() > old.getMax()) { old.setMax(other.getMax()); } old.setSum(old.getSum() + other.getSum()); old.setSum2(old.getSum2() + other.getSum2()); if (old.getTotalCount() > 0) { old.setFailPercent(old.getFailCount() * 100.0 / old.getTotalCount()); old.setAvg(old.getSum() / old.getTotalCount()); old.setStd(std(old.getTotalCount(), old.getAvg(), old.getSum2(), old.getMax())); } if (old.getSuccessMessageUrl() == null) { old.setSuccessMessageUrl(other.getSuccessMessageUrl()); } if (old.getFailMessageUrl() == null) { old.setFailMessageUrl(other.getFailMessageUrl()); } } // visitType @Override public void visitType(TransactionType type) { for (TransactionName name : type.getNames().values()) { visitName(name); } for (Range2 range2 : type.getRange2s().values()) { visitRange2(range2); } for (AllDuration allDuration : type.getAllDurations().values()) { visitAllDuration(allDuration); } } // 消息merge完后,回到Handler, 设置概要信息 private void buildTransactionMetaInfo(Model model, Payload payload, TransactionReport report) { String type = payload.getType(); String sorted = payload.getSortBy(); String queryName = payload.getQueryName(); String ip = payload.getIpAddress(); if (!StringUtils.isEmpty(type)) { DisplayNames displayNames = new DisplayNames(); model.setDisplayNameReport(displayNames.display(sorted, type, ip, report, queryName)); // 创建 pie 饼图 buildTransactionNamePieChart(displayNames.getResults(), model); } else { model.setDisplayTypeReport(new DisplayTypes().display(sorted, ip, report)); } }
// m_jspViewer.view(ctx, model); jsp 模板渲染,输出页面内容:
// m_jspViewer.view(ctx, model); 找到对应模板jsp, 转发 public void view(S ctx, T model) throws ServletException, IOException { HttpServletRequest req = ctx.getHttpServletRequest(); HttpServletResponse res = ctx.getHttpServletResponse(); req.setAttribute("ctx", ctx); req.setAttribute("payload", ctx.getPayload()); req.setAttribute("model", model); if (m_modelHandler != null) { m_modelHandler.handle(req, res); } if (!ctx.isProcessStopped()) { try { // 找到各自配置的模板文件(其实很麻烦了) String path = getJspFilePath(ctx, model); // 转发 req.getRequestDispatcher(path).forward(req, res); } catch (EOFException e) { // Caused by: java.net.SocketException: Broken pipe // ignore it System.out.println(String.format("[%s] HTTP request(%s) stopped by client(%s) explicitly!", new Date(), req.getRequestURI(), req.getRemoteAddr())); } } }
// 模板jsp示例: /jsp/report/transaction/transaction.jsp
<%@ page session="false" language="java" pageEncoding="UTF-8" %> <%@ page contentType="text/html; charset=utf-8"%> <%@ taglib prefix="a" uri="/WEB-INF/app.tld"%> <%@ taglib prefix="w" uri="http://www.unidal.org/web/core"%> <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%> <%@ taglib prefix="res" uri="http://www.unidal.org/webres"%> <!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"> <jsp:useBean id="ctx" type="com.dianping.cat.report.page.transaction.Context" scope="request" /> <jsp:useBean id="payload" type="com.dianping.cat.report.page.transaction.Payload" scope="request" /> <jsp:useBean id="model" type="com.dianping.cat.report.page.transaction.Model" scope="request" /> <c:set var="report" value="${model.report}"/> <a:report title="Transaction Report${empty payload.type ? '' : ' :: '}<a href='?domain=${model.domain}&date=${model.date}&type=${payload.encodedType}'>${payload.type}</a>" navUrlPrefix="ip=${model.ipAddress}&queryname=${model.queryName}&domain=${model.domain}${empty payload.type ? '' : '&type='}${payload.encodedType}" timestamp="${w:format(model.creatTime,'yyyy-MM-dd HH:mm:ss')}"> <jsp:attribute name="subtitle">${w:format(report.startTime,'yyyy-MM-dd HH:mm:ss')} to ${w:format(report.endTime,'yyyy-MM-dd HH:mm:ss')}</jsp:attribute> <jsp:body> <res:useJs value="${res.js.local['baseGraph.js']}" target="head-js"/> <table class="machines"> <tr class="left"> <th> [ <c:choose> <c:when test="${model.ipAddress eq 'All'}"> <a href="?domain=${model.domain}&date=${model.date}&type=${payload.encodedType}&queryname=${model.queryName}" class="current">All</a> </c:when> <c:otherwise> <a href="?domain=${model.domain}&date=${model.date}&type=${payload.encodedType}&queryname=${model.queryName}">All</a> </c:otherwise> </c:choose> ] <c:forEach var="ip" items="${model.ips}"> [ <c:choose> <c:when test="${model.ipAddress eq ip}"> <a href="?domain=${model.domain}&ip=${ip}&date=${model.date}&type=${payload.encodedType}&queryname=${model.queryName}" class="current">${ip}</a> </c:when> <c:otherwise> <a href="?domain=${model.domain}&ip=${ip}&date=${model.date}&type=${payload.encodedType}&queryname=${model.queryName}">${ip}</a> </c:otherwise> </c:choose> ] </c:forEach> </th> </tr> </table> <script type="text/javascript" src="/cat/js/appendHostname.js"></script> <script type="text/javascript"> $(document).ready(function() { appendHostname(${model.ipToHostnameStr}); }); </script> <table class="groups"> <tr class="left"> <th> <c:forEach var="group" items="${model.groups}"> [ <a href="?op=groupReport&domain=${model.domain}&date=${model.date}&group=${group}">${group}</a> ] </c:forEach> </th> </tr> </table> <table class='table table-striped table-condensed table-hover ' style="width:100%;"> <c:choose> <c:when test="${empty payload.type}"> <tr><th class="left"><a href="?domain=${model.domain}&date=${model.date}&ip=${model.ipAddress}&sort=type">Type</a></th> <th class="right"><a href="?domain=${model.domain}&date=${model.date}&ip=${model.ipAddress}&sort=total">Total</a></th> <th class="right"><a href="?domain=${model.domain}&date=${model.date}&ip=${model.ipAddress}&sort=failure">Failure</a></th> <th class="right"><a href="?domain=${model.domain}&date=${model.date}&ip=${model.ipAddress}&sort=failurePercent">Failure%</a></th> <th class="right">Sample Link</th> <th class="right"><a href="?domain=${model.domain}&date=${model.date}&ip=${model.ipAddress}&sort=min">Min</a>(ms)</th> <th class="right"><a href="?domain=${model.domain}&date=${model.date}&ip=${model.ipAddress}&sort=max">Max</a>(ms)</th> <th class="right"><a href="?domain=${model.domain}&date=${model.date}&ip=${model.ipAddress}&sort=avg">Avg</a>(ms)</th> <th class="right"><a href="?domain=${model.domain}&date=${model.date}&ip=${model.ipAddress}&sort=95line">95Line</a>(ms)</th> <th class="right"><a href="?domain=${model.domain}&date=${model.date}&ip=${model.ipAddress}&sort=99line">99.9Line</a>(ms)</th> <th class="right"><a href="?domain=${model.domain}&date=${model.date}&ip=${model.ipAddress}&sort=std">Std</a>(ms)</th> <th class="right"><a href="?domain=${model.domain}&date=${model.date}&ip=${model.ipAddress}&sort=total">QPS</a></th> </tr> <c:forEach var="item" items="${model.displayTypeReport.results}" varStatus="status"> <c:set var="e" value="${item.detail}"/> <c:set var="lastIndex" value="${status.index}"/> <tr class=" right"> <td class="left"><a href="?op=graphs&domain=${report.domain}&date=${model.date}&ip=${model.ipAddress}&type=${item.type}" class="graph_link" data-status="${status.index}">[:: show ::]</a> <a href="?domain=${report.domain}&date=${model.date}&ip=${model.ipAddress}&type=${item.type}"> ${item.detail.id}</a></td> <td>${w:format(e.totalCount,'#,###,###,###,##0')}</td> <td>${w:format(e.failCount,'#,###,###,###,##0')}</td> <td> ${w:format(e.failPercent/100,'0.0000%')}</td> <td><a href="/cat/r/m/${empty e.failMessageUrl ? e.successMessageUrl : e.failMessageUrl}?domain=${model.domain}">Log View</a></td> <td>${w:format(e.min,'###,##0.#')}</td> <td>${w:format(e.max,'###,##0.#')}</td> <td>${w:format(e.avg,'###,##0.0')}</td> <td>${w:format(e.line95Value,'###,##0.0')}</td> <td>${w:format(e.line99Value,'###,##0.0')}</td> <td>${w:format(e.std,'###,##0.0')}</td> <td>${w:format(e.tps,'###,##0.0')}</td> </tr> <tr class="graphs"><td colspan="13" style="display:none"><div id="${status.index}" style="display:none"></div></td></tr> <tr style="display:none"></tr> </c:forEach> </c:when> <c:otherwise> <tr><th class="left" colspan="13"><input type="text" name="queryname" id="queryname" size="40" value="${model.queryName}"> <input class="btn btn-primary btn-sm" value="Filter" onclick="selectByName('${model.date}','${model.domain}','${model.ipAddress}','${payload.type}')" type="submit"> 支持多个字符串查询,例如sql|url|task,查询结果为包含任一sql、url、task的列。 </th></tr> <tr> <th style="text-align: left;"><a href="?op=graphs&domain=${report.domain}&date=${model.date}&ip=${model.ipAddress}&type=${payload.encodedType}" class="graph_link" data-status="-1">[:: show ::]</a> <a href="?domain=${model.domain}&date=${model.date}&ip=${model.ipAddress}&type=${payload.encodedType}&sort=type&queryname=${model.queryName}">Name</a></th> <th class="right"><a href="?domain=${model.domain}&date=${model.date}&ip=${model.ipAddress}&type=${payload.encodedType}&sort=total&queryname=${model.queryName}">Total</a></th> <th class="right"><a href="?domain=${model.domain}&date=${model.date}&ip=${model.ipAddress}&type=${payload.encodedType}&sort=failure&queryname=${model.queryName}">Failure</a></th> <th class="right"><a href="?domain=${model.domain}&date=${model.date}&ip=${model.ipAddress}&type=${payload.encodedType}&sort=failurePercent&queryname=${model.queryName}">Failure%</a></th> <th class="right">Sample Link</th> <th class="right"><a href="?domain=${model.domain}&date=${model.date}&ip=${model.ipAddress}&type=${payload.encodedType}&sort=min&queryname=${model.queryName}">Min</a>(ms)</th> <th class="right"><a href="?domain=${model.domain}&date=${model.date}&ip=${model.ipAddress}&type=${payload.encodedType}&sort=max&queryname=${model.queryName}">Max</a>(ms)</th> <th class="right"><a href="?domain=${model.domain}&date=${model.date}&ip=${model.ipAddress}&type=${payload.encodedType}&sort=avg&queryname=${model.queryName}">Avg</a>(ms)</th> <th class="right"><a href="?domain=${model.domain}&date=${model.date}&ip=${model.ipAddress}&type=${payload.encodedType}&sort=95line&queryname=${model.queryName}">95Line</a>(ms)</th> <th class="right"><a href="?domain=${model.domain}&date=${model.date}&ip=${model.ipAddress}&type=${payload.encodedType}&sort=99line&queryname=${model.queryName}">99.9Line</a>(ms)</th> <th class="right"><a href="?domain=${model.domain}&date=${model.date}&ip=${model.ipAddress}&type=${payload.encodedType}&sort=std&queryname=${model.queryName}">Std</a>(ms)</th> <th class="right"><a href="?domain=${model.domain}&date=${model.date}&ip=${model.ipAddress}&type=${payload.encodedType}&sort=total&queryname=${model.queryName}">QPS</a></th> <th class="right"><a href="?domain=${model.domain}&date=${model.date}&ip=${model.ipAddress}&type=${payload.encodedType}&sort=total&queryname=${model.queryName}">Percent%</a></th></tr> <tr class="graphs"><td colspan="13" style="display:none"><div id="-1" style="display:none"></div></td></tr> <c:forEach var="item" items="${model.displayNameReport.results}" varStatus="status"> <c:set var="e" value="${item.detail}"/> <c:set var="lastIndex" value="${status.index}"/> <tr class=" right"> <c:choose> <c:when test="${status.index > 0}"> <td class="left longText" style="white-space:normal"> <a href="?op=graphs&domain=${report.domain}&date=${model.date}&ip=${model.ipAddress}&type=${payload.encodedType}&name=${item.name}" class="graph_link" data-status="${status.index}">[:: show ::]</a> ${w:shorten(e.id, 120)}</td> </c:when> <c:otherwise> <td class="center" style="white-space:normal">${w:shorten(e.id, 120)}</td> </c:otherwise> </c:choose> <td>${w:format(e.totalCount,'#,###,###,###,##0')}</td> <td>${w:format(e.failCount,'#,###,###,###,##0')}</td> <td> ${w:format(e.failPercent/100,'0.0000%')}</td> <td class="center"><a href="/cat/r/m/${empty e.failMessageUrl ? e.successMessageUrl : e.failMessageUrl}?domain=${model.domain}">Log View</a></td> <td>${w:format(e.min,'###,##0.#')}</td> <td>${w:format(e.max,'###,##0.#')}</td> <td>${w:format(e.avg,'###,##0.0')}</td> <c:choose> <c:when test="${status.index > 0}"> <td>${w:format(e.line95Value,'###,##0.0')}</td> <td>${w:format(e.line99Value,'###,##0.0')}</td> </c:when> <c:otherwise> <td class="center">-</td> <td class="center">-</td> </c:otherwise> </c:choose> <td>${w:format(e.std,'###,##0.0')}</td> <td>${w:format(e.tps,'###,##0.0')}</td> <td>${w:format(e.totalPercent,'0.00%')}</td> </tr> <tr class=" "><td colspan="13" style="display:none"><div id="${status.index}" style="display:none"></div></td></tr> <tr></tr> </c:forEach> </c:otherwise> </c:choose> </table> <font color="white">${lastIndex}</font> <res:useJs value="${res.js.local.transaction_js}" target="bottom-js" /> <c:choose> <c:when test="${not empty payload.type}"> <table> <tr> <td><div id="transactionGraph" class="pieChart"></div> </td> </tr> </table> <script type="text/javascript"> var data = ${model.pieChart}; graphPieChart(document.getElementById('transactionGraph'), data); </script> </c:when> </c:choose> </jsp:body> </a:report>
// 如上过程,简单或复杂的页面业务已经ok了
其他业务逻辑看代码自然一目了然了!
如有必要再添加几个有意义的 方法。
具体的业务展现逻辑一般都比较简单的,这里就不赘述了,主要在于熟悉业务,熟悉表结构。
加载请求流程图:
出入站处理流程:
cat本身是好几年前的产物,技术自然算不上新,要去深究意义也不大,重在学习吧。
不要害怕今日的苦,你要相信明天,更苦!