应用监控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>&nbsp;[&nbsp; <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> &nbsp;]&nbsp; <c:forEach var="ip" items="${model.ips}">
                 &nbsp;[&nbsp;
                 <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>
                &nbsp;]&nbsp;
             </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}">
                     &nbsp;[&nbsp;
                         <a href="?op=groupReport&domain=${model.domain}&date=${model.date}&group=${group}">${group}</a>
                    &nbsp;]&nbsp;
             </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>
                    &nbsp;&nbsp;<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>&nbsp;${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> 
                            &nbsp;&nbsp;${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>&nbsp;${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>
View Code

// 如上过程,简单或复杂的页面业务已经ok了

  其他业务逻辑看代码自然一目了然了!

  如有必要再添加几个有意义的 方法。

 

  具体的业务展现逻辑一般都比较简单的,这里就不赘述了,主要在于熟悉业务,熟悉表结构。

加载请求流程图:

 

 出入站处理流程:

 

 

  cat本身是好几年前的产物,技术自然算不上新,要去深究意义也不大,重在学习吧。

posted @ 2018-08-20 22:00  阿牛20  阅读(3140)  评论(0编辑  收藏  举报