/* * Copyright (C) 2018 The Android Open Source Project * * 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 android.cts.statsd.metric; import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertWithMessage; import android.cts.statsd.atom.DeviceAtomTestCase; import com.android.internal.os.StatsdConfigProto; import com.android.internal.os.StatsdConfigProto.FieldMatcher; import com.android.internal.os.StatsdConfigProto.Position; import com.android.os.AtomsProto.Atom; import com.android.os.AtomsProto.AppBreadcrumbReported; import com.android.os.AtomsProto.BleScanStateChanged; import com.android.os.AtomsProto.WakelockStateChanged; import com.android.os.AttributionNode; import com.android.os.StatsLog; import com.android.os.StatsLog.ConfigMetricsReport; import com.android.os.StatsLog.ConfigMetricsReportList; import com.android.os.StatsLog.CountBucketInfo; import com.android.os.StatsLog.CountMetricData; import com.android.os.StatsLog.StatsLogReport; import com.android.tradefed.device.DeviceNotAvailableException; import com.android.tradefed.log.LogUtil; import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; import java.util.stream.IntStream; public class CountMetricsTests extends DeviceAtomTestCase { public void testSimpleEventCountMetric() throws Exception { int matcherId = 1; StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder(); builder.addCountMetric(StatsdConfigProto.CountMetric.newBuilder() .setId(MetricsUtils.COUNT_METRIC_ID) .setBucket(StatsdConfigProto.TimeUnit.CTS) .setWhat(matcherId)) .addAtomMatcher(MetricsUtils.simpleAtomMatcher(matcherId)); uploadConfig(builder); doAppBreadcrumbReportedStart(0); Thread.sleep(100); doAppBreadcrumbReportedStop(0); Thread.sleep(2000); // Wait for the metrics to propagate to statsd. StatsLogReport metricReport = getStatsLogReport(); LogUtil.CLog.d("Got the following stats log report: \n" + metricReport.toString()); assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.COUNT_METRIC_ID); assertThat(metricReport.hasCountMetrics()).isTrue(); StatsLogReport.CountMetricDataWrapper countData = metricReport.getCountMetrics(); assertThat(countData.getDataCount()).isGreaterThan(0); assertThat(countData.getData(0).getBucketInfo(0).getCount()).isEqualTo(2); } public void testEventCountWithCondition() throws Exception { int startMatcherId = 1; int endMatcherId = 2; int whatMatcherId = 3; int conditionId = 4; StatsdConfigProto.AtomMatcher whatMatcher = MetricsUtils.unspecifiedAtomMatcher(whatMatcherId); StatsdConfigProto.AtomMatcher predicateStartMatcher = MetricsUtils.startAtomMatcher(startMatcherId); StatsdConfigProto.AtomMatcher predicateEndMatcher = MetricsUtils.stopAtomMatcher(endMatcherId); StatsdConfigProto.Predicate p = StatsdConfigProto.Predicate.newBuilder() .setSimplePredicate(StatsdConfigProto.SimplePredicate.newBuilder() .setStart(startMatcherId) .setStop(endMatcherId) .setCountNesting(false)) .setId(conditionId) .build(); StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder() .addCountMetric(StatsdConfigProto.CountMetric.newBuilder() .setId(MetricsUtils.COUNT_METRIC_ID) .setBucket(StatsdConfigProto.TimeUnit.CTS) .setWhat(whatMatcherId) .setCondition(conditionId)) .addAtomMatcher(whatMatcher) .addAtomMatcher(predicateStartMatcher) .addAtomMatcher(predicateEndMatcher) .addPredicate(p); uploadConfig(builder); doAppBreadcrumbReported(0, AppBreadcrumbReported.State.UNSPECIFIED.ordinal()); Thread.sleep(10); doAppBreadcrumbReportedStart(0); Thread.sleep(10); doAppBreadcrumbReported(0, AppBreadcrumbReported.State.UNSPECIFIED.ordinal()); Thread.sleep(10); doAppBreadcrumbReportedStop(0); Thread.sleep(10); doAppBreadcrumbReported(0, AppBreadcrumbReported.State.UNSPECIFIED.ordinal()); Thread.sleep(2000); // Wait for the metrics to propagate to statsd. StatsLogReport metricReport = getStatsLogReport(); assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.COUNT_METRIC_ID); assertThat(metricReport.hasCountMetrics()).isTrue(); StatsLogReport.CountMetricDataWrapper countData = metricReport.getCountMetrics(); assertThat(countData.getDataCount()).isGreaterThan(0); assertThat(countData.getData(0).getBucketInfo(0).getCount()).isEqualTo(1); } public void testEventCountWithConditionAndActivation() throws Exception { int startMatcherId = 1; int startMatcherLabel = 1; int endMatcherId = 2; int endMatcherLabel = 2; int whatMatcherId = 3; int whatMatcherLabel = 3; int conditionId = 4; int activationMatcherId = 5; int activationMatcherLabel = 5; int ttlSec = 5; StatsdConfigProto.AtomMatcher whatMatcher = MetricsUtils.appBreadcrumbMatcherWithLabel(whatMatcherId, whatMatcherLabel); StatsdConfigProto.AtomMatcher predicateStartMatcher = MetricsUtils.startAtomMatcherWithLabel(startMatcherId, startMatcherLabel); StatsdConfigProto.AtomMatcher predicateEndMatcher = MetricsUtils.stopAtomMatcherWithLabel(endMatcherId, endMatcherLabel); StatsdConfigProto.AtomMatcher activationMatcher = MetricsUtils.appBreadcrumbMatcherWithLabel(activationMatcherId, activationMatcherLabel); StatsdConfigProto.Predicate p = StatsdConfigProto.Predicate.newBuilder() .setSimplePredicate(StatsdConfigProto.SimplePredicate.newBuilder() .setStart(startMatcherId) .setStop(endMatcherId) .setCountNesting(false)) .setId(conditionId) .build(); StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder() .addCountMetric(StatsdConfigProto.CountMetric.newBuilder() .setId(MetricsUtils.COUNT_METRIC_ID) .setBucket(StatsdConfigProto.TimeUnit.CTS) .setWhat(whatMatcherId) .setCondition(conditionId) ) .addAtomMatcher(whatMatcher) .addAtomMatcher(predicateStartMatcher) .addAtomMatcher(predicateEndMatcher) .addAtomMatcher(activationMatcher) .addPredicate(p) .addMetricActivation(StatsdConfigProto.MetricActivation.newBuilder() .setMetricId(MetricsUtils.COUNT_METRIC_ID) .setActivationType(StatsdConfigProto.ActivationType.ACTIVATE_IMMEDIATELY) .addEventActivation(StatsdConfigProto.EventActivation.newBuilder() .setAtomMatcherId(activationMatcherId) .setTtlSeconds(ttlSec))); uploadConfig(builder); // Activate the metric. doAppBreadcrumbReported(activationMatcherLabel); Thread.sleep(10); // Set the condition to true. doAppBreadcrumbReportedStart(startMatcherLabel); Thread.sleep(10); // Log an event that should be counted. Bucket 1 Count 1. doAppBreadcrumbReported(whatMatcherLabel); Thread.sleep(10); // Log an event that should be counted. Bucket 1 Count 2. doAppBreadcrumbReported(whatMatcherLabel); Thread.sleep(10); // Set the condition to false. doAppBreadcrumbReportedStop(endMatcherLabel); Thread.sleep(10); // Log an event that should not be counted because condition is false. doAppBreadcrumbReported(whatMatcherLabel); Thread.sleep(10); // Let the metric deactivate. Thread.sleep(ttlSec * 1000); // Log an event that should not be counted. doAppBreadcrumbReported(whatMatcherLabel); Thread.sleep(10); // Condition to true again. doAppBreadcrumbReportedStart(startMatcherLabel); Thread.sleep(10); // Event should not be counted, metric is still not active. doAppBreadcrumbReported(whatMatcherLabel); Thread.sleep(10); // Activate the metric. doAppBreadcrumbReported(activationMatcherLabel); Thread.sleep(10); // Log an event that should be counted. doAppBreadcrumbReported(whatMatcherLabel); Thread.sleep(10); // Let the metric deactivate. Thread.sleep(ttlSec * 1000); // Log an event that should not be counted. doAppBreadcrumbReported(whatMatcherLabel); Thread.sleep(10); // Wait for the metrics to propagate to statsd. Thread.sleep(2000); StatsLogReport metricReport = getStatsLogReport(); assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.COUNT_METRIC_ID); LogUtil.CLog.d("Received the following data: " + metricReport.toString()); assertThat(metricReport.hasCountMetrics()).isTrue(); assertThat(metricReport.getIsActive()).isFalse(); StatsLogReport.CountMetricDataWrapper countData = metricReport.getCountMetrics(); assertThat(countData.getDataCount()).isEqualTo(1); assertThat(countData.getData(0).getBucketInfoCount()).isEqualTo(2); assertThat(countData.getData(0).getBucketInfo(0).getCount()).isEqualTo(2); assertThat(countData.getData(0).getBucketInfo(1).getCount()).isEqualTo(1); } public void testPartialBucketCountMetric() throws Exception { int matcherId = 1; StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder(); builder .addCountMetric( StatsdConfigProto.CountMetric.newBuilder() .setId(MetricsUtils.COUNT_METRIC_ID) .setBucket(StatsdConfigProto.TimeUnit.ONE_DAY) // Ensures partial bucket. .setWhat(matcherId) .setSplitBucketForAppUpgrade(true)) .addAtomMatcher(MetricsUtils.simpleAtomMatcher(matcherId)); uploadConfig(builder); doAppBreadcrumbReportedStart(0); builder.getCountMetricBuilder(0).setBucket(StatsdConfigProto.TimeUnit.CTS); uploadConfig(builder); // The count metric had a partial bucket. doAppBreadcrumbReportedStart(0); Thread.sleep(10); doAppBreadcrumbReportedStart(0); Thread.sleep(WAIT_TIME_LONG); // Finish the current bucket. ConfigMetricsReportList reports = getReportList(); LogUtil.CLog.d("Got following report list: " + reports.toString()); assertThat(reports.getReportsCount()).isEqualTo(2); boolean inOrder = reports.getReports(0).getCurrentReportWallClockNanos() < reports.getReports(1).getCurrentReportWallClockNanos(); // Only 1 metric, so there should only be 1 StatsLogReport. for (ConfigMetricsReport report : reports.getReportsList()) { assertThat(report.getMetricsCount()).isEqualTo(1); assertThat(report.getMetrics(0).getCountMetrics().getDataCount()).isEqualTo(1); } CountMetricData data1 = reports.getReports(inOrder? 0 : 1).getMetrics(0).getCountMetrics().getData(0); CountMetricData data2 = reports.getReports(inOrder? 1 : 0).getMetrics(0).getCountMetrics().getData(0); // Data1 should have only 1 bucket, and it should be a partial bucket. // The count should be 1. assertThat(data1.getBucketInfoCount()).isEqualTo(1); CountBucketInfo bucketInfo = data1.getBucketInfo(0); assertThat(bucketInfo.getCount()).isEqualTo(1); assertWithMessage("First report's bucket should be less than 1 day") .that(bucketInfo.getEndBucketElapsedNanos()) .isLessThan(bucketInfo.getStartBucketElapsedNanos() + 1_000_000_000L * 60L * 60L * 24L); //Second report should have a count of 2. assertThat(data2.getBucketInfoCount()).isAtMost(2); int totalCount = 0; for (CountBucketInfo bucket : data2.getBucketInfoList()) { totalCount += bucket.getCount(); } assertThat(totalCount).isEqualTo(2); } public void testSlicedStateCountMetricNoReset() throws Exception { int whatMatcherId = 3; int stateId = 4; int onStateGroupId = 5; int offStateGroupId = 6; // Atom 9998 { // repeated AttributionNode attribution_node = 1; // optional WakeLockLevelEnum type = 2; // optional string tag = 3; // } int whatAtomId = 9_998; StatsdConfigProto.AtomMatcher whatMatcher = MetricsUtils.getAtomMatcher(whatAtomId) .setId(whatMatcherId) .build(); StatsdConfigProto.State state = StatsdConfigProto.State.newBuilder() .setId(stateId) .setAtomId(Atom.WAKELOCK_STATE_CHANGED_FIELD_NUMBER) .setMap(StatsdConfigProto.StateMap.newBuilder() .addGroup(StatsdConfigProto.StateMap.StateGroup.newBuilder() .setGroupId(onStateGroupId) .addValue(WakelockStateChanged.State.ACQUIRE_VALUE) .addValue(WakelockStateChanged.State.CHANGE_ACQUIRE_VALUE) ) .addGroup(StatsdConfigProto.StateMap.StateGroup.newBuilder() .setGroupId(offStateGroupId) .addValue(WakelockStateChanged.State.RELEASE_VALUE) .addValue(WakelockStateChanged.State.CHANGE_RELEASE_VALUE) ) ) .build(); StatsdConfigProto.MetricStateLink stateLink = StatsdConfigProto.MetricStateLink.newBuilder() .setStateAtomId(Atom.WAKELOCK_STATE_CHANGED_FIELD_NUMBER) .setFieldsInWhat(FieldMatcher.newBuilder() .setField(whatAtomId) .addChild(FieldMatcher.newBuilder() .setField(1) .setPosition(Position.FIRST) .addChild(FieldMatcher.newBuilder() .setField(AttributionNode.UID_FIELD_NUMBER) ) ) .addChild(FieldMatcher.newBuilder() .setField(2) ) .addChild(FieldMatcher.newBuilder() .setField(3) ) ) .setFieldsInState(FieldMatcher.newBuilder() .setField(Atom.WAKELOCK_STATE_CHANGED_FIELD_NUMBER) .addChild(FieldMatcher.newBuilder() .setField(WakelockStateChanged.ATTRIBUTION_NODE_FIELD_NUMBER) .setPosition(Position.FIRST) .addChild(FieldMatcher.newBuilder() .setField(AttributionNode.UID_FIELD_NUMBER) ) ) .addChild(FieldMatcher.newBuilder() .setField(WakelockStateChanged.TYPE_FIELD_NUMBER) ) .addChild(FieldMatcher.newBuilder() .setField(WakelockStateChanged.TAG_FIELD_NUMBER) ) ) .build(); StatsdConfigProto.StatsdConfig.Builder builder = createConfigBuilder() .addCountMetric(StatsdConfigProto.CountMetric.newBuilder() .setId(MetricsUtils.COUNT_METRIC_ID) .setBucket(StatsdConfigProto.TimeUnit.CTS) .setWhat(whatMatcherId) .addSliceByState(stateId) .addStateLink(stateLink) .setDimensionsInWhat( FieldMatcher.newBuilder() .setField(whatAtomId) .addChild(FieldMatcher.newBuilder() .setField(1) .setPosition(Position.FIRST) .addChild(FieldMatcher.newBuilder() .setField(AttributionNode.UID_FIELD_NUMBER) ) ) .addChild(FieldMatcher.newBuilder() .setField(2) ) .addChild(FieldMatcher.newBuilder() .setField(3) ) ) ) .addAtomMatcher(whatMatcher) .addState(state); uploadConfig(builder); runDeviceTests(DEVICE_SIDE_TEST_PACKAGE, ".AtomTests", "testSliceByWakelockState"); StatsLogReport metricReport = getStatsLogReport(); LogUtil.CLog.d("Got the following stats log report: \n" + metricReport.toString()); assertThat(metricReport.getMetricId()).isEqualTo(MetricsUtils.COUNT_METRIC_ID); assertThat(metricReport.hasCountMetrics()).isTrue(); StatsLogReport.CountMetricDataWrapper dataWrapper = metricReport.getCountMetrics(); assertThat(dataWrapper.getDataCount()).isEqualTo(2); List sortedDataList = IntStream.range(0, dataWrapper.getDataCount()) .mapToObj(i -> { CountMetricData data = dataWrapper.getData(i); assertWithMessage("Unexpected SliceByState count for data[%s]", "" + i) .that(data.getSliceByStateCount()).isEqualTo(1); return data; }) .sorted((data1, data2) -> Long.compare(data1.getSliceByState(0).getGroupId(), data2.getSliceByState(0).getGroupId()) ) .collect(Collectors.toList()); CountMetricData data = sortedDataList.get(0); assertThat(data.getSliceByState(0).getAtomId()) .isEqualTo(Atom.WAKELOCK_STATE_CHANGED_FIELD_NUMBER); assertThat(data.getSliceByState(0).getGroupId()) .isEqualTo(onStateGroupId); long totalCount = data.getBucketInfoList().stream() .mapToLong(CountBucketInfo::getCount) .sum(); assertThat(totalCount).isEqualTo(6); data = sortedDataList.get(1); assertThat(data.getSliceByState(0).getAtomId()) .isEqualTo(Atom.WAKELOCK_STATE_CHANGED_FIELD_NUMBER); assertThat(data.getSliceByState(0).getGroupId()) .isEqualTo(offStateGroupId); totalCount = data.getBucketInfoList().stream() .mapToLong(CountBucketInfo::getCount) .sum(); assertThat(totalCount).isEqualTo(3); } }