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.entity.TestEntity; 20 import com.android.vts.util.EmailHelper; 21 import com.android.vts.util.PerformanceSummary; 22 import com.android.vts.util.PerformanceUtil; 23 import com.android.vts.util.PerformanceUtil.TimeInterval; 24 import com.android.vts.util.ProfilingPointSummary; 25 import com.android.vts.util.StatSummary; 26 import com.google.appengine.api.datastore.DatastoreService; 27 import com.google.appengine.api.datastore.DatastoreServiceFactory; 28 import com.google.appengine.api.datastore.Entity; 29 import com.google.appengine.api.datastore.Key; 30 import com.google.appengine.api.datastore.Query; 31 import java.io.IOException; 32 import java.math.RoundingMode; 33 import java.text.DecimalFormat; 34 import java.text.SimpleDateFormat; 35 import java.util.ArrayList; 36 import java.util.Date; 37 import java.util.HashSet; 38 import java.util.List; 39 import java.util.Set; 40 import javax.servlet.http.HttpServletRequest; 41 import javax.servlet.http.HttpServletResponse; 42 43 /** Represents the notifications service which is automatically called on a fixed schedule. */ 44 public class VtsPerformanceJobServlet extends BaseServlet { 45 private static final String MEAN = "Mean"; 46 private static final String MAX = "Max"; 47 private static final String MIN = "Min"; 48 private static final String MIN_DELTA = "ΔMin (%)"; 49 private static final String MAX_DELTA = "ΔMax (%)"; 50 private static final String HIGHER_IS_BETTER = 51 "Note: Higher values are better. Maximum is the best-case performance."; 52 private static final String LOWER_IS_BETTER = 53 "Note: Lower values are better. Minimum is the best-case performance."; 54 private static final String STD = "Std"; 55 private static final String SUBJECT_PREFIX = "Daily Performance Digest: "; 56 private static final String LAST_WEEK = "Last Week"; 57 private static final String LABEL_STYLE = "font-family: arial"; 58 private static final String SUBTEXT_STYLE = "font-family: arial; font-size: 12px"; 59 private static final String TABLE_STYLE = 60 "width: 100%; border-collapse: collapse; border: 1px solid black; font-size: 12px; font-family: arial;"; 61 private static final String SECTION_LABEL_STYLE = 62 "border: 1px solid black; border-bottom: none; background-color: lightgray;"; 63 private static final String COL_LABEL_STYLE = 64 "border: 1px solid black; border-bottom-width: 2px; border-top: 1px dotted gray; background-color: lightgray;"; 65 private static final String HEADER_COL_STYLE = 66 "border-top: 1px dotted gray; border-right: 2px solid black; text-align: right; background-color: lightgray;"; 67 private static final String INNER_CELL_STYLE = 68 "border-top: 1px dotted gray; border-right: 1px dotted gray; text-align: right;"; 69 private static final String OUTER_CELL_STYLE = 70 "border-top: 1px dotted gray; border-right: 2px solid black; text-align: right;"; 71 72 private static final DecimalFormat FORMATTER; 73 74 /** 75 * Initialize the decimal formatter. 76 */ 77 static { 78 FORMATTER = new DecimalFormat("#.##"); 79 FORMATTER.setRoundingMode(RoundingMode.HALF_UP); 80 } 81 82 @Override getNavbarLinks(HttpServletRequest request)83 public List<String[]> getNavbarLinks(HttpServletRequest request) { 84 return null; 85 } 86 87 /** 88 * Generates an HTML summary of the performance changes for the profiling results in the 89 * specified 90 * table. 91 * 92 * <p>Retrieves the past 24 hours of profiling data and compares it to the 24 hours that 93 * preceded 94 * it. Creates a table representation of the mean and standard deviation for each profiling 95 * point. 96 * When performance degrades, the cell is shaded red. 97 * 98 * @param testName The name of the test whose profiling data to summarize. 99 * @param perfSummaries List of PerformanceSummary objects for each profiling run (in reverse 100 * chronological order). 101 * @param labels List of string labels for use as the column headers. 102 * @returns An HTML string containing labeled table summaries. 103 */ getPeformanceSummary( String testName, List<PerformanceSummary> perfSummaries, List<String> labels)104 public static String getPeformanceSummary( 105 String testName, List<PerformanceSummary> perfSummaries, List<String> labels) { 106 if (perfSummaries.size() == 0) 107 return ""; 108 PerformanceSummary now = perfSummaries.get(0); 109 String tableHTML = "<p style='" + LABEL_STYLE + "'><b>"; 110 tableHTML += testName + "</b></p>"; 111 for (String profilingPoint : now.getProfilingPointNames()) { 112 ProfilingPointSummary summary = now.getProfilingPointSummary(profilingPoint); 113 tableHTML += "<table cellpadding='2' style='" + TABLE_STYLE + "'>"; 114 115 // Format header rows 116 String[] headerRows = new String[] {profilingPoint, summary.yLabel}; 117 int colspan = labels.size() * 4; 118 for (String content : headerRows) { 119 tableHTML += "<tr><td colspan='" + colspan + "'>" + content + "</td></tr>"; 120 } 121 122 // Format section labels 123 tableHTML += "<tr>"; 124 for (int i = 0; i < labels.size(); i++) { 125 String content = labels.get(i); 126 tableHTML += "<th style='" + SECTION_LABEL_STYLE + "' "; 127 if (i == 0) 128 tableHTML += "colspan='1'"; 129 else if (i == 1) 130 tableHTML += "colspan='3'"; 131 else 132 tableHTML += "colspan='4'"; 133 tableHTML += ">" + content + "</th>"; 134 } 135 tableHTML += "</tr>"; 136 137 String deltaString; 138 String bestCaseString; 139 String subtext; 140 switch (now.getProfilingPointSummary(profilingPoint).getRegressionMode()) { 141 case VTS_REGRESSION_MODE_DECREASING: 142 deltaString = MAX_DELTA; 143 bestCaseString = MAX; 144 subtext = HIGHER_IS_BETTER; 145 break; 146 default: 147 deltaString = MIN_DELTA; 148 bestCaseString = MIN; 149 subtext = LOWER_IS_BETTER; 150 break; 151 } 152 153 // Format column labels 154 tableHTML += "<tr>"; 155 for (int i = 0; i < labels.size(); i++) { 156 if (i > 1) { 157 tableHTML += "<th style='" + COL_LABEL_STYLE + "'>" + deltaString + "</th>"; 158 } 159 if (i == 0) { 160 tableHTML += "<th style='" + COL_LABEL_STYLE + "'>"; 161 tableHTML += summary.xLabel + "</th>"; 162 } else if (i > 0) { 163 tableHTML += "<th style='" + COL_LABEL_STYLE + "'>" + bestCaseString + "</th>"; 164 tableHTML += "<th style='" + COL_LABEL_STYLE + "'>" + MEAN + "</th>"; 165 tableHTML += "<th style='" + COL_LABEL_STYLE + "'>" + STD + "</th>"; 166 } 167 } 168 tableHTML += "</tr>"; 169 170 // Populate data cells 171 for (StatSummary stats : summary) { 172 String label = stats.getLabel(); 173 tableHTML += "<tr><td style='" + HEADER_COL_STYLE + "'>" + label; 174 tableHTML += "</td><td style='" + INNER_CELL_STYLE + "'>"; 175 tableHTML += FORMATTER.format(stats.getBestCase()) + "</td>"; 176 tableHTML += "<td style='" + INNER_CELL_STYLE + "'>"; 177 tableHTML += FORMATTER.format(stats.getMean()) + "</td>"; 178 tableHTML += "<td style='" + OUTER_CELL_STYLE + "'>"; 179 tableHTML += FORMATTER.format(stats.getStd()) + "</td>"; 180 for (int i = 1; i < perfSummaries.size(); i++) { 181 PerformanceSummary oldPerfSummary = perfSummaries.get(i); 182 if (oldPerfSummary.hasProfilingPoint(profilingPoint)) { 183 StatSummary baseline = 184 oldPerfSummary.getProfilingPointSummary(profilingPoint) 185 .getStatSummary(label); 186 tableHTML += PerformanceUtil.getBestCasePerformanceComparisonHTML( 187 baseline, stats, "", "", INNER_CELL_STYLE, OUTER_CELL_STYLE); 188 } else 189 tableHTML += "<td></td><td></td><td></td><td></td>"; 190 } 191 tableHTML += "</tr>"; 192 } 193 tableHTML += "</table>"; 194 tableHTML += "<i style='" + SUBTEXT_STYLE + "'>" + subtext + "</i><br><br>"; 195 } 196 return tableHTML; 197 } 198 199 @Override doGet(HttpServletRequest request, HttpServletResponse response)200 public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException { 201 doGetHandler(request, response); 202 } 203 204 @Override doGetHandler(HttpServletRequest request, HttpServletResponse response)205 public void doGetHandler(HttpServletRequest request, HttpServletResponse response) 206 throws IOException { 207 DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); 208 Set<Key> allTestKeys = new HashSet<>(); 209 210 Query q = new Query(TestEntity.KIND).setKeysOnly(); 211 for (Entity test : datastore.prepare(q).asIterable()) { 212 if (test.getKey().getName() == null) { 213 continue; 214 } 215 allTestKeys.add(test.getKey()); 216 } 217 218 // Add today to the list of time intervals to analyze 219 List<TimeInterval> timeIntervals = new ArrayList<>(); 220 long now = System.currentTimeMillis(); 221 String dateString = new SimpleDateFormat("MM-dd-yyyy").format(new Date(now)); 222 TimeInterval today = new TimeInterval(now - ONE_DAY / MILLI_TO_MICRO, now, dateString); 223 timeIntervals.add(today); 224 225 // Add yesterday as a baseline time interval for analysis 226 long oneDayAgo = now - ONE_DAY / MILLI_TO_MICRO; 227 String dateStringYesterday = new SimpleDateFormat("MM-dd-yyyy").format(new Date(oneDayAgo)); 228 TimeInterval yesterday = new TimeInterval( 229 oneDayAgo - ONE_DAY / MILLI_TO_MICRO, oneDayAgo, dateStringYesterday); 230 timeIntervals.add(yesterday); 231 232 // Add last week as a baseline time interval for analysis 233 long oneWeek = 7 * ONE_DAY / MILLI_TO_MICRO; 234 long oneWeekAgo = now - oneWeek; 235 TimeInterval lastWeek = new TimeInterval(oneWeekAgo - oneWeek, oneWeekAgo, LAST_WEEK); 236 timeIntervals.add(lastWeek); 237 238 for (Key testKey : allTestKeys) { 239 List<PerformanceSummary> perfSummaries = new ArrayList<>(); 240 List<String> labels = new ArrayList<>(); 241 labels.add(""); 242 for (TimeInterval interval : timeIntervals) { 243 PerformanceSummary perfSummary = new PerformanceSummary(); 244 PerformanceUtil.updatePerformanceSummary( 245 testKey.getName(), interval.start, interval.end, null, perfSummary); 246 if (perfSummary.size() == 0) { 247 continue; 248 } 249 perfSummaries.add(perfSummary); 250 labels.add(interval.label); 251 } 252 String body = getPeformanceSummary(testKey.getName(), perfSummaries, labels); 253 if (body == null || body.equals("")) { 254 continue; 255 } 256 List<String> emails = EmailHelper.getSubscriberEmails(testKey); 257 if (emails.size() == 0) { 258 continue; 259 } 260 String subject = SUBJECT_PREFIX + testKey.getName(); 261 EmailHelper.send(emails, subject, body); 262 } 263 } 264 } 265