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