1 /* 2 * Copyright (C) 2018 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may 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 implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.android.game.qualification.metric; 17 18 import com.android.annotations.Nullable; 19 import com.android.game.qualification.CertificationRequirements; 20 import com.android.tradefed.device.metric.DeviceMetricData; 21 import com.android.tradefed.invoker.IInvocationContext; 22 import com.android.tradefed.metrics.proto.MetricMeasurement.DataType; 23 import com.android.tradefed.metrics.proto.MetricMeasurement.Measurements; 24 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric; 25 26 import com.google.common.base.Preconditions; 27 28 import java.util.ArrayList; 29 import java.util.HashMap; 30 import java.util.LinkedHashMap; 31 import java.util.List; 32 import java.util.Map; 33 import java.util.Objects; 34 import java.util.stream.Collectors; 35 36 /** 37 * Summary of frame time metrics data. 38 */ 39 public class MetricSummary { 40 public enum TimeType { 41 PRESENT, 42 READY 43 } 44 45 private int loopCount; 46 private long loadTimeMs; 47 private Map<TimeType, List<LoopSummary>> summaries; 48 MetricSummary( int loopCount, long loadTimeMs, Map<TimeType, List<LoopSummary>> summaries)49 private MetricSummary( 50 int loopCount, 51 long loadTimeMs, 52 Map<TimeType, List<LoopSummary>> summaries) { 53 this.loopCount = loopCount; 54 this.loadTimeMs = loadTimeMs; 55 this.summaries = summaries; 56 } 57 58 @Nullable parseRunMetrics( IInvocationContext context, HashMap<String, Metric> metrics)59 public static MetricSummary parseRunMetrics( 60 IInvocationContext context, HashMap<String, Metric> metrics) { 61 int loopCount = 0; 62 if (metrics.containsKey("loop_count")) { 63 loopCount = (int) metrics.get("loop_count").getMeasurements().getSingleInt(); 64 } 65 66 if (loopCount == 0) { 67 return null; 68 } 69 70 Map<TimeType, List<LoopSummary>> summaries = new LinkedHashMap<>(); 71 for (TimeType type : TimeType.values()) { 72 summaries.put(type, new ArrayList<>()); 73 for (int i = 0; i < loopCount; i++) { 74 LoopSummary loopSummary = LoopSummary.parseRunMetrics(context, type, i, metrics); 75 summaries.get(type).add(loopSummary); 76 } 77 } 78 return new MetricSummary( 79 loopCount, 80 metrics.get("load_time").getMeasurements().getSingleInt(), 81 summaries); 82 } 83 getLoadTimeMs()84 public long getLoadTimeMs() { 85 return loadTimeMs; 86 } 87 getLoopSummaries()88 public List<LoopSummary> getLoopSummaries() { 89 return summaries.get(TimeType.PRESENT); 90 } 91 addToMetricData(DeviceMetricData runData)92 public void addToMetricData(DeviceMetricData runData) { 93 runData.addMetric( 94 "loop_count", 95 Metric.newBuilder() 96 .setType(DataType.PROCESSED) 97 .setMeasurements(Measurements.newBuilder().setSingleInt(loopCount))); 98 runData.addMetric( 99 "load_time", 100 Metric.newBuilder() 101 .setType(DataType.RAW) 102 .setMeasurements(Measurements.newBuilder().setSingleInt(loadTimeMs))); 103 104 for (int i = 0; i < loopCount; i++) { 105 for (TimeType type : TimeType.values()) { 106 LoopSummary summary = summaries.get(type).get(i); 107 summary.addToMetricData(runData, i, type); 108 } 109 } 110 } 111 112 @Override equals(Object o)113 public boolean equals(Object o) { 114 if (this == o) return true; 115 if (o == null || getClass() != o.getClass()) return false; 116 MetricSummary summary = (MetricSummary) o; 117 return loopCount == summary.loopCount && 118 loadTimeMs == summary.loadTimeMs && 119 Objects.equals(summaries, summary.summaries); 120 } 121 122 @Override hashCode()123 public int hashCode() { 124 return Objects.hash(loopCount, loadTimeMs, summaries); 125 } 126 toString()127 public String toString() { 128 StringBuilder sb = new StringBuilder(); 129 // Report primary metrics. 130 sb.append("Summary\n"); 131 sb.append("-------\n"); 132 sb.append("Load time: "); 133 if (getLoadTimeMs() == -1) { 134 sb.append("unknown"); 135 } else { 136 sb.append(getLoadTimeMs()); 137 sb.append(" ms\n"); 138 } 139 sb.append("\n"); 140 141 // Report secondary metrics. 142 sb.append("Details\n"); 143 sb.append("-------\n"); 144 145 146 for (int i = 0; i < loopCount; i++) { 147 if (summaries.get(TimeType.PRESENT).get(i).getCount() == 0) { 148 continue; 149 } 150 sb.append("Loop "); 151 sb.append(i); 152 sb.append('\n'); 153 for (TimeType type : TimeType.values()) { 154 sb.append(type); 155 sb.append(" Time Statistics\n"); 156 sb.append(summaries.get(type).get(i)); 157 sb.append("\n"); 158 } 159 } 160 return sb.toString(); 161 } 162 msToNs(float value)163 private static long msToNs(float value) { 164 return (long) (value * 1e6f); 165 } 166 167 public static class Builder { 168 @Nullable 169 private CertificationRequirements mRequirements; 170 private long mVSyncPeriodNs; 171 private int loopCount = 0; 172 private long loadTimeMs = -1; 173 private Map<TimeType, List<LoopSummary.Builder>> summaries = new LinkedHashMap<>(); 174 Builder(@ullable CertificationRequirements requirements, long vSyncPeriodNs)175 public Builder(@Nullable CertificationRequirements requirements, long vSyncPeriodNs) { 176 mRequirements = requirements; 177 mVSyncPeriodNs = vSyncPeriodNs; 178 for (TimeType type : TimeType.values()) { 179 summaries.put(type, new ArrayList<>()); 180 } 181 } 182 getLatestSummary(TimeType type)183 private LoopSummary.Builder getLatestSummary(TimeType type) { 184 Preconditions.checkState(loopCount > 0, "First loop has not been started."); 185 List<LoopSummary.Builder> list = summaries.get(type); 186 return list.get(list.size() - 1); 187 } 188 setLoadTimeMs(long loadTimeMs)189 public void setLoadTimeMs(long loadTimeMs) { 190 this.loadTimeMs = loadTimeMs; 191 } 192 addFrameTime(TimeType type, long frameTimeNs)193 public void addFrameTime(TimeType type, long frameTimeNs) { 194 LoopSummary.Builder summary = getLatestSummary(type); 195 summary.addFrameTime(frameTimeNs); 196 } 197 beginLoop()198 public void beginLoop() { 199 loopCount++; 200 for (TimeType type : TimeType.values()) { 201 summaries.get(type).add(new LoopSummary.Builder(mRequirements, mVSyncPeriodNs)); 202 } 203 } 204 endLoop()205 public void endLoop() { 206 // Do nothing. 207 } 208 build()209 public MetricSummary build() { 210 Map<TimeType, List<LoopSummary>> summaryMap = new HashMap<>(); 211 for (Map.Entry<TimeType, List<LoopSummary.Builder>> entry : summaries.entrySet()) { 212 summaryMap.put( 213 entry.getKey(), 214 entry.getValue().stream() 215 .map(LoopSummary.Builder::build) 216 .collect(Collectors.toList())); 217 } 218 return new MetricSummary(loopCount, loadTimeMs, summaryMap); 219 } 220 } 221 } 222