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.DeviceInfoEntity; 20 import com.android.vts.entity.ProfilingPointRunEntity; 21 import com.android.vts.entity.TestEntity; 22 import com.android.vts.entity.TestRunEntity; 23 import com.android.vts.util.DatastoreHelper; 24 import com.android.vts.util.FilterUtil; 25 import com.android.vts.util.Graph; 26 import com.android.vts.util.GraphSerializer; 27 import com.android.vts.util.Histogram; 28 import com.android.vts.util.LineGraph; 29 import com.android.vts.util.PerformanceUtil; 30 import com.google.appengine.api.datastore.DatastoreService; 31 import com.google.appengine.api.datastore.DatastoreServiceFactory; 32 import com.google.appengine.api.datastore.Entity; 33 import com.google.appengine.api.datastore.EntityNotFoundException; 34 import com.google.appengine.api.datastore.Key; 35 import com.google.appengine.api.datastore.KeyFactory; 36 import com.google.appengine.api.datastore.PropertyProjection; 37 import com.google.appengine.api.datastore.Query; 38 import com.google.appengine.api.datastore.Query.Filter; 39 import com.google.gson.Gson; 40 import com.google.gson.GsonBuilder; 41 import java.io.IOException; 42 import java.util.ArrayList; 43 import java.util.Arrays; 44 import java.util.HashMap; 45 import java.util.HashSet; 46 import java.util.List; 47 import java.util.Map; 48 import java.util.Set; 49 import java.util.concurrent.TimeUnit; 50 import java.util.logging.Level; 51 import javax.servlet.RequestDispatcher; 52 import javax.servlet.ServletException; 53 import javax.servlet.http.HttpServletRequest; 54 import javax.servlet.http.HttpServletResponse; 55 import org.apache.commons.lang.StringUtils; 56 57 /** Servlet for handling requests to load graphs. */ 58 public class ShowGraphServlet extends BaseServlet { 59 private static final String GRAPH_JSP = "WEB-INF/jsp/show_graph.jsp"; 60 private static final long DEFAULT_FILTER_OPTION = -1; 61 62 private static final String HIDL_HAL_OPTION = "hidl_hal_mode"; 63 private static final String[] splitKeysArray = new String[] {HIDL_HAL_OPTION}; 64 private static final Set<String> splitKeySet = 65 new HashSet<String>(Arrays.asList(splitKeysArray)); 66 private static final String PROFILING_DATA_ALERT = "No profiling data was found."; 67 68 @Override getNavbarLinks(HttpServletRequest request)69 public List<String[]> getNavbarLinks(HttpServletRequest request) { 70 List<String[]> links = new ArrayList<>(); 71 Page root = Page.HOME; 72 String[] rootEntry = new String[] {root.getUrl(), root.getName()}; 73 links.add(rootEntry); 74 75 Page table = Page.TABLE; 76 String testName = request.getParameter("testName"); 77 String name = table.getName() + testName; 78 String url = table.getUrl() + "?testName=" + testName; 79 String[] tableEntry = new String[] {url, name}; 80 links.add(tableEntry); 81 82 Page graph = Page.GRAPH; 83 String profilingPointName = request.getParameter("profilingPoint"); 84 url = graph.getUrl() + "?testName=" + testName + "&profilingPoint=" + profilingPointName; 85 String[] graphEntry = new String[] {url, graph.getName()}; 86 links.add(graphEntry); 87 return links; 88 } 89 90 /** 91 * Process a profiling report message and add it to the map of graphs. 92 * 93 * @param profilingRun The Entity of a profiling point run to process. 94 * @param idString The ID derived from the test run to identify the profiling report. 95 * @param graphMap A map from graph name to Graph object. 96 */ processProfilingRun( Entity profilingRun, String idString, Map<String, Graph> graphMap)97 private static void processProfilingRun( 98 Entity profilingRun, String idString, Map<String, Graph> graphMap) { 99 ProfilingPointRunEntity pt = ProfilingPointRunEntity.fromEntity(profilingRun); 100 if (pt == null) 101 return; 102 String name = PerformanceUtil.getOptionAlias(profilingRun, splitKeySet); 103 Graph g = null; 104 if (pt.labels != null && pt.labels.size() == pt.values.size()) { 105 g = new LineGraph(name); 106 } else if (pt.labels == null && pt.values.size() > 0) { 107 g = new Histogram(name); 108 } else { 109 return; 110 } 111 if (!graphMap.containsKey(name)) { 112 graphMap.put(name, g); 113 } 114 graphMap.get(name).addData(idString, pt); 115 } 116 117 /** 118 * Get a summary string describing the devices in the test run. 119 * 120 * @param testRun The entity storing test run information. 121 * @param selectedDevice The name of the selected device. 122 * @return A string describing the devices in the test run, or null if it doesn't match filter. 123 */ getDeviceSummary(Entity testRun, String selectedDevice)124 private static String getDeviceSummary(Entity testRun, String selectedDevice) { 125 DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); 126 List<String> buildInfos = new ArrayList<>(); 127 Query deviceQuery = 128 new Query(DeviceInfoEntity.KIND) 129 .setAncestor(testRun.getKey()) 130 .addProjection( 131 new PropertyProjection(DeviceInfoEntity.BUILD_ID, String.class)) 132 .addProjection( 133 new PropertyProjection(DeviceInfoEntity.PRODUCT, String.class)); 134 boolean isSelectedDevice = selectedDevice == null; 135 for (Entity device : datastore.prepare(deviceQuery).asIterable()) { 136 String product = (String) device.getProperty(DeviceInfoEntity.PRODUCT); 137 if (selectedDevice != null && product.equals(selectedDevice)) { 138 isSelectedDevice = true; 139 } 140 String buildId = (String) device.getProperty(DeviceInfoEntity.BUILD_ID); 141 buildInfos.add(product + " (" + buildId + ")"); 142 } 143 return isSelectedDevice ? StringUtils.join(buildInfos, ", ") : null; 144 } 145 146 @Override doGetHandler(HttpServletRequest request, HttpServletResponse response)147 public void doGetHandler(HttpServletRequest request, HttpServletResponse response) 148 throws IOException { 149 RequestDispatcher dispatcher = null; 150 DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); 151 String testName = request.getParameter("testName"); 152 String profilingPointName = request.getParameter("profilingPoint"); 153 String selectedDevice = request.getParameter("device"); 154 Long endTime = null; 155 if (request.getParameter("endTime") != null) { 156 String time = request.getParameter("endTime"); 157 try { 158 endTime = Long.parseLong(time); 159 } catch (NumberFormatException e) { 160 } 161 } 162 if (endTime == null) { 163 endTime = System.currentTimeMillis() * MILLI_TO_MICRO; 164 } 165 Long startTime = endTime - TimeUnit.DAYS.toMicros(1); 166 167 // Set of device names 168 List<String> devices = DatastoreHelper.getAllProducts(testName); 169 if (!devices.contains(selectedDevice)) 170 selectedDevice = null; 171 172 Map<String, Graph> graphMap = new HashMap<>(); 173 174 // Create a query for test runs matching the time window filter 175 Key parentKey = KeyFactory.createKey(TestEntity.KIND, testName); 176 Filter timeFilter = FilterUtil.getTimeFilter(parentKey, startTime, endTime); 177 Query testRunQuery = new Query(TestRunEntity.KIND) 178 .setAncestor(parentKey) 179 .setFilter(timeFilter) 180 .setKeysOnly(); 181 182 // Process the test runs in the query 183 for (Entity testRun : datastore.prepare(testRunQuery).asIterable()) { 184 String buildInfoString = getDeviceSummary(testRun, selectedDevice); 185 if (buildInfoString == null) { 186 continue; 187 } 188 189 try { 190 Entity profilingRun = datastore.get(KeyFactory.createKey( 191 testRun.getKey(), ProfilingPointRunEntity.KIND, profilingPointName)); 192 processProfilingRun(profilingRun, buildInfoString, graphMap); 193 } catch (EntityNotFoundException e) { 194 // Profiling point not collected during this test run 195 continue; 196 } 197 } 198 // Get the names of the graphs to render 199 String[] names = graphMap.keySet().toArray(new String[graphMap.size()]); 200 Arrays.sort(names); 201 202 List<Graph> graphList = new ArrayList<>(); 203 boolean hasHistogram = false; 204 for (String name : names) { 205 Graph g = graphMap.get(name); 206 if (g.size() > 0) { 207 graphList.add(g); 208 if (g instanceof Histogram) 209 hasHistogram = true; 210 } 211 } 212 213 String filterVal = request.getParameter("filterVal"); 214 try { 215 Long.parseLong(filterVal); 216 } catch (NumberFormatException e) { 217 filterVal = Long.toString(DEFAULT_FILTER_OPTION); 218 } 219 request.setAttribute("testName", request.getParameter("testName")); 220 request.setAttribute("filterVal", filterVal); 221 request.setAttribute("endTime", new Gson().toJson(endTime)); 222 request.setAttribute("devices", devices); 223 request.setAttribute("selectedDevice", selectedDevice); 224 request.setAttribute("showFilterDropdown", hasHistogram); 225 if (graphList.size() == 0) 226 request.setAttribute("error", PROFILING_DATA_ALERT); 227 228 Gson gson = new GsonBuilder() 229 .registerTypeHierarchyAdapter(Graph.class, new GraphSerializer()) 230 .create(); 231 request.setAttribute("graphs", gson.toJson(graphList)); 232 233 request.setAttribute("profilingPointName", profilingPointName); 234 dispatcher = request.getRequestDispatcher(GRAPH_JSP); 235 try { 236 dispatcher.forward(request, response); 237 } catch (ServletException e) { 238 logger.log(Level.SEVERE, "Servlet Excpetion caught : ", e); 239 } 240 } 241 } 242