Merge "Add a new "restricted" metric name to reduce noise."
diff --git a/Android.bp b/Android.bp
index 290dfd8..1fad0af 100644
--- a/Android.bp
+++ b/Android.bp
@@ -3692,6 +3692,7 @@
         "protos/perfetto/metrics/android/powrails_metric.proto",
         "protos/perfetto/metrics/android/process_metadata.proto",
         "protos/perfetto/metrics/android/profiler_smaps.proto",
+        "protos/perfetto/metrics/android/rt_runtime_metric.proto",
         "protos/perfetto/metrics/android/simpleperf.proto",
         "protos/perfetto/metrics/android/startup_metric.proto",
         "protos/perfetto/metrics/android/surfaceflinger.proto",
@@ -3751,6 +3752,7 @@
         "protos/perfetto/metrics/android/powrails_metric.proto",
         "protos/perfetto/metrics/android/process_metadata.proto",
         "protos/perfetto/metrics/android/profiler_smaps.proto",
+        "protos/perfetto/metrics/android/rt_runtime_metric.proto",
         "protos/perfetto/metrics/android/simpleperf.proto",
         "protos/perfetto/metrics/android/startup_metric.proto",
         "protos/perfetto/metrics/android/surfaceflinger.proto",
@@ -8094,6 +8096,7 @@
         "src/trace_processor/metrics/sql/android/android_package_list.sql",
         "src/trace_processor/metrics/sql/android/android_powrails.sql",
         "src/trace_processor/metrics/sql/android/android_proxy_power.sql",
+        "src/trace_processor/metrics/sql/android/android_rt_runtime.sql",
         "src/trace_processor/metrics/sql/android/android_simpleperf.sql",
         "src/trace_processor/metrics/sql/android/android_startup.sql",
         "src/trace_processor/metrics/sql/android/android_surfaceflinger.sql",
@@ -10419,14 +10422,14 @@
 
 filegroup {
     name: "perfetto-trace-processor-python-srcs",
-    srcs: ["src/trace_processor/python/perfetto/**/*.py"],
-    path: "src/trace_processor/python",
+    srcs: ["python/perfetto/trace_processor/*.py"],
+    path: "python",
 }
 
 filegroup {
     name: "perfetto-trace-processor-python-data",
-    srcs: ["src/trace_processor/python/perfetto/**/*.descriptor*"],
-    path: "src/trace_processor/python",
+    srcs: ["python/perfetto/trace_processor/*.descriptor*"],
+    path: "python",
 }
 
 // Added automatically by a large-scale-change that took the approach of
diff --git a/Android.bp.extras b/Android.bp.extras
index c447a86..d6deb97 100644
--- a/Android.bp.extras
+++ b/Android.bp.extras
@@ -112,14 +112,14 @@
 
 filegroup {
     name: "perfetto-trace-processor-python-srcs",
-    srcs: ["src/trace_processor/python/perfetto/**/*.py"],
-    path: "src/trace_processor/python",
+    srcs: ["python/perfetto/trace_processor/*.py"],
+    path: "python",
 }
 
 filegroup {
     name: "perfetto-trace-processor-python-data",
-    srcs: ["src/trace_processor/python/perfetto/**/*.descriptor*"],
-    path: "src/trace_processor/python",
+    srcs: ["python/perfetto/trace_processor/*.descriptor*"],
+    path: "python",
 }
 
 // Added automatically by a large-scale-change that took the approach of
diff --git a/BUILD b/BUILD
index 3e7abd7..723d6a4 100644
--- a/BUILD
+++ b/BUILD
@@ -1059,6 +1059,7 @@
         "src/trace_processor/metrics/sql/android/android_package_list.sql",
         "src/trace_processor/metrics/sql/android/android_powrails.sql",
         "src/trace_processor/metrics/sql/android/android_proxy_power.sql",
+        "src/trace_processor/metrics/sql/android/android_rt_runtime.sql",
         "src/trace_processor/metrics/sql/android/android_simpleperf.sql",
         "src/trace_processor/metrics/sql/android/android_startup.sql",
         "src/trace_processor/metrics/sql/android/android_surfaceflinger.sql",
@@ -2627,6 +2628,7 @@
         "protos/perfetto/metrics/android/powrails_metric.proto",
         "protos/perfetto/metrics/android/process_metadata.proto",
         "protos/perfetto/metrics/android/profiler_smaps.proto",
+        "protos/perfetto/metrics/android/rt_runtime_metric.proto",
         "protos/perfetto/metrics/android/simpleperf.proto",
         "protos/perfetto/metrics/android/startup_metric.proto",
         "protos/perfetto/metrics/android/surfaceflinger.proto",
@@ -4185,77 +4187,3 @@
     main = "tools/write_version_header.py",
     python_version = "PY3",
 )
-
-perfetto_py_binary(
-    name = "trace_processor_py_example",
-    srcs = ["src/trace_processor/python/example.py"],
-    deps = [":trace_processor_py"] + PERFETTO_CONFIG.deps.pandas_py,
-    main = "src/trace_processor/python/example.py",
-    python_version = "PY3",
-)
-
-perfetto_py_library(
-    name = "trace_processor_py",
-    srcs = glob(["src/trace_processor/python/perfetto/trace_processor/*.py"]),
-    data = [
-        "src/trace_processor/python/perfetto/trace_processor/trace_processor.descriptor",
-        "src/trace_processor/python/perfetto/trace_processor/metrics.descriptor",
-        ":trace_processor_shell",
-    ] + PERFETTO_CONFIG.deps.tp_init_py,
-    deps = PERFETTO_CONFIG.deps.gfile_py + 
-        PERFETTO_CONFIG.deps.protobuf_py + 
-        PERFETTO_CONFIG.deps.protobuf_descriptor_pb2_py + 
-        PERFETTO_CONFIG.deps.pyglib_py,
-    imports = [
-        "src/trace_processor/python",
-    ],
-    visibility = PERFETTO_CONFIG.public_visibility,
-)
-
-perfetto_py_library(
-    name = "experimental_slice_breakdown_lib",
-    srcs = glob(["tools/slice_breakdown/perfetto/slice_breakdown/*.py"]),
-    deps = [
-        ":trace_processor_py",
-    ],
-    imports = [
-        "tools/slice_breakdown",
-    ],
-)
-
-perfetto_py_binary(
-    name = "experimental_slice_breakdown_bin",
-    srcs = ["tools/slice_breakdown/main.py"],
-    main = "tools/slice_breakdown/main.py",
-    deps = [
-        ":experimental_slice_breakdown_lib",
-        ":trace_processor_py",
-    ] + PERFETTO_CONFIG.deps.pandas_py,
-    python_version = "PY3",
-    legacy_create_init = 0,
-)
-
-perfetto_py_library(
-    name = "batch_trace_processor",
-    srcs = glob([
-      "tools/batch_trace_processor/perfetto/batch_trace_processor/*.py"
-    ]),
-    deps = [
-        ":trace_processor_py",
-    ] + PERFETTO_CONFIG.deps.pandas_py,
-    imports = [
-        "tools/batch_trace_processor",
-    ],
-)
-
-perfetto_py_binary(
-    name = "batch_trace_processor_shell",
-    srcs = ["tools/batch_trace_processor/main.py"],
-    main = "tools/batch_trace_processor/main.py",
-    deps = [
-        ":trace_processor_py",
-        ":batch_trace_processor",
-    ] + PERFETTO_CONFIG.deps.pandas_py,
-    python_version = "PY3",
-    legacy_create_init = 0,
-)
diff --git a/BUILD.extras b/BUILD.extras
index 8c39316..f91b2f4 100644
--- a/BUILD.extras
+++ b/BUILD.extras
@@ -92,77 +92,3 @@
     main = "tools/write_version_header.py",
     python_version = "PY3",
 )
-
-perfetto_py_binary(
-    name = "trace_processor_py_example",
-    srcs = ["src/trace_processor/python/example.py"],
-    deps = [":trace_processor_py"] + PERFETTO_CONFIG.deps.pandas_py,
-    main = "src/trace_processor/python/example.py",
-    python_version = "PY3",
-)
-
-perfetto_py_library(
-    name = "trace_processor_py",
-    srcs = glob(["src/trace_processor/python/perfetto/trace_processor/*.py"]),
-    data = [
-        "src/trace_processor/python/perfetto/trace_processor/trace_processor.descriptor",
-        "src/trace_processor/python/perfetto/trace_processor/metrics.descriptor",
-        ":trace_processor_shell",
-    ] + PERFETTO_CONFIG.deps.tp_init_py,
-    deps = PERFETTO_CONFIG.deps.gfile_py + 
-        PERFETTO_CONFIG.deps.protobuf_py + 
-        PERFETTO_CONFIG.deps.protobuf_descriptor_pb2_py + 
-        PERFETTO_CONFIG.deps.pyglib_py,
-    imports = [
-        "src/trace_processor/python",
-    ],
-    visibility = PERFETTO_CONFIG.public_visibility,
-)
-
-perfetto_py_library(
-    name = "experimental_slice_breakdown_lib",
-    srcs = glob(["tools/slice_breakdown/perfetto/slice_breakdown/*.py"]),
-    deps = [
-        ":trace_processor_py",
-    ],
-    imports = [
-        "tools/slice_breakdown",
-    ],
-)
-
-perfetto_py_binary(
-    name = "experimental_slice_breakdown_bin",
-    srcs = ["tools/slice_breakdown/main.py"],
-    main = "tools/slice_breakdown/main.py",
-    deps = [
-        ":experimental_slice_breakdown_lib",
-        ":trace_processor_py",
-    ] + PERFETTO_CONFIG.deps.pandas_py,
-    python_version = "PY3",
-    legacy_create_init = 0,
-)
-
-perfetto_py_library(
-    name = "batch_trace_processor",
-    srcs = glob([
-      "tools/batch_trace_processor/perfetto/batch_trace_processor/*.py"
-    ]),
-    deps = [
-        ":trace_processor_py",
-    ] + PERFETTO_CONFIG.deps.pandas_py,
-    imports = [
-        "tools/batch_trace_processor",
-    ],
-)
-
-perfetto_py_binary(
-    name = "batch_trace_processor_shell",
-    srcs = ["tools/batch_trace_processor/main.py"],
-    main = "tools/batch_trace_processor/main.py",
-    deps = [
-        ":trace_processor_py",
-        ":batch_trace_processor",
-    ] + PERFETTO_CONFIG.deps.pandas_py,
-    python_version = "PY3",
-    legacy_create_init = 0,
-)
diff --git a/BUILD.gn b/BUILD.gn
index b36d588..2e07e14 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -89,6 +89,7 @@
   }
   all_targets += [
     ":perfetto_integrationtests",
+    "examples/sdk:sdk_example",
     "test:client_api_example",
     "test/stress_test",
   ]
diff --git a/CHANGELOG b/CHANGELOG
index 19f75ba..8dbe728 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -9,6 +9,35 @@
     *
 
 
+v23.0 - 2022-01-11:
+  Tracing service and probes:
+    * Added workaround for a kernel ftrace bug causing some "comm" fields to be
+      not null-terminated. https://github.com/torvalds/linux/commit/f0a5157803 .
+    * Added ability to talk to the newer AIDL-based health hal in traced_probes.
+      It still falls back on the older HIDL interface for older devices.
+  Trace Processor:
+    * Changed the argument for the trace path in constructor of TraceProcessor
+      in the Python API from |file_path| to |trace|.
+      |file_path| is deprecated and may be removed in the future.
+    * Changed the Python API constructor. Now it takes a TraceProcessorConfig
+      instead of passing parameters directly. This may break existing code
+      but migration should be trivial (all current options are still
+      supported).
+    * Fixed a HTTP keepalive bug in trace_processor --httpd. The bug, introduced
+      in v22.0, caused each RPC request to close the connection, effectively
+      defeating the 'Connection: Keep-Alive', after each query made by the UI.
+    * Added parsing of netif_receive_skb events from proto traces.
+    * Added android_netperf metric based on netif events.
+    * Fixed a bug that would cause fetch errors when loading traces > 32 MB when
+      using trace_processor --httpd.
+    * Added a workaround to tokenize properly /proc/pid/cmdline for chrome
+      processes on Linux/CrOS. Chrome rewrites its cmdline replacing \0 -> ' '.
+  UI:
+    *
+  SDK:
+    *
+
+
 v22.1 - 2021-12-07:
   Tracing service and probes:
     * Added workaround for a Linux kernel bug causing some ftrace strings to
diff --git a/docs/analysis/trace-processor.md b/docs/analysis/trace-processor.md
index 3606c00..2385175 100644
--- a/docs/analysis/trace-processor.md
+++ b/docs/analysis/trace-processor.md
@@ -583,17 +583,17 @@
 ```python
 from perfetto.trace_processor import TraceProcessor
 # Initialise TraceProcessor with a trace file
-tp = TraceProcessor(file_path='trace.perfetto-trace')
+tp = TraceProcessor(trace='trace.perfetto-trace')
 ```
 
 NOTE: The TraceProcessor can be initialized in a combination of ways including:
       <br> - An address at which there exists a running instance of `trace_processor` with a
-      loaded trace (e.g. `TraceProcessor(addr='localhost:9001')`)
+      loaded trace (e.g.`TraceProcessor(addr='localhost:9001')`)
       <br> - An address at which there exists a running instance of `trace_processor` and
       needs a trace to be loaded in
-      (e.g. `TraceProcessor(addr='localhost:9001', file_path='trace.perfetto-trace')`)
+      (e.g. `TraceProcessor(trace='trace.perfetto-trace', addr='localhost:9001')`)
       <br> - A path to a `trace_processor` binary and the trace to be loaded in
-      (e.g. `TraceProcessor(bin_path='./trace_processor', file_path='trace.perfetto-trace')`)
+      (e.g. `TraceProcessor(trace='trace.perfetto-trace', config=TraceProcessorConfig(bin_path='./trace_processor'))`)
 
 
 ### API
@@ -608,7 +608,7 @@
 
 ```python
 from perfetto.trace_processor import TraceProcessor
-tp = TraceProcessor(file_path='trace.perfetto-trace')
+tp = TraceProcessor(trace='trace.perfetto-trace')
 
 qr_it = tp.query('SELECT ts, dur, name FROM slice')
 for row in qr_it:
@@ -627,7 +627,7 @@
 requires you to have both the `NumPy` and `Pandas` modules installed.
 ```python
 from perfetto.trace_processor import TraceProcessor
-tp = TraceProcessor(file_path='trace.perfetto-trace')
+tp = TraceProcessor(trace='trace.perfetto-trace')
 
 qr_it = tp.query('SELECT ts, dur, name FROM slice')
 qr_df = qr_it.as_pandas_dataframe()
@@ -648,7 +648,7 @@
 make visualisations from the trace data.
 ```python
 from perfetto.trace_processor import TraceProcessor
-tp = TraceProcessor(file_path='trace.perfetto-trace')
+tp = TraceProcessor(trace='trace.perfetto-trace')
 
 qr_it = tp.query('SELECT ts, value FROM counter WHERE track_id=50')
 qr_df = qr_it.as_pandas_dataframe()
@@ -665,7 +665,7 @@
 
 ```python
 from perfetto.trace_processor import TraceProcessor
-tp = TraceProcessor(file_path='trace.perfetto-trace')
+tp = TraceProcessor(trace='trace.perfetto-trace')
 
 ad_cpu_metrics = tp.metric(['android_cpu'])
 print(ad_cpu_metrics)
diff --git a/docs/instrumentation/tracing-sdk.md b/docs/instrumentation/tracing-sdk.md
index 252e637..0af249c 100644
--- a/docs/instrumentation/tracing-sdk.md
+++ b/docs/instrumentation/tracing-sdk.md
@@ -30,7 +30,7 @@
 To start using the Client API, first check out the latest SDK release:
 
 ```bash
-git clone https://android.googlesource.com/platform/external/perfetto -b v22.1
+git clone https://android.googlesource.com/platform/external/perfetto -b v23.0
 ```
 
 The SDK consists of two files, `sdk/perfetto.h` and `sdk/perfetto.cc`. These are
diff --git a/docs/quickstart/trace-analysis.md b/docs/quickstart/trace-analysis.md
index 7141394..62269dc 100644
--- a/docs/quickstart/trace-analysis.md
+++ b/docs/quickstart/trace-analysis.md
@@ -334,7 +334,7 @@
 #### Query
 ```python
 from perfetto.trace_processor import TraceProcessor
-tp = TraceProcessor(file_path='trace.perfetto-trace')
+tp = TraceProcessor(trace='trace.perfetto-trace')
 
 qr_it = tp.query('SELECT name FROM slice')
 for row in qr_it:
@@ -352,7 +352,7 @@
 #### Query as Pandas DataFrame
 ```python
 from perfetto.trace_processor import TraceProcessor
-tp = TraceProcessor(file_path='trace.perfetto-trace')
+tp = TraceProcessor(trace='trace.perfetto-trace')
 
 qr_it = tp.query('SELECT ts, name FROM slice')
 qr_df = qr_it.as_pandas_dataframe()
@@ -372,7 +372,7 @@
 #### Metric
 ```python
 from perfetto.trace_processor import TraceProcessor
-tp = TraceProcessor(file_path='trace.perfetto-trace')
+tp = TraceProcessor(trace='trace.perfetto-trace')
 
 cpu_metrics = tp.metric(['android_cpu'])
 print(cpu_metrics)
diff --git a/docs/visualization/perfetto-ui-release-process.md b/docs/visualization/perfetto-ui-release-process.md
index 8f12b1a..ed0af8d 100644
--- a/docs/visualization/perfetto-ui-release-process.md
+++ b/docs/visualization/perfetto-ui-release-process.md
@@ -103,3 +103,6 @@
 [go/perfetto-ui-autopush](http://go/perfetto-ui-autopush) and
 [go/perfetto-ui-channels](http://go/perfetto-ui-channels) for the design docs of
 the serving infrastructure.
+
+## Publishing the Perfetto Chrome extension
+Googlers: see go/perfetto-release-chrome-extension
diff --git a/examples/sdk/BUILD.gn b/examples/sdk/BUILD.gn
new file mode 100644
index 0000000..8da2991
--- /dev/null
+++ b/examples/sdk/BUILD.gn
@@ -0,0 +1,30 @@
+# Copyright (C) 2022 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.
+
+import("../../gn/perfetto.gni")
+
+executable("sdk_example") {
+  sources = [
+    "example.cc",
+    "trace_categories.cc",
+    "trace_categories.h",
+  ]
+  configs -= [ "//gn/standalone:extra_warnings" ]
+  cflags = [ "-DPERFETTO_SDK_EXAMPLE_USE_INTERNAL_HEADERS" ]
+  testonly = true
+  deps = [
+    "../..:libperfetto_client_experimental",
+    "../../gn:default_deps",
+  ]
+}
diff --git a/examples/sdk/README.md b/examples/sdk/README.md
index 8c56873..aa88171 100644
--- a/examples/sdk/README.md
+++ b/examples/sdk/README.md
@@ -15,7 +15,7 @@
 First, check out the latest Perfetto release:
 
 ```bash
-git clone https://android.googlesource.com/platform/external/perfetto -b v22.1
+git clone https://android.googlesource.com/platform/external/perfetto -b v23.0
 ```
 
 Then, build using CMake:
diff --git a/examples/sdk/example.cc b/examples/sdk/example.cc
index b282617..8ac92ab 100644
--- a/examples/sdk/example.cc
+++ b/examples/sdk/example.cc
@@ -15,6 +15,14 @@
  */
 
 // This example demonstrates in-process tracing with Perfetto.
+// This program adds trace in a few example functions like DrawPlayer DrawGame
+// etc. and collect the trace in file `example.pftrace`.
+// This output file is not readable directly. It can be read after converting
+// it to text, by running the command:
+// `./tools/traceconv text example.pftrace`
+// or it can be opened in UI : https://ui.perfetto.dev
+//
+// To compile this file, run: `./tools/ninja -C out/default sdk_example`.
 
 #include "trace_categories.h"
 
@@ -22,6 +30,8 @@
 #include <fstream>
 #include <thread>
 
+namespace {
+
 void InitializePerfetto() {
   perfetto::TracingInitArgs args;
   // The backends determine where trace events are recorded. For this example we
@@ -61,8 +71,11 @@
   // directly into a file by passing a file descriptor into Setup() above.
   std::ofstream output;
   output.open("example.pftrace", std::ios::out | std::ios::binary);
-  output.write(&trace_data[0], trace_data.size());
+  output.write(&trace_data[0], std::streamsize(trace_data.size()));
   output.close();
+  PERFETTO_LOG(
+      "Trace written in example.pftrace file. To read this trace in "
+      "text form, run `./tools/traceconv text example.pftrace`");
 }
 
 void DrawPlayer(int player_number) {
@@ -83,6 +96,8 @@
   TRACE_COUNTER("rendering", "Framerate", 120);
 }
 
+}  // namespace
+
 int main(int, const char**) {
   InitializePerfetto();
   auto tracing_session = StartTracing();
diff --git a/examples/sdk/trace_categories.cc b/examples/sdk/trace_categories.cc
index cce90f8..646af7a 100644
--- a/examples/sdk/trace_categories.cc
+++ b/examples/sdk/trace_categories.cc
@@ -17,4 +17,4 @@
 #include "trace_categories.h"
 
 // Reserves internal static storage for our tracing categories.
-PERFETTO_TRACK_EVENT_STATIC_STORAGE();
\ No newline at end of file
+PERFETTO_TRACK_EVENT_STATIC_STORAGE();
diff --git a/examples/sdk/trace_categories.h b/examples/sdk/trace_categories.h
index c2dd852..2fbfe12 100644
--- a/examples/sdk/trace_categories.h
+++ b/examples/sdk/trace_categories.h
@@ -17,7 +17,17 @@
 #ifndef TRACE_CATEGORIES_H
 #define TRACE_CATEGORIES_H
 
+// This source file can be built in two ways:
+// 1. As part of the regular GN build, against standard includes.
+// 2. To test that the amalgmated SDK works, against the perfetto.h source.
+#ifdef PERFETTO_SDK_EXAMPLE_USE_INTERNAL_HEADERS
+#include "perfetto/tracing/core/trace_config.h"
+#include "perfetto/tracing/tracing.h"
+#include "perfetto/tracing/track_event.h"
+#include "protos/perfetto/trace/track_event/process_descriptor.gen.h"
+#else
 #include <perfetto.h>
+#endif
 
 // The set of track event categories that the example is using.
 PERFETTO_DEFINE_CATEGORIES(
diff --git a/include/perfetto/base/logging.h b/include/perfetto/base/logging.h
index eaa5710..3599dda 100644
--- a/include/perfetto/base/logging.h
+++ b/include/perfetto/base/logging.h
@@ -147,7 +147,8 @@
                           __LINE__, ##__VA_ARGS__);                           \
   } while (0)
 #elif defined(PERFETTO_DISABLE_LOG)
-#define PERFETTO_XLOG(...) ::perfetto::base::ignore_result(__VA_ARGS__)
+#define PERFETTO_XLOG(level, fmt, ...) ::perfetto::base::ignore_result(level, \
+                                fmt, ##__VA_ARGS__)
 #else
 #define PERFETTO_XLOG(level, fmt, ...)                                      \
   ::perfetto::base::LogMessage(level, ::perfetto::base::Basename(__FILE__), \
diff --git a/protos/perfetto/metrics/android/BUILD.gn b/protos/perfetto/metrics/android/BUILD.gn
index f09aa37..3e0b2e7 100644
--- a/protos/perfetto/metrics/android/BUILD.gn
+++ b/protos/perfetto/metrics/android/BUILD.gn
@@ -46,6 +46,7 @@
     "powrails_metric.proto",
     "process_metadata.proto",
     "profiler_smaps.proto",
+    "rt_runtime_metric.proto",
     "simpleperf.proto",
     "startup_metric.proto",
     "surfaceflinger.proto",
diff --git a/protos/perfetto/metrics/android/rt_runtime_metric.proto b/protos/perfetto/metrics/android/rt_runtime_metric.proto
new file mode 100644
index 0000000..edb66c5
--- /dev/null
+++ b/protos/perfetto/metrics/android/rt_runtime_metric.proto
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2022 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.
+ */
+
+syntax = "proto2";
+
+package perfetto.protos;
+
+// measure max RT runtime and RT tasks running over 5ms.
+message AndroidRtRuntimeMetric {
+  message RtSlice {
+    // thread name
+    optional string tname = 1;
+    // timestamp
+    optional int64 ts = 2;
+    // runtime of RT task
+    optional int64 dur = 3;
+  }
+
+  // max runtime of RT tasks
+  optional int64 max_runtime = 1;
+  // how many RT tasks are over 5ms.
+  optional int64 over_5ms_count = 2;
+  // information for top 10 RT tasks
+  repeated RtSlice longest_rt_slices = 3;
+}
+
diff --git a/protos/perfetto/metrics/metrics.proto b/protos/perfetto/metrics/metrics.proto
index d8a29a1..8b1a173 100644
--- a/protos/perfetto/metrics/metrics.proto
+++ b/protos/perfetto/metrics/metrics.proto
@@ -43,6 +43,7 @@
 import "protos/perfetto/metrics/android/package_list.proto";
 import "protos/perfetto/metrics/android/powrails_metric.proto";
 import "protos/perfetto/metrics/android/profiler_smaps.proto";
+import "protos/perfetto/metrics/android/rt_runtime_metric.proto";
 import "protos/perfetto/metrics/android/simpleperf.proto";
 import "protos/perfetto/metrics/android/startup_metric.proto";
 import "protos/perfetto/metrics/android/surfaceflinger.proto";
@@ -96,7 +97,7 @@
 
 // Root message for all Perfetto-based metrics.
 //
-// Next id: 42
+// Next id: 43
 message TraceMetrics {
   reserved 4, 10, 13, 14, 16, 19;
 
@@ -206,6 +207,8 @@
   // is clear that this data is necessary.
   optional AndroidCameraUnaggregatedMetric android_camera_unagg = 41;
 
+  optional AndroidRtRuntimeMetric android_rt_runtime = 42;
+
   // Demo extensions.
   extensions 450 to 499;
 
diff --git a/protos/perfetto/metrics/perfetto_merged_metrics.proto b/protos/perfetto/metrics/perfetto_merged_metrics.proto
index 100c76c..7472ce3 100644
--- a/protos/perfetto/metrics/perfetto_merged_metrics.proto
+++ b/protos/perfetto/metrics/perfetto_merged_metrics.proto
@@ -980,6 +980,30 @@
 
 // End of protos/perfetto/metrics/android/profiler_smaps.proto
 
+// Begin of protos/perfetto/metrics/android/rt_runtime_metric.proto
+
+// measure max RT runtime and RT tasks running over 5ms.
+message AndroidRtRuntimeMetric {
+  message RtSlice {
+    // thread name
+    optional string tname = 1;
+    // timestamp
+    optional int64 ts = 2;
+    // runtime of RT task
+    optional int64 dur = 3;
+  }
+
+  // max runtime of RT tasks
+  optional int64 max_runtime = 1;
+  // how many RT tasks are over 5ms.
+  optional int64 over_5ms_count = 2;
+  // information for top 10 RT tasks
+  repeated RtSlice longest_rt_slices = 3;
+}
+
+
+// End of protos/perfetto/metrics/android/rt_runtime_metric.proto
+
 // Begin of protos/perfetto/metrics/android/simpleperf.proto
 
 // Metric that stores information related to atrace events generated by
@@ -1396,7 +1420,7 @@
 
 // Root message for all Perfetto-based metrics.
 //
-// Next id: 42
+// Next id: 43
 message TraceMetrics {
   reserved 4, 10, 13, 14, 16, 19;
 
@@ -1506,6 +1530,8 @@
   // is clear that this data is necessary.
   optional AndroidCameraUnaggregatedMetric android_camera_unagg = 41;
 
+  optional AndroidRtRuntimeMetric android_rt_runtime = 42;
+
   // Demo extensions.
   extensions 450 to 499;
 
diff --git a/python/BUILD b/python/BUILD
new file mode 100644
index 0000000..f330ba8
--- /dev/null
+++ b/python/BUILD
@@ -0,0 +1,98 @@
+# Copyright (C) 2022 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.
+
+load("@perfetto_cfg//:perfetto_cfg.bzl", "PERFETTO_CONFIG")
+load(
+    "@perfetto//bazel:rules.bzl",
+    "perfetto_py_binary",
+    "perfetto_py_library",
+)
+
+licenses(["notice"])
+
+package(default_visibility = ["//visibility:private"])
+
+perfetto_py_binary(
+    name = "trace_processor_py_example",
+    srcs = ["example.py"],
+    deps = [":trace_processor_py"] + PERFETTO_CONFIG.deps.pandas_py,
+    main = "example.py",
+    python_version = "PY3",
+)
+
+perfetto_py_library(
+    name = "trace_processor_py",
+    srcs = glob(["perfetto/trace_processor/*.py"]),
+    data = [
+        "perfetto/trace_processor/trace_processor.descriptor",
+        "perfetto/trace_processor/metrics.descriptor",
+        PERFETTO_CONFIG.root + ":trace_processor_shell",
+    ] + PERFETTO_CONFIG.deps.tp_init_py,
+    deps = PERFETTO_CONFIG.deps.gfile_py +
+        PERFETTO_CONFIG.deps.protobuf_py +
+        PERFETTO_CONFIG.deps.protobuf_descriptor_pb2_py +
+        PERFETTO_CONFIG.deps.pyglib_py,
+    imports = [
+        ".",
+    ],
+    visibility = PERFETTO_CONFIG.public_visibility,
+)
+
+perfetto_py_library(
+    name = "experimental_slice_breakdown_lib",
+    srcs = glob(["perfetto/experimental/slice_breakdown/*.py"]),
+    deps = [
+        ":trace_processor_py",
+    ],
+    imports = [
+        "tools/slice_breakdown",
+    ],
+)
+
+perfetto_py_binary(
+    name = "experimental_slice_breakdown_bin",
+    srcs = ["tools/slice_breakdown.py"],
+    main = "tools/slice_breakdown.py",
+    deps = [
+        ":experimental_slice_breakdown_lib",
+        ":trace_processor_py",
+    ] + PERFETTO_CONFIG.deps.pandas_py,
+    python_version = "PY3",
+    legacy_create_init = 0,
+)
+
+perfetto_py_library(
+    name = "batch_trace_processor",
+    srcs = glob([
+      "perfetto/batch_trace_processor/*.py"
+    ]),
+    deps = [
+        ":trace_processor_py",
+    ] + PERFETTO_CONFIG.deps.pandas_py,
+    imports = [
+        ".",
+    ],
+)
+
+perfetto_py_binary(
+    name = "batch_trace_processor_shell",
+    srcs = ["tools/batch_trace_processor_shell.py"],
+    main = "tools/batch_trace_processor_shell.py",
+    deps = [
+        ":trace_processor_py",
+        ":batch_trace_processor",
+    ] + PERFETTO_CONFIG.deps.pandas_py,
+    python_version = "PY3",
+    legacy_create_init = 0,
+)
diff --git a/src/trace_processor/python/LICENSE b/python/LICENSE
similarity index 100%
rename from src/trace_processor/python/LICENSE
rename to python/LICENSE
diff --git a/src/trace_processor/python/README.md b/python/README.md
similarity index 100%
rename from src/trace_processor/python/README.md
rename to python/README.md
diff --git a/src/trace_processor/python/example.py b/python/example.py
similarity index 85%
rename from src/trace_processor/python/example.py
rename to python/example.py
index b3925a4..ffdd9ac 100644
--- a/src/trace_processor/python/example.py
+++ b/python/example.py
@@ -15,7 +15,7 @@
 
 import argparse
 
-from perfetto.trace_processor import TraceProcessor
+from perfetto.trace_processor import TraceProcessor, TraceProcessorConfig
 
 
 def main():
@@ -34,16 +34,17 @@
   parser.add_argument("-f", "--file", help="Absolute path to trace", type=str)
   args = parser.parse_args()
 
+  config = TraceProcessorConfig(bin_path=args.binary)
+
   # Pass arguments into api to construct the trace processor and load the trace
   if args.address is None and args.file is None:
     raise Exception("You must specify an address or a file path to trace")
   elif args.address is None:
-    tp = TraceProcessor(file_path=args.file, bin_path=args.binary)
+    tp = TraceProcessor(trace=args.file, config=config)
   elif args.file is None:
-    tp = TraceProcessor(addr=args.address)
+    tp = TraceProcessor(addr=args.address, config=config)
   else:
-    tp = TraceProcessor(
-        addr=args.address, file_path=args.file, bin_path=args.binary)
+    tp = TraceProcessor(trace=args.file, addr=args.address, config=config)
 
   # Iterate through QueryResultIterator
   res_it = tp.query('select * from slice limit 10')
diff --git a/src/trace_processor/python/perfetto/__init__.py b/python/perfetto/__init__.py
similarity index 100%
rename from src/trace_processor/python/perfetto/__init__.py
rename to python/perfetto/__init__.py
diff --git a/src/trace_processor/python/perfetto/trace_processor/__init__.py b/python/perfetto/batch_trace_processor/__init__.py
similarity index 67%
rename from src/trace_processor/python/perfetto/trace_processor/__init__.py
rename to python/perfetto/batch_trace_processor/__init__.py
index 7106a6c..9d9f785 100644
--- a/src/trace_processor/python/perfetto/trace_processor/__init__.py
+++ b/python/perfetto/batch_trace_processor/__init__.py
@@ -1,5 +1,4 @@
-#!/usr/bin/env python3
-# Copyright (C) 2020 The Android Open Source Project
+# Copyright (C) 2022 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.
@@ -13,5 +12,6 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from .api import TraceProcessor, TraceProcessorException
-from .http import TraceProcessorHttp
+from perfetto.batch_trace_processor.api import BatchLoadableTrace
+from perfetto.batch_trace_processor.api import BatchTraceProcessorConfig
+from perfetto.batch_trace_processor.api import BatchTraceProcessor
diff --git a/python/perfetto/batch_trace_processor/api.py b/python/perfetto/batch_trace_processor/api.py
new file mode 100644
index 0000000..caf9f74
--- /dev/null
+++ b/python/perfetto/batch_trace_processor/api.py
@@ -0,0 +1,307 @@
+#!/usr/bin/env python3
+# Copyright (C) 2021 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.
+"""Contains classes for BatchTraceProcessor API."""
+
+from concurrent.futures.thread import ThreadPoolExecutor
+import dataclasses as dc
+import multiprocessing
+from typing import Any, Callable, Dict, Optional, Tuple, Union, List
+from numpy.lib.npyio import load
+
+import pandas as pd
+
+from perfetto.trace_processor import LoadableTrace
+from perfetto.trace_processor import TraceProcessor
+from perfetto.trace_processor import TraceProcessorException
+from perfetto.trace_processor import TraceProcessorConfig
+
+
+@dc.dataclass
+class BatchLoadableTrace:
+  trace: LoadableTrace
+  args: Dict[str, str]
+
+
+@dc.dataclass
+class BatchTraceProcessorConfig:
+  TraceProvider = Callable[[str], List[
+      Union[LoadableTrace, BatchLoadableTrace]]]
+
+  tp_config: TraceProcessorConfig
+
+  query_executor: Optional[ThreadPoolExecutor]
+  load_executor: Optional[ThreadPoolExecutor]
+
+  trace_provider: TraceProvider
+
+  def __default_trace_provider(custom_string: str):
+    del custom_string
+    raise TraceProcessorException(
+        'Passed a string to batch trace processor constructor without '
+        'a trace provider being registered.')
+
+  def __init__(self,
+               tp_config: TraceProcessorConfig = TraceProcessorConfig(),
+               query_executor: Optional[ThreadPoolExecutor] = None,
+               load_executor: Optional[ThreadPoolExecutor] = None,
+               trace_provider: TraceProvider = __default_trace_provider):
+    self.tp_config = tp_config
+
+    self.query_executor = query_executor
+    self.load_executor = load_executor
+
+    self.trace_provider = trace_provider
+
+    try:
+      # This is the only place in batch trace processor which should import
+      # from a "vendor" namespace - the purpose of this code is to allow
+      # for users to set their own "default" config for batch trace processor
+      # without needing to specify the config in every place when batch
+      # trace processor is used.
+      from .vendor import override_batch_tp_config
+      override_batch_tp_config(self)
+    except ModuleNotFoundError:
+      pass
+
+
+class BatchTraceProcessor:
+  """Run ad-hoc SQL queries across many Perfetto traces.
+
+  Usage:
+    with BatchTraceProcessor(traces) as btp:
+      dfs = btp.query('select * from slice')
+      for df in dfs:
+        print(df)
+  """
+
+  def __init__(
+      self,
+      traces: Union[str, List[Union[LoadableTrace, BatchLoadableTrace]]],
+      config: BatchTraceProcessorConfig = BatchTraceProcessorConfig()):
+    """Creates a batch trace processor instance.
+
+    BatchTraceProcessor is the blessed way of running ad-hoc queries in
+    Python across many traces.
+
+    Args:
+      traces: Either a list of traces or a custom string which will be
+        converted to a list of traces.
+
+        If a list, each item can be one of the following types:
+        1) path to a trace file to open and read
+        2) a file like object (file, io.BytesIO or similar) to read
+        3) a generator yielding bytes
+        4) a BatchLoadableTrace object; this is basically a wrapper around
+           one of the above types plus an args field; see |query_and_flatten|
+           for the motivation for the args field.
+
+        If a string, it is passed to BatchTraceProcessorConfig.trace_provider to
+        convert to a list of traces; the default implementation of this
+        function just throws an exception so an implementation must be provided
+        if strings will be passed.
+      config: configuration options which customize functionality of batch
+        trace processor and underlying trace processors.
+    """
+
+    def _create_batch_trace(x: Union[LoadableTrace, BatchLoadableTrace]
+                           ) -> BatchLoadableTrace:
+      if isinstance(x, BatchLoadableTrace):
+        return x
+      return BatchLoadableTrace(trace=x, args={})
+
+    def create_tp(trace: BatchLoadableTrace) -> TraceProcessor:
+      return TraceProcessor(trace=trace.trace, config=config.tp_config)
+
+    if isinstance(traces, str):
+      trace_list = config.trace_provider(traces)
+    else:
+      trace_list = traces
+
+    batch_traces = [_create_batch_trace(t) for t in trace_list]
+
+    # As trace processor is completely CPU bound, it makes sense to just
+    # max out the CPUs available.
+    query_executor = config.query_executor or ThreadPoolExecutor(
+        max_workers=multiprocessing.cpu_count())
+    load_exectuor = config.load_executor or query_executor
+
+    self.tps = None
+    self.closed = False
+    self.query_executor = query_executor
+    self.args = [t.args for t in batch_traces]
+    self.tps = list(load_exectuor.map(create_tp, batch_traces))
+
+  def metric(self, metrics: List[str]):
+    """Computes the provided metrics.
+
+    The computation happens in parallel across all the traces.
+
+    Args:
+      metrics: A list of valid metrics as defined in TraceMetrics
+
+    Returns:
+      A list of TraceMetric protos (one for each trace).
+    """
+    return self.execute(lambda tp: tp.metric(metrics))
+
+  def query(self, sql: str):
+    """Executes the provided SQL statement (returning a single row).
+
+    The execution happens in parallel across all the traces.
+
+    Args:
+      sql: The SQL statement to execute.
+
+    Returns:
+      A list of Pandas dataframes with the result of executing the query (one
+      per trace).
+
+    Raises:
+      TraceProcessorException: An error occurred running the query.
+    """
+    return self.execute(lambda tp: tp.query(sql).as_pandas_dataframe())
+
+  def query_and_flatten(self, sql: str):
+    """Executes the provided SQL statement and flattens the result.
+
+    The execution happens in parallel across all the traces and the
+    resulting Pandas dataframes are flattened into a single dataframe.
+
+    Args:
+      sql: The SQL statement to execute.
+
+    Returns:
+      A concatenated Pandas dataframe containing the result of executing the
+      query across all the traces.
+
+      If |BatchLoadableTrace| objects were passed to the constructor, the
+      contents of the |args| dictionary will also be emitted as extra columns
+      (key being column name, value being the value in the dataframe).
+
+      For example:
+        traces = [BatchLoadableTrace(trace='/tmp/path', args={"foo": "bar"})]
+        with BatchTraceProcessor(traces) as btp:
+          df = btp.query_and_flatten('select count(1) as cnt from slice')
+
+      Then df will look like this:
+        cnt             foo
+        100             bar
+
+    Raises:
+      TraceProcessorException: An error occurred running the query.
+    """
+    return self.execute_and_flatten(lambda tp: tp.query(sql).
+                                    as_pandas_dataframe())
+
+  def query_single_result(self, sql: str):
+    """Executes the provided SQL statement (returning a single row).
+
+    The execution happens in parallel across all the traces.
+
+    Args:
+      sql: The SQL statement to execute. This statement should return exactly
+        one row on any trace.
+
+    Returns:
+      A list of values with the result of executing the query (one per trace).
+
+    Raises:
+      TraceProcessorException: An error occurred running the query or more than
+        one result was returned.
+    """
+
+    def query_single_result_inner(tp):
+      df = tp.query(sql).as_pandas_dataframe()
+      if len(df.index) != 1:
+        raise TraceProcessorException("Query should only return a single row")
+
+      if len(df.columns) != 1:
+        raise TraceProcessorException(
+            "Query should only return a single column")
+
+      return df.iloc[0, 0]
+
+    return self.execute(query_single_result_inner)
+
+  def execute(self, fn: Callable[[TraceProcessor], Any]) -> List[Any]:
+    """Executes the provided function.
+
+    The execution happens in parallel across all the trace processor instances
+    owned by this object.
+
+    Args:
+      fn: The function to execute.
+
+    Returns:
+      A list of values with the result of executing the fucntion (one per
+      trace).
+    """
+    return list(self.query_executor.map(fn, self.tps))
+
+  def execute_and_flatten(self, fn: Callable[[TraceProcessor], pd.DataFrame]
+                         ) -> pd.DataFrame:
+    """Executes the provided function and flattens the result.
+
+    The execution happens in parallel across all the trace processor
+    instances owned by this object and the returned Pandas dataframes are
+    flattened into a single dataframe.
+
+    Args:
+      fn: The function to execute which returns a Pandas dataframe.
+
+    Returns:
+      A Pandas dataframe containing the result of executing the query across all
+      the traces. Extra columns containing the file path and args will
+      be added to the dataframe (see |query_and_flatten| for details).
+    """
+
+    def wrapped(pair: Tuple[TraceProcessor, BatchLoadableTrace]):
+      (tp, args) = pair
+      df = fn(tp)
+      for key, value in args.items():
+        df[key] = value
+      return df
+
+    df = pd.concat(
+        list(self.query_executor.map(wrapped, zip(self.tps, self.args))))
+    return df.reset_index(drop=True)
+
+  def close(self):
+    """Closes this batch trace processor instance.
+
+    This closes all spawned trace processor instances, releasing all the memory
+    and resources those instances take.
+
+    No further calls to other methods in this class should be made after
+    calling this method.
+    """
+    if self.closed:
+      return
+    self.closed = True
+
+    if self.tps:
+      for tp in self.tps:
+        tp.close()
+
+  def __enter__(self):
+    return self
+
+  def __exit__(self, a, b, c):
+    del a, b, c  # Unused.
+    self.close()
+    return False
+
+  def __del__(self):
+    self.close()
diff --git a/tools/slice_breakdown/perfetto/slice_breakdown/__init__.py b/python/perfetto/experimental/slice_breakdown/__init__.py
similarity index 78%
rename from tools/slice_breakdown/perfetto/slice_breakdown/__init__.py
rename to python/perfetto/experimental/slice_breakdown/__init__.py
index 6e6169f..53d1e85 100644
--- a/tools/slice_breakdown/perfetto/slice_breakdown/__init__.py
+++ b/python/perfetto/experimental/slice_breakdown/__init__.py
@@ -12,4 +12,5 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from .breakdown import compute_breakdown, compute_breakdown_for_startup
\ No newline at end of file
+from perfetto.experimental.slice_breakdown.breakdown import compute_breakdown
+from perfetto.experimental.slice_breakdown.breakdown import compute_breakdown_for_startup
\ No newline at end of file
diff --git a/tools/slice_breakdown/perfetto/slice_breakdown/breakdown.py b/python/perfetto/experimental/slice_breakdown/breakdown.py
similarity index 100%
rename from tools/slice_breakdown/perfetto/slice_breakdown/breakdown.py
rename to python/perfetto/experimental/slice_breakdown/breakdown.py
diff --git a/src/trace_processor/python/perfetto/trace_processor/__init__.py b/python/perfetto/trace_processor/__init__.py
similarity index 67%
copy from src/trace_processor/python/perfetto/trace_processor/__init__.py
copy to python/perfetto/trace_processor/__init__.py
index 7106a6c..e151656 100644
--- a/src/trace_processor/python/perfetto/trace_processor/__init__.py
+++ b/python/perfetto/trace_processor/__init__.py
@@ -13,5 +13,8 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
-from .api import TraceProcessor, TraceProcessorException
-from .http import TraceProcessorHttp
+from perfetto.trace_processor.api import LoadableTrace
+from perfetto.trace_processor.api import TraceProcessor
+from perfetto.trace_processor.api import TraceProcessorConfig
+from perfetto.trace_processor.api import TraceProcessorException
+from perfetto.trace_processor.http import TraceProcessorHttp
diff --git a/src/trace_processor/python/perfetto/trace_processor/api.py b/python/perfetto/trace_processor/api.py
similarity index 64%
rename from src/trace_processor/python/perfetto/trace_processor/api.py
rename to python/perfetto/trace_processor/api.py
index ce06edf..3f24a13 100644
--- a/src/trace_processor/python/perfetto/trace_processor/api.py
+++ b/python/perfetto/trace_processor/api.py
@@ -12,12 +12,18 @@
 # See the License for the specific language governing permissions and
 # limitations under the License.
 
+import dataclasses as dc
+from enum import unique
 from urllib.parse import urlparse
+from typing import BinaryIO, Callable, Generator, List, Optional, Tuple, Union
 
-from .http import TraceProcessorHttp
-from .loader import get_loader
-from .protos import ProtoFactory
-from .shell import load_shell
+from perfetto.trace_processor.http import TraceProcessorHttp
+from perfetto.trace_processor.loader import get_loader
+from perfetto.trace_processor.protos import ProtoFactory
+from perfetto.trace_processor.shell import load_shell
+
+# Union of types supported for a trace which can be loaded by shell.
+LoadableTrace = Union[None, str, BinaryIO, Generator[bytes, None, None]]
 
 
 # Custom exception raised if any trace_processor functions return a
@@ -28,6 +34,53 @@
     super().__init__(message)
 
 
+@dc.dataclass
+class TraceProcessorConfig:
+  bin_path: Optional[str]
+  unique_port: bool
+  verbose: bool
+
+  read_tp_descriptor: Callable[[], bytes]
+  read_metrics_descriptor: Callable[[], bytes]
+  parse_file: Callable[[TraceProcessorHttp, str], TraceProcessorHttp]
+  get_shell_path: Callable[[str], None]
+  get_free_port: Callable[[bool], Tuple[str, str]]
+
+  def __init__(
+      self,
+      bin_path: Optional[str] = None,
+      unique_port: bool = True,
+      verbose: bool = False,
+      read_tp_descriptor: Callable[[], bytes] = get_loader().read_tp_descriptor,
+      read_metrics_descriptor: Callable[[], bytes] = get_loader(
+      ).read_metrics_descriptor,
+      parse_file: Callable[[TraceProcessorHttp, str],
+                           TraceProcessorHttp] = get_loader().parse_file,
+      get_shell_path: Callable[[str], None] = get_loader().get_shell_path,
+      get_free_port: Callable[[bool], Tuple[str, str]] = get_loader(
+      ).get_free_port):
+    self.bin_path = bin_path
+    self.unique_port = unique_port
+    self.verbose = verbose
+
+    self.read_tp_descriptor = read_tp_descriptor
+    self.read_metrics_descriptor = read_metrics_descriptor
+    self.parse_file = parse_file
+    self.get_shell_path = get_shell_path
+    self.get_free_port = get_free_port
+
+    try:
+      # This is the only place in trace processor which should import
+      # from a "vendor" namespace - the purpose of this code is to allow
+      # for users to set their own "default" config for trace processor
+      # without needing to specify the config in every place when trace
+      # processor is used.
+      from .vendor import override_default_tp_config
+      return override_default_tp_config(self)
+    except ModuleNotFoundError:
+      pass
+
+
 class TraceProcessor:
 
   # Values of these constants correspond to the QueryResponse message at
@@ -122,7 +175,6 @@
     # TraceProcesor.
     def as_pandas_dataframe(self):
       try:
-        import numpy as np
         import pandas as pd
 
         # Populate the dataframe with the query results
@@ -144,7 +196,8 @@
           rows.append(row)
 
         df = pd.DataFrame(rows, columns=self.__column_names)
-        return df.where(df.notnull(), None).reset_index(drop=True)
+        return df.astype(object).where(df.notnull(),
+                                       None).reset_index(drop=True)
 
       except ModuleNotFoundError:
         raise TraceProcessorException(
@@ -176,27 +229,69 @@
       return result
 
   def __init__(self,
-               addr=None,
-               file_path=None,
-               bin_path=None,
-               unique_port=True,
-               verbose=False):
-    # Load trace_processor_shell or access via given address
-    if addr:
-      p = urlparse(addr)
-      tp = TraceProcessorHttp(p.netloc if p.netloc else p.path)
-    else:
+               trace: LoadableTrace = None,
+               addr: Optional[str] = None,
+               config: TraceProcessorConfig = TraceProcessorConfig(),
+               file_path: Optional[str] = None):
+    """Create a trace processor instance.
+
+    Args:
+      trace: trace to be loaded into the trace processor instance. One of
+        three types of argument is supported:
+        1) path to a trace file to open and read
+        2) a file like object (file, io.BytesIO or similar) to read
+        3) a generator yielding bytes
+        4) a custom string format which can be understood by
+           TraceProcessorConfig.parse_file function. The default
+           implementation of this function only supports file paths (i.e. option
+           1) but callers can choose to change the implementation to parse
+           a custom string format and use that to retrieve a race.
+      addr: address of a running trace processor instance. Useful to query an
+        already loaded trace.
+      config: configuration options which customize functionality of trace
+        processor and the Python binding.
+      file_path (deprecated): path to a trace file to load. Use
+        |trace| instead of this field: specifying both will cause
+        an exception to be thrown.
+    """
+
+    def create_tp_http(protos: ProtoFactory) -> TraceProcessorHttp:
+      if addr:
+        p = urlparse(addr)
+        return TraceProcessorHttp(
+            p.netloc if p.netloc else p.path, protos=protos)
+
       url, self.subprocess = load_shell(
-          bin_path=bin_path, unique_port=unique_port, verbose=verbose)
-      tp = TraceProcessorHttp(url)
-    self.http = tp
-    self.protos = ProtoFactory()
+          bin_path=config.bin_path,
+          unique_port=config.unique_port,
+          verbose=config.verbose)
+      return TraceProcessorHttp(url, protos=protos)
 
-    # Parse trace by its file_path into the loaded instance of trace_processor
+    if trace and file_path:
+      raise TraceProcessorException(
+          "trace and file_path cannot both be specified.")
+
+    self.protos = ProtoFactory(config.read_tp_descriptor(),
+                               config.read_metrics_descriptor())
+    self.http = create_tp_http(self.protos)
+
     if file_path:
-      get_loader().parse_file(self.http, file_path)
+      config.parse_file(self.http, file_path)
+    elif isinstance(trace, str):
+      config.parse_file(self.http, trace)
+    elif hasattr(trace, 'read'):
+      while True:
+        chunk = trace.read(32 * 1024 * 1024)
+        if not chunk:
+          break
+        self.http.parse(chunk)
+      self.http.notify_eof()
+    elif trace:
+      for chunk in trace:
+        self.http.parse(chunk)
+      self.http.notify_eof()
 
-  def query(self, sql):
+  def query(self, sql: str):
     """Executes passed in SQL query using class defined HTTP API, and returns
     the response as a QueryResultIterator. Raises TraceProcessorException if
     the response returns with an error.
@@ -216,7 +311,7 @@
     return TraceProcessor.QueryResultIterator(response.column_names,
                                               response.batch)
 
-  def metric(self, metrics):
+  def metric(self, metrics: List[str]):
     """Returns the metrics data corresponding to the passed in trace metric.
     Raises TraceProcessorException if the response returns with an error.
 
@@ -254,7 +349,8 @@
   def __enter__(self):
     return self
 
-  def __exit__(self, _, __, ___):
+  def __exit__(self, a, b, c):
+    del a, b, c  # Unused.
     self.close()
     return False
 
diff --git a/src/trace_processor/python/perfetto/trace_processor/http.py b/python/perfetto/trace_processor/http.py
similarity index 88%
rename from src/trace_processor/python/perfetto/trace_processor/http.py
rename to python/perfetto/trace_processor/http.py
index bf751f9..71db4ab 100644
--- a/src/trace_processor/python/perfetto/trace_processor/http.py
+++ b/python/perfetto/trace_processor/http.py
@@ -14,17 +14,18 @@
 # limitations under the License.
 
 import http.client
+from typing import List
 
-from .protos import ProtoFactory
+from perfetto.trace_processor.protos import ProtoFactory
 
 
 class TraceProcessorHttp:
 
-  def __init__(self, url):
-    self.protos = ProtoFactory()
+  def __init__(self, url: str, protos: ProtoFactory):
+    self.protos = protos
     self.conn = http.client.HTTPConnection(url)
 
-  def execute_query(self, query):
+  def execute_query(self, query: str):
     args = self.protos.RawQueryArgs()
     args.sql_query = query
     byte_data = args.SerializeToString()
@@ -34,7 +35,7 @@
       result.ParseFromString(f.read())
       return result
 
-  def compute_metric(self, metrics):
+  def compute_metric(self, metrics: List[str]):
     args = self.protos.ComputeMetricArgs()
     args.metric_names.extend(metrics)
     byte_data = args.SerializeToString()
@@ -44,7 +45,7 @@
       result.ParseFromString(f.read())
       return result
 
-  def parse(self, chunk):
+  def parse(self, chunk: bytes):
     self.conn.request('POST', '/parse', body=chunk)
     with self.conn.getresponse() as f:
       return f.read()
diff --git a/src/trace_processor/python/perfetto/trace_processor/loader.py b/python/perfetto/trace_processor/loader.py
similarity index 94%
rename from src/trace_processor/python/perfetto/trace_processor/loader.py
rename to python/perfetto/trace_processor/loader.py
index e57145f..0a1b16b 100644
--- a/src/trace_processor/python/perfetto/trace_processor/loader.py
+++ b/python/perfetto/trace_processor/loader.py
@@ -53,7 +53,7 @@
     tp_http.notify_eof()
     return tp_http
 
-  def get_shell_path(bin_path=None):
+  def get_shell_path(bin_path):
     # Try to use preexisting binary before attempting to download
     # trace_processor
     if bin_path is None:
@@ -68,7 +68,7 @@
         raise Exception('Path to binary is not valid')
       return bin_path
 
-  def get_free_port(unique_port=False):
+  def get_free_port(unique_port):
     if not unique_port:
       return LoaderStandalone.TP_PORT, f'localhost:{LoaderStandalone.TP_PORT}'
     free_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
@@ -80,6 +80,8 @@
 
 
 # Return vendor class if it exists before falling back on LoaderStandalone
+# TODO(lalitm): remove this after migrating all consumers to
+# TraceProcessorConfig.
 def get_loader():
   try:
     from .loader_vendor import LoaderVendor
diff --git a/src/trace_processor/python/perfetto/trace_processor/metrics.descriptor b/python/perfetto/trace_processor/metrics.descriptor
similarity index 96%
rename from src/trace_processor/python/perfetto/trace_processor/metrics.descriptor
rename to python/perfetto/trace_processor/metrics.descriptor
index e3a3ea8..053e838 100644
--- a/src/trace_processor/python/perfetto/trace_processor/metrics.descriptor
+++ b/python/perfetto/trace_processor/metrics.descriptor
Binary files differ
diff --git a/python/perfetto/trace_processor/metrics.descriptor.sha1 b/python/perfetto/trace_processor/metrics.descriptor.sha1
new file mode 100644
index 0000000..17f0868
--- /dev/null
+++ b/python/perfetto/trace_processor/metrics.descriptor.sha1
@@ -0,0 +1,6 @@
+
+// SHA1(tools/gen_binary_descriptors)
+// c4a38769074f8a8c2ffbf514b267919b5f2d47df
+// SHA1(protos/perfetto/metrics/metrics.proto)
+// 22722c7fde543d5abd1299f48edd49c69c5f5c3e
+  
\ No newline at end of file
diff --git a/src/trace_processor/python/perfetto/trace_processor/protos.py b/python/perfetto/trace_processor/protos.py
similarity index 90%
rename from src/trace_processor/python/perfetto/trace_processor/protos.py
rename to python/perfetto/trace_processor/protos.py
index b5e3700..0053fdb 100644
--- a/src/trace_processor/python/perfetto/trace_processor/protos.py
+++ b/python/perfetto/trace_processor/protos.py
@@ -16,27 +16,23 @@
 from google.protobuf import message_factory
 from google.protobuf.descriptor_pool import DescriptorPool
 
-from .loader import get_loader
-
 
 class ProtoFactory:
 
-  def __init__(self):
+  def __init__(self, tp_descriptor: bytes, metrics_descriptor: bytes):
     # Declare descriptor pool
     self.descriptor_pool = DescriptorPool()
 
     # Load trace processor descriptor and add to descriptor pool
-    tp_descriptor_bytes = get_loader().read_tp_descriptor()
     tp_file_desc_set_pb2 = descriptor_pb2.FileDescriptorSet()
-    tp_file_desc_set_pb2.MergeFromString(tp_descriptor_bytes)
+    tp_file_desc_set_pb2.MergeFromString(tp_descriptor)
 
     for f_desc_pb2 in tp_file_desc_set_pb2.file:
       self.descriptor_pool.Add(f_desc_pb2)
 
     # Load metrics descriptor and add to descriptor pool
-    metrics_descriptor_bytes = get_loader().read_metrics_descriptor()
     metrics_file_desc_set_pb2 = descriptor_pb2.FileDescriptorSet()
-    metrics_file_desc_set_pb2.MergeFromString(metrics_descriptor_bytes)
+    metrics_file_desc_set_pb2.MergeFromString(metrics_descriptor)
 
     for f_desc_pb2 in metrics_file_desc_set_pb2.file:
       self.descriptor_pool.Add(f_desc_pb2)
diff --git a/src/trace_processor/python/perfetto/trace_processor/shell.py b/python/perfetto/trace_processor/shell.py
similarity index 96%
rename from src/trace_processor/python/perfetto/trace_processor/shell.py
rename to python/perfetto/trace_processor/shell.py
index 00749d1..8daa956 100644
--- a/src/trace_processor/python/perfetto/trace_processor/shell.py
+++ b/python/perfetto/trace_processor/shell.py
@@ -18,7 +18,7 @@
 import time
 from urllib import request, error
 
-from .loader import get_loader
+from perfetto.trace_processor.loader import get_loader
 
 
 def load_shell(bin_path, unique_port, verbose):
diff --git a/src/trace_processor/python/perfetto/trace_processor/trace_processor.descriptor b/python/perfetto/trace_processor/trace_processor.descriptor
similarity index 100%
rename from src/trace_processor/python/perfetto/trace_processor/trace_processor.descriptor
rename to python/perfetto/trace_processor/trace_processor.descriptor
Binary files differ
diff --git a/src/trace_processor/python/perfetto/trace_processor/trace_processor.descriptor.sha1 b/python/perfetto/trace_processor/trace_processor.descriptor.sha1
similarity index 76%
rename from src/trace_processor/python/perfetto/trace_processor/trace_processor.descriptor.sha1
rename to python/perfetto/trace_processor/trace_processor.descriptor.sha1
index 23712a1..92ce69e 100644
--- a/src/trace_processor/python/perfetto/trace_processor/trace_processor.descriptor.sha1
+++ b/python/perfetto/trace_processor/trace_processor.descriptor.sha1
@@ -1,6 +1,6 @@
 
 // SHA1(tools/gen_binary_descriptors)
-// 9fc6d77de57ec76a80b76aa282f4c7cf5ce55eec
+// c4a38769074f8a8c2ffbf514b267919b5f2d47df
 // SHA1(protos/perfetto/trace_processor/trace_processor.proto)
 // e303e1fc877a9fe4f8dd8413c03266ee68dfd3aa
   
\ No newline at end of file
diff --git a/tools/run_python_api_tests.py b/python/run_tests.py
similarity index 82%
rename from tools/run_python_api_tests.py
rename to python/run_tests.py
index 78a78a1..6ac0977 100755
--- a/tools/run_python_api_tests.py
+++ b/python/run_tests.py
@@ -18,6 +18,9 @@
 import sys
 import unittest
 
+from test import api_unittest
+from test import api_integrationtest
+
 ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 
 
@@ -29,13 +32,6 @@
     print('Cannot proceed. Please `pip3 install pandas numpy`', file=sys.stderr)
     return 1
 
-  # Append test and src paths so that all imports are loaded in correctly
-  sys.path.append(os.path.join(ROOT_DIR, 'test', 'trace_processor', 'python'))
-  sys.path.append(
-      os.path.join(ROOT_DIR, 'src', 'trace_processor', 'python', 'perfetto'))
-  import api_unittest
-  import api_integrationtest
-
   # Set paths to trace_processor_shell and root directory as environment
   # variables
   parser = argparse.ArgumentParser()
@@ -43,7 +39,6 @@
   os.environ["SHELL_PATH"] = parser.parse_args().shell
   os.environ["ROOT_DIR"] = ROOT_DIR
 
-  # Initialise test suite
   loader = unittest.TestLoader()
   suite = unittest.TestSuite()
 
diff --git a/src/trace_processor/python/setup.py b/python/setup.py
similarity index 100%
rename from src/trace_processor/python/setup.py
rename to python/setup.py
diff --git a/tools/batch_trace_processor/perfetto/batch_trace_processor/__init__.py b/python/test/__init__.py
similarity index 100%
rename from tools/batch_trace_processor/perfetto/batch_trace_processor/__init__.py
rename to python/test/__init__.py
diff --git a/python/test/api_integrationtest.py b/python/test/api_integrationtest.py
new file mode 100644
index 0000000..8d83eaf
--- /dev/null
+++ b/python/test/api_integrationtest.py
@@ -0,0 +1,105 @@
+#!/usr/bin/env python3
+# Copyright (C) 2020 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.
+
+import io
+import os
+from typing import Optional
+import unittest
+
+from perfetto.trace_processor.api import TraceProcessor
+from perfetto.trace_processor.api import TraceProcessorConfig
+from perfetto.trace_processor.api import LoadableTrace
+
+
+def create_tp(trace: LoadableTrace):
+  return TraceProcessor(
+      trace=trace,
+      config=TraceProcessorConfig(bin_path=os.environ["SHELL_PATH"]))
+
+
+def example_android_trace_path():
+  return os.path.join(os.environ["ROOT_DIR"], 'test', 'data',
+                      'example_android_trace_30s.pb')
+
+
+class TestApi(unittest.TestCase):
+
+  def test_trace_path(self):
+    # Get path to trace_processor_shell and construct TraceProcessor
+    tp = create_tp(trace=example_android_trace_path())
+    qr_iterator = tp.query('select * from slice limit 10')
+    dur_result = [
+        178646, 119740, 58073, 155000, 173177, 20209377, 3589167, 90104, 275312,
+        65313
+    ]
+
+    for num, row in enumerate(qr_iterator):
+      self.assertEqual(row.type, 'internal_slice')
+      self.assertEqual(row.dur, dur_result[num])
+
+    # Test the batching logic by issuing a large query and ensuring we receive
+    # all rows, not just a truncated subset.
+    qr_iterator = tp.query('select count(*) as cnt from slice')
+    expected_count = next(qr_iterator).cnt
+    self.assertGreater(expected_count, 0)
+
+    qr_iterator = tp.query('select * from slice')
+    count = sum(1 for _ in qr_iterator)
+    self.assertEqual(count, expected_count)
+
+    tp.close()
+
+  def test_trace_byteio(self):
+    f = io.BytesIO(
+        b'\n(\n&\x08\x00\x12\x12\x08\x01\x10\xc8\x01\x1a\x0b\x12\t'
+        b'B|200|foo\x12\x0e\x08\x02\x10\xc8\x01\x1a\x07\x12\x05E|200')
+    with create_tp(trace=f) as tp:
+      qr_iterator = tp.query('select * from slice limit 10')
+      res = list(qr_iterator)
+
+      self.assertEqual(len(res), 1)
+
+      row = res[0]
+      self.assertEqual(row.ts, 1)
+      self.assertEqual(row.dur, 1)
+      self.assertEqual(row.name, 'foo')
+
+  def test_trace_file(self):
+    with open(example_android_trace_path(), 'rb') as file:
+      with create_tp(trace=file) as tp:
+        qr_iterator = tp.query('select * from slice limit 10')
+        dur_result = [
+            178646, 119740, 58073, 155000, 173177, 20209377, 3589167, 90104,
+            275312, 65313
+        ]
+
+        for num, row in enumerate(qr_iterator):
+          self.assertEqual(row.dur, dur_result[num])
+
+  def test_trace_generator(self):
+
+    def reader_generator():
+      with open(example_android_trace_path(), 'rb') as file:
+        yield file.read(1024)
+
+    with create_tp(trace=reader_generator()) as tp:
+      qr_iterator = tp.query('select * from slice limit 10')
+      dur_result = [
+          178646, 119740, 58073, 155000, 173177, 20209377, 3589167, 90104,
+          275312, 65313
+      ]
+
+      for num, row in enumerate(qr_iterator):
+        self.assertEqual(row.dur, dur_result[num])
diff --git a/test/trace_processor/python/api_unittest.py b/python/test/api_unittest.py
similarity index 90%
rename from test/trace_processor/python/api_unittest.py
rename to python/test/api_unittest.py
index 29abd5f..732438a 100755
--- a/test/trace_processor/python/api_unittest.py
+++ b/python/test/api_unittest.py
@@ -15,23 +15,30 @@
 
 import unittest
 
-from trace_processor.api import TraceProcessor, TraceProcessorException
-from trace_processor.protos import ProtoFactory
+from perfetto.trace_processor.api import TraceProcessor
+from perfetto.trace_processor.api import TraceProcessorException
+from perfetto.trace_processor.api import TraceProcessorConfig
+from perfetto.trace_processor.protos import ProtoFactory
+
+TP_CONFIG = TraceProcessorConfig()
+PROTO_FACTORY = ProtoFactory(
+    tp_descriptor=TP_CONFIG.read_tp_descriptor(),
+    metrics_descriptor=TP_CONFIG.read_metrics_descriptor())
 
 
 class TestQueryResultIterator(unittest.TestCase):
   # The numbers input into cells correspond the CellType enum values
   # defined under trace_processor.proto
-  CELL_VARINT = ProtoFactory().CellsBatch().CELL_VARINT
-  CELL_STRING = ProtoFactory().CellsBatch().CELL_STRING
-  CELL_INVALID = ProtoFactory().CellsBatch().CELL_INVALID
-  CELL_NULL = ProtoFactory().CellsBatch().CELL_NULL
+  CELL_VARINT = PROTO_FACTORY.CellsBatch().CELL_VARINT
+  CELL_STRING = PROTO_FACTORY.CellsBatch().CELL_STRING
+  CELL_INVALID = PROTO_FACTORY.CellsBatch().CELL_INVALID
+  CELL_NULL = PROTO_FACTORY.CellsBatch().CELL_NULL
 
   def test_one_batch(self):
     int_values = [100, 200]
     str_values = ['bar1', 'bar2']
 
-    batch = ProtoFactory().CellsBatch()
+    batch = PROTO_FACTORY.CellsBatch()
     batch.cells.extend([
         TestQueryResultIterator.CELL_STRING,
         TestQueryResultIterator.CELL_VARINT,
@@ -56,7 +63,7 @@
     int_values = [100, 200, 300, 400]
     str_values = ['bar1', 'bar2', 'bar3', 'bar4']
 
-    batch_1 = ProtoFactory().CellsBatch()
+    batch_1 = PROTO_FACTORY.CellsBatch()
     batch_1.cells.extend([
         TestQueryResultIterator.CELL_STRING,
         TestQueryResultIterator.CELL_VARINT,
@@ -69,7 +76,7 @@
     batch_1.string_cells = "\0".join(str_values[:2]) + "\0"
     batch_1.is_last_batch = False
 
-    batch_2 = ProtoFactory().CellsBatch()
+    batch_2 = PROTO_FACTORY.CellsBatch()
     batch_2.cells.extend([
         TestQueryResultIterator.CELL_STRING,
         TestQueryResultIterator.CELL_VARINT,
@@ -91,7 +98,7 @@
       self.assertEqual(row.foo_null, None)
 
   def test_empty_batch(self):
-    batch = ProtoFactory().CellsBatch()
+    batch = PROTO_FACTORY.CellsBatch()
     batch.is_last_batch = True
 
     qr_iterator = TraceProcessor.QueryResultIterator([], [batch])
@@ -101,7 +108,7 @@
       self.assertIsNone(row.foo_num)
 
   def test_invalid_batch(self):
-    batch = ProtoFactory().CellsBatch()
+    batch = PROTO_FACTORY.CellsBatch()
 
     # Since the batch isn't defined as the last batch, the QueryResultsIterator
     # expects another batch and thus raises IndexError as no next batch exists.
@@ -112,7 +119,7 @@
     int_values = [100, 200, 300, 500, 600]
     str_values = ['bar1', 'bar2', 'bar3']
 
-    batch = ProtoFactory().CellsBatch()
+    batch = PROTO_FACTORY.CellsBatch()
     batch.cells.extend([
         TestQueryResultIterator.CELL_STRING,
         TestQueryResultIterator.CELL_VARINT,
@@ -143,7 +150,7 @@
   def test_incorrect_cells_batch(self):
     str_values = ['bar1', 'bar2']
 
-    batch = ProtoFactory().CellsBatch()
+    batch = PROTO_FACTORY.CellsBatch()
     batch.cells.extend([
         TestQueryResultIterator.CELL_STRING,
         TestQueryResultIterator.CELL_VARINT,
@@ -163,7 +170,7 @@
         pass
 
   def test_incorrect_columns_batch(self):
-    batch = ProtoFactory().CellsBatch()
+    batch = PROTO_FACTORY.CellsBatch()
     batch.cells.extend([
         TestQueryResultIterator.CELL_VARINT, TestQueryResultIterator.CELL_VARINT
     ])
@@ -178,7 +185,7 @@
           ['foo_id', 'foo_num', 'foo_dur', 'foo_ms'], [batch])
 
   def test_invalid_cell_type(self):
-    batch = ProtoFactory().CellsBatch()
+    batch = PROTO_FACTORY.CellsBatch()
     batch.cells.extend([
         TestQueryResultIterator.CELL_INVALID,
         TestQueryResultIterator.CELL_VARINT
@@ -200,7 +207,7 @@
     int_values = [100, 200]
     str_values = ['bar1', 'bar2']
 
-    batch = ProtoFactory().CellsBatch()
+    batch = PROTO_FACTORY.CellsBatch()
     batch.cells.extend([
         TestQueryResultIterator.CELL_STRING,
         TestQueryResultIterator.CELL_VARINT,
@@ -226,7 +233,7 @@
     int_values = [100, 200, 300, 400]
     str_values = ['bar1', 'bar2', 'bar3', 'bar4']
 
-    batch_1 = ProtoFactory().CellsBatch()
+    batch_1 = PROTO_FACTORY.CellsBatch()
     batch_1.cells.extend([
         TestQueryResultIterator.CELL_STRING,
         TestQueryResultIterator.CELL_VARINT,
@@ -239,7 +246,7 @@
     batch_1.string_cells = "\0".join(str_values[:2]) + "\0"
     batch_1.is_last_batch = False
 
-    batch_2 = ProtoFactory().CellsBatch()
+    batch_2 = PROTO_FACTORY.CellsBatch()
     batch_2.cells.extend([
         TestQueryResultIterator.CELL_STRING,
         TestQueryResultIterator.CELL_VARINT,
@@ -262,7 +269,7 @@
       self.assertEqual(row['foo_null'], None)
 
   def test_empty_batch_as_pandas(self):
-    batch = ProtoFactory().CellsBatch()
+    batch = PROTO_FACTORY.CellsBatch()
     batch.is_last_batch = True
 
     qr_iterator = TraceProcessor.QueryResultIterator([], [batch])
@@ -276,7 +283,7 @@
     int_values = [100, 200, 300, 500, 600]
     str_values = ['bar1', 'bar2', 'bar3']
 
-    batch = ProtoFactory().CellsBatch()
+    batch = PROTO_FACTORY.CellsBatch()
     batch.cells.extend([
         TestQueryResultIterator.CELL_STRING,
         TestQueryResultIterator.CELL_VARINT,
@@ -308,7 +315,7 @@
   def test_incorrect_cells_batch_as_pandas(self):
     str_values = ['bar1', 'bar2']
 
-    batch = ProtoFactory().CellsBatch()
+    batch = PROTO_FACTORY.CellsBatch()
     batch.cells.extend([
         TestQueryResultIterator.CELL_STRING,
         TestQueryResultIterator.CELL_VARINT,
@@ -327,7 +334,7 @@
       qr_df = qr_iterator.as_pandas_dataframe()
 
   def test_invalid_cell_type_as_pandas(self):
-    batch = ProtoFactory().CellsBatch()
+    batch = PROTO_FACTORY.CellsBatch()
     batch.cells.extend([
         TestQueryResultIterator.CELL_INVALID,
         TestQueryResultIterator.CELL_VARINT
diff --git a/tools/batch_trace_processor/main.py b/python/tools/batch_trace_processor_shell.py
similarity index 91%
rename from tools/batch_trace_processor/main.py
rename to python/tools/batch_trace_processor_shell.py
index b73f7d9..651247d 100644
--- a/tools/batch_trace_processor/main.py
+++ b/python/tools/batch_trace_processor_shell.py
@@ -23,8 +23,8 @@
 import pandas as pd
 import plotille
 
-from perfetto.batch_trace_processor.api import BatchTraceProcessor
-from perfetto.trace_processor import TraceProcessorException
+from perfetto.batch_trace_processor.api import BatchTraceProcessor, BatchTraceProcessorConfig
+from perfetto.trace_processor import TraceProcessorException, TraceProcessorConfig
 from typing import List
 
 
@@ -107,8 +107,13 @@
     logging.info("At least one file must be specified in files or file list")
 
   logging.info('Loading traces...')
-  with BatchTraceProcessor(
-      files, bin_path=args.shell_path, verbose=args.verbose) as batch_tp:
+  config = BatchTraceProcessorConfig(
+      tp_config=TraceProcessorConfig(
+          bin_path=args.shell_path,
+          verbose=args.verbose,
+      ))
+
+  with BatchTraceProcessor(files, config) as batch_tp:
     if args.query_file:
       logging.info('Running query file...')
 
diff --git a/tools/slice_breakdown/main.py b/python/tools/slice_breakdown.py
similarity index 75%
rename from tools/slice_breakdown/main.py
rename to python/tools/slice_breakdown.py
index 960e44b..9c481ba 100644
--- a/tools/slice_breakdown/main.py
+++ b/python/tools/slice_breakdown.py
@@ -19,21 +19,21 @@
 import argparse
 import sys
 
-from perfetto.slice_breakdown import compute_breakdown, compute_breakdown_for_startup
+from perfetto.experimental.slice_breakdown import compute_breakdown
+from perfetto.experimental.slice_breakdown import compute_breakdown_for_startup
 from perfetto.trace_processor import TraceProcessor
+from perfetto.trace_processor import TraceProcessorConfig
 
 
 def compute_breakdown_wrapper(args):
-  tp = TraceProcessor(
-      file_path=args.file, bin_path=args.shell_path, verbose=args.verbose)
-  if args.startup_bounds:
-    breakdown = compute_breakdown_for_startup(tp, args.startup_package,
-                                              args.process_name)
-  else:
-    breakdown = compute_breakdown(tp, args.start_ts, args.end_ts,
-                                  args.process_name)
-  tp.close()
-
+  config = TraceProcessorConfig(bin_path=args.shell_path, verbose=args.verbose)
+  with TraceProcessor(trace=args.file, config=config) as tp:
+    if args.startup_bounds:
+      breakdown = compute_breakdown_for_startup(tp, args.startup_package,
+                                                args.process_name)
+    else:
+      breakdown = compute_breakdown(tp, args.start_ts, args.end_ts,
+                                    args.process_name)
   return breakdown
 
 
diff --git a/src/base/file_utils.cc b/src/base/file_utils.cc
index 91a5fc4..e3139bf 100644
--- a/src/base/file_utils.cc
+++ b/src/base/file_utils.cc
@@ -44,6 +44,14 @@
 namespace base {
 namespace {
 constexpr size_t kBufSize = 2048;
+
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+// Wrap FindClose to: (1) make the return unix-style; (2) deal with stdcall.
+int CloseFindHandle(HANDLE h) {
+  return FindClose(h) ? 0 : -1;
+};
+#endif
+
 }  // namespace
 
 ssize_t Read(int fd, void* dst, size_t dst_size) {
@@ -238,9 +246,7 @@
       return base::ErrStatus("Directory path %s is too long", dir_path.c_str());
     WIN32_FIND_DATAA ffd;
 
-    // Wrap FindClose to: (1) make the return unix-style; (2) deal w/ stdcall.
-    static auto find_close = [](HANDLE h) { return FindClose(h) ? 0 : -1; };
-    base::ScopedResource<HANDLE, find_close, nullptr, false,
+    base::ScopedResource<HANDLE, CloseFindHandle, nullptr, false,
                          base::PlatformHandleChecker>
         hFind(FindFirstFileA(glob_path.c_str(), &ffd));
     if (!hFind) {
diff --git a/src/trace_processor/metrics/sql/BUILD.gn b/src/trace_processor/metrics/sql/BUILD.gn
index 9432bf5..6d9f8f3 100644
--- a/src/trace_processor/metrics/sql/BUILD.gn
+++ b/src/trace_processor/metrics/sql/BUILD.gn
@@ -62,6 +62,7 @@
   "android/process_metadata.sql",
   "android/process_oom_score.sql",
   "android/profiler_smaps.sql",
+  "android/android_rt_runtime.sql",
   "android/mem_stats_priority_breakdown.sql",
   "android/android_multiuser.sql",
   "android/android_multiuser_populator.sql",
diff --git a/src/trace_processor/metrics/sql/android/android_rt_runtime.sql b/src/trace_processor/metrics/sql/android/android_rt_runtime.sql
new file mode 100644
index 0000000..01d8a64
--- /dev/null
+++ b/src/trace_processor/metrics/sql/android/android_rt_runtime.sql
@@ -0,0 +1,46 @@
+--
+-- Copyright 2022 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
+--
+--     https://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.
+
+DROP VIEW IF EXISTS rt_runtime_all;
+
+CREATE VIEW rt_runtime_all
+AS
+SELECT ts, dur, thread.name AS tname
+FROM sched_slice
+LEFT JOIN thread
+  USING (utid)
+LEFT JOIN process
+  USING (upid)
+WHERE priority < 100
+ORDER BY dur DESC;
+
+DROP VIEW IF EXISTS android_rt_runtime_output;
+
+CREATE VIEW android_rt_runtime_output
+AS
+SELECT
+  AndroidRtRuntimeMetric(
+    'max_runtime',
+    (SELECT dur FROM rt_runtime_all LIMIT 1),
+    'over_5ms_count',
+    (SELECT COUNT(*) FROM rt_runtime_all WHERE dur > 5e6),
+    'longest_rt_slices',
+    (
+      SELECT
+        RepeatedField(
+          AndroidRtRuntimeMetric_RtSlice(
+            'tname', tname, 'ts', ts, 'dur', dur))
+      FROM (SELECT ts, dur, tname FROM rt_runtime_all LIMIT 10)
+    ));
diff --git a/src/trace_processor/python/perfetto/trace_processor/metrics.descriptor.sha1 b/src/trace_processor/python/perfetto/trace_processor/metrics.descriptor.sha1
deleted file mode 100644
index 9658367..0000000
--- a/src/trace_processor/python/perfetto/trace_processor/metrics.descriptor.sha1
+++ /dev/null
@@ -1,6 +0,0 @@
-
-// SHA1(tools/gen_binary_descriptors)
-// 9fc6d77de57ec76a80b76aa282f4c7cf5ce55eec
-// SHA1(protos/perfetto/metrics/metrics.proto)
-// 3d9357a253dc649bdd67069d156fc6217f2e6a39
-  
\ No newline at end of file
diff --git a/src/trace_processor/rpc/httpd.cc b/src/trace_processor/rpc/httpd.cc
index 72c0f12..7b467a6 100644
--- a/src/trace_processor/rpc/httpd.cc
+++ b/src/trace_processor/rpc/httpd.cc
@@ -169,7 +169,6 @@
 
     // Terminate chunked stream.
     conn.SendResponseBody("0\r\n\r\n", 5);
-    conn.Close();
     return;
   }
 
diff --git a/src/trace_processor/trace_processor_shell.cc b/src/trace_processor/trace_processor_shell.cc
index 1eede6f..f601930 100644
--- a/src/trace_processor/trace_processor_shell.cc
+++ b/src/trace_processor/trace_processor_shell.cc
@@ -35,6 +35,7 @@
 #include "perfetto/base/time.h"
 #include "perfetto/ext/base/file_utils.h"
 #include "perfetto/ext/base/getopt.h"
+#include "perfetto/ext/base/optional.h"
 #include "perfetto/ext/base/scoped_file.h"
 #include "perfetto/ext/base/string_splitter.h"
 #include "perfetto/ext/base/string_utils.h"
@@ -1254,7 +1255,9 @@
       ".load-metrics-sql Reloads SQL from extension and custom metric paths\n"
       "                  specified in command line args.\n"
       ".run-metrics      Runs metrics specified in command line args\n"
-      "                  and prints the result.\n");
+      "                  and prints the result.\n"
+      ".width WIDTH      Changes the column width of interactive query\n"
+      "                  output.");
 }
 
 struct InteractiveOptions {
@@ -1268,6 +1271,7 @@
 util::Status StartInteractiveShell(const InteractiveOptions& options) {
   SetupLineEditor();
 
+  uint32_t column_width = options.column_width;
   for (;;) {
     ScopedLine line = GetLine("> ");
     if (!line)
@@ -1294,6 +1298,13 @@
         if (!status.ok()) {
           PERFETTO_ELOG("%s", status.c_message());
         }
+      } else if (strcmp(command, "width") == 0 && strlen(arg)) {
+        base::Optional<uint32_t> width = base::CStringToUInt32(arg);
+        if (!width) {
+          PERFETTO_ELOG("Invalid column width specified");
+          continue;
+        }
+        column_width = *width;
       } else if (strcmp(command, "load-metrics-sql") == 0) {
         base::Status status =
             LoadMetricsAndExtensionsSql(options.metrics, options.extensions);
@@ -1319,7 +1330,7 @@
 
     base::TimeNanos t_start = base::GetWallTimeNs();
     auto it = g_tp->ExecuteQuery(line.get());
-    PrintQueryResultInteractively(&it, t_start, options.column_width);
+    PrintQueryResultInteractively(&it, t_start, column_width);
   }
   return util::OkStatus();
 }
diff --git a/test/ci/linux_tests.sh b/test/ci/linux_tests.sh
index fd165f2..1a68291 100755
--- a/test/ci/linux_tests.sh
+++ b/test/ci/linux_tests.sh
@@ -41,7 +41,7 @@
   --perf-file=/ci/artifacts/perf/tp-perf-all.json \
   ${TP_SHELL}
 
-tools/run_python_api_tests.py ${TP_SHELL}
+python/run_tests.py ${TP_SHELL}
 
 # Don't run benchmarks under x86 (running out of address space because of 4GB)
 # limit or debug (too slow and pointless).
diff --git a/test/trace_processor/python/api_integrationtest.py b/test/trace_processor/python/api_integrationtest.py
deleted file mode 100644
index 392e28e..0000000
--- a/test/trace_processor/python/api_integrationtest.py
+++ /dev/null
@@ -1,50 +0,0 @@
-#!/usr/bin/env python3
-# Copyright (C) 2020 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.
-
-import os
-import unittest
-
-from trace_processor.api import TraceProcessor
-
-
-class TestApi(unittest.TestCase):
-
-  def test_trace_file(self):
-    # Get path to trace_processor_shell and construct TraceProcessor
-    tp = TraceProcessor(
-        file_path=os.path.join(os.environ["ROOT_DIR"], 'test', 'data',
-                               'example_android_trace_30s.pb'),
-        bin_path=os.environ["SHELL_PATH"])
-    qr_iterator = tp.query('select * from slice limit 10')
-    dur_result = [
-        178646, 119740, 58073, 155000, 173177, 20209377, 3589167, 90104, 275312,
-        65313
-    ]
-
-    for num, row in enumerate(qr_iterator):
-      self.assertEqual(row.type, 'internal_slice')
-      self.assertEqual(row.dur, dur_result[num])
-
-    # Test the batching logic by issuing a large query and ensuring we receive
-    # all rows, not just a truncated subset.
-    qr_iterator = tp.query('select count(*) as cnt from slice')
-    expected_count = next(qr_iterator).cnt
-    self.assertGreater(expected_count, 0)
-
-    qr_iterator = tp.query('select * from slice')
-    count = sum(1 for _ in qr_iterator)
-    self.assertEqual(count, expected_count)
-
-    tp.close()
diff --git a/tools/batch_trace_processor/perfetto/batch_trace_processor/api.py b/tools/batch_trace_processor/perfetto/batch_trace_processor/api.py
deleted file mode 100644
index fad76d8..0000000
--- a/tools/batch_trace_processor/perfetto/batch_trace_processor/api.py
+++ /dev/null
@@ -1,252 +0,0 @@
-#!/usr/bin/env python3
-# Copyright (C) 2021 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.
-
-
-"""Contains classes for BatchTraceProcessor API."""
-
-import concurrent.futures as cf
-import dataclasses as dc
-from typing import Any, Callable, Dict, Tuple, Union, List
-
-import pandas as pd
-
-from perfetto.trace_processor import TraceProcessor
-from perfetto.trace_processor import TraceProcessorException
-
-
-@dc.dataclass
-class _TpArg:
-  bin_path: str
-  verbose: bool
-  file: str
-
-
-@dc.dataclass
-class TraceFile:
-  trace_path: str
-  args: Dict[str, str]
-
-
-def _create_trace_file(path_or_trace_file: Union[str, TraceFile]) -> TraceFile:
-  if isinstance(path_or_trace_file, str):
-    return TraceFile(trace_path=path_or_trace_file, args={})
-  return path_or_trace_file
-
-
-class BatchTraceProcessor:
-  """Run ad-hoc SQL queries across many Perfetto traces.
-
-  Usage:
-    with BatchTraceProcessor(files=files) as btp:
-      dfs = btp.query('select * from slice')
-      for df in dfs:
-        print(df)
-  """
-
-  def __init__(self,
-               files: Union[List[TraceFile], List[str]],
-               bin_path: str = None,
-               verbose: bool = False):
-    """Creates a batch trace processor instance.
-
-    BatchTraceProcessor is the blessed way of running ad-hoc queries in
-    Python across many traces.
-
-    Args:
-      files: Either a list of trace file paths or a list of TraceFile objects
-        indicating the traces to load into this batch trace processor instance.
-      bin_path: Optional path to a trace processor shell binary to use to
-        load the traces.
-      verbose: Optional flag indiciating whether verbose trace processor
-        output should be printed to stderr.
-    """
-    self.tps = None
-    self.closed = False
-    self.executor = cf.ThreadPoolExecutor()
-
-    self.files = [_create_trace_file(file) for file in files]
-
-    def create_tp(arg: _TpArg) -> TraceProcessor:
-      return TraceProcessor(
-          file_path=arg.file, bin_path=arg.bin_path, verbose=arg.verbose)
-
-    tp_args = [
-        _TpArg(bin_path, verbose, file.trace_path) for file in self.files
-    ]
-    self.tps = list(self.executor.map(create_tp, tp_args))
-
-  def metric(self, metrics: List[str]):
-    """Computes the provided metrics.
-
-    The computation happens in parallel across all the traces.
-
-    Args:
-      metrics: A list of valid metrics as defined in TraceMetrics
-
-    Returns:
-      A list of TraceMetric protos (one for each trace).
-    """
-    return self.execute(lambda tp: tp.metric(metrics))
-
-  def query(self, sql: str):
-    """Executes the provided SQL statement (returning a single row).
-
-    The execution happens in parallel across all the traces.
-
-    Args:
-      sql: The SQL statement to execute.
-
-    Returns:
-      A list of Pandas dataframes with the result of executing the query (one
-      per trace).
-
-    Raises:
-      TraceProcessorException: An error occurred running the query.
-    """
-    return self.execute(lambda tp: tp.query(sql).as_pandas_dataframe())
-
-  def query_and_flatten(self, sql: str):
-    """Executes the provided SQL statement and flattens the result.
-
-    The execution happens in parallel across all the traces and the
-    resulting Pandas dataframes are flattened into a single dataframe.
-
-    Args:
-      sql: The SQL statement to execute.
-
-    Returns:
-      A Pandas dataframe containing the result of executing the query across all
-      the traces. The dataframe will have an additional column 'trace_path'
-      indicating the trace file associated with that row. Also, if |TraceFile|
-      objects were passed to the constructor, the contents of the |args|
-      dictionary will also be emitted as other columns (key being column name,
-      value being the value in the dataframe).
-
-      For example:
-        files = [TraceFile(trace_path='/tmp/path', args={"foo": "bar"})]
-        with BatchTraceProcessor(files=files) as btp:
-          df = btp.query_and_flatten('select count(1) as cnt from slice')
-
-      Then df will look like this:
-        cnt           trace_path              foo
-        100           /tmp/path               bar
-
-    Raises:
-      TraceProcessorException: An error occurred running the query.
-    """
-    return self.execute_and_flatten(lambda tp: tp.query(sql).
-                                    as_pandas_dataframe())
-
-  def query_single_result(self, sql: str):
-    """Executes the provided SQL statement (returning a single row).
-
-    The execution happens in parallel across all the traces.
-
-    Args:
-      sql: The SQL statement to execute. This statement should return exactly
-        one row on any trace.
-
-    Returns:
-      A list of values with the result of executing the query (one per trace).
-
-    Raises:
-      TraceProcessorException: An error occurred running the query or more than
-        one result was returned.
-    """
-
-    def query_single_result_inner(tp):
-      df = tp.query(sql).as_pandas_dataframe()
-      if len(df.index) != 1:
-        raise TraceProcessorException("Query should only return a single row")
-
-      if len(df.columns) != 1:
-        raise TraceProcessorException(
-            "Query should only return a single column")
-
-      return df.iloc[0, 0]
-
-    return self.execute(query_single_result_inner)
-
-  def execute(self, fn: Callable[[TraceProcessor], Any]) -> List[Any]:
-    """Executes the provided function.
-
-    The execution happens in parallel across all the trace processor instances
-    owned by this object.
-
-    Args:
-      fn: The function to execute.
-
-    Returns:
-      A list of values with the result of executing the fucntion (one per
-      trace).
-    """
-    return list(self.executor.map(fn, self.tps))
-
-  def execute_and_flatten(self, fn: Callable[[TraceProcessor], pd.DataFrame]
-                         ) -> pd.DataFrame:
-    """Executes the provided function and flattens the result.
-
-    The execution happens in parallel across all the trace processor
-    instances owned by this object and the returned Pandas dataframes are
-    flattened into a single dataframe.
-
-    Args:
-      fn: The function to execute which returns a Pandas dataframe.
-
-    Returns:
-      A Pandas dataframe containing the result of executing the query across all
-      the traces. Extra columns containing the file path and args will
-      be added to the dataframe (see |query_and_flatten| for details).
-    """
-
-    def wrapped(pair: Tuple[TraceProcessor, TraceFile]):
-      (tp, file) = pair
-      df = fn(tp)
-      df["trace_path"] = file.trace_path
-      for key, value in file.args.items():
-        df[key] = value
-      return df
-
-    df = pd.concat(list(self.executor.map(wrapped, zip(self.tps, self.files))))
-    return df.reset_index(drop=True)
-
-  def close(self):
-    """Closes this batch trace processor instance.
-
-    This closes all spawned trace processor instances, releasing all the memory
-    and resources those instances take.
-
-    No further calls to other methods in this class should be made after
-    calling this method.
-    """
-    if self.closed:
-      return
-    self.closed = True
-    self.executor.shutdown()
-
-    if self.tps:
-      for tp in self.tps:
-        tp.close()
-
-  def __enter__(self):
-    return self
-
-  def __exit__(self, a, b, c):
-    del a, b, c  # Unused.
-    self.close()
-    return False
-
-  def __del__(self):
-    self.close()
diff --git a/tools/gen_binary_descriptors b/tools/gen_binary_descriptors
index 5388c48..dbbf8c7 100755
--- a/tools/gen_binary_descriptors
+++ b/tools/gen_binary_descriptors
@@ -26,10 +26,10 @@
 
 SOURCE_TARGET = [
     ('protos/perfetto/trace_processor/trace_processor.proto',
-     'src/trace_processor/python/perfetto/trace_processor/trace_processor.descriptor'
+     'python/perfetto/trace_processor/trace_processor.descriptor'
     ),
     ('protos/perfetto/metrics/metrics.proto',
-     'src/trace_processor/python/perfetto/trace_processor/metrics.descriptor'),
+     'python/perfetto/trace_processor/metrics.descriptor'),
 ]
 
 ROOT_DIR = os.path.dirname(os.path.dirname(os.path.realpath(__file__)))
diff --git a/tools/heap_profile b/tools/heap_profile
index a327133..6eaaa47 100755
--- a/tools/heap_profile
+++ b/tools/heap_profile
@@ -576,7 +576,7 @@
 
 
 # BEGIN_SECTION_GENERATED_BY(roll-prebuilts)
-# Revision: v22.1
+# Revision: v23.0
 PERFETTO_PREBUILT_MANIFEST = [{
     'tool':
         'trace_to_text',
@@ -585,11 +585,11 @@
     'file_name':
         'trace_to_text',
     'file_size':
-        6956224,
+        6939864,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v22.1/mac-amd64/trace_to_text',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v23.0/mac-amd64/trace_to_text',
     'sha256':
-        'b40ec5ef358e35ecebacd490dba8320aa3a0bf223da4219f2eba1d3ee7ef3a48',
+        '1626880d1fbec8efc9702583b6c22d617079a08226df414d65888a6b3c7572c8',
     'platform':
         'darwin',
     'machine': ['x86_64']
@@ -601,11 +601,11 @@
     'file_name':
         'trace_to_text.exe',
     'file_size':
-        6663680,
+        6658560,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v22.1/windows-amd64/trace_to_text.exe',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v23.0/windows-amd64/trace_to_text.exe',
     'sha256':
-        '603eb120cbb5db41cf258aa742902ea73ee700d8e6d61900f64fa996d3e1714b',
+        '14334fc93ecb0498201bf2ad72ebe69938c43cb6641cf94cfd7ad646603039ed',
     'platform':
         'win32',
     'machine': ['amd64']
@@ -617,11 +617,11 @@
     'file_name':
         'trace_to_text',
     'file_size':
-        7518480,
+        7513024,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v22.1/linux-amd64/trace_to_text',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v23.0/linux-amd64/trace_to_text',
     'sha256':
-        '480e851deb76413f3f7ab113f230bcac19ca4bdd9f5c275f7a4d3cc9318c2db6',
+        'e19163f152717d9922e1926489b64c50b30ea772ebb0f21f6abb6e8cd25615b3',
     'platform':
         'linux',
     'machine': ['x86_64']
diff --git a/tools/open_trace_in_ui b/tools/open_trace_in_ui
index c2360bb..0ec63b4 100755
--- a/tools/open_trace_in_ui
+++ b/tools/open_trace_in_ui
@@ -50,32 +50,41 @@
   print(colors + msg + ANSI.END)
 
 
-def open_trace_in_browser(path):
+def open_trace(path, open_browser):
   # We reuse the HTTP+RPC port because it's the only one allowed by the CSP.
   PORT = 9001
   os.chdir(os.path.dirname(path))
   fname = os.path.basename(path)
   socketserver.TCPServer.allow_reuse_address = True
   with socketserver.TCPServer(('127.0.0.1', PORT), HttpHandler) as httpd:
-    webbrowser.open_new_tab(
-        'https://ui.perfetto.dev/#!/?url=http://127.0.0.1:%d/%s' %
-        (PORT, fname))
+    if open_browser:
+      webbrowser.open_new_tab(
+          'https://ui.perfetto.dev/#!/?url=http://127.0.0.1:%d/%s' %
+          (PORT, fname))
+    else:
+      print('Open URL in browser: '
+            'https://ui.perfetto.dev/#!/?url=http://127.0.0.1:%d/%s' %
+            (PORT, fname))
+
     while httpd.__dict__.get('last_request') != '/' + fname:
       httpd.handle_request()
 
 
 def main():
   examples = '\n'.join([
-      ANSI.BOLD + 'Usage:' + ANSI.END, '  -i path/trace_file_name'
+      ANSI.BOLD + 'Usage:' + ANSI.END, '  -i path/trace_file_name [-n]'
   ])
   parser = argparse.ArgumentParser(
       epilog=examples, formatter_class=argparse.RawTextHelpFormatter)
 
   help = 'Input trace filename'
   parser.add_argument('-i', '--trace', help=help)
+  parser.add_argument('-n', '--no-open-browser', action='store_true',
+                      default=False)
 
   args = parser.parse_args()
   trace_file = args.trace
+  open_browser = not args.no_open_browser
 
   if trace_file is None:
     prt('Please specify trace file name with -i/--trace argument', ANSI.RED)
@@ -85,7 +94,7 @@
     sys.exit(1)
 
   prt('Opening the trace (%s) in the browser' % trace_file)
-  open_trace_in_browser(trace_file)
+  open_trace(trace_file, open_browser)
 
 
 if __name__ == '__main__':
diff --git a/tools/record_android_trace b/tools/record_android_trace
index bc3f826..ccd8473 100755
--- a/tools/record_android_trace
+++ b/tools/record_android_trace
@@ -380,7 +380,7 @@
 
 
 # BEGIN_SECTION_GENERATED_BY(roll-prebuilts)
-# Revision: v22.1
+# Revision: v23.0
 PERFETTO_PREBUILT_MANIFEST = [{
     'tool':
         'tracebox',
@@ -389,11 +389,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        1034636,
+        1038732,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v22.1/android-arm/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v23.0/android-arm/tracebox',
     'sha256':
-        'f97875395431b87a38830f73f22b45fedbf5bbcefcdd9f61ab87bcd64b0594e4'
+        'b37507c28e8eade93fa9f0da86d6b9779f69aadea9d7e33cd3b85866c7b755fc'
 }, {
     'tool':
         'tracebox',
@@ -402,11 +402,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        1563840,
+        1572032,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v22.1/android-arm64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v23.0/android-arm64/tracebox',
     'sha256':
-        '45cca4277d83b30d33ff5e2bf99120844498d5512bd15c40301157921b3482aa'
+        '83306c567da4f1b3eb910a0407abf88fcc099d22a191ceb096837381b2ebff79'
 }, {
     'tool':
         'tracebox',
@@ -415,11 +415,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        1591652,
+        1595748,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v22.1/android-x86/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v23.0/android-x86/tracebox',
     'sha256':
-        'fc4f86b92837d3efee4df202da092cd5a945613078b4fe4b8829de0794bf5cdb'
+        '527c2ff54350fe73197b5164e6f7ed21f3d10c38a667e57781222d679dca4707'
 }, {
     'tool':
         'tracebox',
@@ -428,11 +428,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        1809600,
+        1821888,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v22.1/android-x64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v23.0/android-x64/tracebox',
     'sha256':
-        '4610eff243aec24dc0d948503464e466d73045d0383bc625ee820cfd43d3f9a5'
+        '99cfbb37e7c3ae4ea70fc51811601c409fcd81589d4311a4b805a67f4760203b'
 }]
 
 
diff --git a/tools/trace_processor b/tools/trace_processor
index e532901..69e8a91 100755
--- a/tools/trace_processor
+++ b/tools/trace_processor
@@ -27,7 +27,7 @@
 TOOL_NAME = 'trace_processor_shell'
 
 # BEGIN_SECTION_GENERATED_BY(roll-prebuilts)
-# Revision: v22.1
+# Revision: v23.0
 PERFETTO_PREBUILT_MANIFEST = [{
     'tool':
         'trace_processor_shell',
@@ -36,11 +36,11 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        6924200,
+        6924224,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v22.1/mac-amd64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v23.0/mac-amd64/trace_processor_shell',
     'sha256':
-        '428fc3b61b507053c72c4c65876c18a80c1dae013e5d9c8c4bbe10d08c3dddaf',
+        '08bde02f5920fa1af8dc64b857db9c000ac6940f2bb49030742d62adf04e3ff3',
     'platform':
         'darwin',
     'machine': ['x86_64']
@@ -52,11 +52,11 @@
     'file_name':
         'trace_processor_shell.exe',
     'file_size':
-        6655488,
+        6656512,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v22.1/windows-amd64/trace_processor_shell.exe',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v23.0/windows-amd64/trace_processor_shell.exe',
     'sha256':
-        'e8bd04f3de59a483036f58b86b47d79a9c2bb00f9b1c9590ed1a36250b4f91a0',
+        '8151996f74ad7b3e6fb8c04c5acd351a74fe24e939902cc74380e9789c203309',
     'platform':
         'win32',
     'machine': ['amd64']
@@ -68,11 +68,11 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        7495384,
+        7495496,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v22.1/linux-amd64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v23.0/linux-amd64/trace_processor_shell',
     'sha256':
-        '6a7d27e91ab233e82786d1652739dc84e9d86dde5782ec69392f2b5977142596',
+        'e71bf3c16224a8ecbfb2485864dd3762f642fb052acf560d0631ef6c18a740fd',
     'platform':
         'linux',
     'machine': ['x86_64']
@@ -84,11 +84,11 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        4870060,
+        4867252,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v22.1/linux-arm/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v23.0/linux-arm/trace_processor_shell',
     'sha256':
-        '3e260ece4b254886b8cf7db0abf05257e747ae69d8d8ea252edb9b518f7cf34a',
+        'e909add14c46bcc5a2ed1736169886696867cebc17c5218cb9c143caced886f5',
     'platform':
         'linux',
     'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -100,11 +100,11 @@
     'file_name':
         'trace_processor_shell',
     'file_size':
-        6742552,
+        6742056,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v22.1/linux-arm64/trace_processor_shell',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v23.0/linux-arm64/trace_processor_shell',
     'sha256':
-        'aa4834949fe4b81d7289b9b15e57c4a5437773b356dced7b55afe46655f696c2',
+        'b2cad70adbb139a8878d69999e0dfad40ca24e94f001b7bd67dcbbedcdb6c6dc',
     'platform':
         'linux',
     'machine': ['aarch64']
diff --git a/tools/tracebox b/tools/tracebox
index 069053e..ece2014 100755
--- a/tools/tracebox
+++ b/tools/tracebox
@@ -27,7 +27,7 @@
 TOOL_NAME = 'tracebox'
 
 # BEGIN_SECTION_GENERATED_BY(roll-prebuilts)
-# Revision: v22.1
+# Revision: v23.0
 PERFETTO_PREBUILT_MANIFEST = [{
     'tool':
         'tracebox',
@@ -36,11 +36,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        1316160,
+        1316192,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v22.1/mac-amd64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v23.0/mac-amd64/tracebox',
     'sha256':
-        '48539da3f73f04fe1c22dcd0aeb8cbed45405df3f4dfb3f2ff4077d6d023463e',
+        '54101d81876ceed8c2d24701835fd11cd0edc4d43af655ed0420013809138951',
     'platform':
         'darwin',
     'machine': ['x86_64']
@@ -52,11 +52,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        1733320,
+        1739352,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v22.1/linux-amd64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v23.0/linux-amd64/tracebox',
     'sha256':
-        'b9ab0a535a7e042675f690b0543e158cad01fb8b3e56dc3b7748f44b821ff6d5',
+        'b42cfaadf4dd35c5c540775a0d909dcbfa5338d1c022767db3a4b1324c19e58b',
     'platform':
         'linux',
     'machine': ['x86_64']
@@ -68,11 +68,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        1733320,
+        1739352,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v22.1/linux-amd64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v23.0/linux-amd64/tracebox',
     'sha256':
-        'b9ab0a535a7e042675f690b0543e158cad01fb8b3e56dc3b7748f44b821ff6d5',
+        'b42cfaadf4dd35c5c540775a0d909dcbfa5338d1c022767db3a4b1324c19e58b',
     'platform':
         'linux',
     'machine': ['x86_64']
@@ -84,11 +84,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        998700,
+        1002132,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v22.1/linux-arm/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v23.0/linux-arm/tracebox',
     'sha256':
-        '825a1705f426f333c21679e38676c6118442b7b48379a0e2c2b87f052b3e2f8d',
+        '65351d250c3c52f8a69afa78069d9a87931c45cf3bf415c207e93de6eb75dbbe',
     'platform':
         'linux',
     'machine': ['armv6l', 'armv7l', 'armv8l']
@@ -100,11 +100,11 @@
     'file_name':
         'tracebox',
     'file_size':
-        1613536,
+        1619248,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v22.1/linux-arm64/tracebox',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v23.0/linux-arm64/tracebox',
     'sha256':
-        'e664b2374a6842141b0f8c68838a37d0d387bae576b934db3168b4ae4999f182',
+        '2cb0be71da6b46167a8dd4a9d8af5229dba89b8e508554c16cde20b81f175d3d',
     'platform':
         'linux',
     'machine': ['aarch64']
diff --git a/tools/traceconv b/tools/traceconv
index 406bd4d..4754a87 100755
--- a/tools/traceconv
+++ b/tools/traceconv
@@ -27,7 +27,7 @@
 TOOL_NAME = 'trace_to_text'
 
 # BEGIN_SECTION_GENERATED_BY(roll-prebuilts)
-# Revision: v22.1
+# Revision: v23.0
 PERFETTO_PREBUILT_MANIFEST = [{
     'tool':
         'trace_to_text',
@@ -36,11 +36,11 @@
     'file_name':
         'trace_to_text',
     'file_size':
-        6956224,
+        6939864,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v22.1/mac-amd64/trace_to_text',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v23.0/mac-amd64/trace_to_text',
     'sha256':
-        'b40ec5ef358e35ecebacd490dba8320aa3a0bf223da4219f2eba1d3ee7ef3a48',
+        '1626880d1fbec8efc9702583b6c22d617079a08226df414d65888a6b3c7572c8',
     'platform':
         'darwin',
     'machine': ['x86_64']
@@ -52,11 +52,11 @@
     'file_name':
         'trace_to_text',
     'file_size':
-        7518480,
+        7513024,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v22.1/linux-amd64/trace_to_text',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v23.0/linux-amd64/trace_to_text',
     'sha256':
-        '480e851deb76413f3f7ab113f230bcac19ca4bdd9f5c275f7a4d3cc9318c2db6',
+        'e19163f152717d9922e1926489b64c50b30ea772ebb0f21f6abb6e8cd25615b3',
     'platform':
         'linux',
     'machine': ['x86_64']
@@ -68,11 +68,11 @@
     'file_name':
         'trace_to_text.exe',
     'file_size':
-        6663680,
+        6658560,
     'url':
-        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v22.1/windows-amd64/trace_to_text.exe',
+        'https://commondatastorage.googleapis.com/perfetto-luci-artifacts/v23.0/windows-amd64/trace_to_text.exe',
     'sha256':
-        '603eb120cbb5db41cf258aa742902ea73ee700d8e6d61900f64fa996d3e1714b',
+        '14334fc93ecb0498201bf2ad72ebe69938c43cb6641cf94cfd7ad646603039ed',
     'platform':
         'win32',
     'machine': ['amd64']