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