Merge "Add TraceConfig trace starts" into main
diff --git a/src_common/com/android/traceur/PerfettoUtils.java b/src_common/com/android/traceur/PerfettoUtils.java
index 98a489d..5d3949e 100644
--- a/src_common/com/android/traceur/PerfettoUtils.java
+++ b/src_common/com/android/traceur/PerfettoUtils.java
@@ -29,6 +29,7 @@
 
 import perfetto.protos.DataSourceDescriptorOuterClass.DataSourceDescriptor;
 import perfetto.protos.FtraceDescriptorOuterClass.FtraceDescriptor.AtraceCategory;
+import perfetto.protos.TraceConfigOuterClass.TraceConfig;
 import perfetto.protos.TracingServiceStateOuterClass.TracingServiceState;
 import perfetto.protos.TracingServiceStateOuterClass.TracingServiceState.DataSource;
 
@@ -85,6 +86,19 @@
         return OUTPUT_EXTENSION;
     }
 
+    // Traceur will not verify that the input TraceConfig will start properly before attempting to
+    // record a trace.
+    public boolean traceStart(TraceConfig config) {
+        if (isTracingOn()) {
+            Log.e(TAG, "Attempting to start perfetto trace but trace is already in progress");
+            return false;
+        } else {
+            recoverExistingRecording();
+        }
+
+        return startPerfettoWithProtoConfig(config);
+    }
+
     public boolean traceStart(Collection<String> tags, int bufferSizeKb, boolean winscope,
             boolean apps, boolean longTrace, boolean attachToBugreport, int maxLongTraceSizeMb,
             int maxLongTraceDurationMinutes) {
@@ -119,7 +133,7 @@
         appendProcStatsConfig(config, tags, /* targetBuffer = */ 1);
         appendAdditionalDataSources(config, tags, winscope, longTrace, /* targetBuffer = */ 1);
 
-        return startPerfettoWithConfig(config.toString());
+        return startPerfettoWithTextConfig(config.toString());
     }
 
     public boolean stackSampleStart(boolean attachToBugreport) {
@@ -141,7 +155,7 @@
         appendLinuxPerfConfig(config, /* targetBuffer = */ 0);
         appendProcStatsConfig(config, /* tags = */ null, /* targetBuffer = */ 0);
 
-        return startPerfettoWithConfig(config.toString());
+        return startPerfettoWithTextConfig(config.toString());
     }
 
     public boolean heapDumpStart(Collection<String> processes, boolean continuousDump,
@@ -172,7 +186,7 @@
                 /* targetBuffer = */ 0);
         appendProcStatsConfig(config, /* tags = */ null, /* targetBuffer = */ 0);
 
-        return startPerfettoWithConfig(config.toString());
+        return startPerfettoWithTextConfig(config.toString());
     }
 
     public void traceStop() {
@@ -291,7 +305,7 @@
     }
 
     // Starts Perfetto with the provided config string.
-    private boolean startPerfettoWithConfig(String config) {
+    private boolean startPerfettoWithTextConfig(String config) {
         // If the here-doc ends early, within the config string, exit immediately.
         // This should never happen.
         if (config.contains(MARKER)) {
@@ -303,7 +317,7 @@
                 + " -c - --txt"
                 + " <<" + MARKER +"\n" + config + "\n" + MARKER;
 
-        Log.v(TAG, "Starting perfetto trace.");
+        Log.v(TAG, "Starting perfetto trace with text config.");
         try {
             Process process = TraceUtils.execWithTimeout(cmd, TEMP_DIR, STARTUP_TIMEOUT_MS);
             if (process == null) {
@@ -320,6 +334,29 @@
         return true;
     }
 
+    // Starts Perfetto with the provided TraceConfig proto.
+    private boolean startPerfettoWithProtoConfig(TraceConfig config) {
+        String cmd = "perfetto --detach=" + PERFETTO_TAG
+                + " -o " + TEMP_TRACE_LOCATION
+                + " -c - ";
+        Log.v(TAG, "Starting perfetto trace with proto config.");
+        try {
+            Process process = TraceUtils.execWithTimeout(cmd, TEMP_DIR,
+                    STARTUP_TIMEOUT_MS, config.toByteArray());
+            if (process == null) {
+                return false;
+            } else if (process.exitValue() != 0) {
+                Log.e(TAG, "perfetto trace start failed with: " + process.exitValue());
+                return false;
+            }
+        } catch (Exception e) {
+            throw new RuntimeException(e);
+        }
+
+        Log.v(TAG, "perfetto traceStart succeeded!");
+        return true;
+    }
+
     // Saves an existing temporary recording under a "recovered" filename.
     private void recoverExistingRecording() {
         File recoveredFile = TraceUtils.getOutputFile(
diff --git a/src_common/com/android/traceur/TraceUtils.java b/src_common/com/android/traceur/TraceUtils.java
index 5a5c153..9942efb 100644
--- a/src_common/com/android/traceur/TraceUtils.java
+++ b/src_common/com/android/traceur/TraceUtils.java
@@ -46,6 +46,8 @@
 import java.util.concurrent.TimeUnit;
 import java.util.stream.Collectors;
 
+import perfetto.protos.TraceConfigOuterClass.TraceConfig;
+
 /**
  * Utility functions for tracing.
  */
@@ -63,6 +65,18 @@
       UNKNOWN, TRACE, STACK_SAMPLES, HEAP_DUMP
     }
 
+    public static boolean traceStart(ContentResolver contentResolver, TraceConfig config,
+            boolean winscope) {
+        // 'winscope' isn't passed to traceStart because the TraceConfig should specify any
+        // winscope-related data sources to be recorded using Perfetto. Winscope data that isn't yet
+        // available in Perfetto is captured using WinscopeUtils instead.
+        if (!mTraceEngine.traceStart(config)) {
+            return false;
+        }
+        WinscopeUtils.traceStart(contentResolver, winscope);
+        return true;
+    }
+
     public static boolean traceStart(ContentResolver contentResolver, Collection<String> tags,
             int bufferSizeKb, boolean winscope, boolean apps, boolean longTrace,
             boolean attachToBugreport, int maxLongTraceSizeMb, int maxLongTraceDurationMinutes) {
@@ -159,11 +173,22 @@
         return process;
     }
 
-    // Returns the Process if the command terminated on time and null if not.
     public static Process execWithTimeout(String cmd, String tmpdir, long timeout)
             throws IOException {
+        return execWithTimeout(cmd, tmpdir, timeout, null);
+    }
+
+    // Returns the Process if the command terminated on time and null if not.
+    public static Process execWithTimeout(String cmd, String tmpdir, long timeout, byte[] input)
+            throws IOException {
         Process process = exec(cmd, tmpdir, true);
         try {
+            if (input != null) {
+                OutputStream os = process.getOutputStream();
+                os.write(input);
+                os.flush();
+                os.close();
+            }
             if (!process.waitFor(timeout, TimeUnit.MILLISECONDS)) {
                 Log.e(TAG, "Command '" + cmd + "' has timed out after " + timeout + " ms.");
                 process.destroyForcibly();