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