nGrinder源码分析:详细报告页数据展示

来源:互联网 发布:苹果青少年编程 编辑:程序博客网 时间:2024/06/05 14:43

0.背景

当你查看nGrinder详细报告页,会展示性能数据结果和时序图,这些内部是怎么实现的呢?本文将分析源码,给你一个清晰的认识。

1.源码分析

当你请求 http://ip/perftest/{testid}/detail_report; 查看详细报告页时,Controller端将请求转给perftest/detail_report模板(ngrinder-controller/src/main/webapp/WEB-INF/ftl/perftest/detail_report.ftl ):

/** * Get the detailed perf test report. * * @param model model * @param id    test id * @return perftest/detail_report * * 返回给detail_report.ftl */@SuppressWarnings("MVCPathVariableInspection")@RequestMapping(value = {"/{id}/detail_report", /** for backward compatibility */"/{id}/report"})public String getReport(ModelMap model, @PathVariable("id") long id) {    model.addAttribute("test", perfTestService.getOne(id));    model.addAttribute("plugins", perfTestService.getAvailableReportPlugins(id));    return "perftest/detail_report";}

model.addAttribute("test", perfTestService.getOne(id));

ModelMap:构建model数据给前端的Map

addAttribute:添加指定名对应的属性

perfTestService.getOne(id):获取对应测试id下的Perftest(性能统计数据model)实例

detail_report.ftl中可以根据test获取PerfTest成员变量值

detail_report.ftl模板中数据展示分成两大部分:

这里写图片描述

1-1. 第一部分:DB数据

DB:h2, 文件保存路径:/root/.ngrinder/db

这里写图片描述

detail_report.ftl局部代码示例:

<tr>    <th><@spring.message "perfTest.report.totalVusers"/></th>    <td><strong>${test.vuserPerAgent * test.agentCount}</strong></td></tr>

test.vuserPerAgent 是根据perfTestService.getOne(id),从DB中获取PerfTest实例,前端直接展示成员变量值。

2-2. 第二部分:文件数据

第二部分标签为detail_panel;默认为perfMenu

JS脚本:

<div id="detail_panel"></div><#-- 左下,执行报告,目标服务器,PLUGINS --><script>    $(document).ready(function () {        var $perfMenu = $("li.perf");        var $monitorMenu = $("li.monitor");        var $pluginMenu = $("li.plugin");        $perfMenu.click(function () {            $("#detail_panel").load("${req.getContextPath()}/perftest/${(test.id)?c}/detail_report/perf");            changActiveLink($(this));        });        $monitorMenu.click(function () {            $("#detail_panel").load("${req.getContextPath()}/perftest/${(test.id)?c}/detail_report/monitor?targetIP=" + $(this).attr("ip"));            changActiveLink($(this));        });        $pluginMenu.click(function () {            $("#detail_panel").load("${req.getContextPath()}/perftest/${(test.id)?c}/detail_report/plugin/" + $(this).attr("plugin") +                    "?kind=" + $(this).attr("ip"));            changActiveLink($(this));        });        $.ajaxSetup({"cache": false});        $perfMenu.click();    });    function changActiveLink(obj) {        $("li.active").removeClass("active");        obj.addClass("active");    }</script>

执行报告请求:
${req.getContextPath()}/perftest/${(test.id)?c}/detail_report/perf

对应的Controller:

/** * Get the detailed perf test report. * * @param id test id * @return perftest/detail_report/perf */@SuppressWarnings({"MVCPathVariableInspection", "UnusedParameters"})@RequestMapping("/{id}/detail_report/perf")public String getDetailPerfReport(@PathVariable("id") long id) {    return "perftest/detail_report/perf";

映射到ngrinder-controller/src/main/webapp/WEB-INF/ftl/perftest/detail_report/perf.ftl模板

<div class="bigchart" id="tps_chart"></div><h6><@spring.message "perfTest.report.header.meantime"/>&nbsp;(ms)</h6><div class="chart" id="mean_time_chart"></div><h6 id="min_time_first_byte_chart_header"><@spring.message "perfTest.report.header.meantimeToFirstByte"/>&nbsp;(ms)</h6><div class="chart" id="min_time_first_byte_chart"></div><h6 id="vuser_chart_header"><@spring.message "perfTest.report.header.vuser"/></h6><div class="chart" id="vuser_chart"></div><h6 id="user_defined_chart_header"><@spring.message "perfTest.report.header.userDefinedChart"/></h6><div class="chart" id="user_defined_chart"></div><h6><@spring.message "perfTest.report.header.errors"/></h6><div class="chart" id="error_chart"></div><script>    //@ sourceURL=/perftest/detail_report/perf    $("#tps_title").popover({trigger: 'hover', container:'body'});    function getGraphDataAndDraw(testId) {        var ajaxObj = new AjaxObj("/perftest/api/" + testId + "/perf");        ajaxObj.params = {            dataType : 'TPS,Errors,Mean_Test_Time_(ms),Mean_time_to_first_byte,User_defined,Vuser',            imgWidth : parseInt($("#tps_chart").width())        };        ajaxObj.success = function (data) {            var interval = data.chartInterval;            drawChart("tps_chart", data.TPS.data, interval, data.TPS.labels);            drawChart("mean_time_chart", data.Mean_Test_Time_ms.data, interval, data.Mean_Test_Time_ms.labels);            drawChart('vuser_chart', data.Vuser.data, interval, data.Vuser.labels);            drawChart('error_chart', data.Errors.data, interval, data.Errors.labels);            drawOptionalChart("min_time_first_byte_chart", data.Mean_time_to_first_byte.data, interval,                    data.Mean_time_to_first_byte.labels);            drawOptionalChart("user_defined_chart", data.User_defined.data, interval, data.User_defined.labels);            createChartExportButton("<@spring.message "perfTest.report.exportImg.button"/>", "<@spring.message "perfTest.report.exportImg.title"/>");        };        ajaxObj.call();    }    function drawChart(id, data, interval, labels) {        new Chart(id, data, interval, { labels: labels }).plot();    }    function drawOptionalChart(id, data, interval, labels) {        if (data !== undefined && data.length != 0) {            drawChart(id, data, interval, labels);        } else {            $("#" + id).hide();            $("#" + id + "_header").hide();        }    }    $("#download_csv").click(function () {        document.forms.download_csv_form.action = "${req.getContextPath()}/perftest/${id}/download_csv";        document.forms.download_csv_form.submit();    });    getGraphDataAndDraw(${id});</script>

注意: var ajaxObj = new AjaxObj("/perftest/api/" + testId + "/perf");

去请求了@RequestMapping({"/api/{id}/perf", "/api/{id}/graph"})

/**     * Get the detailed report graph data for the given perf test id.     * This method returns the appropriate points based on the given imgWidth.     *     * @param id       test id     * @param dataType which data     * @param imgWidth imageWidth     * @return json string.     */    @SuppressWarnings("MVCPathVariableInspection")    @RestAPI    @RequestMapping({"/api/{id}/perf", "/api/{id}/graph"})    public HttpEntity<String> getPerfGraph(@PathVariable("id") long id,                                           @RequestParam(required = true, defaultValue = "") String dataType,                                           @RequestParam(defaultValue = "false") boolean onlyTotal,                                           @RequestParam int imgWidth) {        String[] dataTypes = checkNotEmpty(StringUtils.split(dataType, ","), "dataType argument should be provided");        return toJsonHttpEntity(getPerfGraphData(id, dataTypes, onlyTotal, imgWidth));    }
其中:getPerfGraphData(id, dataTypes, onlyTotal, imgWidth)根据*.data文件获取数据(*.data是在你执行性能测试时,将采集的性能数据写到对应路径下的文件,路径:`/root/.ngrinder/perftest/0_999/$testid/report`)getPerfGraphData(id, dataTypes, onlyTotal, imgWidth):
    private Map<String, Object> getPerfGraphData(Long id, String[] dataTypes, boolean onlyTotal, int imgWidth) {        final PerfTest test = perfTestService.getOne(id);        int interval = perfTestService.getReportDataInterval(id, dataTypes[0], imgWidth);        Map<String, Object> resultMap = Maps.newHashMap();        for (String each : dataTypes) {            Pair<ArrayList<String>, ArrayList<String>> tpsResult = perfTestService.getReportData(id, each, onlyTotal, interval);            Map<String, Object> dataMap = Maps.newHashMap();            dataMap.put("labels", tpsResult.getFirst());            dataMap.put("data", tpsResult.getSecond());            resultMap.put(StringUtils.replaceChars(each, "()", ""), dataMap);        }        resultMap.put(PARAM_TEST_CHART_INTERVAL, interval * test.getSamplingInterval());        return resultMap;    }
perfTestService.getReportData():
/** * Get list that contains test report data as a string. * * @param testId   test id * @param key      report key * @param onlyTotal true if only total show be passed * @param interval interval to collect data * @return list containing label and tps value list */public Pair<ArrayList<String>, ArrayList<String>> getReportData(long testId, String key, boolean onlyTotal, int interval) {    Pair<ArrayList<String>, ArrayList<String>> resultPair = Pair.of(new ArrayList<String>(),            new ArrayList<String>());    List<File> reportDataFiles = onlyTotal ? Lists.newArrayList(getReportDataFile(testId, key)) : getReportDataFiles(testId, key);    for (File file : reportDataFiles) {        String buildReportName = buildReportName(key, file);        if (key.equals(buildReportName)) {            buildReportName = "Total";        } else {            buildReportName = buildReportName.replace("_", " ");        }        resultPair.getFirst().add(buildReportName);        resultPair.getSecond().add(getFileDataAsJson(file, interval));    }    return resultPair;}
// 读取*.data转换成son stringgetFileDataAsJson(file, interval):
/**     * Get the test report data as a json string.     *     * @param targetFile target file     * @param interval   interval to collect data     * @return json string     */    private String getFileDataAsJson(File targetFile, int interval) {        if (!targetFile.exists()) {            return "[]";        }        StringBuilder reportData = new StringBuilder("[");        FileReader reader = null;        BufferedReader br = null;        try {            reader = new FileReader(targetFile);            br = new BufferedReader(reader);            String data = br.readLine();            int current = 0;            while (StringUtils.isNotBlank(data)) {                if (0 == current) {                    reportData.append(data);                    reportData.append(",");                }                if (++current >= interval) {                    current = 0;                }                data = br.readLine();            }            if (reportData.charAt(reportData.length() - 1) == ',') {                reportData.deleteCharAt(reportData.length() - 1);            }        } catch (IOException e) {            LOGGER.error("Report data retrieval is failed: {}", e.getMessage());            LOGGER.debug("Trace is : ", e);        } finally {            IOUtils.closeQuietly(reader);            IOUtils.closeQuietly(br);        }        return reportData.append("]").toString();    }

1-3.时序图数据接口与*data文件比对

这里写图片描述

这里写图片描述

0 0
原创粉丝点击