1 /* 2 * Copyright (C) 2012 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.monkey; 18 19 import com.android.loganalysis.item.AnrItem; 20 import com.android.loganalysis.item.BugreportItem; 21 import com.android.loganalysis.item.IItem; 22 import com.android.loganalysis.item.JavaCrashItem; 23 import com.android.loganalysis.item.LogcatItem; 24 import com.android.loganalysis.item.MonkeyLogItem; 25 import com.android.loganalysis.item.NativeCrashItem; 26 import com.android.loganalysis.parser.BugreportParser; 27 import com.android.loganalysis.parser.MonkeyLogParser; 28 import com.android.tradefed.log.LogUtil.CLog; 29 import com.android.tradefed.result.ITestInvocationListener; 30 import com.android.tradefed.result.InputStreamSource; 31 import com.android.tradefed.result.LogDataType; 32 import com.android.tradefed.result.ResultForwarder; 33 import com.android.tradefed.result.TestDescription; 34 35 import com.google.common.base.Throwables; 36 37 import org.junit.Assert; 38 39 import java.io.BufferedReader; 40 import java.io.IOException; 41 import java.io.InputStreamReader; 42 import java.util.HashMap; 43 import java.util.Map; 44 45 /** 46 * A {@link ResultForwarder} that intercepts monkey and bug report logs, extracts relevant metrics 47 * from them using brillopad, and forwards the results to the specified {@link 48 * ITestInvocationListener}. 49 */ 50 public class MonkeyBrillopadForwarder extends ResultForwarder { 51 52 private enum MonkeyStatus { 53 FINISHED("Money completed without errors."), 54 CRASHED("Monkey run stopped because of a crash."), 55 MISSING_COUNT( 56 "Monkey run failed to complete due to an unknown reason. " 57 + "Check logs for details."), 58 FALSE_COUNT("Monkey run reported an invalid count. " + "Check logs for details."), 59 UPTIME_FAILURE( 60 "Monkey output is indicating an invalid uptime. " 61 + "Device may have reset during run."), 62 TIMEOUT("Monkey did not complete within the specified time"); 63 64 private String mDescription; 65 MonkeyStatus(String desc)66 MonkeyStatus(String desc) { 67 mDescription = desc; 68 } 69 70 /** Returns a User friendly description of the status. */ getDescription()71 String getDescription() { 72 return mDescription; 73 } 74 } 75 76 private BugreportItem mBugreport = null; 77 private MonkeyLogItem mMonkeyLog = null; 78 private final long mMonkeyTimeoutMs; 79 MonkeyBrillopadForwarder(ITestInvocationListener listener, long monkeyTimeoutMs)80 public MonkeyBrillopadForwarder(ITestInvocationListener listener, long monkeyTimeoutMs) { 81 super(listener); 82 mMonkeyTimeoutMs = monkeyTimeoutMs; 83 } 84 85 /** {@inheritDoc} */ 86 @Override testLog(String dataName, LogDataType dataType, InputStreamSource dataStream)87 public void testLog(String dataName, LogDataType dataType, InputStreamSource dataStream) { 88 try { 89 // just parse the logs for now. Forwarding of results will happen on test completion 90 if (LogDataType.BUGREPORT.equals(dataType)) { 91 CLog.i("Parsing %s", dataName); 92 mBugreport = 93 new BugreportParser() 94 .parse( 95 new BufferedReader( 96 new InputStreamReader( 97 dataStream.createInputStream()))); 98 } 99 if (LogDataType.MONKEY_LOG.equals(dataType)) { 100 CLog.i("Parsing %s", dataName); 101 mMonkeyLog = 102 new MonkeyLogParser() 103 .parse( 104 new BufferedReader( 105 new InputStreamReader( 106 dataStream.createInputStream()))); 107 } 108 } catch (IOException e) { 109 CLog.e("Could not parse file %s", dataName); 110 } 111 super.testLog(dataName, dataType, dataStream); 112 } 113 114 /** {@inheritDoc} */ 115 @Override testEnded(TestDescription monkeyTest, Map<String, String> metrics)116 public void testEnded(TestDescription monkeyTest, Map<String, String> metrics) { 117 Map<String, String> monkeyMetrics = new HashMap<String, String>(); 118 try { 119 Assert.assertNotNull("Failed to parse or retrieve bug report", mBugreport); 120 Assert.assertNotNull( 121 "Cannot report run to brillopad, monkey log does not exist", mMonkeyLog); 122 Assert.assertNotNull( 123 "monkey log is missing start time info", mMonkeyLog.getStartUptimeDuration()); 124 Assert.assertNotNull( 125 "monkey log is missing stop time info", mMonkeyLog.getStopUptimeDuration()); 126 LogcatItem systemLog = mBugreport.getSystemLog(); 127 128 MonkeyStatus status = reportMonkeyStats(mMonkeyLog, monkeyMetrics); 129 StringBuilder crashTrace = new StringBuilder(); 130 reportAnrs(mMonkeyLog, monkeyMetrics, crashTrace); 131 reportJavaCrashes(mMonkeyLog, monkeyMetrics, crashTrace); 132 if (systemLog != null) { 133 reportNativeCrashes(systemLog, monkeyMetrics, crashTrace); 134 } else { 135 CLog.w("Failed to get system log from bugreport"); 136 } 137 138 if (!status.equals(MonkeyStatus.FINISHED)) { 139 String failure = 140 String.format("%s.\n%s", status.getDescription(), crashTrace.toString()); 141 super.testFailed(monkeyTest, failure); 142 } 143 } catch (AssertionError e) { 144 super.testFailed(monkeyTest, Throwables.getStackTraceAsString(e)); 145 } catch (RuntimeException e) { 146 super.testFailed(monkeyTest, Throwables.getStackTraceAsString(e)); 147 } finally { 148 super.testEnded(monkeyTest, monkeyMetrics); 149 } 150 } 151 152 /** Report stats about the monkey run from the monkey log. */ reportMonkeyStats( MonkeyLogItem monkeyLog, Map<String, String> monkeyMetrics)153 private MonkeyStatus reportMonkeyStats( 154 MonkeyLogItem monkeyLog, Map<String, String> monkeyMetrics) { 155 MonkeyStatus status = getStatus(monkeyLog); 156 monkeyMetrics.put("throttle", convertToString(monkeyLog.getThrottle())); 157 monkeyMetrics.put("status", status.toString()); 158 monkeyMetrics.put("target_count", convertToString(monkeyLog.getTargetCount())); 159 monkeyMetrics.put("injected_count", convertToString(monkeyLog.getFinalCount())); 160 monkeyMetrics.put("run_duration", convertToString(monkeyLog.getTotalDuration())); 161 monkeyMetrics.put( 162 "uptime", 163 convertToString( 164 (monkeyLog.getStopUptimeDuration() - monkeyLog.getStartUptimeDuration()))); 165 return status; 166 } 167 168 /** 169 * A utility method that converts an {@link Integer} to a {@link String}, and that can handle 170 * null. 171 */ convertToString(Integer integer)172 private static String convertToString(Integer integer) { 173 return integer == null ? "" : integer.toString(); 174 } 175 176 /** 177 * A utility method that converts a {@link Long} to a {@link String}, and that can handle null. 178 */ convertToString(Long longInt)179 private static String convertToString(Long longInt) { 180 return longInt == null ? "" : longInt.toString(); 181 } 182 183 /** Report stats about Java crashes from the monkey log. */ reportJavaCrashes( MonkeyLogItem monkeyLog, Map<String, String> metrics, StringBuilder crashTrace)184 private void reportJavaCrashes( 185 MonkeyLogItem monkeyLog, Map<String, String> metrics, StringBuilder crashTrace) { 186 187 if (monkeyLog.getCrash() != null && monkeyLog.getCrash() instanceof JavaCrashItem) { 188 JavaCrashItem jc = (JavaCrashItem) monkeyLog.getCrash(); 189 metrics.put("java_crash", "1"); 190 crashTrace.append("Detected java crash:\n"); 191 crashTrace.append(jc.getStack()); 192 crashTrace.append("\n"); 193 } 194 } 195 196 /** Report stats about the native crashes from the bugreport. */ reportNativeCrashes( LogcatItem systemLog, Map<String, String> metrics, StringBuilder crashTrace)197 private void reportNativeCrashes( 198 LogcatItem systemLog, Map<String, String> metrics, StringBuilder crashTrace) { 199 if (systemLog.getEvents().size() > 0) { 200 int nativeCrashes = 0; 201 for (IItem item : systemLog.getEvents()) { 202 if (item instanceof NativeCrashItem) { 203 nativeCrashes++; 204 crashTrace.append("Detected native crash:\n"); 205 crashTrace.append(((NativeCrashItem) item).getStack()); 206 crashTrace.append("\n"); 207 } 208 } 209 metrics.put("native_crash", Integer.toString(nativeCrashes)); 210 } 211 } 212 213 /** Report stats about the ANRs from the monkey log. */ reportAnrs( MonkeyLogItem monkeyLog, Map<String, String> metrics, StringBuilder crashTrace)214 private void reportAnrs( 215 MonkeyLogItem monkeyLog, Map<String, String> metrics, StringBuilder crashTrace) { 216 if (monkeyLog.getCrash() != null && monkeyLog.getCrash() instanceof AnrItem) { 217 AnrItem anr = (AnrItem) monkeyLog.getCrash(); 218 metrics.put("anr_crash", "1"); 219 crashTrace.append("Detected ANR:\n"); 220 crashTrace.append(anr.getStack()); 221 crashTrace.append("\n"); 222 } 223 } 224 225 /** Return the {@link MonkeyStatus} based on how the monkey run ran. */ getStatus(MonkeyLogItem monkeyLog)226 private MonkeyStatus getStatus(MonkeyLogItem monkeyLog) { 227 // Uptime 228 try { 229 long startUptime = monkeyLog.getStartUptimeDuration(); 230 long stopUptime = monkeyLog.getStopUptimeDuration(); 231 long totalDuration = monkeyLog.getTotalDuration(); 232 if (stopUptime - startUptime < totalDuration - MonkeyBase.UPTIME_BUFFER) { 233 return MonkeyStatus.UPTIME_FAILURE; 234 } 235 if (totalDuration >= mMonkeyTimeoutMs) { 236 return MonkeyStatus.TIMEOUT; 237 } 238 } catch (NullPointerException e) { 239 return MonkeyStatus.UPTIME_FAILURE; 240 } 241 242 // False count 243 if (monkeyLog.getIsFinished() 244 && monkeyLog.getIntermediateCount() + 100 < monkeyLog.getTargetCount()) { 245 return MonkeyStatus.FALSE_COUNT; 246 } 247 248 // Finished 249 if (monkeyLog.getIsFinished()) { 250 return MonkeyStatus.FINISHED; 251 } 252 253 // Crashed 254 if (monkeyLog.getFinalCount() != null) { 255 return MonkeyStatus.CRASHED; 256 } 257 258 // Missing count 259 return MonkeyStatus.MISSING_COUNT; 260 } 261 } 262