• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 package com.android.game.qualification.metric;
2 
3 import com.android.annotations.Nullable;
4 import com.android.game.qualification.CertificationRequirements;
5 import com.android.tradefed.device.metric.DeviceMetricData;
6 import com.android.tradefed.invoker.IInvocationContext;
7 import com.android.tradefed.metrics.proto.MetricMeasurement;
8 import com.android.tradefed.metrics.proto.MetricMeasurement.DataType;
9 import com.android.tradefed.metrics.proto.MetricMeasurement.Directionality;
10 import com.android.tradefed.metrics.proto.MetricMeasurement.Measurements;
11 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
12 
13 import java.util.ArrayList;
14 import java.util.Collections;
15 import java.util.Comparator;
16 import java.util.HashMap;
17 import java.util.List;
18 import java.util.Locale;
19 import java.util.Objects;
20 
21 /**
22  * Summary of frame time metrics for a single loop.
23  */
24 public class LoopSummary {
25     private long count;
26     private long totalTimeNs;
27     private double jankRate;
28     private long minFrameTime;
29     private long maxFrameTime;
30     private double avgFrameTime;
31     private long percentile90;
32     private long percentile95;
33     private long percentile99;
34     private double targetPercentile;
35 
parseRunMetrics( IInvocationContext context, MetricSummary.TimeType type, int runIndex, HashMap<String, Metric> runMetrics)36     public static LoopSummary parseRunMetrics(
37             IInvocationContext context,
38             MetricSummary.TimeType type,
39             int runIndex,
40             HashMap<String, Metric> runMetrics) {
41         return new LoopSummary(
42                 getMetricLongValue(context, type, runIndex, "frame_count", runMetrics),
43                 getMetricLongValue(context, type, runIndex, "duration", runMetrics),
44                 getMetricDoubleValue(context, type, runIndex, "jank_rate", runMetrics),
45                 getMetricLongValue(context, type, runIndex, "min_frametime", runMetrics),
46                 getMetricLongValue(context, type, runIndex, "max_frametime", runMetrics),
47                 getMetricDoubleValue(context, type, runIndex, "frametime", runMetrics),
48                 getMetricLongValue(context, type, runIndex, "90th_percentile", runMetrics),
49                 getMetricLongValue(context, type, runIndex, "95th_percentile", runMetrics),
50                 getMetricLongValue(context, type, runIndex, "99th_percentile", runMetrics),
51                 getMetricDoubleValue(context, type, runIndex, "target_percentile", runMetrics));
52     }
53 
addToMetricData(DeviceMetricData runData, int index, MetricSummary.TimeType type)54     void addToMetricData(DeviceMetricData runData, int index, MetricSummary.TimeType type) {
55         runData.addMetric(
56                 getMetricKey(type, index, "frame_count"),
57                 Metric.newBuilder()
58                         .setType(MetricMeasurement.DataType.PROCESSED)
59                         .setMeasurements(
60                                 MetricMeasurement.Measurements.newBuilder()
61                                         .setSingleInt(getCount())));
62         runData.addMetric(
63                 getMetricKey(type, index, "duration"),
64                 getNsMetric(getDuration()));
65         runData.addMetric(
66                 getMetricKey(type, index, "jank_rate"),
67                 Metric.newBuilder()
68                         .setType(DataType.PROCESSED)
69                         .setMeasurements(Measurements.newBuilder().setSingleDouble(getJankRate())));
70         runData.addMetric(
71                 getMetricKey(type, index, "min_frametime"),
72                 getNsMetric(getMinFrameTime()));
73         runData.addMetric(
74                 getMetricKey(type, index, "max_frametime"),
75                 getNsMetric(getMaxFrameTime()));
76         runData.addMetric(
77                 getMetricKey(type, index, "frametime"),
78                 getNsMetric(getAvgFrameTime()));
79         runData.addMetric(
80                 getMetricKey(type, index, "90th_percentile"),
81                 getNsMetric(get90thPercentile()));
82         runData.addMetric(
83                 getMetricKey(type, index, "95th_percentile"),
84                 getNsMetric(get95thPercentile()));
85         runData.addMetric(
86                 getMetricKey(type, index, "99th_percentile"),
87                 getNsMetric(get99thPercentile()));
88         runData.addMetric(
89                 getMetricKey(type, index, "target_percentile"),
90                 Metric.newBuilder()
91                         .setType(DataType.PROCESSED)
92                         .setMeasurements(
93                                 Measurements.newBuilder().setSingleDouble(getTargetPercentile())));
94     }
95 
LoopSummary( long count, long totalTimeNs, double jankRate, long minFrameTime, long maxFrameTime, double avgFrameTime, long percentile90, long percentile95, long percentile99, double targetPercentile)96     private LoopSummary(
97             long count,
98             long totalTimeNs,
99             double jankRate,
100             long minFrameTime,
101             long maxFrameTime,
102             double avgFrameTime,
103             long percentile90,
104             long percentile95,
105             long percentile99,
106             double targetPercentile) {
107         this.count = count;
108         this.totalTimeNs = totalTimeNs;
109         this.jankRate = jankRate;
110         this.minFrameTime = minFrameTime;
111         this.maxFrameTime = maxFrameTime;
112         this.avgFrameTime = avgFrameTime;
113         this.percentile90 = percentile90;
114         this.percentile95 = percentile95;
115         this.percentile99 = percentile99;
116         this.targetPercentile = targetPercentile;
117     }
118 
getCount()119     public long getCount() {
120         return count;
121     }
122 
getDuration()123     public long getDuration() {
124         return totalTimeNs;
125     }
126 
getJankRate()127     public double getJankRate() {
128         return jankRate;
129     }
130 
getMinFrameTime()131     public long getMinFrameTime() {
132         return minFrameTime;
133     }
134 
getMaxFrameTime()135     public long getMaxFrameTime() {
136         return maxFrameTime;
137     }
138 
getAvgFrameTime()139     public double getAvgFrameTime() {
140         return avgFrameTime;
141     }
142 
getMinFPS()143     public double getMinFPS() {
144         return 1.0e9 / maxFrameTime;
145     }
146 
getMaxFPS()147     public double getMaxFPS() {
148         return 1.0e9 / minFrameTime;
149     }
150 
getAvgFPS()151     public double getAvgFPS() {
152         return 1.0e9 / avgFrameTime;
153     }
154 
get90thPercentile()155     public long get90thPercentile() {
156         return percentile90;
157     }
158 
get95thPercentile()159     public long get95thPercentile() {
160         return percentile95;
161     }
162 
get99thPercentile()163     public long get99thPercentile() {
164         return percentile99;
165     }
166 
getTargetPercentile()167     public double getTargetPercentile() {
168         return targetPercentile;
169     }
170 
171     @Override
equals(Object o)172     public boolean equals(Object o) {
173         if (this == o) return true;
174         if (o == null || getClass() != o.getClass()) return false;
175         LoopSummary that = (LoopSummary) o;
176         return count == that.count &&
177                 totalTimeNs == that.totalTimeNs &&
178                 Double.compare(that.jankRate, jankRate) == 0 &&
179                 minFrameTime == that.minFrameTime &&
180                 maxFrameTime == that.maxFrameTime &&
181                 Double.compare(that.avgFrameTime, avgFrameTime) == 0 &&
182                 percentile90 == that.percentile90 &&
183                 percentile95 == that.percentile95 &&
184                 percentile99 == that.percentile99 &&
185                 Double.compare(that.targetPercentile, targetPercentile) == 0;
186     }
187 
188     @Override
hashCode()189     public int hashCode() {
190         return Objects.hash(
191                 count,
192                 totalTimeNs,
193                 jankRate,
194                 minFrameTime,
195                 maxFrameTime,
196                 avgFrameTime,
197                 percentile90,
198                 percentile95,
199                 percentile99,
200                 targetPercentile);
201     }
202 
toString()203     public String toString() {
204         return String.format(
205                 "duration: %.3f ms\n"
206                         + "Jank Rate: %7.3f/s\n"
207                         + "avg Frame Time: %7.3f ms\t\tavg FPS = %.3f fps\n"
208                         + "max Frame Time: %7.3f ms\n"
209                         + "min Frame Time: %7.3f ms\n"
210                         + "90th Percentile Frame Time: %7.3f ms\n"
211                         + "95th Percentile Frame Time: %7.3f ms\n"
212                         + "99th Percentile Frame Time: %7.3f ms\n"
213                         + "Percentile below target: %7.3f\n",
214                 nsToMs(getDuration()),
215                 getJankRate(),
216                 nsToMs(getAvgFrameTime()), getAvgFPS(),
217                 nsToMs(getMaxFrameTime()),
218                 nsToMs(getMinFrameTime()),
219                 nsToMs(get90thPercentile()),
220                 nsToMs(get95thPercentile()),
221                 nsToMs(get99thPercentile()),
222                 targetPercentile * 100);
223     }
224 
225     static class Builder {
226         @Nullable
227         private final CertificationRequirements mRequirements;
228         private final long mVSyncPeriodNs;
229         private long totalTimeNs = 0;
230         private double jankScore;
231         private List<Long> frameTimes = new ArrayList<>();
232 
Builder(@ullable CertificationRequirements requirements, long VSyncPeriodNs)233         public Builder(@Nullable CertificationRequirements requirements, long VSyncPeriodNs) {
234             mRequirements = requirements;
235             mVSyncPeriodNs = VSyncPeriodNs;
236         }
237 
build()238         public LoopSummary build() {
239             if (frameTimes.isEmpty()) {
240                 return new LoopSummary(0, 0, 0, 0, 0, 0, 0, 0, 0, 0);
241             }
242             frameTimes.sort(Comparator.naturalOrder());
243             int size = frameTimes.size();
244             long targetFrameTime = mRequirements == null ? 0 : msToNs(mRequirements.getFrameTime());
245 
246             // Find the percentage of frames below the target.
247             // Allow for small amount of slack because frame times have some variability.  Frames
248             // that misses the target should be off by at least 1 VSYNC period, so frames that are
249             // just slightly above the target is still considered to be within target.
250             final long slack = (long)(0.1 * mVSyncPeriodNs);
251             int index = Collections.binarySearch(frameTimes, targetFrameTime + slack);
252             // binarySearch return a negative number if exact match is not found.
253             index = index < 0 ? -index - 1: index + 1;
254 
255             return new LoopSummary(
256                     frameTimes.size(),
257                     totalTimeNs,
258                     jankScore * 1000000000 / totalTimeNs,
259                     frameTimes.get(0),
260                     frameTimes.get(frameTimes.size() - 1),
261                     (double)totalTimeNs / frameTimes.size(),
262                     frameTimes.get((int)Math.ceil(size * 0.90) - 1),
263                     frameTimes.get((int)Math.ceil(size * 0.95) - 1),
264                     frameTimes.get((int)Math.ceil(size * 0.99) - 1),
265                     (double)index / size);
266         }
267 
268         public void addFrameTime(long frameTimeNs) {
269             if (mRequirements != null) {
270                 long targetFrameTime = msToNs(mRequirements.getFrameTime());
271                 long roundedFrameTimeNs =
272                         Math.round(frameTimeNs / (double)mVSyncPeriodNs) * mVSyncPeriodNs;
273                 if (roundedFrameTimeNs > targetFrameTime) {
274                     double score = (roundedFrameTimeNs - targetFrameTime) / targetFrameTime;
275                     jankScore += score;
276                 }
277             }
278             totalTimeNs = totalTimeNs + frameTimeNs;
279             frameTimes.add(frameTimeNs);
280         }
281 
msToNs(float value)282         private static long msToNs(float value) {
283             return (long) (value * 1e6f);
284         }
285     }
286 
getMetricDoubleValue( IInvocationContext context, MetricSummary.TimeType type, int runIndex, String metric, HashMap<String, Metric> runMetrics)287     private static double getMetricDoubleValue(
288             IInvocationContext context,
289             MetricSummary.TimeType type,
290             int runIndex,
291             String metric,
292             HashMap<String, Metric> runMetrics) {
293         Metric m = runMetrics.get(
294                 getActualMetricKey(context, type, runIndex, metric));
295         if (!m.hasMeasurements()) {
296             throw new RuntimeException();
297         }
298         return m.getMeasurements().getSingleDouble();
299     }
300 
getMetricLongValue( IInvocationContext context, MetricSummary.TimeType type, int runIndex, String metric, HashMap<String, Metric> runMetrics)301     private static long getMetricLongValue(
302             IInvocationContext context,
303             MetricSummary.TimeType type,
304             int runIndex,
305             String metric,
306             HashMap<String, Metric> runMetrics) {
307         Metric m = runMetrics.get(
308                 getActualMetricKey(context, type, runIndex, metric));
309         if (!m.hasMeasurements()) {
310             throw new RuntimeException();
311         }
312         return m.getMeasurements().getSingleInt();
313     }
314 
getNsMetric(long value)315     private static Metric.Builder getNsMetric(long value) {
316         return Metric.newBuilder()
317                 .setUnit("ns")
318                 .setDirection(MetricMeasurement.Directionality.DOWN_BETTER)
319                 .setType(MetricMeasurement.DataType.PROCESSED)
320                 .setMeasurements(MetricMeasurement.Measurements.newBuilder().setSingleInt(value));
321     }
322 
getNsMetric(double value)323     private static Metric.Builder getNsMetric(double value) {
324         return Metric.newBuilder()
325                 .setUnit("ns")
326                 .setDirection(Directionality.DOWN_BETTER)
327                 .setType(DataType.PROCESSED)
328                 .setMeasurements(Measurements.newBuilder().setSingleDouble(value));
329     }
330 
getActualMetricKey( IInvocationContext context, MetricSummary.TimeType type, int loopIndex, String label)331     private static String getActualMetricKey(
332             IInvocationContext context, MetricSummary.TimeType type, int loopIndex, String label) {
333         // DeviceMetricData automatically add the deviceName to the metric key if there are more
334         // than one devices.  We don't really want or care about the device in the metric data, but
335         // we need to get the actual key that was added in order to parse it correctly.
336         if (context.getDevices().size() > 1) {
337             String deviceName = context.getDeviceName(context.getDevices().get(0));
338             return String.format("{%s}:%s", deviceName, getMetricKey(type, loopIndex, label));
339         }
340         return getMetricKey(type, loopIndex, label);
341     }
342 
getMetricKey(MetricSummary.TimeType type, int loopIndex, String label)343     private static String getMetricKey(MetricSummary.TimeType type, int loopIndex, String label) {
344         return "run_" + loopIndex + "." + type.name().toLowerCase(Locale.US) + "_" + label;
345     }
346 
nsToMs(long value)347     private static double nsToMs(long value) {
348         return value / 1e6;
349     }
350 
nsToMs(double value)351     private static double nsToMs(double value) {
352         return value / 1e6;
353     }
354 }
355