diff options
author | Ryan Campbell <ryanjcampbell@google.com> | 2017-07-10 23:28:45 +0000 |
---|---|---|
committer | android-build-merger <android-build-merger@google.com> | 2017-07-10 23:28:45 +0000 |
commit | fdc61a9b553c0783647a68b608691e377736b65b (patch) | |
tree | f037c7fce37523a03df81ba3082eafb274dfb283 | |
parent | d5fa773aaadb13bb53365bf5549ab662bcad22d3 (diff) | |
parent | 269700b5b3ad032d80fdaf509e6809fd9f5637e0 (diff) | |
download | dashboard-fdc61a9b553c0783647a68b608691e377736b65b.tar.gz |
Merge changes I1926ed1f,I921313cb
am: 269700b5b3
Change-Id: I91a5ea6e34238eb9e30c6f6ba7ee0a5fc95c11d7
-rw-r--r-- | src/main/java/com/android/vts/servlet/ShowProfilingOverviewServlet.java | 146 | ||||
-rw-r--r-- | src/main/java/com/android/vts/util/BoxPlot.java | 179 | ||||
-rw-r--r-- | src/main/java/com/android/vts/util/Graph.java | 2 | ||||
-rw-r--r-- | src/main/java/com/android/vts/util/ProfilingPointSummary.java | 12 | ||||
-rw-r--r-- | src/main/webapp/WEB-INF/jsp/show_profiling_overview.jsp | 148 | ||||
-rw-r--r-- | src/main/webapp/WEB-INF/web.xml | 10 | ||||
-rw-r--r-- | src/main/webapp/css/show_profiling_overview.css | 23 | ||||
-rw-r--r-- | src/main/webapp/js/time.js | 17 |
8 files changed, 528 insertions, 9 deletions
diff --git a/src/main/java/com/android/vts/servlet/ShowProfilingOverviewServlet.java b/src/main/java/com/android/vts/servlet/ShowProfilingOverviewServlet.java new file mode 100644 index 0000000..bb1174e --- /dev/null +++ b/src/main/java/com/android/vts/servlet/ShowProfilingOverviewServlet.java @@ -0,0 +1,146 @@ +/* + * Copyright (C) 2017 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); you + * may not use this file except in compliance with the License. You may + * obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.vts.servlet; + +import com.android.vts.entity.ProfilingPointRunEntity; +import com.android.vts.entity.TestEntity; +import com.android.vts.entity.TestRunEntity; +import com.android.vts.proto.VtsReportMessage; +import com.android.vts.util.BoxPlot; +import com.android.vts.util.DatastoreHelper; +import com.android.vts.util.FilterUtil; +import com.android.vts.util.GraphSerializer; +import com.android.vts.util.PerformanceUtil; +import com.google.appengine.api.datastore.DatastoreService; +import com.google.appengine.api.datastore.DatastoreServiceFactory; +import com.google.appengine.api.datastore.Entity; +import com.google.appengine.api.datastore.Key; +import com.google.appengine.api.datastore.KeyFactory; +import com.google.appengine.api.datastore.Query; +import com.google.appengine.api.datastore.Query.Filter; +import com.google.gson.Gson; +import com.google.gson.GsonBuilder; +import java.io.IOException; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.Comparator; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; +import javax.servlet.RequestDispatcher; +import javax.servlet.ServletException; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +/** Servlet for handling requests to load graphs. */ +public class ShowProfilingOverviewServlet extends BaseServlet { + private static final String PROFILING_OVERVIEW_JSP = "WEB-INF/jsp/show_profiling_overview.jsp"; + + private static final String HIDL_HAL_OPTION = "hidl_hal_mode"; + private static final String[] splitKeysArray = new String[] {HIDL_HAL_OPTION}; + private static final Set<String> splitKeySet = new HashSet<>(Arrays.asList(splitKeysArray)); + + @Override + public PageType getNavParentType() { + return PageType.TOT; + } + + @Override + public List<Page> getBreadcrumbLinks(HttpServletRequest request) { + List<Page> links = new ArrayList<>(); + String testName = request.getParameter("testName"); + links.add(new Page(PageType.TABLE, testName, "?testName=" + testName)); + + links.add(new Page(PageType.GRAPH, "?testName=" + testName)); + return links; + } + + @Override + public void doGetHandler(HttpServletRequest request, HttpServletResponse response) + throws IOException { + RequestDispatcher dispatcher = null; + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); + String testName = request.getParameter("testName"); + long endTime = TimeUnit.MILLISECONDS.toMicros(System.currentTimeMillis()); + long startTime = endTime - TimeUnit.DAYS.toMicros(14); + + // Create a query for test runs matching the time window filter + Key parentKey = KeyFactory.createKey(TestEntity.KIND, testName); + Filter profilingFilter = + FilterUtil.getProfilingTimeFilter( + parentKey, TestRunEntity.KIND, startTime, endTime); + Query profilingQuery = + new Query(ProfilingPointRunEntity.KIND) + .setAncestor(parentKey) + .setFilter(profilingFilter); + Map<String, BoxPlot> plotMap = new HashMap<>(); + for (Entity e : + datastore + .prepare(profilingQuery) + .asIterable(DatastoreHelper.getLargeBatchOptions())) { + ProfilingPointRunEntity pt = ProfilingPointRunEntity.fromEntity(e); + if (pt == null + || pt.regressionMode + == VtsReportMessage.VtsProfilingRegressionMode + .VTS_REGRESSION_MODE_DISABLED) continue; + String option = PerformanceUtil.getOptionAlias(pt, splitKeySet); + + if (!plotMap.containsKey(pt.name)) { + plotMap.put(pt.name, new BoxPlot(pt.name)); + } + + BoxPlot plot = plotMap.get(pt.name); + long days = (endTime - e.getParent().getId()) / TimeUnit.DAYS.toMicros(1); + long time = endTime - days * TimeUnit.DAYS.toMicros(1); + + plot.addSeriesData(Long.toString(time), option, pt); + } + + List<BoxPlot> plots = new ArrayList<>(); + for (String key : plotMap.keySet()) { + BoxPlot plot = plotMap.get(key); + if (plot.size() == 0) continue; + plots.add(plot); + } + Collections.sort( + plots, + new Comparator<BoxPlot>() { + @Override + public int compare(BoxPlot b1, BoxPlot b2) { + return b1.getName().compareTo(b2.getName()); + } + }); + + Gson gson = + new GsonBuilder() + .registerTypeHierarchyAdapter(BoxPlot.class, new GraphSerializer()) + .create(); + request.setAttribute("plots", gson.toJson(plots)); + request.setAttribute("testName", request.getParameter("testName")); + dispatcher = request.getRequestDispatcher(PROFILING_OVERVIEW_JSP); + try { + dispatcher.forward(request, response); + } catch (ServletException e) { + logger.log(Level.SEVERE, "Servlet Exception caught : ", e); + } + } +} diff --git a/src/main/java/com/android/vts/util/BoxPlot.java b/src/main/java/com/android/vts/util/BoxPlot.java new file mode 100644 index 0000000..b135410 --- /dev/null +++ b/src/main/java/com/android/vts/util/BoxPlot.java @@ -0,0 +1,179 @@ +/* + * Copyright (c) 2017 Google Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you + * may not use this file except in compliance with the License. You may + * obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + * implied. See the License for the specific language governing + * permissions and limitations under the License. + */ + +package com.android.vts.util; + +import com.android.vts.entity.ProfilingPointRunEntity; +import com.google.gson.Gson; +import com.google.gson.JsonObject; +import java.util.ArrayList; +import java.util.Collections; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +/** Helper object for describing time-series box plot data. */ +public class BoxPlot extends Graph { + private static final String LABEL_KEY = "label"; + private static final String SERIES_KEY = "seriesList"; + private static final String MEAN_KEY = "mean"; + private static final String STD_KEY = "std"; + + private final String xLabel = "Day"; + private String yLabel; + private String name; + private GraphType type = GraphType.BOX_PLOT; + private int count; + private final Map<String, ProfilingPointSummary> seriesMap; + private final Set<String> labelSet; + private final List<String> labels; + + public BoxPlot(String name) { + this.name = name; + this.count = 0; + seriesMap = new HashMap<>(); + labelSet = new HashSet<>(); + labels = new ArrayList<>(); + } + + /** + * Get the x axis label. + * + * @return The x axis label. + */ + @Override + public String getXLabel() { + return xLabel; + } + + /** + * Get the graph type. + * + * @return The graph type. + */ + @Override + public GraphType getType() { + return type; + } + + /** + * Get the name of the graph. + * + * @return The name of the graph. + */ + @Override + public String getName() { + return name; + } + + /** + * Get the y axis label. + * + * @return The y axis label. + */ + @Override + public String getYLabel() { + return yLabel; + } + + /** + * Get the number of data points stored in the graph. + * + * @return The number of data points stored in the graph. + */ + @Override + public int size() { + return this.count; + } + + /** + * Add data to the graph. + * + * @param label The name of the category. + * @param profilingPoint The ProfilingPointRunEntity containing data to add. + */ + @Override + public void addData(String label, ProfilingPointRunEntity profilingPoint) { + addSeriesData(label, "", profilingPoint); + } + + /** + * Add data to the graph. + * + * @param label The name of the category. + * @param series The data series to add data to. + * @param profilingPoint The ProfilingPointRunEntity containing data to add. + */ + public void addSeriesData(String label, String series, ProfilingPointRunEntity profilingPoint) { + if (profilingPoint.values.size() == 0) + return; + if (!seriesMap.containsKey(series)) { + seriesMap.put(series, new ProfilingPointSummary()); + } + ProfilingPointSummary summary = seriesMap.get(series); + summary.updateLabel(profilingPoint, label); + if (labelSet.add(label)) { + labels.add(label); + } + yLabel = profilingPoint.xLabel; + ++count; + } + + /** + * Serializes the graph to json format. + * + * @return A JsonElement object representing the graph object. + */ + @Override + public JsonObject toJson() { + JsonObject json = super.toJson(); + List<JsonObject> stats = new ArrayList<>(); + List<String> seriesList = new ArrayList<>(seriesMap.keySet()); + Collections.sort(seriesList); + Collections.reverse(labels); + for (String label : labels) { + JsonObject statJson = new JsonObject(); + String boxLabel = null; + List<JsonObject> statList = new ArrayList<>(seriesList.size()); + for (String series : seriesList) { + ProfilingPointSummary summary = seriesMap.get(series); + JsonObject statSummary = new JsonObject(); + Double mean = null; + Double std = null; + if (summary.hasLabel(label) && summary.getStatSummary(label).getCount() > 0) { + StatSummary stat = summary.getStatSummary(label); + boxLabel = stat.getLabel(); + mean = stat.getMean(); + std = 0.; + if (stat.getCount() > 1) { + std = stat.getStd(); + } + } + statSummary.addProperty(MEAN_KEY, mean); + statSummary.addProperty(STD_KEY, std); + statList.add(statSummary); + } + statJson.addProperty(LABEL_KEY, boxLabel); + statJson.add(VALUE_KEY, new Gson().toJsonTree(statList)); + stats.add(statJson); + } + json.add(VALUE_KEY, new Gson().toJsonTree(stats)); + json.add(SERIES_KEY, new Gson().toJsonTree(seriesList)); + return json; + } +} diff --git a/src/main/java/com/android/vts/util/Graph.java b/src/main/java/com/android/vts/util/Graph.java index 2fe98bb..00c2a3d 100644 --- a/src/main/java/com/android/vts/util/Graph.java +++ b/src/main/java/com/android/vts/util/Graph.java @@ -29,7 +29,7 @@ public abstract class Graph { public static final String NAME_KEY = "name"; public static final String TYPE_KEY = "type"; - public static enum GraphType { LINE_GRAPH, HISTOGRAM } + public static enum GraphType { LINE_GRAPH, HISTOGRAM, BOX_PLOT } /** * Get the graph type. diff --git a/src/main/java/com/android/vts/util/ProfilingPointSummary.java b/src/main/java/com/android/vts/util/ProfilingPointSummary.java index 64a1036..8e0e24a 100644 --- a/src/main/java/com/android/vts/util/ProfilingPointSummary.java +++ b/src/main/java/com/android/vts/util/ProfilingPointSummary.java @@ -1,5 +1,5 @@ /* - * Copyright (c) 2016 Google Inc. All Rights Reserved. + * Copyright (C) 2017 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); you * may not use this file except in compliance with the License. You may @@ -96,14 +96,12 @@ public class ProfilingPointSummary implements Iterable<StatSummary> { /** * Updates the profiling summary at a label with the data from a new profiling report. * - * <p>Updates the summary specified by the label with all values provided in the report. If - * labels - * are provided in the report, they will be ignored -- all values are updated only to the - * provided - * label. + * Updates the summary specified by the label with all values provided in the report. If + * labels are provided in the report, they will be ignored -- all values are updated only to the + * provided label. * * @param profilingEntity The ProfilingPointRunEntity object containing profiling data. - * @param label The ByteString label for which all values in the report will be updated. + * @param label The String label for which all values in the report will be updated. */ public void updateLabel(ProfilingPointRunEntity profilingEntity, String label) { if (!labelIndices.containsKey(label)) { diff --git a/src/main/webapp/WEB-INF/jsp/show_profiling_overview.jsp b/src/main/webapp/WEB-INF/jsp/show_profiling_overview.jsp new file mode 100644 index 0000000..28f8b48 --- /dev/null +++ b/src/main/webapp/WEB-INF/jsp/show_profiling_overview.jsp @@ -0,0 +1,148 @@ +<%-- + ~ Copyright (C) 2017 The Android Open Source Project + ~ + ~ Licensed under the Apache License, Version 2.0 (the "License"); you + ~ may not use this file except in compliance with the License. You may + ~ obtain a copy of the License at + ~ + ~ http://www.apache.org/licenses/LICENSE-2.0 + ~ + ~ Unless required by applicable law or agreed to in writing, software + ~ distributed under the License is distributed on an "AS IS" BASIS, + ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or + ~ implied. See the License for the specific language governing + ~ permissions and limitations under the License. + --%> +<%@ page contentType='text/html;charset=UTF-8' language='java' %> +<%@ taglib prefix='fn' uri='http://java.sun.com/jsp/jstl/functions' %> +<%@ taglib prefix='c' uri='http://java.sun.com/jsp/jstl/core'%> + +<html> + <%@ include file="header.jsp" %> + <link type='text/css' href='/css/datepicker.css' rel='stylesheet'> + <link type='text/css' href='/css/show_profiling_overview.css' rel='stylesheet'> + <link rel='stylesheet' href='https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.0/jquery-ui.css'> + <script src='https://www.gstatic.com/external_hosted/moment/min/moment-with-locales.min.js'></script> + <script src='js/time.js'></script> + <script type='text/javascript' src='https://www.gstatic.com/charts/loader.js'></script> + <script type='text/javascript' src='https://ajax.googleapis.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js'></script> + <body> + <script type='text/javascript'> + google.charts.load('current', {'packages':['corechart']}); + google.charts.setOnLoadCallback(drawAllPlots); + + var plots = ${plots}; + + /** + * Draw a box plot. + * + * Args: + * container: the jquery selector in which to draw the graph. + * lineGraph: a JSON-serialized BoxPlot. + */ + function drawBoxPlot(container, plot) { + var data = new google.visualization.DataTable(); + data.addColumn('string', 'x'); + plot.seriesList.forEach(function(series) { + data.addColumn('number', series); + data.addColumn({id:'fill', type:'number', role:'interval'}); + data.addColumn({id:'fill', type:'number', role:'interval'}); + data.addColumn({id:'bar', type:'number', role:'interval'}); + data.addColumn({id:'bar', type:'number', role:'interval'}); + data.addColumn({type:'string', role:'tooltip', p: {'html': true}}); + }); + var rows = plot.values.map(function(day) { + var dateString = new moment().renderDate(1 * day.label); + var statArray = day.values.map(function(stat) { + var tooltip = ( + dateString + + '</br><b>Mean:</b> ' + + stat.mean.toFixed(2) + + '</br><b>Std:</b> ' + + stat.std.toFixed(2)); + return [ + stat.mean, + stat.mean + stat.std, + stat.mean - stat.std, + stat.mean + stat.std, + stat.mean - stat.std, + tooltip]; + }); + return [].concat.apply( + [dateString], statArray); + }); + data.addRows(rows); + + var options = { + title: plot.name, + curveType: 'function', + intervals: { 'color' : 'series-color' }, + interval: { + 'fill': { + 'style': 'area', + 'curveType': 'function', + 'fillOpacity': 0.2 + }, + 'bar': { + 'style': 'bars', + 'barWidth': 0, + 'lineWidth': 1, + 'pointSize': 3, + 'fillOpacity': 1 + }}, + legend: { position: 'bottom' }, + tooltip: { isHtml: true }, + fontName: 'Roboto', + titleTextStyle: { + color: '#757575', + fontSize: 16, + bold: false + }, + pointsVisible: true, + vAxis:{ + title: plot.y_label, + titleTextStyle: { + color: '#424242', + fontSize: 12, + italic: false + }, + textStyle: { + fontSize: 12, + color: '#757575' + }, + }, + hAxis: { + textStyle: { + fontSize: 12, + color: '#757575' + }, + titleTextStyle: { + color: '#424242', + fontSize: 12, + italic: false + } + }, + }; + var plot = new google.visualization.LineChart(container[0]); + plot.draw(data, options); + } + + // Draw all graphs. + function drawAllPlots() { + var container = $('#profiling-container'); + container.empty(); + + plots.forEach(function(g) { + var plot = $('<div class="box-plot row card"></div>'); + plot.appendTo(container); + drawBoxPlot(plot.append('<div></div>'), g); + }); + } + </script> + <div class='container wide'> + <div id='profiling-container'> + </div> + </div> + <%@ include file="footer.jsp" %> + </body> +</html> diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index 8aa6323..ad494e7 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -46,6 +46,11 @@ Copyright 2016 Google Inc. All Rights Reserved. </servlet> <servlet> + <servlet-name>show_profiling_overview</servlet-name> + <servlet-class>com.android.vts.servlet.ShowProfilingOverviewServlet</servlet-class> +</servlet> + +<servlet> <servlet-name>show_plan_release</servlet-name> <servlet-class>com.android.vts.servlet.ShowPlanReleaseServlet</servlet-class> </servlet> @@ -136,6 +141,11 @@ Copyright 2016 Google Inc. All Rights Reserved. </servlet-mapping> <servlet-mapping> + <servlet-name>show_profiling_overview</servlet-name> + <url-pattern>/show_profiling_overview/*</url-pattern> +</servlet-mapping> + +<servlet-mapping> <servlet-name>show_plan_release</servlet-name> <url-pattern>/show_plan_release/*</url-pattern> </servlet-mapping> diff --git a/src/main/webapp/css/show_profiling_overview.css b/src/main/webapp/css/show_profiling_overview.css new file mode 100644 index 0000000..76c194d --- /dev/null +++ b/src/main/webapp/css/show_profiling_overview.css @@ -0,0 +1,23 @@ +/* Copyright (C) 2017 The Android Open Source Project + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +.row.card.box-plot { + height: 400px; + padding: 25px 10px; +} +div.google-visualization-tooltip { + padding: 10px; + white-space: nowrap; +} diff --git a/src/main/webapp/js/time.js b/src/main/webapp/js/time.js index c5fbef6..dc8c4d6 100644 --- a/src/main/webapp/js/time.js +++ b/src/main/webapp/js/time.js @@ -26,7 +26,7 @@ var time = moment(timestamp / 1000); var format = 'H:mm:ss'; if (!time.isSame(moment(), 'd')) { - format = 'M/D/YY ' + format; + format = 'YY/M/DD ' + format; } if (!!showTimezone) { format = format + 'ZZ'; @@ -35,6 +35,21 @@ } /** + * Renders a date in the user timezone. + * @param timestamp The long timestamp to render (in microseconds). + * @param showTimezone True if the timezone should be rendered, false otherwise. + * @returns the string-formatted version of the provided timestamp. + */ + moment.prototype.renderDate = function (timestamp, showTimezone) { + var time = moment(timestamp / 1000); + var format = 'YY/M/DD'; + if (!!showTimezone) { + format = format + 'ZZ'; + } + return time.format(format); + } + + /** * Renders a duration in the user timezone. * @param durationTimestamp The long duration to render (in microseconds). * @returns the string-formatted duration of the provided duration timestamp. |