summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorRyan Campbell <ryanjcampbell@google.com>2017-07-10 23:28:45 +0000
committerandroid-build-merger <android-build-merger@google.com>2017-07-10 23:28:45 +0000
commitfdc61a9b553c0783647a68b608691e377736b65b (patch)
treef037c7fce37523a03df81ba3082eafb274dfb283
parentd5fa773aaadb13bb53365bf5549ab662bcad22d3 (diff)
parent269700b5b3ad032d80fdaf509e6809fd9f5637e0 (diff)
downloaddashboard-fdc61a9b553c0783647a68b608691e377736b65b.tar.gz
Merge changes I1926ed1f,I921313cb
am: 269700b5b3 Change-Id: I91a5ea6e34238eb9e30c6f6ba7ee0a5fc95c11d7
-rw-r--r--src/main/java/com/android/vts/servlet/ShowProfilingOverviewServlet.java146
-rw-r--r--src/main/java/com/android/vts/util/BoxPlot.java179
-rw-r--r--src/main/java/com/android/vts/util/Graph.java2
-rw-r--r--src/main/java/com/android/vts/util/ProfilingPointSummary.java12
-rw-r--r--src/main/webapp/WEB-INF/jsp/show_profiling_overview.jsp148
-rw-r--r--src/main/webapp/WEB-INF/web.xml10
-rw-r--r--src/main/webapp/css/show_profiling_overview.css23
-rw-r--r--src/main/webapp/js/time.js17
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.