1 /*
2  * Copyright (c) 2017 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.entity;
18 
19 import com.android.vts.proto.VtsReportMessage.ProfilingReportMessage;
20 import com.android.vts.proto.VtsReportMessage.VtsProfilingRegressionMode;
21 import com.android.vts.proto.VtsReportMessage.VtsProfilingType;
22 import com.google.appengine.api.datastore.Entity;
23 import com.google.appengine.api.datastore.Key;
24 import com.google.appengine.api.datastore.KeyFactory;
25 import com.google.common.collect.Lists;
26 import com.google.protobuf.ByteString;
27 import com.googlecode.objectify.annotation.Cache;
28 import com.googlecode.objectify.annotation.Id;
29 import com.googlecode.objectify.annotation.Ignore;
30 import com.googlecode.objectify.annotation.Index;
31 import com.googlecode.objectify.annotation.Parent;
32 import java.util.ArrayList;
33 import java.util.Date;
34 import java.util.List;
35 import java.util.Objects;
36 import lombok.Data;
37 import lombok.NoArgsConstructor;
38 import lombok.extern.log4j.Log4j2;
39 
40 import static com.googlecode.objectify.ObjectifyService.ofy;
41 
42 @com.googlecode.objectify.annotation.Entity(name = "ProfilingPointRun")
43 @Cache
44 @Data
45 @NoArgsConstructor
46 @Log4j2
47 /** Entity describing a profiling point execution. */
48 public class ProfilingPointRunEntity implements DashboardEntity {
49 
50     public static final String KIND = "ProfilingPointRun";
51 
52     // Property keys
53     public static final String TYPE = "type";
54     public static final String REGRESSION_MODE = "regressionMode";
55     public static final String LABELS = "labels";
56     public static final String VALUES = "values";
57     public static final String X_LABEL = "xLabel";
58     public static final String Y_LABEL = "yLabel";
59     public static final String OPTIONS = "options";
60 
61     /** This value will set the limit size of values array field */
62     public static final int VALUE_SIZE_LIMIT = 50000;
63 
64     @Ignore
65     private Key key;
66 
67     /** ID field using profilingPointName */
68     @Id
69     private String name;
70 
71     /** parent field based on Test and TestRun key */
72     @Parent
73     private com.googlecode.objectify.Key<?> parent;
74 
75     /** VtsProfilingType in ProfilingPointRunEntity class  */
76     private int type;
77 
78     /** VtsProfilingType in ProfilingPointRunEntity class  */
79     private int regressionMode;
80 
81     /** list of label name  */
82     private List<String> labels;
83 
84     /** list of values  */
85     private List<Long> values;
86 
87     /** X axis label name */
88     private String xLabel;
89 
90     /** Y axis label name */
91     private String yLabel;
92 
93     /** Test Suite file name field */
94     private List<String> options;
95 
96     /** When this record was created or updated */
97     @Index Date updated;
98 
99     /**
100      * Create a ProfilingPointRunEntity object.
101      *
102      * @param parentKey The Key object for the parent TestRunEntity in datastore.
103      * @param name The name of the profiling point.
104      * @param type The (number) type of the profiling point data.
105      * @param regressionMode The (number) mode to use for detecting regression.
106      * @param labels List of data labels, or null if the data is unlabeled.
107      * @param values List of data values.
108      * @param xLabel The x axis label.
109      * @param yLabel The y axis label.
110      * @param options The list of key=value options for the profiling point run.
111      */
ProfilingPointRunEntity( Key parentKey, String name, int type, int regressionMode, List<String> labels, List<Long> values, String xLabel, String yLabel, List<String> options)112     public ProfilingPointRunEntity(
113             Key parentKey,
114             String name,
115             int type,
116             int regressionMode,
117             List<String> labels,
118             List<Long> values,
119             String xLabel,
120             String yLabel,
121             List<String> options) {
122         this.key = KeyFactory.createKey(parentKey, KIND, name);
123         this.name = name;
124         this.type = type;
125         this.regressionMode = regressionMode;
126         this.labels = labels == null ? null : new ArrayList<>(labels);
127         this.values = new ArrayList<>(values);
128         this.xLabel = xLabel;
129         this.yLabel = yLabel;
130         this.options = options;
131         this.updated = new Date();
132     }
133 
134 
135     /**
136      * Create a ProfilingPointRunEntity object.
137      *
138      * @param parent The objectify Key for the parent TestRunEntity in datastore.
139      * @param name The name of the profiling point.
140      * @param type The (number) type of the profiling point data.
141      * @param regressionMode The (number) mode to use for detecting regression.
142      * @param labels List of data labels, or null if the data is unlabeled.
143      * @param values List of data values.
144      * @param xLabel The x axis label.
145      * @param yLabel The y axis label.
146      * @param options The list of key=value options for the profiling point run.
147      */
ProfilingPointRunEntity( com.googlecode.objectify.Key parent, String name, int type, int regressionMode, List<String> labels, List<Long> values, String xLabel, String yLabel, List<String> options)148     public ProfilingPointRunEntity(
149             com.googlecode.objectify.Key parent,
150             String name,
151             int type,
152             int regressionMode,
153             List<String> labels,
154             List<Long> values,
155             String xLabel,
156             String yLabel,
157             List<String> options) {
158         this.parent = parent;
159         this.name = name;
160         this.type = type;
161         this.regressionMode = regressionMode;
162         this.labels = labels == null ? null : new ArrayList<>(labels);
163         this.values = new ArrayList<>(values);
164         this.xLabel = xLabel;
165         this.yLabel = yLabel;
166         this.options = options;
167         this.updated = new Date();
168     }
169 
170     /**
171      * Get VtsProfilingType from int value.
172      *
173      * @return VtsProfilingType class.
174      */
getVtsProfilingType(int type)175     public VtsProfilingType getVtsProfilingType(int type) {
176         return VtsProfilingType.forNumber(type);
177     }
178 
179     /**
180      * Get VtsProfilingRegressionMode from int value.
181      *
182      * @return VtsProfilingType class.
183      */
getVtsProfilingRegressionMode(int regressionMode)184     public VtsProfilingRegressionMode getVtsProfilingRegressionMode(int regressionMode) {
185         return VtsProfilingRegressionMode.forNumber(regressionMode);
186     }
187 
188     /**
189      * Save multi rows function when the record exceed the limit which is 1MB.
190      *
191      * @return ProfilingPointRunEntity's key value.
192      */
saveMultiRow()193     public com.googlecode.objectify.Key<ProfilingPointRunEntity> saveMultiRow() {
194         if (this.getValues().size() > VALUE_SIZE_LIMIT) {
195 
196             List<List<Long>> partitionedValueList =
197                     Lists.partition(this.getValues(), VALUE_SIZE_LIMIT);
198             int partitionedValueListSize = partitionedValueList.size();
199 
200             List<List<String>> partitionedLabelList = new ArrayList<>();
201             if (Objects.nonNull(this.getLabels()) && this.getLabels().size() > VALUE_SIZE_LIMIT) {
202                 partitionedLabelList = Lists.partition(this.getLabels(), VALUE_SIZE_LIMIT);
203             }
204 
205             com.googlecode.objectify.Key<ProfilingPointRunEntity> profilingPointRunEntityKey = null;
206             if (partitionedValueListSize < VALUE_SIZE_LIMIT) {
207                 for (int index = 0; index < partitionedValueListSize; index++) {
208                     if (index > 0) {
209                         this.values.addAll(partitionedValueList.get(index));
210                         if (index < partitionedLabelList.size()) {
211                             this.labels.addAll(partitionedLabelList.get(index));
212                         }
213                     } else {
214                         this.values = partitionedValueList.get(index);
215                         if (index < partitionedLabelList.size()) {
216                             this.labels = partitionedLabelList.get(index);
217                         }
218                     }
219                     profilingPointRunEntityKey = ofy().save().entity(this).now();
220                 }
221             }
222             return profilingPointRunEntityKey;
223         } else {
224             return ofy().save().entity(this).now();
225         }
226     }
227 
228     /** Saving function for the instance of this class */
229     @Override
save()230     public com.googlecode.objectify.Key<ProfilingPointRunEntity> save() {
231         return ofy().save().entity(this).now();
232     }
233 
toEntity()234     public Entity toEntity() {
235         Entity profilingRun = new Entity(this.key);
236         profilingRun.setUnindexedProperty(TYPE, this.type);
237         profilingRun.setUnindexedProperty(REGRESSION_MODE, this.regressionMode);
238         if (this.labels != null) {
239             profilingRun.setUnindexedProperty(LABELS, this.labels);
240         }
241         profilingRun.setUnindexedProperty(VALUES, this.values);
242         profilingRun.setUnindexedProperty(X_LABEL, this.xLabel);
243         profilingRun.setUnindexedProperty(Y_LABEL, this.yLabel);
244         if (this.options != null) {
245             profilingRun.setUnindexedProperty(OPTIONS, this.options);
246         }
247 
248         return profilingRun;
249     }
250 
251     /**
252      * Convert an Entity object to a ProflilingPointRunEntity.
253      *
254      * @param e The entity to process.
255      * @return ProfilingPointRunEntity object with the properties from e, or null if incompatible.
256      */
257     @SuppressWarnings("unchecked")
fromEntity(Entity e)258     public static ProfilingPointRunEntity fromEntity(Entity e) {
259         if (!e.getKind().equals(KIND)
260                 || e.getKey().getName() == null
261                 || !e.hasProperty(TYPE)
262                 || !e.hasProperty(REGRESSION_MODE)
263                 || !e.hasProperty(VALUES)
264                 || !e.hasProperty(X_LABEL)
265                 || !e.hasProperty(Y_LABEL)) {
266             log.error("Missing profiling point attributes in entity: " + e.toString());
267             return null;
268         }
269         try {
270             Key parentKey = e.getParent();
271             String name = e.getKey().getName();
272             int type = (int) (long) e.getProperty(TYPE);
273             int regressionMode = (int) (long) e.getProperty(REGRESSION_MODE);
274             List<Long> values = (List<Long>) e.getProperty(VALUES);
275             String xLabel = (String) e.getProperty(X_LABEL);
276             String yLabel = (String) e.getProperty(Y_LABEL);
277             List<String> labels = null;
278             if (e.hasProperty(LABELS)) {
279                 labels = (List<String>) e.getProperty(LABELS);
280             }
281             List<String> options = null;
282             if (e.hasProperty(OPTIONS)) {
283                 options = (List<String>) e.getProperty(OPTIONS);
284             }
285             return new ProfilingPointRunEntity(
286                     parentKey, name, type, regressionMode, labels, values, xLabel, yLabel, options);
287         } catch (ClassCastException exception) {
288             // Invalid cast
289             log.warn("Error parsing profiling point run entity.", exception);
290         }
291         return null;
292     }
293 
294     /**
295      * Convert a coverage report to a CoverageEntity.
296      *
297      * @param parent The ancestor objectify key for the coverage entity.
298      * @param profilingReport The profiling report containing profiling data.
299      * @return The ProfilingPointRunEntity for the profiling report message, or null if incompatible
300      */
fromProfilingReport( com.googlecode.objectify.Key parent, ProfilingReportMessage profilingReport)301     public static ProfilingPointRunEntity fromProfilingReport(
302             com.googlecode.objectify.Key parent, ProfilingReportMessage profilingReport) {
303         if (!profilingReport.hasName()
304                 || !profilingReport.hasType()
305                 || profilingReport.getType() == VtsProfilingType.UNKNOWN_VTS_PROFILING_TYPE
306                 || !profilingReport.hasRegressionMode()
307                 || !profilingReport.hasXAxisLabel()
308                 || !profilingReport.hasYAxisLabel()) {
309             return null; // invalid profiling report;
310         }
311         String name = profilingReport.getName().toStringUtf8();
312         VtsProfilingType type = profilingReport.getType();
313         VtsProfilingRegressionMode regressionMode = profilingReport.getRegressionMode();
314         String xLabel = profilingReport.getXAxisLabel().toStringUtf8();
315         String yLabel = profilingReport.getYAxisLabel().toStringUtf8();
316         List<Long> values;
317         List<String> labels = null;
318         switch (type) {
319             case VTS_PROFILING_TYPE_TIMESTAMP:
320                 if (!profilingReport.hasStartTimestamp()
321                         || !profilingReport.hasEndTimestamp()
322                         || profilingReport.getEndTimestamp()
323                                 < profilingReport.getStartTimestamp()) {
324                     return null; // missing timestamp
325                 }
326                 long value =
327                         profilingReport.getEndTimestamp() - profilingReport.getStartTimestamp();
328                 values = new ArrayList<>();
329                 values.add(value);
330                 break;
331             case VTS_PROFILING_TYPE_LABELED_VECTOR:
332                 if (profilingReport.getValueCount() != profilingReport.getLabelCount()) {
333                     return null; // jagged data
334                 }
335                 labels = new ArrayList<>();
336                 for (ByteString label : profilingReport.getLabelList()) {
337                     labels.add(label.toStringUtf8());
338                 }
339                 values = profilingReport.getValueList();
340                 break;
341             case VTS_PROFILING_TYPE_UNLABELED_VECTOR:
342                 values = profilingReport.getValueList();
343                 break;
344             default: // should never happen
345                 return null;
346         }
347         if (values.size() > VALUE_SIZE_LIMIT) {
348             values = values.subList(0, VALUE_SIZE_LIMIT);
349         }
350         List<String> options = null;
351         if (profilingReport.getOptionsCount() > 0) {
352             options = new ArrayList<>();
353             for (ByteString optionBytes : profilingReport.getOptionsList()) {
354                 options.add(optionBytes.toStringUtf8());
355             }
356         }
357         return new ProfilingPointRunEntity(
358                 parent,
359                 name,
360                 type.getNumber(),
361                 regressionMode.getNumber(),
362                 labels,
363                 values,
364                 xLabel,
365                 yLabel,
366                 options);
367     }
368 }
369