1 /*
2  * Copyright (c) 2016 Google Inc. All Rights Reserved.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License"); you
5  * may not use this file except in compliance with the License. You may
6  * obtain a copy of the License at
7  *
8  *     http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
13  * implied. See the License for the specific language governing
14  * permissions and limitations under the License.
15  */
16 
17 package com.android.vts.servlet;
18 
19 import com.android.vts.util.DatastoreHelper;
20 import com.android.vts.util.PerformanceSummary;
21 import com.android.vts.util.PerformanceUtil;
22 import com.android.vts.util.ProfilingPointSummary;
23 import com.android.vts.util.StatSummary;
24 import java.io.IOException;
25 import java.math.RoundingMode;
26 import java.text.DecimalFormat;
27 import java.util.ArrayList;
28 import java.util.List;
29 import java.util.concurrent.TimeUnit;
30 import java.util.logging.Level;
31 import javax.servlet.RequestDispatcher;
32 import javax.servlet.ServletException;
33 import javax.servlet.http.HttpServletRequest;
34 import javax.servlet.http.HttpServletResponse;
35 
36 /** Servlet for producing tabular performance summaries. */
37 public class ShowPerformanceDigestServlet extends BaseServlet {
38     private static final String PERF_DIGEST_JSP = "WEB-INF/jsp/show_performance_digest.jsp";
39 
40     private static final String MEAN = "Mean";
41     private static final String MIN = "Min";
42     private static final String MAX = "Max";
43     private static final String MEAN_DELTA = "ΔMean (%)";
44     private static final String HIGHER_IS_BETTER =
45             "Note: Higher values are better. Maximum is the best-case performance.";
46     private static final String LOWER_IS_BETTER =
47             "Note: Lower values are better. Minimum is the best-case performance.";
48     private static final String STD = "Std";
49 
50     private static final DecimalFormat FORMATTER;
51 
52     /** Initialize the decimal formatter. */
53     static {
54         FORMATTER = new DecimalFormat("#.##");
55         FORMATTER.setRoundingMode(RoundingMode.HALF_UP);
56     }
57 
58     @Override
getNavParentType()59     public PageType getNavParentType() {
60         return PageType.TOT;
61     }
62 
63     @Override
getBreadcrumbLinks(HttpServletRequest request)64     public List<Page> getBreadcrumbLinks(HttpServletRequest request) {
65         List<Page> links = new ArrayList<>();
66         String testName = request.getParameter("testName");
67         links.add(new Page(PageType.TABLE, testName, "?testName=" + testName));
68         links.add(new Page(PageType.PERFORMANCE_DIGEST, "?testName=" + testName));
69         return links;
70     }
71 
72     /**
73      * Generates an HTML summary of the performance changes for the profiling results in the
74      * specified table.
75      *
76      * <p>Retrieves the past 24 hours of profiling data and compares it to the 24 hours that
77      * preceded it. Creates a table representation of the mean and standard deviation for each
78      * profiling point. When performance degrades, the cell is shaded red.
79      *
80      * @param profilingPoint The name of the profiling point to summarize.
81      * @param testSummary The ProfilingPointSummary object to compare against.
82      * @param perfSummaries List of PerformanceSummary objects for each profiling run (in reverse
83      *     chronological order).
84      * @param sectionLabels HTML string for the section labels (i.e. for each time interval).
85      * @returns An HTML string for a table comparing the profiling point results across time
86      *     intervals.
87      */
getPerformanceSummary( String profilingPoint, ProfilingPointSummary testSummary, List<PerformanceSummary> perfSummaries, String sectionLabels)88     public static String getPerformanceSummary(
89             String profilingPoint,
90             ProfilingPointSummary testSummary,
91             List<PerformanceSummary> perfSummaries,
92             String sectionLabels) {
93         String tableHTML = "<table>";
94 
95         // Format section labels
96         tableHTML += "<tr>";
97         tableHTML += "<th class='section-label grey lighten-2'>";
98         tableHTML += testSummary.yLabel + "</th>";
99         tableHTML += sectionLabels;
100         tableHTML += "</tr>";
101 
102         String bestCaseString;
103         switch (testSummary.getRegressionMode()) {
104             case VTS_REGRESSION_MODE_DECREASING:
105                 bestCaseString = MAX;
106                 break;
107             default:
108                 bestCaseString = MIN;
109                 break;
110         }
111 
112         // Format column labels
113         tableHTML += "<tr>";
114         for (int i = 0; i <= perfSummaries.size() + 1; i++) {
115             if (i > 1) {
116                 tableHTML += "<th class='section-label grey lighten-2'>" + MEAN_DELTA + "</th>";
117             }
118             if (i == 0) {
119                 tableHTML += "<th class='section-label grey lighten-2'>";
120                 tableHTML += testSummary.xLabel + "</th>";
121             } else if (i > 0) {
122                 tableHTML += "<th class='section-label grey lighten-2'>" + bestCaseString + "</th>";
123                 tableHTML += "<th class='section-label grey lighten-2'>" + MEAN + "</th>";
124                 tableHTML += "<th class='section-label grey lighten-2'>" + STD + "</th>";
125             }
126         }
127         tableHTML += "</tr>";
128 
129         // Populate data cells
130         for (StatSummary stats : testSummary) {
131             String label = stats.getLabel();
132             tableHTML += "<tr><td class='axis-label grey lighten-2'>" + label;
133             tableHTML += "</td><td class='cell inner-cell'>";
134             tableHTML += FORMATTER.format(stats.getBestCase()) + "</td>";
135             tableHTML += "<td class='cell inner-cell'>";
136             tableHTML += FORMATTER.format(stats.getMean()) + "</td>";
137             tableHTML += "<td class='cell outer-cell'>";
138             if (stats.getCount() < 2) {
139                 tableHTML += " - </td>";
140             } else {
141                 tableHTML += FORMATTER.format(stats.getStd()) + "</td>";
142             }
143             for (PerformanceSummary prevPerformance : perfSummaries) {
144                 if (prevPerformance.hasProfilingPoint(profilingPoint)) {
145                     StatSummary baseline =
146                             prevPerformance
147                                     .getProfilingPointSummary(profilingPoint)
148                                     .getStatSummary(label);
149                     tableHTML +=
150                             PerformanceUtil.getAvgCasePerformanceComparisonHTML(
151                                     baseline, stats, "cell inner-cell", "cell outer-cell", "", "");
152                 } else {
153                     tableHTML += "<td></td><td></td><td></td><td></td>";
154                 }
155             }
156             tableHTML += "</tr>";
157         }
158         tableHTML += "</table>";
159         return tableHTML;
160     }
161 
162     @Override
doGetHandler(HttpServletRequest request, HttpServletResponse response)163     public void doGetHandler(HttpServletRequest request, HttpServletResponse response)
164             throws IOException {
165         RequestDispatcher dispatcher = null;
166         String testName = request.getParameter("testName");
167         String selectedDevice = request.getParameter("device");
168         Long startTime = null;
169         if (request.getParameter("startTime") != null) {
170             String time = request.getParameter("startTime");
171             try {
172                 startTime = Long.parseLong(time);
173             } catch (NumberFormatException e) {
174                 logger.log(Level.WARNING, "Invalid start time passed to digest servlet: " + time);
175             }
176         }
177         if (startTime == null) {
178             startTime = TimeUnit.MILLISECONDS.toMicros(System.currentTimeMillis());
179         }
180 
181         // Add today to the list of time intervals to analyze
182         List<PerformanceSummary> summaries = new ArrayList<>();
183         PerformanceSummary today =
184                 new PerformanceSummary(startTime - TimeUnit.DAYS.toMicros(1), startTime);
185         summaries.add(today);
186 
187         // Add yesterday as a baseline time interval for analysis
188         long oneDayAgo = startTime - TimeUnit.DAYS.toMicros(1);
189         PerformanceSummary yesterday =
190                 new PerformanceSummary(oneDayAgo - TimeUnit.DAYS.toMicros(1), oneDayAgo);
191         summaries.add(yesterday);
192 
193         // Add last week as a baseline time interval for analysis
194         long oneWeek = TimeUnit.DAYS.toMicros(7);
195         long oneWeekAgo = startTime - oneWeek;
196         String spanString = "<span class='date-label'>";
197         String label =
198                 spanString + TimeUnit.MICROSECONDS.toMillis(oneWeekAgo - oneWeek) + "</span>";
199         label += " - " + spanString + TimeUnit.MICROSECONDS.toMillis(oneWeekAgo) + "</span>";
200         PerformanceSummary lastWeek =
201                 new PerformanceSummary(oneWeekAgo - oneWeek, oneWeekAgo, label);
202         summaries.add(lastWeek);
203         PerformanceUtil.updatePerformanceSummary(
204                 testName, oneWeekAgo - oneWeek, startTime, selectedDevice, summaries);
205 
206         int colCount = 0;
207         String sectionLabels = "";
208         List<PerformanceSummary> nonEmptySummaries = new ArrayList<>();
209         for (PerformanceSummary perfSummary : summaries) {
210             if (perfSummary.size() == 0) continue;
211 
212             nonEmptySummaries.add(perfSummary);
213             String content = perfSummary.label;
214             sectionLabels += "<th class='section-label grey lighten-2 center' ";
215             if (colCount++ == 0) sectionLabels += "colspan='3'";
216             else sectionLabels += "colspan='4'";
217             sectionLabels += ">" + content + "</th>";
218         }
219 
220         List<String> tables = new ArrayList<>();
221         List<String> tableTitles = new ArrayList<>();
222         List<String> tableSubtitles = new ArrayList<>();
223         if (nonEmptySummaries.size() != 0) {
224             PerformanceSummary todayPerformance = nonEmptySummaries.remove(0);
225             String[] profilingNames = todayPerformance.getProfilingPointNames();
226 
227             for (String profilingPointName : profilingNames) {
228                 ProfilingPointSummary baselinePerformance =
229                         todayPerformance.getProfilingPointSummary(profilingPointName);
230                 String table =
231                         getPerformanceSummary(
232                                 profilingPointName,
233                                 baselinePerformance,
234                                 nonEmptySummaries,
235                                 sectionLabels);
236                 if (table != null) {
237                     tables.add(table);
238                     tableTitles.add(profilingPointName);
239                     switch (baselinePerformance.getRegressionMode()) {
240                         case VTS_REGRESSION_MODE_DECREASING:
241                             tableSubtitles.add(HIGHER_IS_BETTER);
242                             break;
243                         default:
244                             tableSubtitles.add(LOWER_IS_BETTER);
245                             break;
246                     }
247                 }
248             }
249         }
250 
251         request.setAttribute("testName", testName);
252         request.setAttribute("tables", tables);
253         request.setAttribute("tableTitles", tableTitles);
254         request.setAttribute("tableSubtitles", tableSubtitles);
255         request.setAttribute("startTime", Long.toString(startTime));
256         request.setAttribute("selectedDevice", selectedDevice);
257         request.setAttribute("devices", DatastoreHelper.getAllBuildFlavors());
258 
259         dispatcher = request.getRequestDispatcher(PERF_DIGEST_JSP);
260         try {
261             dispatcher.forward(request, response);
262         } catch (ServletException e) {
263             logger.log(Level.SEVERE, "Servlet Exception caught : " + e.toString());
264         }
265     }
266 }
267