/* * Copyright (C) 2012 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.monkey; import com.android.ddmlib.testrunner.TestIdentifier; import com.android.loganalysis.item.AnrItem; import com.android.loganalysis.item.BugreportItem; import com.android.loganalysis.item.IItem; import com.android.loganalysis.item.JavaCrashItem; import com.android.loganalysis.item.LogcatItem; import com.android.loganalysis.item.MonkeyLogItem; import com.android.loganalysis.item.NativeCrashItem; import com.android.loganalysis.parser.BugreportParser; import com.android.loganalysis.parser.MonkeyLogParser; import com.android.tradefed.log.LogUtil.CLog; import com.android.tradefed.result.ITestInvocationListener; import com.android.tradefed.result.InputStreamSource; import com.android.tradefed.result.LogDataType; import com.android.tradefed.result.ResultForwarder; import com.google.common.base.Throwables; import junit.framework.Assert; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.HashMap; import java.util.Map; /** * A {@link ResultForwarded} that intercepts monkey and bug report logs, extracts relevant metrics * from them using brillopad, and forwards the results to the specified * {@link ITestInvocationListener}. */ public class MonkeyBrillopadForwarder extends ResultForwarder { private enum MonkeyStatus { FINISHED("Money completed without errors."), CRASHED("Monkey run stopped because of a crash."), MISSING_COUNT("Monkey run failed to complete due to an unknown reason. " + "Check logs for details."), FALSE_COUNT("Monkey run reported an invalid count. " + "Check logs for details."), UPTIME_FAILURE("Monkey output is indicating an invalid uptime. " + "Device may have reset during run."), TIMEOUT("Monkey did not complete within the specified time"); private String mDescription; MonkeyStatus(String desc) { mDescription = desc; } /** * A User friendly description of the status * @return */ String getDescription() { return mDescription; } } private BugreportItem mBugreport = null; private MonkeyLogItem mMonkeyLog = null; private final long mMonkeyTimeoutMs; public MonkeyBrillopadForwarder(ITestInvocationListener listener, long monkeyTimeoutMs) { super(listener); mMonkeyTimeoutMs = monkeyTimeoutMs; } /** * {@inheritDoc} */ @Override public void testLog(String dataName, LogDataType dataType, InputStreamSource dataStream) { try { // just parse the logs for now. Forwarding of results will happen on test completion if (dataName.startsWith(MonkeyBase.BUGREPORT_NAME)) { CLog.i("Parsing %s", dataName); mBugreport = new BugreportParser().parse(new BufferedReader(new InputStreamReader( dataStream.createInputStream()))); } if (dataName.startsWith(MonkeyBase.MONKEY_LOG_NAME)) { CLog.i("Parsing %s", dataName); mMonkeyLog = new MonkeyLogParser().parse(new BufferedReader(new InputStreamReader( dataStream.createInputStream()))); } } catch (IOException e) { CLog.e("Could not parse file %s", dataName); } super.testLog(dataName, dataType, dataStream); } /** * {@inheritDoc} */ @Override public void testEnded(TestIdentifier monkeyTest, Map<String, String> metrics) { Map<String, String> monkeyMetrics = new HashMap<String, String>(); try { Assert.assertNotNull("Failed to parse or retrieve bug report", mBugreport); Assert.assertNotNull("Cannot report run to brillopad, monkey log does not exist", mMonkeyLog); Assert.assertNotNull("monkey log is missing start time info", mMonkeyLog.getStartUptimeDuration()); Assert.assertNotNull("monkey log is missing stop time info", mMonkeyLog.getStopUptimeDuration()); LogcatItem systemLog = mBugreport.getSystemLog(); Assert.assertNotNull("system log is missing from bugreport", systemLog); MonkeyStatus status = reportMonkeyStats(mMonkeyLog, monkeyMetrics); StringBuilder crashTrace = new StringBuilder(); reportAnrs(mMonkeyLog, monkeyMetrics, crashTrace); reportJavaCrashes(mMonkeyLog, monkeyMetrics, crashTrace); reportNativeCrashes(systemLog, monkeyMetrics, crashTrace); if (!status.equals(MonkeyStatus.FINISHED)) { String failure = String.format("%s.\n%s", status.getDescription(), crashTrace.toString()); super.testFailed(TestFailure.FAILURE, monkeyTest, failure); } } catch (AssertionError e) { super.testFailed(TestFailure.FAILURE, monkeyTest, Throwables.getStackTraceAsString(e)); } catch (RuntimeException e) { super.testFailed(TestFailure.ERROR, monkeyTest, Throwables.getStackTraceAsString(e)); } finally { super.testEnded(monkeyTest, monkeyMetrics); } } /** * Report stats about the monkey run from the monkey log. */ private MonkeyStatus reportMonkeyStats(MonkeyLogItem monkeyLog, Map<String, String> monkeyMetrics) { MonkeyStatus status = getStatus(monkeyLog); monkeyMetrics.put("throttle", convertToString(monkeyLog.getThrottle())); monkeyMetrics.put("status", status.toString()); monkeyMetrics.put("target_count", convertToString(monkeyLog.getTargetCount())); monkeyMetrics.put("injected_count", convertToString(monkeyLog.getFinalCount())); monkeyMetrics.put("run_duration", convertToString(monkeyLog.getTotalDuration())); monkeyMetrics.put("uptime", convertToString((monkeyLog.getStopUptimeDuration() - monkeyLog.getStartUptimeDuration()))); return status; } /** * A utility method that converts an {@link Integer} to a {@link String}, and that can handle * null. */ private static String convertToString(Integer integer) { return integer == null ? "" : integer.toString(); } /** * A utility method that converts a {@link Long} to a {@link String}, and that can handle null. */ private static String convertToString(Long longInt) { return longInt == null ? "" : longInt.toString(); } /** * Report stats about Java crashes from the monkey log. */ private void reportJavaCrashes(MonkeyLogItem monkeyLog, Map<String, String> metrics, StringBuilder crashTrace) { if (monkeyLog.getCrash() != null && monkeyLog.getCrash() instanceof JavaCrashItem) { JavaCrashItem jc = (JavaCrashItem) monkeyLog.getCrash(); metrics.put("java_crash", "1"); crashTrace.append("Detected java crash:\n"); crashTrace.append(jc.getStack()); crashTrace.append("\n"); } } /** * Report stats about the native crashes from the bugreport. */ private void reportNativeCrashes(LogcatItem systemLog, Map<String, String> metrics, StringBuilder crashTrace) { if (systemLog.getEvents().size() > 0) { int nativeCrashes = 0; for (IItem item : systemLog.getEvents()) { if (item instanceof NativeCrashItem) { nativeCrashes++; crashTrace.append("Detected native crash:\n"); crashTrace.append(((NativeCrashItem)item).getStack()); crashTrace.append("\n"); } } metrics.put("native_crash", Integer.toString(nativeCrashes)); } } /** * Report stats about the ANRs from the monkey log. */ private void reportAnrs(MonkeyLogItem monkeyLog, Map<String, String> metrics, StringBuilder crashTrace) { if (monkeyLog.getCrash() != null && monkeyLog.getCrash() instanceof AnrItem) { AnrItem anr = (AnrItem) monkeyLog.getCrash(); metrics.put("anr_crash", "1"); crashTrace.append("Detected ANR:\n"); crashTrace.append(anr.getStack()); crashTrace.append("\n"); } } /** * Return the {@link MonkeyStatus} based on how the monkey run ran. */ private MonkeyStatus getStatus(MonkeyLogItem monkeyLog) { // Uptime try { long startUptime = monkeyLog.getStartUptimeDuration(); long stopUptime = monkeyLog.getStopUptimeDuration(); long totalDuration = monkeyLog.getTotalDuration(); if (stopUptime - startUptime < totalDuration - MonkeyBase.UPTIME_BUFFER) { return MonkeyStatus.UPTIME_FAILURE; } if (totalDuration >= mMonkeyTimeoutMs) { return MonkeyStatus.TIMEOUT; } } catch (NullPointerException e) { return MonkeyStatus.UPTIME_FAILURE; } // False count if (monkeyLog.getIsFinished() && monkeyLog.getIntermediateCount() + 100 < monkeyLog.getTargetCount()) { return MonkeyStatus.FALSE_COUNT; } // Finished if (monkeyLog.getIsFinished()) { return MonkeyStatus.FINISHED; } // Crashed if (monkeyLog.getFinalCount() != null) { return MonkeyStatus.CRASHED; } // Missing count return MonkeyStatus.MISSING_COUNT; } }