1 /*
2  * Copyright (C) 2022 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 com.android.tradefed.device.metric;
18 
19 import com.android.tradefed.device.DeviceNotAvailableException;
20 import com.android.tradefed.device.ITestDevice;
21 import com.android.tradefed.invoker.IInvocationContext;
22 import com.android.tradefed.log.LogUtil.CLog;
23 import com.android.tradefed.metrics.proto.MetricMeasurement;
24 import com.android.tradefed.result.FileInputStreamSource;
25 import com.android.tradefed.result.ITestInvocationListener;
26 import com.android.tradefed.result.LogDataType;
27 import com.android.tradefed.util.PerfettoTraceRecorder;
28 
29 import java.io.File;
30 import java.io.IOException;
31 import java.util.LinkedHashMap;
32 import java.util.Map;
33 /**
34  * Collector that will start perfetto trace when a test run starts and log trace file at the end.
35  */
36 public class DeviceTraceCollector extends BaseDeviceMetricCollector {
37     // Format of the trace name should be: device-trace_<device-serial>_<trace-count>_<event-name>.
38     private static final String NAME_FORMAT = "device-trace_%s_%d_%s";
39     private PerfettoTraceRecorder mPerfettoTraceRecorder = new PerfettoTraceRecorder();
40     // package name for an instrumentation test, null otherwise.
41     private String mInstrumentationPkgName;
42 
43     private Map<ITestDevice, Integer> mTraceCountMap = new LinkedHashMap<>();
44     // Map of trace files and the proper name it should be logged with
45     private Map<File, String> mTraceFilesMap = new LinkedHashMap();
46 
DeviceTraceCollector()47     public DeviceTraceCollector() {
48         setDisableReceiver(false);
49     }
50 
51     @Override
extraInit(IInvocationContext context, ITestInvocationListener listener)52     public void extraInit(IInvocationContext context, ITestInvocationListener listener)
53             throws DeviceNotAvailableException {
54         super.extraInit(context, listener);
55         for (ITestDevice device : getRealDevices()) {
56             startTraceOnDevice(device);
57         }
58     }
59 
60     @Override
onTestRunEnd( DeviceMetricData runData, Map<String, MetricMeasurement.Metric> currentRunMetrics)61     public void onTestRunEnd(
62             DeviceMetricData runData, Map<String, MetricMeasurement.Metric> currentRunMetrics)
63             throws DeviceNotAvailableException {
64         for (ITestDevice device : getRealDevices()) {
65             collectTraceFileFromDevice(device, "testRunEnded");
66         }
67         logTraceFiles();
68     }
69 
startTraceOnDevice(ITestDevice device)70     private void startTraceOnDevice(ITestDevice device) {
71         // count should be increased even if no trace file collected to make missing traces visible.
72         mTraceCountMap.put(device, mTraceCountMap.getOrDefault(device, 0) + 1);
73         try {
74             Map<String, String> extraConfigs = new LinkedHashMap<>();
75             if (mInstrumentationPkgName != null) {
76                 extraConfigs.put("atrace_apps", String.format("\"%s\"", mInstrumentationPkgName));
77             }
78             mPerfettoTraceRecorder.startTrace(device, extraConfigs);
79         } catch (IOException e) {
80             CLog.d(
81                     "Failed to start perfetto trace on %s trace-count:%d with error: %s",
82                     device.getSerialNumber(), mTraceCountMap.get(device), e.getMessage());
83         }
84     }
85 
collectTraceFileFromDevice(ITestDevice device, String eventName)86     private void collectTraceFileFromDevice(ITestDevice device, String eventName) {
87         File traceFile = mPerfettoTraceRecorder.stopTrace(device);
88         if (traceFile == null) {
89             CLog.d(
90                     "Failed to collect device trace from %s on event:%s trace-count:%d.",
91                     device.getSerialNumber(), eventName, mTraceCountMap.get(device));
92             return;
93         }
94         CLog.d(
95                 "Collected device trace from %s on event:%s. trace-count:%d. size:%d",
96                 device.getSerialNumber(),
97                 eventName,
98                 mTraceCountMap.get(device),
99                 traceFile.length());
100         String name =
101                 String.format(
102                         NAME_FORMAT,
103                         device.getSerialNumber(),
104                         mTraceCountMap.get(device),
105                         eventName);
106         mTraceFilesMap.put(traceFile, name);
107     }
108 
logTraceFiles()109     private void logTraceFiles() {
110         for (Map.Entry<File, String> entry : mTraceFilesMap.entrySet()) {
111             try (FileInputStreamSource source = new FileInputStreamSource(entry.getKey(), true)) {
112                 super.testLog(entry.getValue(), LogDataType.PERFETTO, source);
113             }
114         }
115     }
116 
setInstrumentationPkgName(String packageName)117     public void setInstrumentationPkgName(String packageName) {
118         mInstrumentationPkgName = packageName;
119     }
120 
121     @Override
rebootStarted(ITestDevice device)122     public void rebootStarted(ITestDevice device) throws DeviceNotAvailableException {
123         super.rebootStarted(device);
124         // save previous trace running on this device.
125         collectTraceFileFromDevice(device, "rebootStarted");
126     }
127 
128     @Override
rebootEnded(ITestDevice device)129     public void rebootEnded(ITestDevice device) throws DeviceNotAvailableException {
130         super.rebootEnded(device);
131         // start new trace running on this device
132         startTraceOnDevice(device);
133     }
134 }
135