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 android.cts.statsd.metric;
17 
18 import static com.google.common.truth.Truth.assertThat;
19 
20 import android.cts.statsd.atom.DeviceAtomTestCase;
21 
22 import com.android.internal.os.StatsdConfigProto;
23 import com.android.internal.os.StatsdConfigProto.AtomMatcher;
24 import com.android.internal.os.StatsdConfigProto.FieldMatcher;
25 import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
26 import com.android.internal.os.StatsdConfigProto.Position;
27 import com.android.internal.os.StatsdConfigProto.Predicate;
28 import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher;
29 import com.android.internal.os.StatsdConfigProto.SimplePredicate;
30 import com.android.os.AtomsProto.AppBreadcrumbReported;
31 import com.android.os.AtomsProto.Atom;
32 import com.android.os.StatsLog.ConfigMetricsReport;
33 import com.android.os.StatsLog.ConfigMetricsReportList;
34 import com.android.os.StatsLog.DurationBucketInfo;
35 import com.android.os.StatsLog.StatsLogReport;
36 import com.android.tradefed.device.DeviceNotAvailableException;
37 import com.android.tradefed.log.LogUtil;
38 
39 import com.google.common.collect.Range;
40 
41 import java.util.List;
42 
43 public class DurationMetricsTests extends DeviceAtomTestCase {
44 
45     private static final int APP_BREADCRUMB_REPORTED_A_MATCH_START_ID = 0;
46     private static final int APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID = 1;
47     private static final int APP_BREADCRUMB_REPORTED_B_MATCH_START_ID = 2;
48     private static final int APP_BREADCRUMB_REPORTED_B_MATCH_STOP_ID = 3;
49 
50     public void testDurationMetric() throws Exception {
51         final int label = 1;
52         // Add AtomMatchers.
53         AtomMatcher startAtomMatcher =
54             MetricsUtils.startAtomMatcherWithLabel(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID, label);
55         AtomMatcher stopAtomMatcher =
56             MetricsUtils.stopAtomMatcherWithLabel(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID, label);
57 
58         StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder();
59         builder.addAtomMatcher(startAtomMatcher);
60         builder.addAtomMatcher(stopAtomMatcher);
61 
62         // Add Predicates.
63         SimplePredicate simplePredicate = SimplePredicate.newBuilder()
64                 .setStart(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID)
65                 .setStop(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID)
66                 .build();
67         Predicate predicate = Predicate.newBuilder()
68                                   .setId(MetricsUtils.StringToId("Predicate"))
69                                   .setSimplePredicate(simplePredicate)
70                                   .build();
71         builder.addPredicate(predicate);
72 
73         // Add DurationMetric.
74         builder.addDurationMetric(
75             StatsdConfigProto.DurationMetric.newBuilder()
76                 .setId(MetricsUtils.DURATION_METRIC_ID)
77                 .setWhat(predicate.getId())
78                 .setAggregationType(StatsdConfigProto.DurationMetric.AggregationType.SUM)
79                 .setBucket(StatsdConfigProto.TimeUnit.CTS));
80 
81         // Upload config.
82         uploadConfig(builder);
83 
84         // Create AppBreadcrumbReported Start/Stop events.
85         doAppBreadcrumbReportedStart(label);
86         Thread.sleep(2000);
87         doAppBreadcrumbReportedStop(label);
88 
89         // Wait for the metrics to propagate to statsd.
90         Thread.sleep(2000);
91 
92         StatsLogReport metricReport = getStatsLogReport();
93         assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.DURATION_METRIC_ID);
94         LogUtil.CLog.d("Received the following data: " + metricReport.toString());
95         assertThat(metricReport.hasDurationMetrics()).isTrue();
96         StatsLogReport.DurationMetricDataWrapper durationData
97                 = metricReport.getDurationMetrics();
98         assertThat(durationData.getDataCount()).isEqualTo(1);
99         assertThat(durationData.getData(0).getBucketInfo(0).getDurationNanos())
100                 .isIn(Range.open(0L, (long)1e9));
101     }
102 
103     public void testDurationMetricWithCondition() throws Exception {
104         final int durationLabel = 1;
105         final int conditionLabel = 2;
106 
107         // Add AtomMatchers.
108         AtomMatcher startAtomMatcher = MetricsUtils.startAtomMatcherWithLabel(
109                 APP_BREADCRUMB_REPORTED_A_MATCH_START_ID, durationLabel);
110         AtomMatcher stopAtomMatcher = MetricsUtils.stopAtomMatcherWithLabel(
111                 APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID, durationLabel);
112         AtomMatcher conditionStartAtomMatcher = MetricsUtils.startAtomMatcherWithLabel(
113                 APP_BREADCRUMB_REPORTED_B_MATCH_START_ID, conditionLabel);
114         AtomMatcher conditionStopAtomMatcher = MetricsUtils.stopAtomMatcherWithLabel(
115                 APP_BREADCRUMB_REPORTED_B_MATCH_STOP_ID, conditionLabel);
116 
117         StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder()
118                 .addAtomMatcher(startAtomMatcher)
119                 .addAtomMatcher(stopAtomMatcher)
120                 .addAtomMatcher(conditionStartAtomMatcher)
121                 .addAtomMatcher(conditionStopAtomMatcher);
122 
123         // Add Predicates.
124         SimplePredicate simplePredicate = SimplePredicate.newBuilder()
125                 .setStart(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID)
126                 .setStop(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID)
127                 .build();
128         Predicate predicate = Predicate.newBuilder()
129                                   .setId(MetricsUtils.StringToId("Predicate"))
130                                   .setSimplePredicate(simplePredicate)
131                                   .build();
132 
133         SimplePredicate conditionSimplePredicate = SimplePredicate.newBuilder()
134                 .setStart(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID)
135                 .setStop(APP_BREADCRUMB_REPORTED_B_MATCH_STOP_ID)
136                 .build();
137         Predicate conditionPredicate = Predicate.newBuilder()
138                                   .setId(MetricsUtils.StringToId("ConditionPredicate"))
139                                   .setSimplePredicate(conditionSimplePredicate)
140                                   .build();
141 
142         builder
143             .addPredicate(predicate)
144             .addPredicate(conditionPredicate);
145 
146         // Add DurationMetric.
147         builder
148                 .addDurationMetric(StatsdConfigProto.DurationMetric.newBuilder()
149                         .setId(MetricsUtils.DURATION_METRIC_ID)
150                         .setWhat(predicate.getId())
151                         .setAggregationType(StatsdConfigProto.DurationMetric.AggregationType.SUM)
152                         .setBucket(StatsdConfigProto.TimeUnit.CTS)
153                         .setCondition(conditionPredicate.getId())
154                 );
155 
156         // Upload config.
157         uploadConfig(builder);
158 
159         // Start uncounted duration.
160         doAppBreadcrumbReportedStart(durationLabel);
161         Thread.sleep(10);
162 
163         Thread.sleep(2_000);
164 
165         // Stop uncounted duration.
166         doAppBreadcrumbReportedStop(durationLabel);
167         Thread.sleep(10);
168 
169         // Set the condition to true.
170         doAppBreadcrumbReportedStart(conditionLabel);
171         Thread.sleep(10);
172 
173         // Start counted duration.
174         doAppBreadcrumbReportedStart(durationLabel);
175         Thread.sleep(10);
176 
177         Thread.sleep(2_000);
178 
179         // Stop counted duration.
180         doAppBreadcrumbReportedStop(durationLabel);
181         Thread.sleep(10);
182 
183         // Set the condition to false.
184         doAppBreadcrumbReportedStop(conditionLabel);
185         Thread.sleep(10);
186 
187         // Start uncounted duration.
188         doAppBreadcrumbReportedStart(durationLabel);
189         Thread.sleep(10);
190 
191         Thread.sleep(2_000);
192 
193         // Stop uncounted duration.
194         doAppBreadcrumbReportedStop(durationLabel);
195         Thread.sleep(10);
196 
197         Thread.sleep(2_000);
198         StatsLogReport metricReport = getStatsLogReport();
199         assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.DURATION_METRIC_ID);
200         LogUtil.CLog.d("Received the following data: " + metricReport.toString());
201         assertThat(metricReport.hasDurationMetrics()).isTrue();
202         StatsLogReport.DurationMetricDataWrapper durationData
203                 = metricReport.getDurationMetrics();
204         assertThat(durationData.getDataCount()).isEqualTo(1);
205         long totalDuration = durationData.getData(0).getBucketInfoList().stream()
206                 .mapToLong(bucketInfo -> bucketInfo.getDurationNanos())
207                 .peek(durationNs -> assertThat(durationNs).isIn(Range.openClosed(0L, (long)1e9)))
208                 .sum();
209         assertThat(totalDuration).isIn(Range.open((long)2e9, (long)3e9));
210     }
211 
212     public void testDurationMetricWithActivation() throws Exception {
213         final int activationMatcherId = 5;
214         final int activationMatcherLabel = 5;
215         final int ttlSec = 5;
216         final int durationLabel = 1;
217 
218         // Add AtomMatchers.
219         AtomMatcher startAtomMatcher = MetricsUtils.startAtomMatcherWithLabel(
220                 APP_BREADCRUMB_REPORTED_A_MATCH_START_ID, durationLabel);
221         AtomMatcher stopAtomMatcher = MetricsUtils.stopAtomMatcherWithLabel(
222                 APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID, durationLabel);
223         StatsdConfigProto.AtomMatcher activationMatcher =
224                 MetricsUtils.appBreadcrumbMatcherWithLabel(activationMatcherId,
225                                                            activationMatcherLabel);
226 
227         StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder()
228                 .addAtomMatcher(startAtomMatcher)
229                 .addAtomMatcher(stopAtomMatcher)
230                 .addAtomMatcher(activationMatcher);
231 
232         // Add Predicates.
233         SimplePredicate simplePredicate = SimplePredicate.newBuilder()
234                 .setStart(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID)
235                 .setStop(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID)
236                 .build();
237         Predicate predicate = Predicate.newBuilder()
238                                   .setId(MetricsUtils.StringToId("Predicate"))
239                                   .setSimplePredicate(simplePredicate)
240                                   .build();
241         builder.addPredicate(predicate);
242 
243         // Add DurationMetric.
244         builder
245                 .addDurationMetric(StatsdConfigProto.DurationMetric.newBuilder()
246                         .setId(MetricsUtils.DURATION_METRIC_ID)
247                         .setWhat(predicate.getId())
248                         .setAggregationType(StatsdConfigProto.DurationMetric.AggregationType.SUM)
249                         .setBucket(StatsdConfigProto.TimeUnit.CTS)
250                 )
251                 .addMetricActivation(StatsdConfigProto.MetricActivation.newBuilder()
252                         .setMetricId(MetricsUtils.DURATION_METRIC_ID)
253                         .addEventActivation(StatsdConfigProto.EventActivation.newBuilder()
254                                 .setAtomMatcherId(activationMatcherId)
255                                 .setActivationType(
256                                         StatsdConfigProto.ActivationType.ACTIVATE_IMMEDIATELY)
257                                 .setTtlSeconds(ttlSec)));
258 
259         // Upload config.
260         uploadConfig(builder);
261 
262         // Start uncounted duration.
263         doAppBreadcrumbReportedStart(durationLabel);
264         Thread.sleep(10);
265 
266         Thread.sleep(2_000);
267 
268         // Stop uncounted duration.
269         doAppBreadcrumbReportedStop(durationLabel);
270         Thread.sleep(10);
271 
272         // Activate the metric.
273         doAppBreadcrumbReported(activationMatcherLabel);
274         Thread.sleep(10);
275 
276         // Start counted duration.
277         doAppBreadcrumbReportedStart(durationLabel);
278         Thread.sleep(10);
279 
280         Thread.sleep(2_000);
281 
282         // Stop counted duration.
283         doAppBreadcrumbReportedStop(durationLabel);
284         Thread.sleep(10);
285 
286         Thread.sleep(2_000);
287         StatsLogReport metricReport = getStatsLogReport();
288         assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.DURATION_METRIC_ID);
289         LogUtil.CLog.d("Received the following data: " + metricReport.toString());
290         assertThat(metricReport.hasDurationMetrics()).isTrue();
291         StatsLogReport.DurationMetricDataWrapper durationData
292                 = metricReport.getDurationMetrics();
293         assertThat(durationData.getDataCount()).isEqualTo(1);
294         long totalDuration = durationData.getData(0).getBucketInfoList().stream()
295                 .mapToLong(bucketInfo -> bucketInfo.getDurationNanos())
296                 .peek(durationNs -> assertThat(durationNs).isIn(Range.openClosed(0L, (long)1e9)))
297                 .sum();
298         assertThat(totalDuration).isIn(Range.open((long)2e9, (long)3e9));
299     }
300 
301     public void testDurationMetricWithConditionAndActivation() throws Exception {
302         final int durationLabel = 1;
303         final int conditionLabel = 2;
304         final int activationMatcherId = 5;
305         final int activationMatcherLabel = 5;
306         final int ttlSec = 5;
307 
308         // Add AtomMatchers.
309         AtomMatcher startAtomMatcher = MetricsUtils.startAtomMatcherWithLabel(
310                 APP_BREADCRUMB_REPORTED_A_MATCH_START_ID, durationLabel);
311         AtomMatcher stopAtomMatcher = MetricsUtils.stopAtomMatcherWithLabel(
312                 APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID, durationLabel);
313         AtomMatcher conditionStartAtomMatcher = MetricsUtils.startAtomMatcherWithLabel(
314                 APP_BREADCRUMB_REPORTED_B_MATCH_START_ID, conditionLabel);
315         AtomMatcher conditionStopAtomMatcher = MetricsUtils.stopAtomMatcherWithLabel(
316                 APP_BREADCRUMB_REPORTED_B_MATCH_STOP_ID, conditionLabel);
317         StatsdConfigProto.AtomMatcher activationMatcher =
318                 MetricsUtils.appBreadcrumbMatcherWithLabel(activationMatcherId,
319                                                            activationMatcherLabel);
320 
321         StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder()
322                 .addAtomMatcher(startAtomMatcher)
323                 .addAtomMatcher(stopAtomMatcher)
324                 .addAtomMatcher(conditionStartAtomMatcher)
325                 .addAtomMatcher(conditionStopAtomMatcher)
326                 .addAtomMatcher(activationMatcher);
327 
328         // Add Predicates.
329         SimplePredicate simplePredicate = SimplePredicate.newBuilder()
330                 .setStart(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID)
331                 .setStop(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID)
332                 .build();
333         Predicate predicate = Predicate.newBuilder()
334                                   .setId(MetricsUtils.StringToId("Predicate"))
335                                   .setSimplePredicate(simplePredicate)
336                                   .build();
337         builder.addPredicate(predicate);
338 
339         SimplePredicate conditionSimplePredicate = SimplePredicate.newBuilder()
340                 .setStart(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID)
341                 .setStop(APP_BREADCRUMB_REPORTED_B_MATCH_STOP_ID)
342                 .build();
343         Predicate conditionPredicate = Predicate.newBuilder()
344                                   .setId(MetricsUtils.StringToId("ConditionPredicate"))
345                                   .setSimplePredicate(conditionSimplePredicate)
346                                   .build();
347         builder.addPredicate(conditionPredicate);
348 
349         // Add DurationMetric.
350         builder
351                 .addDurationMetric(StatsdConfigProto.DurationMetric.newBuilder()
352                         .setId(MetricsUtils.DURATION_METRIC_ID)
353                         .setWhat(predicate.getId())
354                         .setAggregationType(StatsdConfigProto.DurationMetric.AggregationType.SUM)
355                         .setBucket(StatsdConfigProto.TimeUnit.CTS)
356                         .setCondition(conditionPredicate.getId())
357                 )
358                 .addMetricActivation(StatsdConfigProto.MetricActivation.newBuilder()
359                         .setMetricId(MetricsUtils.DURATION_METRIC_ID)
360                         .addEventActivation(StatsdConfigProto.EventActivation.newBuilder()
361                                 .setAtomMatcherId(activationMatcherId)
362                                 .setActivationType(
363                                         StatsdConfigProto.ActivationType.ACTIVATE_IMMEDIATELY)
364                                 .setTtlSeconds(ttlSec)));
365 
366         // Upload config.
367         uploadConfig(builder);
368 
369         // Activate the metric.
370         doAppBreadcrumbReported(activationMatcherLabel);
371         Thread.sleep(10);
372 
373         // Set the condition to true.
374         doAppBreadcrumbReportedStart(conditionLabel);
375         Thread.sleep(10);
376 
377         // Start counted duration.
378         doAppBreadcrumbReportedStart(durationLabel);
379         Thread.sleep(10);
380 
381         Thread.sleep(2_000);
382 
383         // Stop counted duration.
384         doAppBreadcrumbReportedStop(durationLabel);
385         Thread.sleep(10);
386 
387         // Set the condition to false.
388         doAppBreadcrumbReportedStop(conditionLabel);
389         Thread.sleep(10);
390 
391         // Start uncounted duration.
392         doAppBreadcrumbReportedStart(durationLabel);
393         Thread.sleep(10);
394 
395         Thread.sleep(2_000);
396 
397         // Stop uncounted duration.
398         doAppBreadcrumbReportedStop(durationLabel);
399         Thread.sleep(10);
400 
401         // Let the metric deactivate.
402         Thread.sleep(ttlSec * 1000);
403         //doAppBreadcrumbReported(99); // TODO: maybe remove?
404         //Thread.sleep(10);
405 
406         // Start uncounted duration.
407         doAppBreadcrumbReportedStart(durationLabel);
408         Thread.sleep(10);
409 
410         Thread.sleep(2_000);
411 
412         // Stop uncounted duration.
413         doAppBreadcrumbReportedStop(durationLabel);
414         Thread.sleep(10);
415 
416         // Set condition to true again.
417         doAppBreadcrumbReportedStart(conditionLabel);
418         Thread.sleep(10);
419 
420         // Start uncounted duration.
421         doAppBreadcrumbReportedStart(durationLabel);
422         Thread.sleep(10);
423 
424         Thread.sleep(2_000);
425 
426         // Stop uncounted duration.
427         doAppBreadcrumbReportedStop(durationLabel);
428         Thread.sleep(10);
429 
430         // Activate the metric.
431         doAppBreadcrumbReported(activationMatcherLabel);
432         Thread.sleep(10);
433 
434         // Start counted duration.
435         doAppBreadcrumbReportedStart(durationLabel);
436         Thread.sleep(10);
437 
438         Thread.sleep(2_000);
439 
440         // Stop counted duration.
441         doAppBreadcrumbReportedStop(durationLabel);
442         Thread.sleep(10);
443 
444         // Let the metric deactivate.
445         Thread.sleep(ttlSec * 1000);
446 
447         // Start uncounted duration.
448         doAppBreadcrumbReportedStart(durationLabel);
449         Thread.sleep(10);
450 
451         Thread.sleep(2_000);
452 
453         // Stop uncounted duration.
454         doAppBreadcrumbReportedStop(durationLabel);
455         Thread.sleep(10);
456 
457         // Wait for the metrics to propagate to statsd.
458         Thread.sleep(2000);
459 
460         StatsLogReport metricReport = getStatsLogReport();
461         LogUtil.CLog.d("Received the following data: " + metricReport.toString());
462         assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.DURATION_METRIC_ID);
463         assertThat(metricReport.hasDurationMetrics()).isTrue();
464         StatsLogReport.DurationMetricDataWrapper durationData
465                 = metricReport.getDurationMetrics();
466         assertThat(durationData.getDataCount()).isEqualTo(1);
467         long totalDuration = durationData.getData(0).getBucketInfoList().stream()
468                 .mapToLong(bucketInfo -> bucketInfo.getDurationNanos())
469                 .peek(durationNs -> assertThat(durationNs).isIn(Range.openClosed(0L, (long)1e9)))
470                 .sum();
471         assertThat(totalDuration).isIn(Range.open((long)4e9, (long)5e9));
472     }
473 
474     public void testDurationMetricWithDimension() throws Exception {
475         // Add AtomMatchers.
476         AtomMatcher startAtomMatcherA =
477             MetricsUtils.startAtomMatcher(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID);
478         AtomMatcher stopAtomMatcherA =
479             MetricsUtils.stopAtomMatcher(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID);
480         AtomMatcher startAtomMatcherB =
481             MetricsUtils.startAtomMatcher(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID);
482         AtomMatcher stopAtomMatcherB =
483             MetricsUtils.stopAtomMatcher(APP_BREADCRUMB_REPORTED_B_MATCH_STOP_ID);
484 
485         StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder();
486         builder.addAtomMatcher(startAtomMatcherA);
487         builder.addAtomMatcher(stopAtomMatcherA);
488         builder.addAtomMatcher(startAtomMatcherB);
489         builder.addAtomMatcher(stopAtomMatcherB);
490 
491         // Add Predicates.
492         SimplePredicate simplePredicateA = SimplePredicate.newBuilder()
493                 .setStart(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID)
494                 .setStop(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID)
495                 .build();
496         Predicate predicateA = Predicate.newBuilder()
497                                    .setId(MetricsUtils.StringToId("Predicate_A"))
498                                    .setSimplePredicate(simplePredicateA)
499                                    .build();
500         builder.addPredicate(predicateA);
501 
502         FieldMatcher.Builder dimensionsBuilder = FieldMatcher.newBuilder()
503                 .setField(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER);
504         dimensionsBuilder
505                 .addChild(FieldMatcher.newBuilder().setField(
506                         AppBreadcrumbReported.LABEL_FIELD_NUMBER));
507         Predicate predicateB =
508             Predicate.newBuilder()
509                 .setId(MetricsUtils.StringToId("Predicate_B"))
510                 .setSimplePredicate(SimplePredicate.newBuilder()
511                                         .setStart(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID)
512                                         .setStop(APP_BREADCRUMB_REPORTED_B_MATCH_STOP_ID)
513                                         .setDimensions(dimensionsBuilder.build())
514                                         .build())
515                 .build();
516         builder.addPredicate(predicateB);
517 
518         // Add DurationMetric.
519         builder.addDurationMetric(
520             StatsdConfigProto.DurationMetric.newBuilder()
521                 .setId(MetricsUtils.DURATION_METRIC_ID)
522                 .setWhat(predicateB.getId())
523                 .setCondition(predicateA.getId())
524                 .setAggregationType(StatsdConfigProto.DurationMetric.AggregationType.SUM)
525                 .setBucket(StatsdConfigProto.TimeUnit.CTS)
526                 .setDimensionsInWhat(
527                     FieldMatcher.newBuilder()
528                             .setField(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
529                             .addChild(FieldMatcher.newBuilder().setField(
530                                     AppBreadcrumbReported.LABEL_FIELD_NUMBER))));
531 
532         // Upload config.
533         uploadConfig(builder);
534 
535         // Trigger events.
536         doAppBreadcrumbReportedStart(1);
537         Thread.sleep(2000);
538         doAppBreadcrumbReportedStart(2);
539         Thread.sleep(2000);
540         doAppBreadcrumbReportedStop(1);
541         Thread.sleep(2000);
542         doAppBreadcrumbReportedStop(2);
543 
544         // Wait for the metrics to propagate to statsd.
545         Thread.sleep(2000);
546 
547         StatsLogReport metricReport = getStatsLogReport();
548         assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.DURATION_METRIC_ID);
549         assertThat(metricReport.hasDurationMetrics()).isTrue();
550         StatsLogReport.DurationMetricDataWrapper durationData
551                 = metricReport.getDurationMetrics();
552         assertThat(durationData.getDataCount()).isEqualTo(2);
553         assertThat(durationData.getData(0).getBucketInfoCount()).isGreaterThan(3);
554         assertThat(durationData.getData(1).getBucketInfoCount()).isGreaterThan(3);
555         long totalDuration = 0;
556         for (DurationBucketInfo bucketInfo : durationData.getData(0).getBucketInfoList()) {
557             assertThat(bucketInfo.getDurationNanos()).isIn(Range.openClosed(0L, (long) 1e9));
558             totalDuration += bucketInfo.getDurationNanos();
559         }
560         // Duration for both labels is expected to be 4s.
561         assertThat(totalDuration).isIn(Range.open((long) 3e9, (long) 8e9));
562         totalDuration = 0;
563         for (DurationBucketInfo bucketInfo : durationData.getData(1).getBucketInfoList()) {
564             assertThat(bucketInfo.getDurationNanos()).isIn(Range.openClosed(0L, (long) 1e9));
565             totalDuration += bucketInfo.getDurationNanos();
566         }
567         assertThat(totalDuration).isIn(Range.open((long) 3e9, (long) 8e9));
568     }
569 }
570