1 /* 2 * Copyright (C) 2019 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.device.metric; 17 18 import static com.android.tradefed.util.proto.TfMetricProtoUtil.stringToMetric; 19 20 import com.android.os.AtomsProto.Atom; 21 import com.android.os.AtomsProto.BootSequenceReported; 22 import com.android.os.StatsLog.EventMetricData; 23 import com.android.tradefed.config.OptionClass; 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.metrics.proto.MetricMeasurement.Metric; 28 import com.android.tradefed.util.statsd.ConfigUtil; 29 import com.android.tradefed.util.statsd.MetricUtil; 30 import com.google.common.annotations.VisibleForTesting; 31 32 import java.io.IOException; 33 import java.util.Arrays; 34 import java.util.ArrayList; 35 import java.util.HashMap; 36 import java.util.List; 37 import java.util.Map; 38 39 /** 40 * Collector that collects device reboot during the test run and report them by reason and counts. 41 */ 42 @OptionClass(alias = "reboot-reason-collector") 43 public class RebootReasonCollector extends BaseDeviceMetricCollector { 44 private static final String METRIC_SEP = "-"; 45 public static final String METRIC_PREFIX = "rebooted" + METRIC_SEP; 46 public static final String COUNT_KEY = String.join(METRIC_SEP, "reboot", "count"); 47 48 private List<ITestDevice> mTestDevices; 49 // Map to store statsd config ids for each device, keyed by the device serial number. 50 private Map<String, Long> mDeviceConfigIds = new HashMap<>(); 51 52 /** Push the statsd config to each device and store the config Ids. */ 53 @Override onTestRunStart(DeviceMetricData runData)54 public void onTestRunStart(DeviceMetricData runData) { 55 mTestDevices = getDevices(); 56 for (ITestDevice device : mTestDevices) { 57 try { 58 mDeviceConfigIds.put( 59 device.getSerialNumber(), 60 pushStatsConfig( 61 device, Arrays.asList(Atom.BOOT_SEQUENCE_REPORTED_FIELD_NUMBER))); 62 } catch (DeviceNotAvailableException | IOException e) { 63 // Error is not thrown as we still want to push the config to other devices. 64 CLog.e( 65 "Failed to push statsd config to device %s. Exception: %s.", 66 device.getSerialNumber(), e.toString()); 67 } 68 } 69 } 70 71 @Override onTestRunEnd( DeviceMetricData runData, final Map<String, Metric> currentRunMetrics)72 public void onTestRunEnd( 73 DeviceMetricData runData, final Map<String, Metric> currentRunMetrics) { 74 for (ITestDevice device : mTestDevices) { 75 List<EventMetricData> metricData = new ArrayList<>(); 76 if (!mDeviceConfigIds.containsKey(device.getSerialNumber())) { 77 CLog.e("No config ID is associated with device %s.", device.getSerialNumber()); 78 continue; 79 } 80 long configId = mDeviceConfigIds.get(device.getSerialNumber()); 81 try { 82 metricData.addAll(getEventMetricData(device, configId)); 83 } catch (DeviceNotAvailableException e) { 84 CLog.e( 85 "Failed to pull metric data from device %s. Exception: %s.", 86 device.getSerialNumber(), e.toString()); 87 } 88 Map<String, Integer> metricsForDevice = new HashMap<>(); 89 int rebootCount = 0; 90 for (EventMetricData eventMetricEntry : metricData) { 91 Atom eventAtom = eventMetricEntry.getAtom(); 92 if (eventAtom.hasBootSequenceReported()) { 93 rebootCount += 1; 94 BootSequenceReported bootAtom = eventAtom.getBootSequenceReported(); 95 String bootReasonKey = 96 METRIC_PREFIX 97 + String.join( 98 METRIC_SEP, 99 bootAtom.getBootloaderReason(), 100 bootAtom.getSystemReason()); 101 // Update the counts for the specific boot reason in the current atom. 102 metricsForDevice.computeIfPresent(bootReasonKey, (k, v) -> v + 1); 103 metricsForDevice.computeIfAbsent(bootReasonKey, k -> 1); 104 } 105 } 106 for (String key : metricsForDevice.keySet()) { 107 runData.addMetricForDevice( 108 device, 109 key, 110 stringToMetric(String.valueOf(metricsForDevice.get(key))).toBuilder()); 111 } 112 // Add the count regardless of whether reboots occurred or not. 113 runData.addMetricForDevice( 114 device, COUNT_KEY, stringToMetric(String.valueOf(rebootCount)).toBuilder()); 115 try { 116 removeConfig(device, configId); 117 } catch (DeviceNotAvailableException e) { 118 // Error is not thrown as we still want to remove the config from other devices. 119 CLog.e( 120 "Failed to remove statsd config from device %s. Exception: %s.", 121 device.getSerialNumber(), e.toString()); 122 } 123 } 124 } 125 126 @VisibleForTesting pushStatsConfig(ITestDevice device, List<Integer> eventAtomIds)127 long pushStatsConfig(ITestDevice device, List<Integer> eventAtomIds) 128 throws IOException, DeviceNotAvailableException { 129 return ConfigUtil.pushStatsConfig(device, eventAtomIds); 130 } 131 132 @VisibleForTesting removeConfig(ITestDevice device, long configId)133 void removeConfig(ITestDevice device, long configId) throws DeviceNotAvailableException { 134 ConfigUtil.removeConfig(device, configId); 135 } 136 137 @VisibleForTesting getEventMetricData(ITestDevice device, long configId)138 List<EventMetricData> getEventMetricData(ITestDevice device, long configId) 139 throws DeviceNotAvailableException { 140 return MetricUtil.getEventMetricData(device, configId); 141 } 142 } 143