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