• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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.FailureDescription;
29 import com.android.tradefed.result.InputStreamSource;
30 import com.android.tradefed.result.LogDataType;
31 import com.android.tradefed.result.TestDescription;
32 import com.android.tradefed.util.IRunUtil;
33 import com.android.tradefed.util.RunUtil;
34 
35 import java.util.HashMap;
36 import java.util.Map;
37 
38 /** Collector that will capture and log a logcat when a test case fails. */
39 public class LogcatOnFailureCollector extends BaseDeviceMetricCollector {
40 
41     private static final int MAX_LOGAT_SIZE_BYTES = 4 * 1024 * 1024;
42     /** Always include a bit of prior data to capture what happened before */
43     private static final int OFFSET_CORRECTION = 10000;
44 
45     private static final String NAME_FORMAT = "%s-%s-logcat-on-failure";
46 
47     private static final String LOGCAT_COLLECT_CMD = "logcat -b all -T 150";
48     // -t implies -d (dump) so it's a one time collection
49     private static final String LOGCAT_COLLECT_CMD_LEGACY = "logcat -b all -t 5000";
50     private static final int API_LIMIT = 20;
51 
52     private static final int THROTTLE_LIMIT_PER_RUN = 10;
53 
54     private Map<ITestDevice, ILogcatReceiver> mLogcatReceivers = new HashMap<>();
55     private Map<ITestDevice, Integer> mOffset = new HashMap<>();
56     private int mCurrentCount = 0;
57     private boolean mFirstThrottle = true;
58 
59     @Override
onTestRunStart(DeviceMetricData runData)60     public void onTestRunStart(DeviceMetricData runData) {
61         mCurrentCount = 0;
62         mFirstThrottle = true;
63         for (ITestDevice device : getRealDevices()) {
64             if (getApiLevelNoThrow(device) < API_LIMIT) {
65                 continue;
66             }
67             // In case of multiple runs for the same test runner, re-init the receiver.
68             initReceiver(device);
69             // Get the current offset of the buffer to be able to query later
70             try (InputStreamSource data = mLogcatReceivers.get(device).getLogcatData()) {
71                 int offset = (int) data.size();
72                 if (offset > OFFSET_CORRECTION) {
73                     offset -= OFFSET_CORRECTION;
74                 }
75                 mOffset.put(device, offset);
76             }
77         }
78     }
79 
80     @Override
onTestStart(DeviceMetricData testData)81     public void onTestStart(DeviceMetricData testData) {
82         // TODO: Handle the buffer to reset it at the test start
83     }
84 
85     @Override
onTestFail(DeviceMetricData testData, TestDescription test)86     public void onTestFail(DeviceMetricData testData, TestDescription test)
87             throws DeviceNotAvailableException {
88         if (mCurrentCount > THROTTLE_LIMIT_PER_RUN) {
89             if (mFirstThrottle) {
90                 CLog.w("Throttle capture of logcat-on-failure due to too many failures.");
91                 mFirstThrottle = false;
92             }
93             return;
94         }
95         // Delay slightly for the error to get in the logcat
96         getRunUtil().sleep(100);
97         collectAndLog(test.toString(), MAX_LOGAT_SIZE_BYTES);
98         mCurrentCount++;
99     }
100 
101     @Override
onTestRunFailed(DeviceMetricData testData, FailureDescription failure)102     public void onTestRunFailed(DeviceMetricData testData, FailureDescription failure)
103             throws DeviceNotAvailableException {
104         // Delay slightly for the error to get in the logcat
105         getRunUtil().sleep(100);
106         // TODO: Improve the name
107         collectAndLog("run-failure", MAX_LOGAT_SIZE_BYTES);
108     }
109 
110     @Override
onTestRunEnd(DeviceMetricData runData, Map<String, Metric> currentRunMetrics)111     public void onTestRunEnd(DeviceMetricData runData, Map<String, Metric> currentRunMetrics) {
112         clearReceivers();
113     }
114 
115     @VisibleForTesting
createLogcatReceiver(ITestDevice device)116     ILogcatReceiver createLogcatReceiver(ITestDevice device) {
117         // Use logcat -T 'count' to only print a few line before we start and not the full buffer
118         return new LogcatReceiver(
119                 device, LOGCAT_COLLECT_CMD, device.getOptions().getMaxLogcatDataSize(), 0);
120     }
121 
122     @VisibleForTesting
createLegacyCollectingReceiver()123     CollectingByteOutputReceiver createLegacyCollectingReceiver() {
124         return new CollectingByteOutputReceiver();
125     }
126 
127     @VisibleForTesting
getRunUtil()128     IRunUtil getRunUtil() {
129         return RunUtil.getDefault();
130     }
131 
collectAndLog(String testName, int size)132     protected void collectAndLog(String testName, int size) throws DeviceNotAvailableException {
133         for (ITestDevice device : getRealDevices()) {
134             boolean isDeviceOnline = isDeviceOnline(device);
135             ILogcatReceiver receiver = mLogcatReceivers.get(device);
136             // Receiver is only initialized above API 19, if not supported, we use a legacy command
137             if (receiver == null) {
138                 if (isDeviceOnline) {
139                     legacyCollection(device, testName);
140                 } else {
141                     CLog.w("Skip legacy LogcatOnFailureCollector device is offline.");
142                 }
143                 continue;
144             }
145             // If supported get the logcat buffer, even if device is offline to get the buffer
146             saveLogcatSource(
147                     testName,
148                     receiver.getLogcatData(size, mOffset.get(device)),
149                     device.getSerialNumber());
150         }
151     }
152 
initReceiver(ITestDevice device)153     private void initReceiver(ITestDevice device) {
154         if (mLogcatReceivers.get(device) == null) {
155             ILogcatReceiver receiver = createLogcatReceiver(device);
156             mLogcatReceivers.put(device, receiver);
157             receiver.start();
158         }
159     }
160 
clearReceivers()161     private void clearReceivers() {
162         for (ILogcatReceiver receiver : mLogcatReceivers.values()) {
163             receiver.stop();
164             receiver.clear();
165         }
166         mLogcatReceivers.clear();
167         mOffset.clear();
168     }
169 
getApiLevelNoThrow(ITestDevice device)170     private int getApiLevelNoThrow(ITestDevice device) {
171         try {
172             return device.getApiLevel();
173         } catch (DeviceNotAvailableException e) {
174             return 1;
175         }
176     }
177 
legacyCollection(ITestDevice device, String testName)178     private void legacyCollection(ITestDevice device, String testName)
179             throws DeviceNotAvailableException {
180         CollectingByteOutputReceiver outputReceiver = createLegacyCollectingReceiver();
181         device.executeShellCommand(LOGCAT_COLLECT_CMD_LEGACY, outputReceiver);
182         saveLogcatSource(
183                 testName,
184                 new ByteArrayInputStreamSource(outputReceiver.getOutput()),
185                 device.getSerialNumber());
186         outputReceiver.cancel();
187     }
188 
saveLogcatSource(String testName, InputStreamSource source, String serial)189     private void saveLogcatSource(String testName, InputStreamSource source, String serial) {
190         if (source == null) {
191             return;
192         }
193         try (InputStreamSource logcatSource = source) {
194             // If the resulting logcat looks wrong or empty, discard it
195             if (logcatSource.size() < 75L) {
196                 CLog.e(
197                         "Discarding logcat on failure (size=%s): it failed to collect something"
198                                 + " relevant likely due to timings.",
199                         logcatSource.size());
200                 return;
201             }
202             String name = String.format(NAME_FORMAT, testName, serial);
203             super.testLog(name, LogDataType.LOGCAT, logcatSource);
204         }
205     }
206 
isDeviceOnline(ITestDevice device)207     private boolean isDeviceOnline(ITestDevice device) {
208         TestDeviceState state = device.getDeviceState();
209         if (!TestDeviceState.ONLINE.equals(state)) {
210             return false;
211         }
212         return true;
213     }
214 }
215