• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.tradefed.util.statsd;
17 
18 import com.android.os.StatsLog.ConfigMetricsReport;
19 import com.android.os.StatsLog.ConfigMetricsReportList;
20 import com.android.os.StatsLog.EventMetricData;
21 import com.android.os.StatsLog.StatsdStatsReport;
22 import com.android.os.StatsLog.StatsLogReport;
23 import com.android.tradefed.device.CollectingByteOutputReceiver;
24 import com.android.tradefed.device.DeviceNotAvailableException;
25 import com.android.tradefed.device.ITestDevice;
26 import com.android.tradefed.log.LogUtil.CLog;
27 import com.android.tradefed.result.ByteArrayInputStreamSource;
28 import com.android.tradefed.result.InputStreamSource;
29 
30 import com.google.common.annotations.VisibleForTesting;
31 import com.google.protobuf.InvalidProtocolBufferException;
32 
33 import java.util.ArrayList;
34 import java.util.Comparator;
35 import java.util.List;
36 
37 /** Utility class for pulling metrics from pushed statsd configurations. */
38 public class MetricUtil {
39     @VisibleForTesting
40     static final String DUMP_REPORT_CMD_TEMPLATE = "cmd stats dump-report %s %s --proto";
41 
42     // Android P version does not support this argument. Make it separate and add only when needed
43     @VisibleForTesting
44     static final String DUMP_REPORT_INCLUDE_CURRENT_BUCKET = "--include_current_bucket";
45 
46     // The command is documented in packages/modules/StatsD/statsd/src/StatsService.cpp.
47     @VisibleForTesting
48     static final String DUMP_STATSD_METADATA_CMD = "dumpsys stats --metadata --proto";
49 
50     @VisibleForTesting static final int SDK_VERSION_Q = 29;
51 
52     /** Get statsd event metrics data from the device using the statsd config id. */
getEventMetricData(ITestDevice device, long configId)53     public static List<EventMetricData> getEventMetricData(ITestDevice device, long configId)
54             throws DeviceNotAvailableException {
55         ConfigMetricsReportList reports = getReportList(device, configId);
56         if (reports.getReportsList().isEmpty()) {
57             CLog.d("No stats report collected.");
58             return new ArrayList<EventMetricData>();
59         }
60         List<EventMetricData> data = new ArrayList<>();
61         // Usually, there is only one report. However, a runtime restart will generate a new report
62         // for the same config, resulting in multiple reports. Their data is independent and can
63         // simply be concatenated together.
64         for (ConfigMetricsReport report : reports.getReportsList()) {
65             for (StatsLogReport metric : report.getMetricsList()) {
66                 data.addAll(metric.getEventMetrics().getDataList());
67             }
68         }
69         data.sort(Comparator.comparing(EventMetricData::getElapsedTimestampNanos));
70         CLog.d("Received EventMetricDataList as following:\n");
71         for (EventMetricData d : data) {
72             CLog.d("Atom at %d:\n%s", d.getElapsedTimestampNanos(), d.getAtom().toString());
73         }
74         return data;
75     }
76 
77     /** Get Statsd report as a byte stream source */
getReportByteStream(ITestDevice device, long configId)78     public static InputStreamSource getReportByteStream(ITestDevice device, long configId)
79             throws DeviceNotAvailableException {
80         return new ByteArrayInputStreamSource(getReportByteArray(device, configId));
81     }
82 
83     /** Get statsd metadata which also contains system server crash information. */
getStatsdMetadata(ITestDevice device)84     public static StatsdStatsReport getStatsdMetadata(ITestDevice device)
85             throws DeviceNotAvailableException, InvalidProtocolBufferException {
86         final CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
87         device.executeShellCommand(DUMP_STATSD_METADATA_CMD, receiver);
88         return StatsdStatsReport.parser().parseFrom(receiver.getOutput());
89     }
90 
91     /** Get the report list proto from the device for the given {@code configId}. */
getReportList(ITestDevice device, long configId)92     private static ConfigMetricsReportList getReportList(ITestDevice device, long configId)
93             throws DeviceNotAvailableException {
94         try {
95             byte[] output = getReportByteArray(device, configId);
96             return ConfigMetricsReportList.parser().parseFrom(output);
97         } catch (InvalidProtocolBufferException e) {
98             throw new RuntimeException(e);
99         }
100     }
101 
getReportByteArray(ITestDevice device, long configId)102     private static byte[] getReportByteArray(ITestDevice device, long configId)
103             throws DeviceNotAvailableException {
104         final CollectingByteOutputReceiver receiver = new CollectingByteOutputReceiver();
105         String dumpCommand =
106                 String.format(
107                         DUMP_REPORT_CMD_TEMPLATE,
108                         String.valueOf(configId),
109                         device.checkApiLevelAgainstNextRelease(SDK_VERSION_Q)
110                                 ? DUMP_REPORT_INCLUDE_CURRENT_BUCKET
111                                 : "");
112         CLog.d("Dumping stats report with command: " + dumpCommand);
113         device.executeShellCommand(dumpCommand, receiver);
114         return receiver.getOutput();
115     }
116 }
117