1 /* 2 * Copyright (C) 2020 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 17 package android.cts.statsdatom.lib; 18 19 import static com.google.common.truth.Truth.assertThat; 20 import static com.google.common.truth.Truth.assertWithMessage; 21 22 import com.android.os.StatsLog; 23 24 import com.android.os.AtomsProto.Atom; 25 import com.android.os.StatsLog; 26 import com.android.os.StatsLog.ConfigMetricsReport; 27 import com.android.os.StatsLog.ConfigMetricsReportList; 28 import com.android.os.StatsLog.EventMetricData; 29 import com.android.os.StatsLog.GaugeBucketInfo; 30 import com.android.os.StatsLog.GaugeMetricData; 31 import com.android.os.StatsLog.StatsLogReport; 32 import com.android.tradefed.device.ITestDevice; 33 import com.android.tradefed.log.LogUtil.CLog; 34 import com.android.tradefed.util.Pair; 35 36 import com.google.protobuf.InvalidProtocolBufferException; 37 38 import java.util.ArrayList; 39 import java.util.Collections; 40 import java.util.Comparator; 41 import java.util.List; 42 import java.util.stream.Collectors; 43 44 public final class ReportUtils { 45 private static final String DUMP_REPORT_CMD = "cmd stats dump-report"; 46 private static final long NS_PER_SEC = (long) 1E+9; 47 48 /** 49 * Returns a list of event metrics, which is sorted by timestamp, from the statsd report. 50 * Note: Calling this function deletes the report from statsd. 51 */ getEventMetricDataList(ITestDevice device)52 public static List<EventMetricData> getEventMetricDataList(ITestDevice device) 53 throws Exception { 54 ConfigMetricsReportList reportList = getReportList(device); 55 return getEventMetricDataList(reportList); 56 } 57 58 /** 59 * Extracts and sorts the EventMetricData from the given ConfigMetricsReportList (which must 60 * contain a single report) and sorts the atoms by timestamp within the report. 61 */ getEventMetricDataList(ConfigMetricsReportList reportList)62 public static List<EventMetricData> getEventMetricDataList(ConfigMetricsReportList reportList) 63 throws Exception { 64 assertThat(reportList.getReportsCount()).isEqualTo(1); 65 ConfigMetricsReport report = reportList.getReports(0); 66 67 List<EventMetricData> data = new ArrayList<>(); 68 for (StatsLogReport metric : report.getMetricsList()) { 69 for (EventMetricData metricData : 70 metric.getEventMetrics().getDataList()) { 71 if (metricData.hasAtom()) { 72 data.add(metricData); 73 } else { 74 data.addAll(backfillAggregatedAtomsInEventMetric(metricData)); 75 } 76 } 77 } 78 data.sort(Comparator.comparing(EventMetricData::getElapsedTimestampNanos)); 79 80 CLog.d("Get EventMetricDataList as following:\n"); 81 for (EventMetricData d : data) { 82 CLog.d("Atom at " + d.getElapsedTimestampNanos() + ":\n" + d.getAtom().toString()); 83 } 84 return data; 85 } 86 87 backfillAggregatedAtomsInEventMetric( EventMetricData metricData)88 private static List<EventMetricData> backfillAggregatedAtomsInEventMetric( 89 EventMetricData metricData) { 90 if (!metricData.hasAggregatedAtomInfo()) { 91 return Collections.emptyList(); 92 } 93 List<EventMetricData> data = new ArrayList<>(); 94 StatsLog.AggregatedAtomInfo atomInfo = metricData.getAggregatedAtomInfo(); 95 for (long timestamp : atomInfo.getElapsedTimestampNanosList()) { 96 data.add(EventMetricData.newBuilder() 97 .setAtom(atomInfo.getAtom()) 98 .setElapsedTimestampNanos(timestamp) 99 .build()); 100 } 101 return data; 102 } 103 getGaugeMetricAtoms(ITestDevice device)104 public static List<Atom> getGaugeMetricAtoms(ITestDevice device) throws Exception { 105 return getGaugeMetricAtoms(device, /*checkTimestampTruncated=*/false); 106 } 107 108 /** 109 * Returns a list of gauge atoms from the statsd report. Assumes that there is only one bucket 110 * for the gauge metric. 111 * Note: calling this function deletes the report from statsd. 112 * 113 * @param checkTimestampTrucated if true, checks that atom timestmaps are properly truncated 114 */ getGaugeMetricAtoms(ITestDevice device, boolean checkTimestampTruncated)115 public static List<Atom> getGaugeMetricAtoms(ITestDevice device, 116 boolean checkTimestampTruncated) throws Exception { 117 ConfigMetricsReportList reportList = getReportList(device); 118 assertThat(reportList.getReportsCount()).isEqualTo(1); 119 ConfigMetricsReport report = reportList.getReports(0); 120 assertThat(report.getMetricsCount()).isEqualTo(1); 121 CLog.d("Got the following report: " + report.getMetrics(0).getGaugeMetrics().toString()); 122 List<Atom> atoms = new ArrayList<>(); 123 for (GaugeMetricData d : report.getMetrics(0).getGaugeMetrics().getDataList()) { 124 assertThat(d.getBucketInfoCount()).isEqualTo(1); 125 GaugeBucketInfo bucketInfo = d.getBucketInfo(0); 126 if (bucketInfo.getAtomCount() != 0) { 127 atoms.addAll(bucketInfo.getAtomList()); 128 } else { 129 atoms.addAll(backFillGaugeBucketAtoms(bucketInfo.getAggregatedAtomInfoList())); 130 } 131 if (checkTimestampTruncated) { 132 for (long timestampNs : bucketInfo.getElapsedTimestampNanosList()) { 133 assertTimestampIsTruncated(timestampNs); 134 } 135 } 136 } 137 138 CLog.d("Got the following GaugeMetric atoms:\n"); 139 for (Atom atom : atoms) { 140 CLog.d("Atom:\n" + atom.toString()); 141 } 142 return atoms; 143 } 144 backFillGaugeBucketAtoms( List<StatsLog.AggregatedAtomInfo> atomInfoList)145 private static List<Atom> backFillGaugeBucketAtoms( 146 List<StatsLog.AggregatedAtomInfo> atomInfoList) { 147 List<Pair<Atom, Long>> atomTimestamp = new ArrayList<>(); 148 for (StatsLog.AggregatedAtomInfo atomInfo : atomInfoList) { 149 for (long timestampNs : atomInfo.getElapsedTimestampNanosList()) { 150 atomTimestamp.add(Pair.create(atomInfo.getAtom(), timestampNs)); 151 } 152 } 153 atomTimestamp.sort(Comparator.comparing(o -> o.second)); 154 return atomTimestamp.stream().map(p -> p.first).collect(Collectors.toList()); 155 } 156 157 /** 158 * Delete all pre-existing reports corresponding to the CTS config. 159 */ clearReports(ITestDevice device)160 public static void clearReports(ITestDevice device) throws Exception { 161 getReportList(device); 162 } 163 164 /** 165 * Retrieves the ConfigMetricsReports corresponding to the CTS config from statsd. 166 * Note: Calling this functions deletes the report from statsd. 167 */ getReportList(ITestDevice device)168 private static ConfigMetricsReportList getReportList(ITestDevice device) throws Exception { 169 try { 170 String cmd = String.join(" ", DUMP_REPORT_CMD, ConfigUtils.CONFIG_ID_STRING, 171 "--include_current_bucket", "--proto"); 172 ConfigMetricsReportList reportList = DeviceUtils.getShellCommandOutput(device, 173 ConfigMetricsReportList.parser(), cmd); 174 return reportList; 175 } catch (InvalidProtocolBufferException ex) { 176 int hostUid = DeviceUtils.getHostUid(device); 177 CLog.e("Failed to fetch and parse the statsd output report. Perhaps there is not a " 178 + "valid statsd config for the requested uid=" + hostUid + ", id=" 179 + ConfigUtils.CONFIG_ID + "."); 180 throw ex; 181 } 182 } 183 184 /** 185 * Checks that a timestamp has been truncated to a multiple of 5 min. 186 */ assertTimestampIsTruncated(long timestampNs)187 private static void assertTimestampIsTruncated(long timestampNs) { 188 long fiveMinutesInNs = NS_PER_SEC * 5 * 60; 189 assertWithMessage("Timestamp is not truncated") 190 .that(timestampNs % fiveMinutesInNs).isEqualTo(0); 191 } 192 ReportUtils()193 private ReportUtils() {} 194 } 195