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