/* * Copyright (C) 2011 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.performance.tests; import com.android.loganalysis.item.BugreportItem; import com.android.loganalysis.item.IItem; import com.android.loganalysis.item.MemInfoItem; import com.android.loganalysis.item.ProcrankItem; import com.android.loganalysis.parser.BugreportParser; import com.android.tradefed.config.Option; import com.android.tradefed.device.DeviceNotAvailableException; import com.android.tradefed.device.ITestDevice; import com.android.tradefed.device.ITestDevice.RecoveryMode; 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.testtype.IDeviceTest; import com.android.tradefed.testtype.IRemoteTest; import com.android.tradefed.util.RunUtil; import junit.framework.Assert; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.util.HashMap; import java.util.Map; /** * Tests to gather device metrics from during and immediately after boot */ public class StartupMetricsTest implements IDeviceTest, IRemoteTest { public static final String BUGREPORT_LOG_NAME = "bugreport_startup.txt"; @Option(name="boot-time-ms", description="Timeout in ms to wait for device to boot.") private long mBootTimeMs = 5 * 60 * 1000; @Option(name="boot-poll-time-ms", description="Delay in ms between polls for device to boot.") private long mBootPoolTimeMs = 500; @Option(name="post-boot-delay-ms", description="Delay in ms after boot complete before taking the bugreport.") private long mPostBootDelayMs = 60000; @Option(name="skip-memory-stats", description="Report boot time only, without memory stats.") private boolean mSkipMemoryStats = false; ITestDevice mTestDevice = null; @Override public void run(ITestInvocationListener listener) throws DeviceNotAvailableException { Assert.assertNotNull(mTestDevice); executeRebootTest(listener); if (!mSkipMemoryStats) { fetchBugReportMetrics(listener); } } /** * Check how long the device takes to come online and become available after * a reboot. * * @param listener the {@link ITestInvocationListener} of test results */ void executeRebootTest(ITestInvocationListener listener) throws DeviceNotAvailableException { Map<String, String> runMetrics = new HashMap<String, String>(); mTestDevice.setRecoveryMode(RecoveryMode.NONE); CLog.d("Reboot test start."); mTestDevice.nonBlockingReboot(); long startTime = System.currentTimeMillis(); mTestDevice.waitForDeviceOnline(); long onlineTime = System.currentTimeMillis(); Assert.assertTrue(waitForBootComplete(mTestDevice, mBootTimeMs, mBootPoolTimeMs)); long availableTime = System.currentTimeMillis(); double offlineDuration = onlineTime - startTime; double unavailDuration = availableTime - startTime; CLog.d("Reboot: %f millis until online, %f until available", offlineDuration, unavailDuration); runMetrics.put("online", Double.toString(offlineDuration/1000.0)); runMetrics.put("bootcomplete", Double.toString(unavailDuration/1000.0)); reportMetrics(listener, "boottime", runMetrics); } /** * Fetch proc rank metrics from the bugreport after reboot. * * @param listener the {@link ITestInvocationListener} of test results * @throws DeviceNotAvailableException */ void fetchBugReportMetrics(ITestInvocationListener listener) throws DeviceNotAvailableException { // Make sure the device is available and settled, before getting bugreport. mTestDevice.waitForDeviceAvailable(); RunUtil.getDefault().sleep(mPostBootDelayMs); BugreportParser parser = new BugreportParser(); BugreportItem bugreport = null; // Retrieve bugreport InputStreamSource bugSource = mTestDevice.getBugreport(); try { listener.testLog(BUGREPORT_LOG_NAME, LogDataType.TEXT, bugSource); bugreport = parser.parse(new BufferedReader(new InputStreamReader( bugSource.createInputStream()))); } catch (IOException e) { Assert.fail(String.format("Failed to fetch and parse bugreport for device %s: %s", mTestDevice.getSerialNumber(), e)); } finally { bugSource.cancel(); } // Process meminfo information and post it to the dashboard IItem item = bugreport.getMemInfo(); if (item != null) { Map<String, String> memInfoMap = convertMap((MemInfoItem) item); reportMetrics(listener, "startup-meminfo", memInfoMap); } // Process procrank information and post it to the dashboard if (bugreport.getProcrank() != null) { parseProcRankMap(listener, bugreport.getProcrank()); } } /** * Helper method to convert Map<String, Integer> to Map<String, String>. * * @param input the {@link Map} to convert from * @return output the converted {@link Map} */ Map<String, String> convertMap(Map<String, Integer> input) { Map<String, String> output = new HashMap<String, String>(); for (Map.Entry<String, Integer> entry : input.entrySet()) { output.put(entry.getKey(), entry.getValue().toString()); } return output; } /** * Report run metrics by creating an empty test run to stick them in * * @param listener the {@link ITestInvocationListener} of test results * @param runName the test name * @param metrics the {@link Map} that contains metrics for the given test */ void reportMetrics(ITestInvocationListener listener, String runName, Map<String, String> metrics) { // Create an empty testRun to report the parsed runMetrics CLog.d("About to report metrics: %s", metrics); listener.testRunStarted(runName, 0); listener.testRunEnded(0, metrics); } /** * Aggregates the procrank data by the pss, rss, and uss values. * * @param listener the {@link ITestInvocationListener} of test results * @param procRankMap the {@link Map} parsed from brillopad for the procrank * section */ void parseProcRankMap(ITestInvocationListener listener, ProcrankItem procrank) { // final maps for pss, rss, and uss. Map<String, String> pssOutput = new HashMap<String, String>(); Map<String, String> rssOutput = new HashMap<String, String>(); Map<String, String> ussOutput = new HashMap<String, String>(); // total number of processes. Integer numProcess = 0; // aggregate pss, rss, uss across all processes. Integer pssTotal = 0; Integer rssTotal = 0; Integer ussTotal = 0; for (Integer pid : procrank.getPids()) { numProcess++; Integer pss = procrank.getPss(pid); Integer rss = procrank.getRss(pid); Integer uss = procrank.getUss(pid); if (pss != null) { pssTotal += pss; pssOutput.put(procrank.getProcessName(pid), pss.toString()); } if (rss != null) { rssTotal += rss; rssOutput.put(procrank.getProcessName(pid), rss.toString()); } if (uss != null) { ussTotal += pss; ussOutput.put(procrank.getProcessName(pid), uss.toString()); } } // Add aggregation data. pssOutput.put("count", numProcess.toString()); pssOutput.put("total", pssTotal.toString()); rssOutput.put("count", numProcess.toString()); rssOutput.put("total", rssTotal.toString()); ussOutput.put("count", numProcess.toString()); ussOutput.put("total", ussTotal.toString()); // Report metrics to dashboard reportMetrics(listener, "startup-procrank-pss", pssOutput); reportMetrics(listener, "startup-procrank-rss", rssOutput); reportMetrics(listener, "startup-procrank-uss", ussOutput); } /** * Blocks until the device's boot complete flag is set. * * @param device the {@link ITestDevice} * @param timeOut time in msecs to wait for the flag to be set * @param pollDelay time in msecs between checks * @return true if device's boot complete flag is set within the timeout * @throws DeviceNotAvailableException */ private boolean waitForBootComplete(ITestDevice device,long timeOut, long pollDelay) throws DeviceNotAvailableException { long startTime = System.currentTimeMillis(); while ((System.currentTimeMillis() - startTime) < timeOut) { String output = device.executeShellCommand("getprop dev.bootcomplete"); output = output.replace('#', ' ').trim(); if (output.equals("1")) { return true; } RunUtil.getDefault().sleep(pollDelay); } CLog.w("Device %s did not boot after %d ms", device.getSerialNumber(), timeOut); return false; } @Override public void setDevice(ITestDevice device) { mTestDevice = device; } @Override public ITestDevice getDevice() { return mTestDevice; } }