Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit d2239828 authored by Mike Ma's avatar Mike Ma
Browse files

Record proto to file in am instrument

Add an option -f to record instrumentdata proto produced by am instrument
to a file in addition to printing to stdout. Default path is
/sdcard/instrument-logs/log-yyyyMMdd-hhmmss-SSS.instrumentation_data_proto.
If the file exits, it will be deleted before writing. Path can be changed
via optional <FILE> argument after -f.
If -f and -m are both present, proto will be written to a file and print
to stdout.

Test: build, flash and run:
bit -bi FrameworksCoreTests
adb shell am instrument -w -r -f tmp/tmp.log \
-e class com.android.internal.os.BatteryStatsNoteTest \
com.android.frameworks.coretests/android.support.test.runner.AndroidJUnitRunner

Change-Id: Iabc320c066d5995eee842c26416623eeb3d403f4
parent f8a91699
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -28,6 +28,7 @@ message ResultsBundleEntry {
    optional double value_double = 5;
    optional sint64 value_long = 6;
    optional ResultsBundle value_bundle = 7;
    optional bytes value_bytes = 8;
}

message ResultsBundle {
+5 −1
Original line number Diff line number Diff line
@@ -160,7 +160,11 @@ public class Am extends BaseCommand {
            } else if (opt.equals("-r")) {
                instrument.rawMode = true;
            } else if (opt.equals("-m")) {
                instrument.proto = true;
                instrument.protoStd = true;
            } else if (opt.equals("-f")) {
                instrument.protoFile = true;
                if (peekNextArg() != null && !peekNextArg().startsWith("-"))
                    instrument.logPath = nextArg();
            } else if (opt.equals("-e")) {
                final String argKey = nextArgRequired();
                final String argValue = nextArgRequired();
+92 −43
Original line number Diff line number Diff line
@@ -25,23 +25,32 @@ import android.content.pm.IPackageManager;
import android.content.pm.InstrumentationInfo;
import android.os.Build;
import android.os.Bundle;
import android.os.Environment;
import android.os.ServiceManager;
import android.os.UserHandle;
import android.util.AndroidException;
import android.util.proto.ProtoOutputStream;
import android.view.IWindowManager;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;


/**
 * Runs the am instrument command
 */
public class Instrument {
    public static final String DEFAULT_LOG_DIR = "instrument-logs";

    private final IActivityManager mAm;
    private final IPackageManager mPm;
    private final IWindowManager mWm;
@@ -50,7 +59,9 @@ public class Instrument {
    public String profileFile = null;
    public boolean wait = false;
    public boolean rawMode = false;
    public boolean proto = false;
    boolean protoStd = false;  // write proto to stdout
    boolean protoFile = false;  // write proto to a file
    String logPath = null;
    public boolean noWindowAnimation = false;
    public String abi = null;
    public int userId = UserHandle.USER_CURRENT;
@@ -178,18 +189,49 @@ public class Instrument {
     * Printer for the protobuf based status reporting.
     */
    private class ProtoStatusReporter implements StatusReporter {

        private File mLog;

        ProtoStatusReporter() {
            if (protoFile) {
                if (logPath == null) {
                    File logDir = new File(Environment.getLegacyExternalStorageDirectory(),
                            DEFAULT_LOG_DIR);
                    if (!logDir.exists() && !logDir.mkdirs()) {
                        System.err.format("Unable to create log directory: %s\n",
                                logDir.getAbsolutePath());
                        protoFile = false;
                        return;
                    }
                    SimpleDateFormat format = new SimpleDateFormat("yyyyMMdd-hhmmss-SSS", Locale.US);
                    String fileName = String.format("log-%s.instrumentation_data_proto",
                            format.format(new Date()));
                    mLog = new File(logDir, fileName);
                } else {
                    mLog = new File(Environment.getLegacyExternalStorageDirectory(), logPath);
                    File logDir = mLog.getParentFile();
                    if (!logDir.exists() && !logDir.mkdirs()) {
                        System.err.format("Unable to create log directory: %s\n",
                                logDir.getAbsolutePath());
                        protoFile = false;
                        return;
                    }
                }
                if (mLog.exists()) mLog.delete();
            }
        }

        @Override
        public void onInstrumentationStatusLocked(ComponentName name, int resultCode,
                Bundle results) {
            final ProtoOutputStream proto = new ProtoOutputStream();

            final long token = proto.startRepeatedObject(InstrumentationData.Session.TEST_STATUS);

            proto.writeSInt32(InstrumentationData.TestStatus.RESULT_CODE, resultCode);
            final long token = proto.start(InstrumentationData.Session.TEST_STATUS);
            proto.write(InstrumentationData.TestStatus.RESULT_CODE, resultCode);
            writeBundle(proto, InstrumentationData.TestStatus.RESULTS, results);
            proto.end(token);

            proto.endRepeatedObject(token);
            writeProtoToStdout(proto);
            outputProto(proto);
        }

        @Override
@@ -197,82 +239,89 @@ public class Instrument {
                Bundle results) {
            final ProtoOutputStream proto = new ProtoOutputStream();

            final long token = proto.startObject(InstrumentationData.Session.SESSION_STATUS);

            proto.writeEnum(InstrumentationData.SessionStatus.STATUS_CODE,
            final long token = proto.start(InstrumentationData.Session.SESSION_STATUS);
            proto.write(InstrumentationData.SessionStatus.STATUS_CODE,
                    InstrumentationData.SESSION_FINISHED);
            proto.writeSInt32(InstrumentationData.SessionStatus.RESULT_CODE, resultCode);
            proto.write(InstrumentationData.SessionStatus.RESULT_CODE, resultCode);
            writeBundle(proto, InstrumentationData.SessionStatus.RESULTS, results);
            proto.end(token);

            proto.endObject(token);
            writeProtoToStdout(proto);
            outputProto(proto);
        }

        @Override
        public void onError(String errorText, boolean commandError) {
            final ProtoOutputStream proto = new ProtoOutputStream();

            final long token = proto.startObject(InstrumentationData.Session.SESSION_STATUS);

            proto.writeEnum(InstrumentationData.SessionStatus.STATUS_CODE,
            final long token = proto.start(InstrumentationData.Session.SESSION_STATUS);
            proto.write(InstrumentationData.SessionStatus.STATUS_CODE,
                    InstrumentationData.SESSION_ABORTED);
            proto.writeString(InstrumentationData.SessionStatus.ERROR_TEXT, errorText);
            proto.write(InstrumentationData.SessionStatus.ERROR_TEXT, errorText);
            proto.end(token);

            proto.endObject(token);
            writeProtoToStdout(proto);
            outputProto(proto);
        }

        private void writeBundle(ProtoOutputStream proto, long fieldId, Bundle bundle) {
            final long bundleToken = proto.startObject(fieldId);
            final long bundleToken = proto.start(fieldId);

            for (final String key: sorted(bundle.keySet())) {
                final long entryToken = proto.startRepeatedObject(
                        InstrumentationData.ResultsBundle.ENTRIES);

                proto.writeString(InstrumentationData.ResultsBundleEntry.KEY, key);
                proto.write(InstrumentationData.ResultsBundleEntry.KEY, key);

                final Object val = bundle.get(key);
                if (val instanceof String) {
                    proto.writeString(InstrumentationData.ResultsBundleEntry.VALUE_STRING,
                    proto.write(InstrumentationData.ResultsBundleEntry.VALUE_STRING,
                            (String)val);
                } else if (val instanceof Byte) {
                    proto.writeSInt32(InstrumentationData.ResultsBundleEntry.VALUE_INT,
                    proto.write(InstrumentationData.ResultsBundleEntry.VALUE_INT,
                            ((Byte)val).intValue());
                } else if (val instanceof Double) {
                    proto.writeDouble(InstrumentationData.ResultsBundleEntry.VALUE_DOUBLE,
                            ((Double)val).doubleValue());
                    proto.write(InstrumentationData.ResultsBundleEntry.VALUE_DOUBLE, (double)val);
                } else if (val instanceof Float) {
                    proto.writeFloat(InstrumentationData.ResultsBundleEntry.VALUE_FLOAT,
                            ((Float)val).floatValue());
                    proto.write(InstrumentationData.ResultsBundleEntry.VALUE_FLOAT, (float)val);
                } else if (val instanceof Integer) {
                    proto.writeSInt32(InstrumentationData.ResultsBundleEntry.VALUE_INT,
                            ((Integer)val).intValue());
                    proto.write(InstrumentationData.ResultsBundleEntry.VALUE_INT, (int)val);
                } else if (val instanceof Long) {
                    proto.writeSInt64(InstrumentationData.ResultsBundleEntry.VALUE_LONG,
                            ((Long)val).longValue());
                    proto.write(InstrumentationData.ResultsBundleEntry.VALUE_LONG, (long)val);
                } else if (val instanceof Short) {
                    proto.writeSInt32(InstrumentationData.ResultsBundleEntry.VALUE_INT,
                            ((Short)val).intValue());
                    proto.write(InstrumentationData.ResultsBundleEntry.VALUE_INT, (short)val);
                } else if (val instanceof Bundle) {
                    writeBundle(proto, InstrumentationData.ResultsBundleEntry.VALUE_BUNDLE,
                            (Bundle)val);
                } else if (val instanceof byte[]) {
                    proto.write(InstrumentationData.ResultsBundleEntry.VALUE_BYTES, (byte[])val);
                }

                proto.endRepeatedObject(entryToken);
                proto.end(entryToken);
            }

            proto.endObject(bundleToken);
            proto.end(bundleToken);
        }

        private void writeProtoToStdout(ProtoOutputStream proto) {
        private void outputProto(ProtoOutputStream proto) {
            byte[] out = proto.getBytes();
            if (protoStd) {
                try {
                System.out.write(proto.getBytes());
                    System.out.write(out);
                    System.out.flush();
                } catch (IOException ex) {
                    System.err.println("Error writing finished response: ");
                    ex.printStackTrace(System.err);
                }
            }
            if (protoFile) {
                try (OutputStream os = new FileOutputStream(mLog, true)) {
                    os.write(proto.getBytes());
                    os.flush();
                } catch (IOException ex) {
                    System.err.format("Cannot write to %s:\n", mLog.getAbsolutePath());
                    ex.printStackTrace();
                }
            }
        }
    }


@@ -374,7 +423,7 @@ public class Instrument {

        try {
            // Choose which output we will do.
            if (proto) {
            if (protoFile || protoStd) {
                reporter = new ProtoStatusReporter();
            } else if (wait) {
                reporter = new TextStatusReporter(rawMode);
@@ -396,7 +445,7 @@ public class Instrument {
                mWm.setAnimationScale(2, 0.0f);
            }

            // Figure out which component we are tring to do.
            // Figure out which component we are trying to do.
            final ComponentName cn = parseComponentName(componentNameArg);

            // Choose an ABI if necessary
+8 −0
Original line number Diff line number Diff line
@@ -105,6 +105,14 @@ public abstract class BaseCommand {
        return mArgs.getNextArg();
    }

    /**
     * Peek the next argument on the command line, whatever it is; if there are
     * no arguments left, return null.
     */
    public String peekNextArg() {
        return mArgs.peekNextArg();
    }

    /**
     * Return the next argument on the command line, whatever it is; if there are
     * no arguments left, throws an IllegalArgumentException to report this to the user.
+4 −1
Original line number Diff line number Diff line
@@ -2476,7 +2476,10 @@ final class ActivityManagerShellCommand extends ShellCommand {
            pw.println("      -e <NAME> <VALUE>: set argument <NAME> to <VALUE>.  For test runners a");
            pw.println("          common form is [-e <testrunner_flag> <value>[,<value>...]].");
            pw.println("      -p <FILE>: write profiling data to <FILE>");
            pw.println("      -m: Write output as protobuf (machine readable)");
            pw.println("      -m: Write output as protobuf to stdout (machine readable)");
            pw.println("      -f <Optional PATH/TO/FILE>: Write output as protobuf to a file (machine");
            pw.println("          readable). If path is not specified, default directory and file name will");
            pw.println("          be used: /sdcard/instrument-logs/log-yyyyMMdd-hhmmss-SSS.instrumentation_data_proto");
            pw.println("      -w: wait for instrumentation to finish before returning.  Required for");
            pw.println("          test runners.");
            pw.println("      --user <USER_ID> | current: Specify user instrumentation runs in;");