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