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 com.android.annotations.VisibleForTesting;
19 import com.android.tradefed.device.CollectingByteOutputReceiver;
20 import com.android.tradefed.device.DeviceNotAvailableException;
21 import com.android.tradefed.device.ILogcatReceiver;
22 import com.android.tradefed.device.ITestDevice;
23 import com.android.tradefed.device.LogcatReceiver;
24 import com.android.tradefed.device.TestDeviceState;
25 import com.android.tradefed.log.LogUtil.CLog;
26 import com.android.tradefed.metrics.proto.MetricMeasurement.Metric;
27 import com.android.tradefed.result.ByteArrayInputStreamSource;
28 import com.android.tradefed.result.InputStreamSource;
29 import com.android.tradefed.result.LogDataType;
30 import com.android.tradefed.result.TestDescription;
31 import com.android.tradefed.util.IRunUtil;
32 import com.android.tradefed.util.RunUtil;
33 
34 import java.util.HashMap;
35 import java.util.Map;
36 
37 /** Collector that will capture and log a logcat when a test case fails. */
38 public class LogcatOnFailureCollector extends BaseDeviceMetricCollector {
39 
40     private static final int MAX_LOGAT_SIZE_BYTES = 4 * 1024 * 1024;
41     /** Always include a bit of prior data to capture what happened before */
42     private static final int OFFSET_CORRECTION = 10000;
43 
44     private static final String NAME_FORMAT = "%s-%s-logcat-on-failure";
45 
46     private static final String LOGCAT_COLLECT_CMD = "logcat -T 150";
47     // -t implies -d (dump) so it's a one time collection
48     private static final String LOGCAT_COLLECT_CMD_LEGACY = "logcat -t 5000";
49     private static final int API_LIMIT = 20;
50 
51     private Map<ITestDevice, ILogcatReceiver> mLogcatReceivers = new HashMap<>();
52     private Map<ITestDevice, Integer> mOffset = new HashMap<>();
53 
54     @Override
onTestRunStart(DeviceMetricData runData)55     public void onTestRunStart(DeviceMetricData runData) {
56         for (ITestDevice device : getRealDevices()) {
57             if (getApiLevelNoThrow(device) < API_LIMIT) {
58                 continue;
59             }
60             // In case of multiple runs for the same test runner, re-init the receiver.
61             initReceiver(device);
62             // Get the current offset of the buffer to be able to query later
63             int offset = (int) mLogcatReceivers.get(device).getLogcatData().size();
64             if (offset > OFFSET_CORRECTION) {
65                 offset -= OFFSET_CORRECTION;
66             }
67             mOffset.put(device, offset);
68         }
69     }
70 
71     @Override
onTestStart(DeviceMetricData testData)72     public void onTestStart(DeviceMetricData testData) {
73         // TODO: Handle the buffer to reset it at the test start
74     }
75 
76     @Override
onTestFail(DeviceMetricData testData, TestDescription test)77     public void onTestFail(DeviceMetricData testData, TestDescription test) {
78         // Delay slightly for the error to get in the logcat
79         getRunUtil().sleep(100);
80         collectAndLog(test);
81     }
82 
83     @Override
onTestRunEnd(DeviceMetricData runData, Map<String, Metric> currentRunMetrics)84     public void onTestRunEnd(DeviceMetricData runData, Map<String, Metric> currentRunMetrics) {
85         clearReceivers();
86     }
87 
88     @VisibleForTesting
createLogcatReceiver(ITestDevice device)89     ILogcatReceiver createLogcatReceiver(ITestDevice device) {
90         // Use logcat -T 'count' to only print a few line before we start and not the full buffer
91         return new LogcatReceiver(
92                 device, LOGCAT_COLLECT_CMD, device.getOptions().getMaxLogcatDataSize(), 0);
93     }
94 
95     @VisibleForTesting
getRunUtil()96     IRunUtil getRunUtil() {
97         return RunUtil.getDefault();
98     }
99 
collectAndLog(TestDescription test)100     private void collectAndLog(TestDescription test) {
101         for (ITestDevice device : getRealDevices()) {
102             if (!shouldCollect(device)) {
103                 continue;
104             }
105             ILogcatReceiver receiver = mLogcatReceivers.get(device);
106             // Receiver is only initialized above API 19, if not supported, we use a legacy command
107             if (receiver == null) {
108                 CollectingByteOutputReceiver outputReceiver = new CollectingByteOutputReceiver();
109                 try {
110                     device.executeShellCommand(LOGCAT_COLLECT_CMD_LEGACY, outputReceiver);
111                     saveLogcatSource(
112                             test,
113                             new ByteArrayInputStreamSource(outputReceiver.getOutput()),
114                             device.getSerialNumber());
115                 } catch (DeviceNotAvailableException e) {
116                     CLog.e(e);
117                 }
118                 continue;
119             }
120             // If supported get the logcat buffer
121             saveLogcatSource(
122                     test,
123                     receiver.getLogcatData(MAX_LOGAT_SIZE_BYTES, mOffset.get(device)),
124                     device.getSerialNumber());
125         }
126     }
127 
initReceiver(ITestDevice device)128     private void initReceiver(ITestDevice device) {
129         if (mLogcatReceivers.get(device) == null) {
130             ILogcatReceiver receiver = createLogcatReceiver(device);
131             mLogcatReceivers.put(device, receiver);
132             receiver.start();
133         }
134     }
135 
clearReceivers()136     private void clearReceivers() {
137         for (ILogcatReceiver receiver : mLogcatReceivers.values()) {
138             receiver.stop();
139             receiver.clear();
140         }
141         mLogcatReceivers.clear();
142         mOffset.clear();
143     }
144 
getApiLevelNoThrow(ITestDevice device)145     private int getApiLevelNoThrow(ITestDevice device) {
146         try {
147             return device.getApiLevel();
148         } catch (DeviceNotAvailableException e) {
149             return 1;
150         }
151     }
152 
saveLogcatSource(TestDescription test, InputStreamSource source, String serial)153     private void saveLogcatSource(TestDescription test, InputStreamSource source, String serial) {
154         try (InputStreamSource logcatSource = source) {
155             String name = String.format(NAME_FORMAT, test.toString(), serial);
156             super.testLog(name, LogDataType.LOGCAT, logcatSource);
157         }
158     }
159 
shouldCollect(ITestDevice device)160     private boolean shouldCollect(ITestDevice device) {
161         TestDeviceState state = device.getDeviceState();
162         if (!TestDeviceState.ONLINE.equals(state)) {
163             CLog.d("Skip LogcatOnFailureCollector device is in state '%s'", state);
164             return false;
165         }
166         return true;
167     }
168 }
169