/* * Copyright (c) 2017 Google Inc. All Rights Reserved. * * Licensed under the Apache License, Version 2.0 (the "License"); you * may not use this file except in compliance with the License. You may * obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * implied. See the License for the specific language governing * permissions and limitations under the License. */ package com.android.vts.entity; import com.android.vts.proto.VtsReportMessage.ProfilingReportMessage; import com.android.vts.proto.VtsReportMessage.VtsProfilingRegressionMode; import com.android.vts.proto.VtsReportMessage.VtsProfilingType; import com.google.appengine.api.datastore.Entity; import com.google.appengine.api.datastore.Key; import com.google.appengine.api.datastore.KeyFactory; import com.google.common.collect.Lists; import com.google.protobuf.ByteString; import com.googlecode.objectify.annotation.Cache; import com.googlecode.objectify.annotation.Id; import com.googlecode.objectify.annotation.Ignore; import com.googlecode.objectify.annotation.Index; import com.googlecode.objectify.annotation.Parent; import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Objects; import lombok.Data; import lombok.NoArgsConstructor; import lombok.extern.log4j.Log4j2; import static com.googlecode.objectify.ObjectifyService.ofy; @com.googlecode.objectify.annotation.Entity(name = "ProfilingPointRun") @Cache @Data @NoArgsConstructor @Log4j2 /** Entity describing a profiling point execution. */ public class ProfilingPointRunEntity implements DashboardEntity { public static final String KIND = "ProfilingPointRun"; // Property keys public static final String TYPE = "type"; public static final String REGRESSION_MODE = "regressionMode"; public static final String LABELS = "labels"; public static final String VALUES = "values"; public static final String X_LABEL = "xLabel"; public static final String Y_LABEL = "yLabel"; public static final String OPTIONS = "options"; /** This value will set the limit size of values array field */ public static final int VALUE_SIZE_LIMIT = 50000; @Ignore private Key key; /** ID field using profilingPointName */ @Id private String name; /** parent field based on Test and TestRun key */ @Parent private com.googlecode.objectify.Key parent; /** VtsProfilingType in ProfilingPointRunEntity class */ private int type; /** VtsProfilingType in ProfilingPointRunEntity class */ private int regressionMode; /** list of label name */ private List labels; /** list of values */ private List values; /** X axis label name */ private String xLabel; /** Y axis label name */ private String yLabel; /** Test Suite file name field */ private List options; /** When this record was created or updated */ @Index Date updated; /** * Create a ProfilingPointRunEntity object. * * @param parentKey The Key object for the parent TestRunEntity in datastore. * @param name The name of the profiling point. * @param type The (number) type of the profiling point data. * @param regressionMode The (number) mode to use for detecting regression. * @param labels List of data labels, or null if the data is unlabeled. * @param values List of data values. * @param xLabel The x axis label. * @param yLabel The y axis label. * @param options The list of key=value options for the profiling point run. */ public ProfilingPointRunEntity( Key parentKey, String name, int type, int regressionMode, List labels, List values, String xLabel, String yLabel, List options) { this.key = KeyFactory.createKey(parentKey, KIND, name); this.name = name; this.type = type; this.regressionMode = regressionMode; this.labels = labels == null ? null : new ArrayList<>(labels); this.values = new ArrayList<>(values); this.xLabel = xLabel; this.yLabel = yLabel; this.options = options; this.updated = new Date(); } /** * Create a ProfilingPointRunEntity object. * * @param parent The objectify Key for the parent TestRunEntity in datastore. * @param name The name of the profiling point. * @param type The (number) type of the profiling point data. * @param regressionMode The (number) mode to use for detecting regression. * @param labels List of data labels, or null if the data is unlabeled. * @param values List of data values. * @param xLabel The x axis label. * @param yLabel The y axis label. * @param options The list of key=value options for the profiling point run. */ public ProfilingPointRunEntity( com.googlecode.objectify.Key parent, String name, int type, int regressionMode, List labels, List values, String xLabel, String yLabel, List options) { this.parent = parent; this.name = name; this.type = type; this.regressionMode = regressionMode; this.labels = labels == null ? null : new ArrayList<>(labels); this.values = new ArrayList<>(values); this.xLabel = xLabel; this.yLabel = yLabel; this.options = options; this.updated = new Date(); } /** * Get VtsProfilingType from int value. * * @return VtsProfilingType class. */ public VtsProfilingType getVtsProfilingType(int type) { return VtsProfilingType.forNumber(type); } /** * Get VtsProfilingRegressionMode from int value. * * @return VtsProfilingType class. */ public VtsProfilingRegressionMode getVtsProfilingRegressionMode(int regressionMode) { return VtsProfilingRegressionMode.forNumber(regressionMode); } /** * Save multi rows function when the record exceed the limit which is 1MB. * * @return ProfilingPointRunEntity's key value. */ public com.googlecode.objectify.Key saveMultiRow() { if (this.getValues().size() > VALUE_SIZE_LIMIT) { List> partitionedValueList = Lists.partition(this.getValues(), VALUE_SIZE_LIMIT); int partitionedValueListSize = partitionedValueList.size(); List> partitionedLabelList = new ArrayList<>(); if (Objects.nonNull(this.getLabels()) && this.getLabels().size() > VALUE_SIZE_LIMIT) { partitionedLabelList = Lists.partition(this.getLabels(), VALUE_SIZE_LIMIT); } com.googlecode.objectify.Key profilingPointRunEntityKey = null; if (partitionedValueListSize < VALUE_SIZE_LIMIT) { for (int index = 0; index < partitionedValueListSize; index++) { if (index > 0) { this.values.addAll(partitionedValueList.get(index)); if (index < partitionedLabelList.size()) { this.labels.addAll(partitionedLabelList.get(index)); } } else { this.values = partitionedValueList.get(index); if (index < partitionedLabelList.size()) { this.labels = partitionedLabelList.get(index); } } profilingPointRunEntityKey = ofy().save().entity(this).now(); } } return profilingPointRunEntityKey; } else { return ofy().save().entity(this).now(); } } /** Saving function for the instance of this class */ @Override public com.googlecode.objectify.Key save() { return ofy().save().entity(this).now(); } public Entity toEntity() { Entity profilingRun = new Entity(this.key); profilingRun.setUnindexedProperty(TYPE, this.type); profilingRun.setUnindexedProperty(REGRESSION_MODE, this.regressionMode); if (this.labels != null) { profilingRun.setUnindexedProperty(LABELS, this.labels); } profilingRun.setUnindexedProperty(VALUES, this.values); profilingRun.setUnindexedProperty(X_LABEL, this.xLabel); profilingRun.setUnindexedProperty(Y_LABEL, this.yLabel); if (this.options != null) { profilingRun.setUnindexedProperty(OPTIONS, this.options); } return profilingRun; } /** * Convert an Entity object to a ProflilingPointRunEntity. * * @param e The entity to process. * @return ProfilingPointRunEntity object with the properties from e, or null if incompatible. */ @SuppressWarnings("unchecked") public static ProfilingPointRunEntity fromEntity(Entity e) { if (!e.getKind().equals(KIND) || e.getKey().getName() == null || !e.hasProperty(TYPE) || !e.hasProperty(REGRESSION_MODE) || !e.hasProperty(VALUES) || !e.hasProperty(X_LABEL) || !e.hasProperty(Y_LABEL)) { log.error("Missing profiling point attributes in entity: " + e.toString()); return null; } try { Key parentKey = e.getParent(); String name = e.getKey().getName(); int type = (int) (long) e.getProperty(TYPE); int regressionMode = (int) (long) e.getProperty(REGRESSION_MODE); List values = (List) e.getProperty(VALUES); String xLabel = (String) e.getProperty(X_LABEL); String yLabel = (String) e.getProperty(Y_LABEL); List labels = null; if (e.hasProperty(LABELS)) { labels = (List) e.getProperty(LABELS); } List options = null; if (e.hasProperty(OPTIONS)) { options = (List) e.getProperty(OPTIONS); } return new ProfilingPointRunEntity( parentKey, name, type, regressionMode, labels, values, xLabel, yLabel, options); } catch (ClassCastException exception) { // Invalid cast log.warn("Error parsing profiling point run entity.", exception); } return null; } /** * Convert a coverage report to a CoverageEntity. * * @param parent The ancestor objectify key for the coverage entity. * @param profilingReport The profiling report containing profiling data. * @return The ProfilingPointRunEntity for the profiling report message, or null if incompatible */ public static ProfilingPointRunEntity fromProfilingReport( com.googlecode.objectify.Key parent, ProfilingReportMessage profilingReport) { if (!profilingReport.hasName() || !profilingReport.hasType() || profilingReport.getType() == VtsProfilingType.UNKNOWN_VTS_PROFILING_TYPE || !profilingReport.hasRegressionMode() || !profilingReport.hasXAxisLabel() || !profilingReport.hasYAxisLabel()) { return null; // invalid profiling report; } String name = profilingReport.getName().toStringUtf8(); VtsProfilingType type = profilingReport.getType(); VtsProfilingRegressionMode regressionMode = profilingReport.getRegressionMode(); String xLabel = profilingReport.getXAxisLabel().toStringUtf8(); String yLabel = profilingReport.getYAxisLabel().toStringUtf8(); List values; List labels = null; switch (type) { case VTS_PROFILING_TYPE_TIMESTAMP: if (!profilingReport.hasStartTimestamp() || !profilingReport.hasEndTimestamp() || profilingReport.getEndTimestamp() < profilingReport.getStartTimestamp()) { return null; // missing timestamp } long value = profilingReport.getEndTimestamp() - profilingReport.getStartTimestamp(); values = new ArrayList<>(); values.add(value); break; case VTS_PROFILING_TYPE_LABELED_VECTOR: if (profilingReport.getValueCount() != profilingReport.getLabelCount()) { return null; // jagged data } labels = new ArrayList<>(); for (ByteString label : profilingReport.getLabelList()) { labels.add(label.toStringUtf8()); } values = profilingReport.getValueList(); break; case VTS_PROFILING_TYPE_UNLABELED_VECTOR: values = profilingReport.getValueList(); break; default: // should never happen return null; } if (values.size() > VALUE_SIZE_LIMIT) { values = values.subList(0, VALUE_SIZE_LIMIT); } List options = null; if (profilingReport.getOptionsCount() > 0) { options = new ArrayList<>(); for (ByteString optionBytes : profilingReport.getOptionsList()) { options.add(optionBytes.toStringUtf8()); } } return new ProfilingPointRunEntity( parent, name, type.getNumber(), regressionMode.getNumber(), labels, values, xLabel, yLabel, options); } }