Home | History | Annotate | Download | only in tests
      1 /*
      2  * Copyright (C) 2015 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.media.tests;
     18 
     19 import com.android.ddmlib.CollectingOutputReceiver;
     20 import com.android.ddmlib.testrunner.TestIdentifier;
     21 import com.android.tradefed.config.IConfiguration;
     22 import com.android.tradefed.config.IConfigurationReceiver;
     23 import com.android.tradefed.config.Option;
     24 import com.android.tradefed.device.DeviceNotAvailableException;
     25 import com.android.tradefed.device.ITestDevice;
     26 import com.android.tradefed.log.LogUtil.CLog;
     27 import com.android.tradefed.result.ByteArrayInputStreamSource;
     28 import com.android.tradefed.result.CollectingTestListener;
     29 import com.android.tradefed.result.FileInputStreamSource;
     30 import com.android.tradefed.result.ITestInvocationListener;
     31 import com.android.tradefed.result.InputStreamSource;
     32 import com.android.tradefed.result.LogDataType;
     33 import com.android.tradefed.testtype.IDeviceTest;
     34 import com.android.tradefed.testtype.IRemoteTest;
     35 import com.android.tradefed.testtype.InstrumentationTest;
     36 import com.android.tradefed.util.FileUtil;
     37 import com.android.tradefed.util.IRunUtil;
     38 import com.android.tradefed.util.RunUtil;
     39 import com.android.tradefed.util.StreamUtil;
     40 
     41 import org.junit.Assert;
     42 
     43 import java.io.BufferedReader;
     44 import java.io.BufferedWriter;
     45 import java.io.File;
     46 import java.io.FileWriter;
     47 import java.io.IOException;
     48 import java.io.StringReader;
     49 import java.util.ArrayList;
     50 import java.util.Collection;
     51 import java.util.HashMap;
     52 import java.util.Map;
     53 import java.util.Timer;
     54 import java.util.TimerTask;
     55 import java.util.concurrent.TimeUnit;
     56 
     57 /**
     58  * Camera test base class
     59  *
     60  * Camera2StressTest, CameraStartupTest, Camera2LatencyTest and CameraPerformanceTest use this base
     61  * class for Camera ivvavik and later.
     62  */
     63 public class CameraTestBase implements IDeviceTest, IRemoteTest, IConfigurationReceiver {
     64 
     65     private static final long SHELL_TIMEOUT_MS = 60 * 1000;  // 1 min
     66     private static final int SHELL_MAX_ATTEMPTS = 3;
     67     protected static final String PROCESS_CAMERA_DAEMON = "mm-qcamera-daemon";
     68     protected static final String PROCESS_MEDIASERVER = "mediaserver";
     69     protected static final String PROCESS_CAMERA_APP = "com.google.android.GoogleCamera";
     70     protected static final String DUMP_ION_HEAPS_COMMAND = "cat /d/ion/heaps/system";
     71     protected static final String ARGUMENT_TEST_ITERATIONS = "iterations";
     72 
     73     @Option(name = "test-package", description = "Test package to run.")
     74     private String mTestPackage = "com.google.android.camera";
     75 
     76     @Option(name = "test-class", description = "Test class to run.")
     77     private String mTestClass = null;
     78 
     79     @Option(name = "test-methods", description = "Test method to run. May be repeated.")
     80     private Collection<String> mTestMethods = new ArrayList<>();
     81 
     82     @Option(name = "test-runner", description = "Test runner for test instrumentation.")
     83     private String mTestRunner = "android.test.InstrumentationTestRunner";
     84 
     85     @Option(name = "test-timeout", description = "Max time allowed in ms for a test run.")
     86     private int mTestTimeoutMs = 60 * 60 * 1000; // 1 hour
     87 
     88     @Option(name = "shell-timeout",
     89             description="The defined timeout (in milliseconds) is used as a maximum waiting time "
     90                     + "when expecting the command output from the device. At any time, if the "
     91                     + "shell command does not output anything for a period longer than defined "
     92                     + "timeout the TF run terminates. For no timeout, set to 0.")
     93     private long mShellTimeoutMs = 60 * 60 * 1000;  // default to 1 hour
     94 
     95     @Option(name = "ru-key", description = "Result key to use when posting to the dashboard.")
     96     private String mRuKey = null;
     97 
     98     @Option(name = "logcat-on-failure", description =
     99             "take a logcat snapshot on every test failure.")
    100     private boolean mLogcatOnFailure = false;
    101 
    102     @Option(
    103         name = "instrumentation-arg",
    104         description = "Additional instrumentation arguments to provide."
    105     )
    106     private Map<String, String> mInstrArgMap = new HashMap<>();
    107 
    108     @Option(name = "dump-meminfo", description =
    109             "take a dumpsys meminfo at a given interval time.")
    110     private boolean mDumpMeminfo = false;
    111 
    112     @Option(name="dump-meminfo-interval-ms",
    113             description="Interval of calling dumpsys meminfo in milliseconds.")
    114     private int mMeminfoIntervalMs = 5 * 60 * 1000;     // 5 minutes
    115 
    116     @Option(name = "dump-ion-heap", description =
    117             "dump ION allocations at the end of test.")
    118     private boolean mDumpIonHeap = false;
    119 
    120     @Option(name = "dump-thread-count", description =
    121             "Count the number of threads in Camera process at a given interval time.")
    122     private boolean mDumpThreadCount = false;
    123 
    124     @Option(name="dump-thread-count-interval-ms",
    125             description="Interval of calling ps to count the number of threads in milliseconds.")
    126     private int mThreadCountIntervalMs = 5 * 60 * 1000; // 5 minutes
    127 
    128     @Option(name="iterations", description="The number of iterations to run. Default to 1. "
    129             + "This takes effect only when Camera2InstrumentationTestRunner is used to execute "
    130             + "framework stress tests.")
    131     private int mIterations = 1;
    132 
    133     private ITestDevice mDevice = null;
    134 
    135     // A base listener to collect the results from each test run. Test results will be forwarded
    136     // to other listeners.
    137     private AbstractCollectingListener mCollectingListener = null;
    138 
    139     private long mStartTimeMs = 0;
    140 
    141     private MeminfoTimer mMeminfoTimer = null;
    142     private ThreadTrackerTimer mThreadTrackerTimer = null;
    143 
    144     protected IConfiguration mConfiguration;
    145 
    146     /**
    147      * {@inheritDoc}
    148      */
    149     @Override
    150     public void run(ITestInvocationListener listener) throws DeviceNotAvailableException {
    151         // ignore
    152     }
    153 
    154     /**
    155      * Run Camera instrumentation test with a default listener.
    156      *
    157      * @param listener the ITestInvocationListener of test results
    158      * @throws DeviceNotAvailableException
    159      */
    160     protected void runInstrumentationTest(ITestInvocationListener listener)
    161             throws DeviceNotAvailableException {
    162         if (mCollectingListener == null) {
    163             mCollectingListener = new DefaultCollectingListener(listener);
    164         }
    165         runInstrumentationTest(listener, mCollectingListener);
    166     }
    167 
    168     /**
    169      * Run Camera instrumentation test with a listener to gather the metrics from the individual
    170      * test runs.
    171      *
    172      * @param listener the ITestInvocationListener of test results
    173      * @param collectingListener the {@link CollectingTestListener} to collect the metrics from
    174      *                           test runs
    175      * @throws DeviceNotAvailableException
    176      */
    177     protected void runInstrumentationTest(ITestInvocationListener listener,
    178             AbstractCollectingListener collectingListener)
    179             throws DeviceNotAvailableException {
    180         Assert.assertNotNull(collectingListener);
    181         mCollectingListener = collectingListener;
    182 
    183         InstrumentationTest instr = new InstrumentationTest();
    184         instr.setDevice(getDevice());
    185         instr.setPackageName(getTestPackage());
    186         instr.setRunnerName(getTestRunner());
    187         instr.setClassName(getTestClass());
    188         instr.setTestTimeout(getTestTimeoutMs());
    189         instr.setShellTimeout(getShellTimeoutMs());
    190         instr.setLogcatOnFailure(mLogcatOnFailure);
    191         instr.setRunName(getRuKey());
    192         instr.setRerunMode(false);
    193 
    194         // Set test iteration.
    195         if (getIterationCount() > 1) {
    196             CLog.v("Setting test iterations: %d", getIterationCount());
    197             Map<String, String> instrArgMap = getInstrumentationArgMap();
    198             instrArgMap.put(ARGUMENT_TEST_ITERATIONS, String.valueOf(getIterationCount()));
    199         }
    200 
    201         for (Map.Entry<String, String> entry : getInstrumentationArgMap().entrySet()) {
    202             instr.addInstrumentationArg(entry.getKey(), entry.getValue());
    203         }
    204 
    205         // Check if dumpheap needs to be taken for native processes before test runs.
    206         if (shouldDumpMeminfo()) {
    207             mMeminfoTimer = new MeminfoTimer(0, mMeminfoIntervalMs);
    208         }
    209         if (shouldDumpThreadCount()) {
    210             long delayMs = mThreadCountIntervalMs / 2;  // Not to run all dump at the same interval.
    211             mThreadTrackerTimer = new ThreadTrackerTimer(delayMs, mThreadCountIntervalMs);
    212         }
    213 
    214         // Run tests.
    215         mStartTimeMs = System.currentTimeMillis();
    216         if (mTestMethods.size() > 0) {
    217             CLog.d(String.format("The number of test methods is: %d", mTestMethods.size()));
    218             for (String testName : mTestMethods) {
    219                 instr.setMethodName(testName);
    220                 instr.run(mCollectingListener);
    221             }
    222         } else {
    223             instr.run(mCollectingListener);
    224         }
    225 
    226         dumpIonHeaps(mCollectingListener, getTestClass());
    227     }
    228 
    229     /**
    230      * A base listener to collect all test results and metrics from Camera instrumentation test run.
    231      * Abstract methods can be overridden to handle test metrics or inform of test run ended.
    232      */
    233     protected abstract class AbstractCollectingListener extends CollectingTestListener {
    234 
    235         private ITestInvocationListener mListener = null;
    236         private Map<String, String> mMetrics = new HashMap<>();
    237         private Map<String, String> mFatalErrors = new HashMap<>();
    238 
    239         private static final String INCOMPLETE_TEST_ERR_MSG_PREFIX =
    240                 "Test failed to run to completion. Reason: 'Instrumentation run failed";
    241 
    242         public AbstractCollectingListener(ITestInvocationListener listener) {
    243             mListener = listener;
    244         }
    245 
    246         /**
    247          * Override only when subclasses need to get the test metrics from an individual
    248          * instrumentation test. To aggregate the metrics from each test, update the
    249          * getAggregatedMetrics and post them at the end of test run.
    250          *
    251          * @param test identifies the test
    252          * @param testMetrics a {@link Map} of the metrics emitted
    253          */
    254         abstract public void handleMetricsOnTestEnded(TestIdentifier test,
    255                 Map<String, String> testMetrics);
    256 
    257         /**
    258          * Override only when it needs to inform subclasses of instrumentation test run ended,
    259          * so that subclasses have a chance to peek the aggregated results at the end of test run
    260          * and to decide what metrics to be posted.
    261          * Either {@link ITestInvocationListener#testRunEnded} or
    262          * {@link ITestInvocationListener#testRunFailed} should be called in this function to
    263          * report the test run status.
    264          *
    265          * @param listener - the ITestInvocationListener of test results
    266          * @param elapsedTime - device reported elapsed time, in milliseconds
    267          * @param runMetrics - key-value pairs reported at the end of an instrumentation test run.
    268          *                   Use getAggregatedMetrics to retrieve the metrics aggregated
    269          *                   from an individual test, instead.
    270          */
    271         abstract public void handleTestRunEnded(ITestInvocationListener listener,
    272                 long elapsedTime, Map<String, String> runMetrics);
    273 
    274         /**
    275          * Report the end of an individual camera test and delegate handling the collected metrics
    276          * to subclasses. Do not override testEnded to manipulate the test metrics after each test.
    277          * Instead, use handleMetricsOnTestEnded.
    278          *
    279          * @param test identifies the test
    280          * @param testMetrics a {@link Map} of the metrics emitted
    281          */
    282         @Override
    283         public void testEnded(TestIdentifier test, long endTime, Map<String, String> testMetrics) {
    284             super.testEnded(test, endTime, testMetrics);
    285             handleMetricsOnTestEnded(test, testMetrics);
    286             stopDumping(test);
    287             mListener.testEnded(test, endTime, testMetrics);
    288         }
    289 
    290         @Override
    291         public void testStarted(TestIdentifier test, long startTime) {
    292             super.testStarted(test, startTime);
    293             startDumping(test);
    294             mListener.testStarted(test, startTime);
    295         }
    296 
    297         @Override
    298         public void testFailed(TestIdentifier test, String trace) {
    299             super.testFailed(test, trace);
    300             // If the test failed to run to complete, this is an exceptional case.
    301             // Let this test run fail so that it can rerun.
    302             if (trace.startsWith(INCOMPLETE_TEST_ERR_MSG_PREFIX)) {
    303                 mFatalErrors.put(test.getTestName(), trace);
    304                 CLog.d("Test (%s) failed due to fatal error : %s", test.getTestName(), trace);
    305             }
    306             mListener.testFailed(test, trace);
    307         }
    308 
    309         @Override
    310         public void testRunFailed(String errorMessage) {
    311             super.testRunFailed(errorMessage);
    312             mFatalErrors.put(getRuKey(), errorMessage);
    313         }
    314 
    315         @Override
    316         public void testRunEnded(long elapsedTime, Map<String, String> runMetrics) {
    317             super.testRunEnded(elapsedTime, runMetrics);
    318             handleTestRunEnded(mListener, elapsedTime, runMetrics);
    319             // never be called since handleTestRunEnded will handle it if needed.
    320             //mListener.testRunEnded(elapsedTime, runMetrics);
    321         }
    322 
    323         @Override
    324         public void testRunStarted(String runName, int testCount) {
    325             super.testRunStarted(runName, testCount);
    326             mListener.testRunStarted(runName, testCount);
    327         }
    328 
    329         @Override
    330         public void testRunStopped(long elapsedTime) {
    331             super.testRunStopped(elapsedTime);
    332             mListener.testRunStopped(elapsedTime);
    333         }
    334 
    335         @Override
    336         public void testLog(String dataName, LogDataType dataType, InputStreamSource dataStream) {
    337             super.testLog(dataName, dataType, dataStream);
    338             mListener.testLog(dataName, dataType, dataStream);
    339         }
    340 
    341         protected void startDumping(TestIdentifier test) {
    342             if (shouldDumpMeminfo()) {
    343                 mMeminfoTimer.start(test);
    344             }
    345             if (shouldDumpThreadCount()) {
    346                 mThreadTrackerTimer.start(test);
    347             }
    348         }
    349 
    350         protected void stopDumping(TestIdentifier test) {
    351             InputStreamSource outputSource = null;
    352             File outputFile = null;
    353             if (shouldDumpMeminfo()) {
    354                 mMeminfoTimer.stop();
    355                 // Grab a snapshot of meminfo file and post it to dashboard.
    356                 try {
    357                     outputFile = mMeminfoTimer.getOutputFile();
    358                     outputSource = new FileInputStreamSource(outputFile, true /* delete */);
    359                     String logName = String.format("meminfo_%s", test.getTestName());
    360                     mListener.testLog(logName, LogDataType.TEXT, outputSource);
    361                 } finally {
    362                     StreamUtil.cancel(outputSource);
    363                 }
    364             }
    365             if (shouldDumpThreadCount()) {
    366                 mThreadTrackerTimer.stop();
    367                 try {
    368                     outputFile = mThreadTrackerTimer.getOutputFile();
    369                     outputSource = new FileInputStreamSource(outputFile, true /* delete */);
    370                     String logName = String.format("ps_%s", test.getTestName());
    371                     mListener.testLog(logName, LogDataType.TEXT, outputSource);
    372                 } finally {
    373                     StreamUtil.cancel(outputSource);
    374                 }
    375             }
    376         }
    377 
    378         public Map<String, String> getAggregatedMetrics() {
    379             return mMetrics;
    380         }
    381 
    382         public ITestInvocationListener getListeners() {
    383             return mListener;
    384         }
    385 
    386         /**
    387          * Determine that the test run failed with fatal errors.
    388          *
    389          * @return True if test run has a failure due to fatal error.
    390          */
    391         public boolean hasTestRunFatalError() {
    392             return (getNumTotalTests() > 0 && mFatalErrors.size() > 0);
    393         }
    394 
    395         public Map<String, String> getFatalErrors() {
    396             return mFatalErrors;
    397         }
    398 
    399         public String getErrorMessage() {
    400             StringBuilder sb = new StringBuilder();
    401             for (Map.Entry<String, String> error : mFatalErrors.entrySet()) {
    402                 sb.append(error.getKey());
    403                 sb.append(" : ");
    404                 sb.append(error.getValue());
    405                 sb.append("\n");
    406             }
    407             return sb.toString();
    408         }
    409     }
    410 
    411     protected class DefaultCollectingListener extends AbstractCollectingListener {
    412 
    413         public DefaultCollectingListener(ITestInvocationListener listener) {
    414             super(listener);
    415         }
    416 
    417         @Override
    418         public void handleMetricsOnTestEnded(TestIdentifier test, Map<String, String> testMetrics) {
    419             if (testMetrics == null) {
    420                 return; // No-op if there is nothing to post.
    421             }
    422             getAggregatedMetrics().putAll(testMetrics);
    423         }
    424 
    425         @Override
    426         public void handleTestRunEnded(ITestInvocationListener listener, long elapsedTime,
    427                 Map<String, String> runMetrics) {
    428             // Post aggregated metrics at the end of test run.
    429             listener.testRunEnded(getTestDurationMs(), getAggregatedMetrics());
    430         }
    431     }
    432 
    433     // TODO: Leverage AUPT to collect system logs (meminfo, ION allocations and processes/threads)
    434     private class MeminfoTimer {
    435 
    436         private static final String LOG_HEADER =
    437                 "uptime,pssCameraDaemon,pssCameraApp,ramTotal,ramFree,ramUsed";
    438         private static final String DUMPSYS_MEMINFO_COMMAND =
    439                 "dumpsys meminfo -c | grep -w -e " + "^ram -e ^time";
    440         private String[] mDumpsysMemInfoProc = {
    441             PROCESS_CAMERA_DAEMON, PROCESS_CAMERA_APP, PROCESS_MEDIASERVER
    442         };
    443         private static final int STATE_STOPPED = 0;
    444         private static final int STATE_SCHEDULED = 1;
    445         private static final int STATE_RUNNING = 2;
    446 
    447         private int mState = STATE_STOPPED;
    448         private Timer mTimer = new Timer(true); // run as a daemon thread
    449         private long mDelayMs = 0;
    450         private long mPeriodMs = 60 * 1000;  // 60 sec
    451         private File mOutputFile = null;
    452         private String mCommand;
    453 
    454         public MeminfoTimer(long delayMs, long periodMs) {
    455             mDelayMs = delayMs;
    456             mPeriodMs = periodMs;
    457             mCommand = DUMPSYS_MEMINFO_COMMAND;
    458             for (String process : mDumpsysMemInfoProc) {
    459                 mCommand += " -e " + process;
    460             }
    461         }
    462 
    463         synchronized void start(TestIdentifier test) {
    464             if (isRunning()) {
    465                 stop();
    466             }
    467             // Create an output file.
    468             if (createOutputFile(test) == null) {
    469                 CLog.w("Stop collecting meminfo since meminfo log file not found.");
    470                 mState = STATE_STOPPED;
    471                 return; // No-op
    472             }
    473             mTimer.scheduleAtFixedRate(new TimerTask() {
    474                 @Override
    475                 public void run() {
    476                     mState = STATE_RUNNING;
    477                     dumpMeminfo(mCommand, mOutputFile);
    478                 }
    479             }, mDelayMs, mPeriodMs);
    480             mState = STATE_SCHEDULED;
    481         }
    482 
    483         synchronized void stop() {
    484             mState = STATE_STOPPED;
    485             mTimer.cancel();
    486         }
    487 
    488         synchronized boolean isRunning() {
    489             return (mState == STATE_RUNNING);
    490         }
    491 
    492         public File getOutputFile() {
    493             return mOutputFile;
    494         }
    495 
    496         private File createOutputFile(TestIdentifier test) {
    497             try {
    498                 mOutputFile = FileUtil.createTempFile(
    499                         String.format("meminfo_%s", test.getTestName()), "csv");
    500                 BufferedWriter writer = new BufferedWriter(new FileWriter(mOutputFile, false));
    501                 writer.write(LOG_HEADER);
    502                 writer.newLine();
    503                 writer.flush();
    504                 writer.close();
    505             } catch (IOException e) {
    506                 CLog.w("Failed to create meminfo log file %s:", mOutputFile.getAbsolutePath());
    507                 CLog.e(e);
    508                 return null;
    509             }
    510             return mOutputFile;
    511         }
    512     }
    513 
    514     void dumpMeminfo(String command, File outputFile) {
    515         try {
    516             CollectingOutputReceiver receiver = new CollectingOutputReceiver();
    517             // Dump meminfo in a compact form.
    518             getDevice().executeShellCommand(command, receiver,
    519                     SHELL_TIMEOUT_MS, TimeUnit.MILLISECONDS, SHELL_MAX_ATTEMPTS);
    520             printMeminfo(outputFile, receiver.getOutput());
    521         } catch (DeviceNotAvailableException e) {
    522             CLog.w("Failed to dump meminfo:");
    523             CLog.e(e);
    524         }
    525     }
    526 
    527     void printMeminfo(File outputFile, String meminfo) {
    528         // Parse meminfo and print each iteration in a line in a .csv format. The meminfo output
    529         // are separated into three different formats:
    530         //
    531         // Format: time,<uptime>,<realtime>
    532         // eg. "time,59459911,63354673"
    533         //
    534         // Format: proc,<oom_label>,<process_name>,<pid>,<pss>,<hasActivities>
    535         // eg. "proc,native,mm-qcamera-daemon,522,12881,e"
    536         //     "proc,fore,com.google.android.GoogleCamera,26560,70880,a"
    537         //
    538         // Format: ram,<total>,<free>,<used>
    539         // eg. "ram,1857364,810189,541516"
    540         BufferedWriter writer = null;
    541         BufferedReader reader = null;
    542         try {
    543             final String DELIMITER = ",";
    544             writer = new BufferedWriter(new FileWriter(outputFile, true));
    545             reader = new BufferedReader(new StringReader(meminfo));
    546             String line;
    547             String uptime = null;
    548             String pssCameraNative = null;
    549             String pssCameraApp = null;
    550             String ramTotal = null;
    551             String ramFree = null;
    552             String ramUsed = null;
    553             while ((line = reader.readLine()) != null) {
    554                 if (line.startsWith("time")) {
    555                     uptime = line.split(DELIMITER)[1];
    556                 } else if (line.startsWith("ram")) {
    557                     String[] ram = line.split(DELIMITER);
    558                     ramTotal = ram[1];
    559                     ramFree = ram[2];
    560                     ramUsed = ram[3];
    561                 } else if (line.contains(PROCESS_CAMERA_DAEMON)) {
    562                     pssCameraNative = line.split(DELIMITER)[4];
    563                 } else if (line.contains(PROCESS_CAMERA_APP)) {
    564                     pssCameraApp = line.split(DELIMITER)[4];
    565                 }
    566             }
    567             String printMsg = String.format(
    568                     "%s,%s,%s,%s,%s,%s", uptime, pssCameraNative, pssCameraApp,
    569                     ramTotal, ramFree, ramUsed);
    570             writer.write(printMsg);
    571             writer.newLine();
    572             writer.flush();
    573         } catch (IOException e) {
    574             CLog.w("Failed to print meminfo to %s:", outputFile.getAbsolutePath());
    575             CLog.e(e);
    576         } finally {
    577             StreamUtil.close(writer);
    578         }
    579     }
    580 
    581     // TODO: Leverage AUPT to collect system logs (meminfo, ION allocations and processes/threads)
    582     private class ThreadTrackerTimer {
    583 
    584         // list all threads in a given process, remove the first header line, squeeze whitespaces,
    585         // select thread name (in 14th column), then sort and group by its name.
    586         // Examples:
    587         //    3 SoundPoolThread
    588         //    3 SoundPool
    589         //    2 Camera Job Disp
    590         //    1 pool-7-thread-1
    591         //    1 pool-6-thread-1
    592         // FIXME: Resolve the error "sh: syntax error: '|' unexpected" using the command below
    593         // $ /system/bin/ps -t -p %s | tr -s ' ' | cut -d' ' -f13- | sort | uniq -c | sort -nr"
    594         private static final String PS_COMMAND_FORMAT = "/system/bin/ps -t -p %s";
    595         private static final String PGREP_COMMAND_FORMAT = "pgrep %s";
    596         private static final int STATE_STOPPED = 0;
    597         private static final int STATE_SCHEDULED = 1;
    598         private static final int STATE_RUNNING = 2;
    599 
    600         private int mState = STATE_STOPPED;
    601         private Timer mTimer = new Timer(true); // run as a daemon thread
    602         private long mDelayMs = 0;
    603         private long mPeriodMs = 60 * 1000;  // 60 sec
    604         private File mOutputFile = null;
    605 
    606         public ThreadTrackerTimer(long delayMs, long periodMs) {
    607             mDelayMs = delayMs;
    608             mPeriodMs = periodMs;
    609         }
    610 
    611         synchronized void start(TestIdentifier test) {
    612             if (isRunning()) {
    613                 stop();
    614             }
    615             // Create an output file.
    616             if (createOutputFile(test) == null) {
    617                 CLog.w("Stop collecting thread counts since log file not found.");
    618                 mState = STATE_STOPPED;
    619                 return; // No-op
    620             }
    621             mTimer.scheduleAtFixedRate(new TimerTask() {
    622                 @Override
    623                 public void run() {
    624                     mState = STATE_RUNNING;
    625                     dumpThreadCount(PS_COMMAND_FORMAT, getPid(PROCESS_CAMERA_APP), mOutputFile);
    626                 }
    627             }, mDelayMs, mPeriodMs);
    628             mState = STATE_SCHEDULED;
    629         }
    630 
    631         synchronized void stop() {
    632             mState = STATE_STOPPED;
    633             mTimer.cancel();
    634         }
    635 
    636         synchronized boolean isRunning() {
    637             return (mState == STATE_RUNNING);
    638         }
    639 
    640         public File getOutputFile() {
    641             return mOutputFile;
    642         }
    643 
    644         File createOutputFile(TestIdentifier test) {
    645             try {
    646                 mOutputFile = FileUtil.createTempFile(
    647                         String.format("ps_%s", test.getTestName()), "txt");
    648                 new BufferedWriter(new FileWriter(mOutputFile, false)).close();
    649             } catch (IOException e) {
    650                 CLog.w("Failed to create processes and threads file %s:",
    651                         mOutputFile.getAbsolutePath());
    652                 CLog.e(e);
    653                 return null;
    654             }
    655             return mOutputFile;
    656         }
    657 
    658         String getPid(String processName) {
    659             String result = null;
    660             try {
    661                 result = getDevice().executeShellCommand(String.format(PGREP_COMMAND_FORMAT,
    662                         processName));
    663             } catch (DeviceNotAvailableException e) {
    664                 CLog.w("Failed to get pid %s:", processName);
    665                 CLog.e(e);
    666             }
    667             return result;
    668         }
    669 
    670         String getUptime() {
    671             String uptime = null;
    672             try {
    673                 // uptime will typically have a format like "5278.73 1866.80".  Use the first one
    674                 // (which is wall-time)
    675                 uptime = getDevice().executeShellCommand("cat /proc/uptime").split(" ")[0];
    676                 Float.parseFloat(uptime);
    677             } catch (NumberFormatException e) {
    678                 CLog.w("Failed to get valid uptime %s: %s", uptime, e);
    679             } catch (DeviceNotAvailableException e) {
    680                 CLog.w("Failed to get valid uptime: %s", e);
    681             }
    682             return uptime;
    683         }
    684 
    685         void dumpThreadCount(String commandFormat, String pid, File outputFile) {
    686             try {
    687                 if ("".equals(pid)) {
    688                     return;
    689                 }
    690                 String result = getDevice().executeShellCommand(String.format(commandFormat, pid));
    691                 String header = String.format("UPTIME: %s", getUptime());
    692                 BufferedWriter writer = new BufferedWriter(new FileWriter(outputFile, true));
    693                 writer.write(header);
    694                 writer.newLine();
    695                 writer.write(result);
    696                 writer.newLine();
    697                 writer.flush();
    698                 writer.close();
    699             } catch (DeviceNotAvailableException | IOException e) {
    700                 CLog.w("Failed to dump thread count:");
    701                 CLog.e(e);
    702             }
    703         }
    704     }
    705 
    706     // TODO: Leverage AUPT to collect system logs (meminfo, ION allocations and
    707     // processes/threads)
    708     protected void dumpIonHeaps(ITestInvocationListener listener, String testClass) {
    709         if (!shouldDumpIonHeap()) {
    710             return; // No-op if option is not set.
    711         }
    712         try {
    713             String result = getDevice().executeShellCommand(DUMP_ION_HEAPS_COMMAND);
    714             if (!"".equals(result)) {
    715                 String fileName = String.format("ionheaps_%s_onEnd", testClass);
    716                 listener.testLog(fileName, LogDataType.TEXT,
    717                         new ByteArrayInputStreamSource(result.getBytes()));
    718             }
    719         } catch (DeviceNotAvailableException e) {
    720             CLog.w("Failed to dump ION heaps:");
    721             CLog.e(e);
    722         }
    723     }
    724 
    725     /**
    726      * {@inheritDoc}
    727      */
    728     @Override
    729     public void setDevice(ITestDevice device) {
    730         mDevice = device;
    731     }
    732 
    733     /**
    734      * {@inheritDoc}
    735      */
    736     @Override
    737     public ITestDevice getDevice() {
    738         return mDevice;
    739     }
    740 
    741     /**
    742      * {@inheritDoc}
    743      */
    744     @Override
    745     public void setConfiguration(IConfiguration configuration) {
    746         mConfiguration = configuration;
    747     }
    748 
    749     /**
    750      * Get the {@link IRunUtil} instance to use.
    751      * <p/>
    752      * Exposed so unit tests can mock.
    753      */
    754     IRunUtil getRunUtil() {
    755         return RunUtil.getDefault();
    756     }
    757 
    758     /**
    759      * Get the duration of Camera test instrumentation in milliseconds.
    760      *
    761      * @return the duration of Camera instrumentation test until it is called.
    762      */
    763     public long getTestDurationMs() {
    764         return System.currentTimeMillis() - mStartTimeMs;
    765     }
    766 
    767     public String getTestPackage() {
    768         return mTestPackage;
    769     }
    770 
    771     public void setTestPackage(String testPackage) {
    772         mTestPackage = testPackage;
    773     }
    774 
    775     public String getTestClass() {
    776         return mTestClass;
    777     }
    778 
    779     public void setTestClass(String testClass) {
    780         mTestClass = testClass;
    781     }
    782 
    783     public String getTestRunner() {
    784         return mTestRunner;
    785     }
    786 
    787     public void setTestRunner(String testRunner) {
    788         mTestRunner = testRunner;
    789     }
    790 
    791     public int getTestTimeoutMs() {
    792         return mTestTimeoutMs;
    793     }
    794 
    795     public void setTestTimeoutMs(int testTimeoutMs) {
    796         mTestTimeoutMs = testTimeoutMs;
    797     }
    798 
    799     public long getShellTimeoutMs() {
    800         return mShellTimeoutMs;
    801     }
    802 
    803     public void setShellTimeoutMs(long shellTimeoutMs) {
    804         mShellTimeoutMs = shellTimeoutMs;
    805     }
    806 
    807     public String getRuKey() {
    808         return mRuKey;
    809     }
    810 
    811     public void setRuKey(String ruKey) {
    812         mRuKey = ruKey;
    813     }
    814 
    815     public boolean shouldDumpMeminfo() {
    816         return mDumpMeminfo;
    817     }
    818 
    819     public boolean shouldDumpIonHeap() {
    820         return mDumpIonHeap;
    821     }
    822 
    823     public boolean shouldDumpThreadCount() {
    824         return mDumpThreadCount;
    825     }
    826 
    827     public AbstractCollectingListener getCollectingListener() {
    828         return mCollectingListener;
    829     }
    830 
    831     public void setLogcatOnFailure(boolean logcatOnFailure) {
    832         mLogcatOnFailure = logcatOnFailure;
    833     }
    834 
    835     public int getIterationCount() {
    836         return mIterations;
    837     }
    838 
    839     public Map<String, String> getInstrumentationArgMap() { return mInstrArgMap; }
    840 }
    841