public class

AtraceLogger

extends java.lang.Object

 java.lang.Object

↳androidx.test.rule.logging.AtraceLogger

Gradle dependencies

compile group: 'androidx.test', name: 'rules', version: '1.6.1'

  • groupId: androidx.test
  • artifactId: rules
  • version: 1.6.1

Artifact androidx.test:rules:1.6.1 it located at Google repository (https://maven.google.com/)

Androidx artifact mapping:

androidx.test:rules com.android.support.test:rules

Androidx class mapping:

androidx.test.rule.logging.AtraceLogger android.support.test.rule.logging.AtraceLogger

Overview

Class contains helper methods to dump atrace info asynchronously while running the test case.

Supported only for minsdk version 21 and above because of UiAutomation#executeShellCommand availability.

Summary

Methods
public voidatraceStart(java.util.Set<java.lang.String> traceCategoriesSet, int atraceBufferSize, int dumpIntervalSecs, java.io.File destDirectory, java.lang.String traceFileName)

Method to start atrace and dump the data at regular interval.

public voidatraceStop()

Method to stop the atrace and write the atrace data cached in byte array list to file.

public static AtraceLoggergetAtraceLoggerInstance(Instrumentation instrumentation)

To make sure only one instance of atrace logger is created.

from java.lang.Objectclone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait

Methods

public static AtraceLogger getAtraceLoggerInstance(Instrumentation instrumentation)

To make sure only one instance of atrace logger is created.

Parameters:

instrumentation: Used to execute atrace shell commands

Returns:

instance of the AtraceLogger

public void atraceStart(java.util.Set<java.lang.String> traceCategoriesSet, int atraceBufferSize, int dumpIntervalSecs, java.io.File destDirectory, java.lang.String traceFileName)

Method to start atrace and dump the data at regular interval. Note : Trace info will not be captured during the dumping if there are multiple dumps between the atraceStart and atraceStop

Parameters:

traceCategoriesSet: Set of atrace categories (i.e atrace --list_categories)
atraceBufferSize: Size of the circular buffer in kb
dumpIntervalSecs: Periodic interval to dump data from circular buffer
destDirectory: Directory under which atrace logs are stored
traceFileName: is optional parameter.Atrace files are dumped under destDirectory. traceFileName will be indexed based on number of dumps between atraceStart and atraceStop under destDirectory. If traceFileName is null or empty "atrace" name will be used for indexing the files and stored under destDirectory

public void atraceStop()

Method to stop the atrace and write the atrace data cached in byte array list to file.

Source

/*
 * Copyright (C) 2017 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 androidx.test.rule.logging;

import android.app.Instrumentation;
import android.app.UiAutomation;
import android.os.Build.VERSION;
import android.os.ParcelFileDescriptor;
import android.util.Log;
import androidx.annotation.RequiresApi;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import java.util.concurrent.CountDownLatch;

/**
 * Class contains helper methods to dump atrace info asynchronously while running the test case.
 *
 * <p>Supported only for minsdk version 21 and above because of UiAutomation#executeShellCommand
 * availability.
 *
 * @deprecated unsupported. Consider running trace from host such as via Android Studio
 */
@RequiresApi(21)
@Deprecated
public class AtraceLogger {

  private static final String ATRACE_START = "atrace --async_start -b %d -c %s";
  private static final String ATRACE_DUMP = "atrace --async_dump -b %d -z %s";
  private static final String ATRACE_STOP = "atrace --async_stop -b %d -z %s";
  private static final String ATRACEHELPER_TAG = "AtraceLogger";
  private static final String CATEGORY_SEPARATOR = " ";
  private static final int BUFFER_SIZE = 8192;
  private static volatile AtraceLogger loggerInstance;
  private UiAutomation uiAutomation;
  private String traceFileName;
  private List<ByteArrayOutputStream> atraceDataList;
  private Thread dumpThread;
  private File destAtraceDirectory;
  private boolean atraceRunning = false;
  private IOException dumpIOException;

  private AtraceLogger(Instrumentation instrumentation) {
    uiAutomation = instrumentation.getUiAutomation();
  }

  /**
   * To make sure only one instance of atrace logger is created.
   *
   * @param instrumentation Used to execute atrace shell commands
   * @return instance of the AtraceLogger
   */
  public static AtraceLogger getAtraceLoggerInstance(Instrumentation instrumentation) {
    if (VERSION.SDK_INT < 21) {
      throw new UnsupportedOperationException("AtraceLogger is only supported on APIs >= 21");
    }
    if (loggerInstance == null) {
      synchronized (AtraceLogger.class) {
        if (loggerInstance == null) {
          loggerInstance = new AtraceLogger(instrumentation);
        }
      }
    }
    return loggerInstance;
  }

  /**
   * Method to start atrace and dump the data at regular interval. Note : Trace info will not be
   * captured during the dumping if there are multiple dumps between the atraceStart and atraceStop
   *
   * @param traceCategoriesSet Set of atrace categories (i.e atrace --list_categories)
   * @param atraceBufferSize Size of the circular buffer in kb
   * @param dumpIntervalSecs Periodic interval to dump data from circular buffer
   * @param destDirectory Directory under which atrace logs are stored
   * @param traceFileName is optional parameter.Atrace files are dumped under destDirectory.
   *     traceFileName will be indexed based on number of dumps between atraceStart and atraceStop
   *     under destDirectory. If traceFileName is null or empty "atrace" name will be used for
   *     indexing the files and stored under destDirectory
   * @throws IOException
   */
  public void atraceStart(
      Set<String> traceCategoriesSet,
      int atraceBufferSize,
      int dumpIntervalSecs,
      File destDirectory,
      String traceFileName)
      throws IOException {
    if (atraceRunning) {
      throw new IllegalStateException("Attempted multiple atrace start");
    }
    if (traceCategoriesSet.isEmpty()) {
      throw new IllegalArgumentException("Empty categories. Should contain atleast one category");
    }
    if (destDirectory == null) {
      throw new IllegalArgumentException("Destination directory cannot be null");
    }
    if (!destDirectory.exists() && !destDirectory.mkdirs()) {
      throw new IOException("Unable to create the destination directory");
    }
    destAtraceDirectory = destDirectory;

    StringBuffer traceCategoriesList = new StringBuffer();
    for (String traceCategory : traceCategoriesSet) {
      traceCategoriesList.append(traceCategory).append(CATEGORY_SEPARATOR);
    }
    if (traceFileName != null && !traceFileName.isEmpty()) {
      this.traceFileName = traceFileName;
    }

    String startCommand =
        String.format(ATRACE_START, atraceBufferSize, traceCategoriesList.toString());

    /*
     * Since execute shell command is not blocked until the command executes successfully,
     * write the output to byte array to make sure atrace start command is completed before
     * starting the test
     */
    ByteArrayOutputStream outStream = new ByteArrayOutputStream();
    try {
      writeDataToByteStream(uiAutomation.executeShellCommand(startCommand), outStream);
    } finally {
      outStream.close();
    }
    atraceRunning = true;
    dumpIOException = null;
    atraceDataList = new ArrayList<ByteArrayOutputStream>();
    DumpTraceRunnable dumpTraceRunnable =
        new DumpTraceRunnable(traceCategoriesList.toString(), atraceBufferSize, dumpIntervalSecs);
    dumpThread = new Thread(dumpTraceRunnable);
    dumpThread.start();
    dumpTraceRunnable.waitForStart();
  }

  /**
   * Method to write data into byte array
   *
   * @param pfDescriptor Used to read the content returned by shell command
   * @param outputStream Write the data to this output stream read from pfDescriptor
   * @throws IOException
   */
  private void writeDataToByteStream(
      ParcelFileDescriptor pfDescriptor, ByteArrayOutputStream outputStream) throws IOException {
    InputStream inputStream = new ParcelFileDescriptor.AutoCloseInputStream(pfDescriptor);
    try {
      byte[] buffer = new byte[BUFFER_SIZE];
      int length;
      while ((length = inputStream.read(buffer)) >= 0) {
        outputStream.write(buffer, 0, length);
      }
    } finally {
      inputStream.close();
    }
  }

  /**
   * Method to stop the atrace and write the atrace data cached in byte array list to file.
   *
   * @throws IOException
   * @throws InterruptedException
   */
  public void atraceStop() throws IOException, InterruptedException {
    if (!atraceRunning) {
      throw new IllegalStateException(
          "ATrace is not running currently. Start atrace before" + "stopping.");
    }
    try {
      dumpThread.interrupt();
      dumpThread.join();
      if (dumpIOException != null) {
        throw dumpIOException;
      }
      atraceWrite();
    } finally {
      for (ByteArrayOutputStream outStream : atraceDataList) {
        outStream.close();
      }
      atraceRunning = false;
      traceFileName = null;
    }
  }

  /**
   * Method to write atrace data buffered in byte array stream to file
   *
   * @throws IOException
   */
  private void atraceWrite() throws IOException {
    int count = 0;
    for (ByteArrayOutputStream outStream : atraceDataList) {
      // Indexing the files from 0..(atraceDataList.size)-1 based on number of dumps
      File file = null;
      if (traceFileName != null) {
        file =
            new File(destAtraceDirectory, String.format("%s-atrace-%d.txt", traceFileName, count));
      } else {
        file = new File(destAtraceDirectory, String.format("atrace-%d.txt", count));
      }
      OutputStream fileOutputStream = new FileOutputStream(file);
      try {
        fileOutputStream.write(outStream.toByteArray());
      } finally {
        fileOutputStream.close();
      }
      count++;
    }
  }

  /*
   * Thread class periodically dumps the atrace data into byte stream
   */
  private class DumpTraceRunnable implements Runnable {
    private String traceCategories;
    private int bufferSize;
    private int dumpIntervalInSecs;
    private final CountDownLatch startLatch = new CountDownLatch(1);

    DumpTraceRunnable(String traceCategories, int bufferSize, int dumpIntervalInSecs) {
      this.traceCategories = traceCategories;
      this.bufferSize = bufferSize;
      this.dumpIntervalInSecs = dumpIntervalInSecs;
    }

    void waitForStart() {
      try {
        startLatch.await();
      } catch (InterruptedException e) {
        // ignore
      }
    }

    @Override
    public void run() {
      startLatch.countDown();
      try {
        while (!Thread.currentThread().isInterrupted()) {
          try {
            Thread.sleep(dumpIntervalInSecs * 1000);
          } catch (InterruptedException e) {
            break;
          }
          String dumpCommand = String.format(ATRACE_DUMP, bufferSize, traceCategories);
          // Dump into byte array and maintain in the list
          long startTime = System.currentTimeMillis();
          ByteArrayOutputStream byteArrayOutStream = new ByteArrayOutputStream();
          writeDataToByteStream(uiAutomation.executeShellCommand(dumpCommand), byteArrayOutStream);
          atraceDataList.add(byteArrayOutStream);
          long endTime = System.currentTimeMillis();
          Log.i(ATRACEHELPER_TAG, "Time taken by - DumpTraceRunnable " + (endTime - startTime));
        }
        String stopCommand = String.format(ATRACE_STOP, bufferSize, traceCategories);
        ByteArrayOutputStream byteArrayOutStream = new ByteArrayOutputStream();
        writeDataToByteStream(uiAutomation.executeShellCommand(stopCommand), byteArrayOutStream);
        atraceDataList.add(byteArrayOutStream);
      } catch (IOException ioe) {
        dumpIOException = ioe;
      }
    }
  }
}