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.ActivationType;
23 import com.android.internal.os.StatsdConfigProto.AtomMatcher;
24 import com.android.internal.os.StatsdConfigProto.EventActivation;
25 import com.android.internal.os.StatsdConfigProto.FieldFilter;
26 import com.android.internal.os.StatsdConfigProto.FieldMatcher;
27 import com.android.internal.os.StatsdConfigProto.FieldValueMatcher;
28 import com.android.internal.os.StatsdConfigProto.MetricActivation;
29 import com.android.internal.os.StatsdConfigProto.Predicate;
30 import com.android.internal.os.StatsdConfigProto.SimpleAtomMatcher;
31 import com.android.internal.os.StatsdConfigProto.SimplePredicate;
32 import com.android.internal.os.StatsdConfigProto.StatsdConfig;
33 import com.android.internal.os.StatsdConfigProto.TimeUnit;
34 import com.android.internal.os.StatsdConfigProto.ValueMetric;
35 
36 import com.android.os.AtomsProto.AppBreadcrumbReported;
37 import com.android.os.AtomsProto.Atom;
38 import com.android.os.AtomsProto.SystemElapsedRealtime;
39 import com.android.os.StatsLog.StatsLogReport;
40 import com.android.os.StatsLog.StatsLogReport.BucketDropReason;
41 import com.android.os.StatsLog.ValueBucketInfo;
42 import com.android.os.StatsLog.ValueMetricData;
43 
44 import com.android.tradefed.log.LogUtil;
45 
46 public class ValueMetricsTests extends DeviceAtomTestCase {
47   private static final int APP_BREADCRUMB_REPORTED_A_MATCH_START_ID = 0;
48   private static final int APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID = 1;
49   private static final int APP_BREADCRUMB_REPORTED_B_MATCH_START_ID = 2;
50 
testValueMetric()51   public void testValueMetric() throws Exception {
52     // Add AtomMatcher's.
53     AtomMatcher startAtomMatcher =
54         MetricsUtils.startAtomMatcher(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID);
55     AtomMatcher stopAtomMatcher =
56         MetricsUtils.stopAtomMatcher(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID);
57     AtomMatcher atomMatcher =
58         MetricsUtils.simpleAtomMatcher(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID);
59 
60     StatsdConfig.Builder builder = createConfigBuilder();
61     builder.addAtomMatcher(startAtomMatcher);
62     builder.addAtomMatcher(stopAtomMatcher);
63     builder.addAtomMatcher(atomMatcher);
64 
65     // Add ValueMetric.
66     builder.addValueMetric(
67         ValueMetric.newBuilder()
68             .setId(MetricsUtils.VALUE_METRIC_ID)
69             .setWhat(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID)
70             .setBucket(TimeUnit.CTS)
71             .setValueField(FieldMatcher.newBuilder()
72                                .setField(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
73                                .addChild(FieldMatcher.newBuilder().setField(
74                                    AppBreadcrumbReported.LABEL_FIELD_NUMBER)))
75             .setDimensionsInWhat(FieldMatcher.newBuilder()
76                                      .setField(APP_BREADCRUMB_REPORTED_B_MATCH_START_ID)
77                                      .build())
78             .build());
79 
80     // Upload config.
81     uploadConfig(builder);
82 
83     // Create AppBreadcrumbReported Start/Stop events.
84     doAppBreadcrumbReportedStart(1);
85     Thread.sleep(1000);
86     doAppBreadcrumbReportedStop(1);
87     doAppBreadcrumbReportedStart(3);
88     doAppBreadcrumbReportedStop(3);
89 
90     // Wait for the metrics to propagate to statsd.
91     Thread.sleep(1000);
92 
93     StatsLogReport metricReport = getStatsLogReport();
94     LogUtil.CLog.d("Got the following value metric data: " + metricReport.toString());
95     assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.VALUE_METRIC_ID);
96     assertThat(metricReport.hasValueMetrics()).isTrue();
97     StatsLogReport.ValueMetricDataWrapper valueData = metricReport.getValueMetrics();
98     assertThat(valueData.getDataCount()).isEqualTo(1);
99 
100     int bucketCount = valueData.getData(0).getBucketInfoCount();
101     assertThat(bucketCount).isGreaterThan(1);
102     ValueMetricData data = valueData.getData(0);
103     int totalValue = 0;
104     for (ValueBucketInfo bucketInfo : data.getBucketInfoList()) {
105       MetricsUtils.assertBucketTimePresent(bucketInfo);
106       assertThat(bucketInfo.getValuesCount()).isEqualTo(1);
107       assertThat(bucketInfo.getValues(0).getIndex()).isEqualTo(0);
108       totalValue += (int) bucketInfo.getValues(0).getValueLong();
109     }
110     assertThat(totalValue).isEqualTo(8);
111   }
112 
113   // Test value metric with pulled atoms and across multiple buckets
testPullerAcrossBuckets()114   public void testPullerAcrossBuckets() throws Exception {
115     // Add AtomMatcher's.
116     final String predicateTrueName = "APP_BREADCRUMB_REPORTED_START";
117     final String predicateFalseName = "APP_BREADCRUMB_REPORTED_STOP";
118     final String predicateName = "APP_BREADCRUMB_REPORTED_IS_STOP";
119 
120     AtomMatcher startAtomMatcher =
121             MetricsUtils.startAtomMatcher(predicateTrueName.hashCode());
122     AtomMatcher stopAtomMatcher =
123             MetricsUtils.stopAtomMatcher(predicateFalseName.hashCode());
124 
125     StatsdConfig.Builder builder = createConfigBuilder();
126     builder.addAtomMatcher(startAtomMatcher);
127     builder.addAtomMatcher(stopAtomMatcher);
128     builder.addPredicate(Predicate.newBuilder()
129             .setId(predicateName.hashCode())
130             .setSimplePredicate(SimplePredicate.newBuilder()
131                     .setStart(predicateTrueName.hashCode())
132                     .setStop(predicateFalseName.hashCode())
133                     .setCountNesting(false)
134             )
135     );
136 
137     final String atomName = "SYSTEM_ELAPSED_REALTIME";
138     SimpleAtomMatcher.Builder sam = SimpleAtomMatcher.newBuilder().setAtomId(Atom.SYSTEM_ELAPSED_REALTIME_FIELD_NUMBER);
139     builder.addAtomMatcher(AtomMatcher.newBuilder()
140             .setId(atomName.hashCode())
141             .setSimpleAtomMatcher(sam));
142 
143     // Add ValueMetric.
144     builder.addValueMetric(
145             ValueMetric.newBuilder()
146                     .setId(MetricsUtils.VALUE_METRIC_ID)
147                     .setWhat(atomName.hashCode())
148                     .setBucket(TimeUnit.ONE_MINUTE)
149                     .setValueField(FieldMatcher.newBuilder()
150                             .setField(Atom.SYSTEM_ELAPSED_REALTIME_FIELD_NUMBER)
151                             .addChild(FieldMatcher.newBuilder().setField(
152                                     SystemElapsedRealtime.TIME_MILLIS_FIELD_NUMBER)))
153                     .setCondition(predicateName.hashCode())
154                     .build());
155 
156     // Upload config.
157     uploadConfig(builder);
158 
159     // Create AppBreadcrumbReported Start/Stop events.
160     doAppBreadcrumbReportedStart(1);
161     // Wait for 2 min and 1 sec to capture at least 2 buckets
162     Thread.sleep(2*60_000 + 10_000);
163     doAppBreadcrumbReportedStop(1);
164 
165     // Wait for the metrics to propagate to statsd.
166     Thread.sleep(1_000);
167 
168     StatsLogReport metricReport = getStatsLogReport();
169     LogUtil.CLog.d("Got the following value metric data: " + metricReport.toString());
170     assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.VALUE_METRIC_ID);
171     assertThat(metricReport.hasValueMetrics()).isTrue();
172     StatsLogReport.ValueMetricDataWrapper valueData = metricReport.getValueMetrics();
173     assertThat(valueData.getDataCount()).isEqualTo(1);
174 
175     int bucketCount = valueData.getData(0).getBucketInfoCount();
176     // should have at least 2 buckets
177     assertThat(bucketCount).isAtLeast(2);
178     ValueMetricData data = valueData.getData(0);
179     int totalValue = 0;
180     for (ValueBucketInfo bucketInfo : data.getBucketInfoList()) {
181       MetricsUtils.assertBucketTimePresent(bucketInfo);
182       assertThat(bucketInfo.getValuesCount()).isEqualTo(1);
183       assertThat(bucketInfo.getValues(0).getIndex()).isEqualTo(0);
184       totalValue += (int) bucketInfo.getValues(0).getValueLong();
185     }
186     // At most we lose one full min bucket
187     assertThat(totalValue).isGreaterThan(130_000 - 60_000);
188   }
189 
190   // Test value metric with pulled atoms and across multiple buckets
testMultipleEventsPerBucket()191   public void testMultipleEventsPerBucket() throws Exception {
192     // Add AtomMatcher's.
193     final String predicateTrueName = "APP_BREADCRUMB_REPORTED_START";
194     final String predicateFalseName = "APP_BREADCRUMB_REPORTED_STOP";
195     final String predicateName = "APP_BREADCRUMB_REPORTED_IS_STOP";
196 
197     AtomMatcher startAtomMatcher =
198             MetricsUtils.startAtomMatcher(predicateTrueName.hashCode());
199     AtomMatcher stopAtomMatcher =
200             MetricsUtils.stopAtomMatcher(predicateFalseName.hashCode());
201 
202     StatsdConfig.Builder builder = createConfigBuilder();
203     builder.addAtomMatcher(startAtomMatcher);
204     builder.addAtomMatcher(stopAtomMatcher);
205     builder.addPredicate(Predicate.newBuilder()
206             .setId(predicateName.hashCode())
207             .setSimplePredicate(SimplePredicate.newBuilder()
208                     .setStart(predicateTrueName.hashCode())
209                     .setStop(predicateFalseName.hashCode())
210                     .setCountNesting(false)
211             )
212     );
213 
214     final String atomName = "SYSTEM_ELAPSED_REALTIME";
215     SimpleAtomMatcher.Builder sam = SimpleAtomMatcher.newBuilder().setAtomId(Atom.SYSTEM_ELAPSED_REALTIME_FIELD_NUMBER);
216     builder.addAtomMatcher(AtomMatcher.newBuilder()
217             .setId(atomName.hashCode())
218             .setSimpleAtomMatcher(sam));
219 
220     // Add ValueMetric.
221     builder.addValueMetric(
222             ValueMetric.newBuilder()
223                     .setId(MetricsUtils.VALUE_METRIC_ID)
224                     .setWhat(atomName.hashCode())
225                     .setBucket(TimeUnit.ONE_MINUTE)
226                     .setValueField(FieldMatcher.newBuilder()
227                             .setField(Atom.SYSTEM_ELAPSED_REALTIME_FIELD_NUMBER)
228                             .addChild(FieldMatcher.newBuilder().setField(
229                                     SystemElapsedRealtime.TIME_MILLIS_FIELD_NUMBER)))
230                     .setCondition(predicateName.hashCode())
231                     .build());
232 
233     // Upload config.
234     uploadConfig(builder);
235 
236     final int NUM_EVENTS = 10;
237     final long GAP_INTERVAL = 10_000;
238     // Create AppBreadcrumbReported Start/Stop events.
239     for (int i = 0; i < NUM_EVENTS; i ++) {
240       doAppBreadcrumbReportedStart(1);
241       Thread.sleep(GAP_INTERVAL);
242       doAppBreadcrumbReportedStop(1);
243       Thread.sleep(GAP_INTERVAL);
244     }
245 
246     // Wait for the metrics to propagate to statsd.
247     Thread.sleep(1_000);
248 
249     StatsLogReport metricReport = getStatsLogReport();
250     LogUtil.CLog.d("Got the following value metric data: " + metricReport.toString());
251     assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.VALUE_METRIC_ID);
252     assertThat(metricReport.hasValueMetrics()).isTrue();
253     StatsLogReport.ValueMetricDataWrapper valueData = metricReport.getValueMetrics();
254     assertThat(valueData.getDataCount()).isEqualTo(1);
255 
256     int bucketCount = valueData.getData(0).getBucketInfoCount();
257     // should have at least 2 buckets
258     assertThat(bucketCount).isAtLeast(2);
259     ValueMetricData data = valueData.getData(0);
260     int totalValue = 0;
261     for (ValueBucketInfo bucketInfo : data.getBucketInfoList()) {
262       MetricsUtils.assertBucketTimePresent(bucketInfo);
263       assertThat(bucketInfo.getValuesCount()).isEqualTo(1);
264       assertThat(bucketInfo.getValues(0).getIndex()).isEqualTo(0);
265       totalValue += (int) bucketInfo.getValues(0).getValueLong();
266     }
267     // At most we lose one full min bucket
268     assertThat((long) totalValue).isGreaterThan(GAP_INTERVAL * NUM_EVENTS - 60_000);
269   }
270 
271   // Test value metric with pulled atoms and across multiple buckets
testPullerAcrossBucketsWithActivation()272   public void testPullerAcrossBucketsWithActivation() throws Exception {
273     StatsdConfig.Builder builder = createConfigBuilder();
274 
275     // Add AtomMatcher's.
276     int activationAtomMatcherId = 1;
277     int activationAtomMatcherLabel = 1;
278     AtomMatcher activationAtomMatcher =
279             MetricsUtils.appBreadcrumbMatcherWithLabel(
280                     activationAtomMatcherId, activationAtomMatcherLabel);
281     final String atomName = "SYSTEM_ELAPSED_REALTIME";
282     SimpleAtomMatcher.Builder sam = SimpleAtomMatcher.newBuilder()
283             .setAtomId(Atom.SYSTEM_ELAPSED_REALTIME_FIELD_NUMBER);
284     builder.addAtomMatcher(activationAtomMatcher)
285             .addAtomMatcher(AtomMatcher.newBuilder()
286                     .setId(atomName.hashCode())
287                     .setSimpleAtomMatcher(sam));
288 
289     // Add ValueMetric.
290     builder.addValueMetric(
291             ValueMetric.newBuilder()
292                     .setId(MetricsUtils.VALUE_METRIC_ID)
293                     .setWhat(atomName.hashCode())
294                     .setBucket(TimeUnit.ONE_MINUTE)
295                     .setValueField(FieldMatcher.newBuilder()
296                             .setField(Atom.SYSTEM_ELAPSED_REALTIME_FIELD_NUMBER)
297                             .addChild(FieldMatcher.newBuilder().setField(
298                                     SystemElapsedRealtime.TIME_MILLIS_FIELD_NUMBER)))
299                     .build());
300     // Add activation.
301     builder.addMetricActivation(MetricActivation.newBuilder()
302           .setMetricId(MetricsUtils.VALUE_METRIC_ID)
303           .setActivationType(ActivationType.ACTIVATE_IMMEDIATELY)
304           .addEventActivation(EventActivation.newBuilder()
305                   .setAtomMatcherId(activationAtomMatcherId)
306                   .setTtlSeconds(5)));
307 
308 
309     // Upload config.
310     uploadConfig(builder);
311 
312     // Wait for 1 min and 10 sec to capture at least 1 bucket
313     Thread.sleep(60_000 + 10_000);
314 
315     // Wait for the metrics to propagate to statsd.
316     Thread.sleep(1_000);
317 
318     StatsLogReport metricReport = getStatsLogReport();
319     LogUtil.CLog.d("Got the following value metric data: " + metricReport.toString());
320     assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.VALUE_METRIC_ID);
321     assertThat(metricReport.getValueMetrics().getDataList()).isEmpty();
322     // Bucket is skipped because metric is not activated.
323     assertThat(metricReport.getValueMetrics().getSkippedList()).isNotEmpty();
324     assertThat(metricReport.getValueMetrics().getSkipped(0).getDropEventList()).isNotEmpty();
325     assertThat(metricReport.getValueMetrics().getSkipped(0).getDropEvent(0).getDropReason())
326             .isEqualTo(BucketDropReason.NO_DATA);
327   }
328 
testValueMetricWithConditionAndActivation()329     public void testValueMetricWithConditionAndActivation() throws Exception {
330         final int conditionLabel = 2;
331         final int activationMatcherId = 5;
332         final int activationMatcherLabel = 5;
333         final int whatMatcherId = 8;
334         final int ttlSec = 5;
335 
336         // Add AtomMatchers.
337         AtomMatcher conditionStartAtomMatcher = MetricsUtils.startAtomMatcherWithLabel(
338                 APP_BREADCRUMB_REPORTED_A_MATCH_START_ID, conditionLabel);
339         AtomMatcher conditionStopAtomMatcher = MetricsUtils.stopAtomMatcherWithLabel(
340                 APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID, conditionLabel);
341         AtomMatcher activationMatcher =
342                 MetricsUtils.startAtomMatcherWithLabel(
343                         activationMatcherId, activationMatcherLabel);
344         AtomMatcher whatMatcher =
345                 MetricsUtils.unspecifiedAtomMatcher(whatMatcherId);
346 
347         StatsdConfig.Builder builder = createConfigBuilder()
348                 .addAtomMatcher(conditionStartAtomMatcher)
349                 .addAtomMatcher(conditionStopAtomMatcher)
350                 .addAtomMatcher(whatMatcher)
351                 .addAtomMatcher(activationMatcher);
352 
353         // Add Predicates.
354         SimplePredicate simplePredicate = SimplePredicate.newBuilder()
355                 .setStart(APP_BREADCRUMB_REPORTED_A_MATCH_START_ID)
356                 .setStop(APP_BREADCRUMB_REPORTED_A_MATCH_STOP_ID)
357                 .build();
358         Predicate predicate = Predicate.newBuilder()
359                                   .setId(MetricsUtils.StringToId("Predicate"))
360                                   .setSimplePredicate(simplePredicate)
361                                   .build();
362         builder.addPredicate(predicate);
363 
364         // Add ValueMetric.
365         builder
366                 .addValueMetric(ValueMetric.newBuilder()
367                         .setId(MetricsUtils.VALUE_METRIC_ID)
368                         .setWhat(whatMatcher.getId())
369                         .setBucket(TimeUnit.ONE_MINUTE)
370                         .setCondition(predicate.getId())
371                         .setValueField(FieldMatcher.newBuilder()
372                                 .setField(Atom.APP_BREADCRUMB_REPORTED_FIELD_NUMBER)
373                                 .addChild(FieldMatcher.newBuilder()
374                                         .setField(AppBreadcrumbReported.LABEL_FIELD_NUMBER))
375                         )
376                         .setDimensionsInWhat(FieldMatcher.newBuilder().setField(whatMatcherId))
377                 )
378                 .addMetricActivation(MetricActivation.newBuilder()
379                         .setMetricId(MetricsUtils.VALUE_METRIC_ID)
380                         .addEventActivation(EventActivation.newBuilder()
381                                 .setAtomMatcherId(activationMatcherId)
382                                 .setActivationType(ActivationType.ACTIVATE_IMMEDIATELY)
383                                 .setTtlSeconds(ttlSec)
384                         )
385                 );
386 
387         uploadConfig(builder);
388 
389         // Activate the metric.
390         doAppBreadcrumbReportedStart(activationMatcherLabel);
391         Thread.sleep(10);
392 
393         // Set the condition to true.
394         doAppBreadcrumbReportedStart(conditionLabel);
395         Thread.sleep(10);
396 
397         // Skipped due to unknown condition at start of bucket.
398         doAppBreadcrumbReported(10);
399         Thread.sleep(10);
400 
401         // Skipped due to unknown condition at start of bucket.
402         doAppBreadcrumbReported(200);
403         Thread.sleep(10);
404 
405         // Set the condition to false.
406         doAppBreadcrumbReportedStop(conditionLabel);
407         Thread.sleep(10);
408 
409         // Log an event that should not be counted because condition is false.
410         doAppBreadcrumbReported(3_000);
411         Thread.sleep(10);
412 
413         // Let the metric deactivate.
414         Thread.sleep(ttlSec * 1000);
415 
416         // Log an event that should not be counted.
417         doAppBreadcrumbReported(40_000);
418         Thread.sleep(10);
419 
420         // Condition to true again.
421         doAppBreadcrumbReportedStart(conditionLabel);
422         Thread.sleep(10);
423 
424         // Event should not be counted, metric is still not active.
425         doAppBreadcrumbReported(500_000);
426         Thread.sleep(10);
427 
428         // Activate the metric.
429         doAppBreadcrumbReportedStart(activationMatcherLabel);
430         Thread.sleep(10);
431 
432         //  Log an event that should be counted.
433         doAppBreadcrumbReported(6_000_000);
434         Thread.sleep(10);
435 
436         // Let the metric deactivate.
437         Thread.sleep(ttlSec * 1000);
438 
439         // Log an event that should not be counted.
440         doAppBreadcrumbReported(70_000_000);
441         Thread.sleep(10);
442 
443         // Wait for the metrics to propagate to statsd.
444         Thread.sleep(2000);
445 
446         StatsLogReport metricReport = getStatsLogReport();
447         LogUtil.CLog.d("Received the following data: " + metricReport.toString());
448         assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.VALUE_METRIC_ID);
449         assertThat(metricReport.hasValueMetrics()).isTrue();
450         assertThat(metricReport.getIsActive()).isFalse();
451 
452         StatsLogReport.ValueMetricDataWrapper valueData = metricReport.getValueMetrics();
453         assertThat(valueData.getDataCount()).isEqualTo(1);
454         assertThat(valueData.getData(0).getBucketInfoCount()).isEqualTo(1);
455         long totalValue = valueData.getData(0).getBucketInfoList().stream()
456                 .peek(MetricsUtils::assertBucketTimePresent)
457                 .peek(bucketInfo -> assertThat(bucketInfo.getValuesCount()).isEqualTo(1))
458                 .map(bucketInfo -> bucketInfo.getValues(0))
459                 .peek(value -> assertThat(value.getIndex()).isEqualTo(0))
460                 .mapToLong(value -> value.getValueLong())
461                 .sum();
462         assertThat(totalValue).isEqualTo(6_000_000);
463     }
464 
465 }
466