Merge "trace_processor: initial implementation of the args table"
diff --git a/include/perfetto/base/metatrace.h b/include/perfetto/base/metatrace.h
index b30b910..95313fc 100644
--- a/include/perfetto/base/metatrace.h
+++ b/include/perfetto/base/metatrace.h
@@ -19,6 +19,8 @@
 
 #include <string.h>
 
+#include <string>
+
 #include "perfetto/base/logging.h"
 #include "perfetto/base/utils.h"
 
@@ -31,6 +33,11 @@
     WriteEvent('B', evt_name, cpu);
   }
 
+  MetaTrace(const std::string& str, size_t cpu)
+      : str_copy_(str), evt_name_(str_copy_.c_str()), cpu_(cpu) {
+    WriteEvent('B', evt_name_, cpu);
+  }
+
   ~MetaTrace() { WriteEvent('E', evt_name_, cpu_); }
 
  private:
@@ -39,6 +46,7 @@
 
   void WriteEvent(char type, const char* evt_name, size_t cpu);
 
+  std::string str_copy_;
   const char* const evt_name_;
   const size_t cpu_;
 };
diff --git a/include/perfetto/base/paged_memory.h b/include/perfetto/base/paged_memory.h
index 615a226..ea2aaae 100644
--- a/include/perfetto/base/paged_memory.h
+++ b/include/perfetto/base/paged_memory.h
@@ -76,8 +76,8 @@
   void EnsureCommitted(size_t /*committed_size*/) {}
 #endif  // TRACK_COMMITTED_SIZE()
 
-  void* Get() const noexcept;
-  bool IsValid() const noexcept;
+  inline void* Get() const noexcept { return p_; }
+  inline bool IsValid() const noexcept { return !!p_; }
 
  private:
   PagedMemory(char* p, size_t size);
diff --git a/include/perfetto/tracing/core/tracing_service.h b/include/perfetto/tracing/core/tracing_service.h
index cdeb575..dda7193 100644
--- a/include/perfetto/tracing/core/tracing_service.h
+++ b/include/perfetto/tracing/core/tracing_service.h
@@ -72,6 +72,18 @@
     virtual void RegisterDataSource(const DataSourceDescriptor&) = 0;
     virtual void UnregisterDataSource(const std::string& name) = 0;
 
+    // Associate the trace writer with the given |writer_id| with
+    // |target_buffer|. The service may use this information to retrieve and
+    // copy uncommitted chunks written by the trace writer into its associated
+    // buffer, e.g. when a producer process crashes or when a flush is
+    // necessary.
+    virtual void RegisterTraceWriter(uint32_t writer_id,
+                                     uint32_t target_buffer) = 0;
+
+    // Remove the association of the trace writer previously created via
+    // RegisterTraceWriter.
+    virtual void UnregisterTraceWriter(uint32_t writer_id) = 0;
+
     // Called by the Producer to signal that some pages in the shared memory
     // buffer (shared between Service and Producer) have changed.
     using CommitDataCallback = std::function<void()>;
diff --git a/protos/perfetto/ipc/producer_port.proto b/protos/perfetto/ipc/producer_port.proto
index 0a27f82..8217795 100644
--- a/protos/perfetto/ipc/producer_port.proto
+++ b/protos/perfetto/ipc/producer_port.proto
@@ -38,6 +38,15 @@
   rpc UnregisterDataSource(UnregisterDataSourceRequest)
       returns (UnregisterDataSourceResponse) {}
 
+  // Associates a trace writer with its target buffer.
+  rpc RegisterTraceWriter(RegisterTraceWriterRequest)
+      returns (RegisterTraceWriterResponse) {}
+
+  // Removes a trace writer association previously added by
+  // RegisterTraceWriter.
+  rpc UnregisterTraceWriter(UnregisterTraceWriterRequest)
+      returns (UnregisterTraceWriterResponse) {}
+
   // Sent by the client to request the service to:
   // 1) Move some chunks from the shmem buffer into the logging buffer.
   // 2) Patch the content of some chunks previously moved.
@@ -97,6 +106,27 @@
 
 message UnregisterDataSourceResponse {}
 
+// Arguments for rpc RegisterTraceWriter().
+
+message RegisterTraceWriterRequest {
+  // The ID of a producer's trace writer.
+  optional uint32 trace_writer_id = 1;
+
+  // The ID of the target buffer that the trace writer commits its chunks to.
+  optional uint32 target_buffer = 2;
+}
+
+message RegisterTraceWriterResponse {}
+
+// Arguments for rpc UnregisterTraceWriter().
+
+message UnregisterTraceWriterRequest {
+  // The ID of a producer's trace writer.
+  optional uint32 trace_writer_id = 1;
+}
+
+message UnregisterTraceWriterResponse {}
+
 // Arguments for rpc CommitData().
 // See commit_data_request.proto for CommitDataRequest. That has its own file
 // because it is used also as input to generate the C++ classes in tracing/core
diff --git a/src/base/metatrace.cc b/src/base/metatrace.cc
index 19e703b..baae164 100644
--- a/src/base/metatrace.cc
+++ b/src/base/metatrace.cc
@@ -45,11 +45,15 @@
   if (fd == -1)
     return;
 
+  // The JSON event format expects both "pid" and "tid" fields to create
+  // per-process tracks. Here what we really want to achieve is having one track
+  // per cpu. So we just pretend that each CPU is its own process with
+  // pid == tid == cpu.
   char json[256];
   int len = sprintf(json,
                     "{\"ts\": %f, \"cat\": \"PERF\", \"ph\": \"%c\", \"name\": "
-                    "\"%s\", \"pid\": %zu},\n",
-                    GetWallTimeNs().count() / 1000.0, type, evt_name, cpu);
+                    "\"%s\", \"pid\": %zu, \"tid\": %zu},\n",
+                    GetWallTimeNs().count() / 1000.0, type, evt_name, cpu, cpu);
   ignore_result(WriteAll(fd, json, static_cast<size_t>(len)));
 }
 
diff --git a/src/base/paged_memory.cc b/src/base/paged_memory.cc
index 79b7b2e..b5433d5 100644
--- a/src/base/paged_memory.cc
+++ b/src/base/paged_memory.cc
@@ -152,13 +152,5 @@
 }
 #endif  // TRACK_COMMITTED_SIZE()
 
-void* PagedMemory::Get() const noexcept {
-  return p_;
-}
-
-bool PagedMemory::IsValid() const noexcept {
-  return p_;
-}
-
 }  // namespace base
 }  // namespace perfetto
diff --git a/src/profiling/memory/README.md b/src/profiling/memory/README.md
index e3b4669..619dba8 100644
--- a/src/profiling/memory/README.md
+++ b/src/profiling/memory/README.md
@@ -4,7 +4,22 @@
 subject to frequent change and will be obsoleted once heapprofd is integrated
 into Perfetto._
 
-Currently heapprofd only works with SELinux disabled and when run as root.
+
+## Using convenience script
+
+Use the `tools/heap_profile` script to heap profile a process. See all the
+arguments using `tools/heap_profile -h`, or use the defaults and just profile a
+process (e.g. `system_server`):
+
+```
+tools/heap_profile --name system_server
+```
+
+This will create a heap dump every second for a default of 1 minute.
+Head to http://pprof/ and upload the gzipped protos to get a visualization.
+
+## Manual
+Currently heapprofd only works with SELinux disabled.
 
 To start profiling the process `${PID}`, run the following sequence of commands.
 Adjust the `INTERVAL` to trade-off runtime impact for higher accuracy of the
diff --git a/src/traced/probes/ftrace/cpu_reader.cc b/src/traced/probes/ftrace/cpu_reader.cc
index 602bcc2..1cffd75 100644
--- a/src/traced/probes/ftrace/cpu_reader.cc
+++ b/src/traced/probes/ftrace/cpu_reader.cc
@@ -27,6 +27,7 @@
 #include "perfetto/base/build_config.h"
 #include "perfetto/base/logging.h"
 #include "perfetto/base/metatrace.h"
+#include "perfetto/base/optional.h"
 #include "perfetto/base/utils.h"
 #include "src/traced/probes/ftrace/ftrace_data_source.h"
 #include "src/traced/probes/ftrace/proto_translation_table.h"
@@ -40,6 +41,33 @@
 
 namespace {
 
+// For further documentation of these constants see the kernel source:
+// linux/include/linux/ring_buffer.h
+// Some information about the values of these constants are exposed to user
+// space at: /sys/kernel/debug/tracing/events/header_event
+constexpr uint32_t kTypeDataTypeLengthMax = 28;
+constexpr uint32_t kTypePadding = 29;
+constexpr uint32_t kTypeTimeExtend = 30;
+constexpr uint32_t kTypeTimeStamp = 31;
+
+constexpr uint32_t kMainThread = 255;  // for METATRACE
+
+struct PageHeader {
+  uint64_t timestamp;
+  uint64_t size;
+  uint64_t overwrite;
+};
+
+struct EventHeader {
+  uint32_t type_or_length : 5;
+  uint32_t time_delta : 27;
+};
+
+struct TimeStamp {
+  uint64_t tv_nsec;
+  uint64_t tv_sec;
+};
+
 bool ReadIntoString(const uint8_t* start,
                     const uint8_t* end,
                     uint32_t field_id,
@@ -81,36 +109,40 @@
   return true;
 }
 
-void SetBlocking(int fd, bool is_blocking) {
+bool SetBlocking(int fd, bool is_blocking) {
   int flags = fcntl(fd, F_GETFL, 0);
   flags = (is_blocking) ? (flags & ~O_NONBLOCK) : (flags | O_NONBLOCK);
-  PERFETTO_CHECK(fcntl(fd, F_SETFL, flags) == 0);
+  return fcntl(fd, F_SETFL, flags) == 0;
 }
 
-// For further documentation of these constants see the kernel source:
-// linux/include/linux/ring_buffer.h
-// Some information about the values of these constants are exposed to user
-// space at: /sys/kernel/debug/tracing/events/header_event
-constexpr uint32_t kTypeDataTypeLengthMax = 28;
-constexpr uint32_t kTypePadding = 29;
-constexpr uint32_t kTypeTimeExtend = 30;
-constexpr uint32_t kTypeTimeStamp = 31;
+base::Optional<PageHeader> ParsePageHeader(const uint8_t** ptr,
+                                           uint16_t page_header_size_len) {
+  const uint8_t* end_of_page = *ptr + base::kPageSize;
+  PageHeader page_header;
+  if (!CpuReader::ReadAndAdvance<uint64_t>(ptr, end_of_page,
+                                           &page_header.timestamp))
+    return base::nullopt;
 
-struct PageHeader {
-  uint64_t timestamp;
-  uint64_t size;
-  uint64_t overwrite;
-};
+  uint32_t overwrite_and_size;
 
-struct EventHeader {
-  uint32_t type_or_length : 5;
-  uint32_t time_delta : 27;
-};
+  // On little endian, we can just read a uint32_t and reject the rest of the
+  // number later.
+  if (!CpuReader::ReadAndAdvance<uint32_t>(
+          ptr, end_of_page, base::AssumeLittleEndian(&overwrite_and_size)))
+    return base::nullopt;
 
-struct TimeStamp {
-  uint64_t tv_nsec;
-  uint64_t tv_sec;
-};
+  page_header.size = (overwrite_and_size & 0x000000000000ffffull) >> 0;
+  page_header.overwrite = (overwrite_and_size & 0x00000000ff000000ull) >> 24;
+  PERFETTO_DCHECK(page_header.size <= base::kPageSize);
+
+  // Reject rest of the number, if applicable. On 32-bit, size_bytes - 4 will
+  // evaluate to 0 and this will be a no-op. On 64-bit, this will advance by 4
+  // bytes.
+  PERFETTO_DCHECK(page_header_size_len >= 4);
+  *ptr += page_header_size_len - 4;
+
+  return base::make_optional(page_header);
+}
 
 }  // namespace
 
@@ -129,7 +161,7 @@
 
   // Make reads from the raw pipe blocking so that splice() can sleep.
   PERFETTO_CHECK(trace_fd_);
-  SetBlocking(*trace_fd_, true);
+  PERFETTO_CHECK(SetBlocking(*trace_fd_, true));
 
   // We need a non-default SIGPIPE handler to make it so that the blocking
   // splice() is woken up when the ~CpuReader() dtor destroys the pipes.
@@ -162,10 +194,14 @@
   // and only then close the staging pipe.
   cmd_ = ThreadCtl::kExit;
   trace_fd_.reset();
-  pthread_kill(worker_thread_.native_handle(), SIGPIPE);
+  InterruptWorkerThreadWithSignal();
   worker_thread_.join();
 }
 
+void CpuReader::InterruptWorkerThreadWithSignal() {
+  pthread_kill(worker_thread_.native_handle(), SIGPIPE);
+}
+
 // static
 void CpuReader::RunWorkerThread(size_t cpu,
                                 int trace_fd,
@@ -244,8 +280,11 @@
 #endif
 }
 
-bool CpuReader::Drain(const std::set<FtraceDataSource*>& data_sources) {
+// Invoked on the main thread by FtraceController, |drain_rate_ms| after the
+// first CPU wakes up from the blocking read()/splice().
+void CpuReader::Drain(const std::set<FtraceDataSource*>& data_sources) {
   PERFETTO_DCHECK_THREAD(thread_checker_);
+  PERFETTO_METATRACE("Drain(" + std::to_string(cpu_) + ")", kMainThread);
   for (;;) {
     uint8_t* buffer = GetBuffer();
     long bytes =
@@ -271,8 +310,6 @@
       bundle->set_overwrite_count(metadata->overwrite_count);
     }
   }
-
-  return true;
 }
 
 uint8_t* CpuReader::GetBuffer() {
@@ -298,36 +335,18 @@
   const uint8_t* const start_of_page = ptr;
   const uint8_t* const end_of_page = ptr + base::kPageSize;
 
-  PageHeader page_header;
-  if (!ReadAndAdvance<uint64_t>(&ptr, end_of_page, &page_header.timestamp))
+  auto page_header = ParsePageHeader(&ptr, table->page_header_size_len());
+  if (!page_header.has_value())
     return 0;
 
-  // TODO(fmayer): Do kernel deepdive to double check this.
-  uint16_t size_bytes = table->ftrace_page_header_spec().size.size;
-  PERFETTO_CHECK(size_bytes >= 4);
-  uint32_t overwrite_and_size;
-  // On little endian, we can just read a uint32_t and reject the rest of the
-  // number later.
-  if (!ReadAndAdvance<uint32_t>(&ptr, end_of_page,
-                                base::AssumeLittleEndian(&overwrite_and_size)))
-    return 0;
+  // ParsePageHeader advances |ptr| to point past the end of the header.
 
-  page_header.size = (overwrite_and_size & 0x000000000000ffffull) >> 0;
-  page_header.overwrite = (overwrite_and_size & 0x00000000ff000000ull) >> 24;
-  metadata->overwrite_count = static_cast<uint32_t>(page_header.overwrite);
-
-  PERFETTO_DCHECK(page_header.size <= base::kPageSize);
-
-  // Reject rest of the number, if applicable. On 32-bit, size_bytes - 4 will
-  // evaluate to 0 and this will be a no-op. On 64-bit, this will advance by 4
-  // bytes.
-  ptr += size_bytes - 4;
-
-  const uint8_t* const end = ptr + page_header.size;
+  metadata->overwrite_count = static_cast<uint32_t>(page_header->overwrite);
+  const uint8_t* const end = ptr + page_header->size;
   if (end > end_of_page)
     return 0;
 
-  uint64_t timestamp = page_header.timestamp;
+  uint64_t timestamp = page_header->timestamp;
 
   while (ptr < end) {
     EventHeader event_header;
@@ -371,10 +390,10 @@
       // Data record:
       default: {
         PERFETTO_CHECK(event_header.type_or_length <= kTypeDataTypeLengthMax);
-        // type_or_length is <=28 so it represents the length of a data record.
-        // if == 0, this is an extended record and the size of the record is
-        // stored in the first uint32_t word in the payload.
-        // See Kernel's include/linux/ring_buffer.h
+        // type_or_length is <=28 so it represents the length of a data
+        // record. if == 0, this is an extended record and the size of the
+        // record is stored in the first uint32_t word in the payload. See
+        // Kernel's include/linux/ring_buffer.h
         uint32_t event_size;
         if (event_header.type_or_length == 0) {
           if (!ReadAndAdvance<uint32_t>(&ptr, end, &event_size))
diff --git a/src/traced/probes/ftrace/cpu_reader.h b/src/traced/probes/ftrace/cpu_reader.h
index e1ff39b..3d3112f 100644
--- a/src/traced/probes/ftrace/cpu_reader.h
+++ b/src/traced/probes/ftrace/cpu_reader.h
@@ -67,7 +67,8 @@
   // Drains all available data from the staging pipe into the buffer of the
   // passed data sources.
   // Should be called in response to the |on_data_available| callback.
-  bool Drain(const std::set<FtraceDataSource*>&);
+  void Drain(const std::set<FtraceDataSource*>&);
+  void InterruptWorkerThreadWithSignal();
 
   template <typename T>
   static bool ReadAndAdvance(const uint8_t** ptr, const uint8_t* end, T* out) {
diff --git a/src/traced/probes/ftrace/ftrace_controller.cc b/src/traced/probes/ftrace/ftrace_controller.cc
index 707482f..189066f 100644
--- a/src/traced/probes/ftrace/ftrace_controller.cc
+++ b/src/traced/probes/ftrace/ftrace_controller.cc
@@ -46,16 +46,6 @@
 namespace perfetto {
 namespace {
 
-#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
-constexpr const char* kTracingPaths[] = {
-    "/sys/kernel/tracing/", "/sys/kernel/debug/tracing/", nullptr,
-};
-#else
-constexpr const char* kTracingPaths[] = {
-    "/sys/kernel/debug/tracing/", nullptr,
-};
-#endif
-
 constexpr int kDefaultDrainPeriodMs = 100;
 constexpr int kMinDrainPeriodMs = 1;
 constexpr int kMaxDrainPeriodMs = 1000 * 60;
@@ -86,6 +76,14 @@
 
 }  // namespace
 
+const char* const FtraceController::kTracingPaths[] = {
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
+    "/sys/kernel/tracing/", "/sys/kernel/debug/tracing/", nullptr,
+#else
+    "/sys/kernel/debug/tracing/", nullptr,
+#endif
+};
+
 // Method of last resort to reset ftrace state.
 // We don't know what state the rest of the system and process is so as far
 // as possible avoid allocations.
diff --git a/src/traced/probes/ftrace/ftrace_controller.h b/src/traced/probes/ftrace/ftrace_controller.h
index 15d7dde..b1845ce 100644
--- a/src/traced/probes/ftrace/ftrace_controller.h
+++ b/src/traced/probes/ftrace/ftrace_controller.h
@@ -49,6 +49,8 @@
 // Utility class for controlling ftrace.
 class FtraceController {
  public:
+  static const char* const kTracingPaths[];
+
   class Observer {
    public:
     virtual ~Observer();
diff --git a/src/traced/probes/ftrace/ftrace_controller_unittest.cc b/src/traced/probes/ftrace/ftrace_controller_unittest.cc
index 7e71737..9d8a53f 100644
--- a/src/traced/probes/ftrace/ftrace_controller_unittest.cc
+++ b/src/traced/probes/ftrace/ftrace_controller_unittest.cc
@@ -437,47 +437,6 @@
   data_source.reset();
 }
 
-// TODO(b/73452932): Fix and reenable this test.
-TEST(FtraceControllerTest, DISABLED_DrainPeriodRespected) {
-  auto controller =
-      CreateTestController(false /* nice runner */, false /* nice procfs */);
-
-  // For this test we don't care about calls to WriteToFile/ClearFile.
-  EXPECT_CALL(*controller->procfs(), WriteToFile(_, _)).Times(AnyNumber());
-  EXPECT_CALL(*controller->procfs(), ClearFile(_)).Times(AnyNumber());
-
-  FtraceConfig config = CreateFtraceConfig({"group/foo"});
-  auto data_source = controller->AddFakeDataSource(config);
-  ASSERT_TRUE(controller->StartDataSource(data_source.get()));
-
-  // Test several cycles of a worker producing data and make sure the drain
-  // delay is consistent with the drain period.
-  const int kCycles = 50;
-  EXPECT_CALL(*controller->runner(),
-              PostDelayedTask(_, controller->drain_period_ms()))
-      .Times(kCycles);
-  EXPECT_CALL(*controller, OnDrainCpuForTesting(_)).Times(kCycles);
-  EXPECT_CALL(*controller->runner(), PostTask(_)).Times(kCycles);
-
-  // Simulate a worker thread continually reporting pages of available data.
-  auto on_data_available = controller->GetDataAvailableCallback(0u);
-  std::thread worker([on_data_available] {
-    for (int i = 0; i < kCycles; i++)
-      on_data_available();
-  });
-
-  for (int i = 0; i < kCycles; i++) {
-    controller->WaitForData(0u);
-    // Run two tasks: one to drain each CPU and another to unblock the worker.
-    controller->runner()->RunLastTask();
-    controller->runner()->RunLastTask();
-    controller->now_ms += controller->drain_period_ms();
-  }
-
-  worker.join();
-  data_source.reset();
-}
-
 TEST(FtraceControllerTest, BackToBackEnableDisable) {
   auto controller =
       CreateTestController(false /* nice runner */, false /* nice procfs */);
diff --git a/src/traced/probes/ftrace/ftrace_procfs_integrationtest.cc b/src/traced/probes/ftrace/ftrace_procfs_integrationtest.cc
index 5667439..b63bd98 100644
--- a/src/traced/probes/ftrace/ftrace_procfs_integrationtest.cc
+++ b/src/traced/probes/ftrace/ftrace_procfs_integrationtest.cc
@@ -32,7 +32,13 @@
 namespace perfetto {
 namespace {
 
-constexpr char kTracingPath[] = "/sys/kernel/debug/tracing/";
+std::string GetFtracePath() {
+  size_t i = 0;
+  while (!FtraceProcfs::Create(FtraceController::kTracingPaths[i])) {
+    i++;
+  }
+  return std::string(FtraceController::kTracingPaths[i]);
+}
 
 void ResetFtrace(FtraceProcfs* ftrace) {
   ftrace->DisableAllEvents();
@@ -42,7 +48,7 @@
 
 std::string ReadFile(const std::string& name) {
   std::string result;
-  PERFETTO_CHECK(base::ReadFile(kTracingPath + name, &result));
+  PERFETTO_CHECK(base::ReadFile(GetFtracePath() + name, &result));
   return result;
 }
 
@@ -63,7 +69,7 @@
 #define MAYBE_CreateWithGoodPath DISABLED_CreateWithGoodPath
 #endif
 TEST(FtraceProcfsIntegrationTest, MAYBE_CreateWithGoodPath) {
-  EXPECT_TRUE(FtraceProcfs::Create(kTracingPath));
+  EXPECT_TRUE(FtraceProcfs::Create(GetFtracePath()));
 }
 
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
@@ -72,7 +78,7 @@
 #define MAYBE_CreateWithBadPath DISABLED_CreateWithBadath
 #endif
 TEST(FtraceProcfsIntegrationTest, MAYBE_CreateWithBadPath) {
-  EXPECT_FALSE(FtraceProcfs::Create(kTracingPath + std::string("bad_path")));
+  EXPECT_FALSE(FtraceProcfs::Create(GetFtracePath() + std::string("bad_path")));
 }
 
 #if PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID)
@@ -81,7 +87,7 @@
 #define MAYBE_ClearTrace DISABLED_ClearTrace
 #endif
 TEST(FtraceProcfsIntegrationTest, MAYBE_ClearTrace) {
-  FtraceProcfs ftrace(kTracingPath);
+  FtraceProcfs ftrace(GetFtracePath());
   ResetFtrace(&ftrace);
   ftrace.WriteTraceMarker("Hello, World!");
   ftrace.ClearTrace();
@@ -94,7 +100,7 @@
 #define MAYBE_TraceMarker DISABLED_TraceMarker
 #endif
 TEST(FtraceProcfsIntegrationTest, MAYBE_TraceMarker) {
-  FtraceProcfs ftrace(kTracingPath);
+  FtraceProcfs ftrace(GetFtracePath());
   ResetFtrace(&ftrace);
   ftrace.WriteTraceMarker("Hello, World!");
   EXPECT_THAT(GetTraceOutput(), HasSubstr("Hello, World!"));
@@ -106,7 +112,7 @@
 #define MAYBE_EnableDisableEvent DISABLED_EnableDisableEvent
 #endif
 TEST(FtraceProcfsIntegrationTest, MAYBE_EnableDisableEvent) {
-  FtraceProcfs ftrace(kTracingPath);
+  FtraceProcfs ftrace(GetFtracePath());
   ResetFtrace(&ftrace);
   ftrace.EnableEvent("sched", "sched_switch");
   sleep(1);
@@ -124,7 +130,7 @@
 #define MAYBE_EnableDisableTracing DISABLED_EnableDisableTracing
 #endif
 TEST(FtraceProcfsIntegrationTest, MAYBE_EnableDisableTracing) {
-  FtraceProcfs ftrace(kTracingPath);
+  FtraceProcfs ftrace(GetFtracePath());
   ResetFtrace(&ftrace);
   EXPECT_TRUE(ftrace.IsTracingEnabled());
   ftrace.WriteTraceMarker("Before");
@@ -145,7 +151,7 @@
 #define MAYBE_ReadFormatFile DISABLED_ReadFormatFile
 #endif
 TEST(FtraceProcfsIntegrationTest, MAYBE_ReadFormatFile) {
-  FtraceProcfs ftrace(kTracingPath);
+  FtraceProcfs ftrace(GetFtracePath());
   std::string format = ftrace.ReadEventFormat("ftrace", "print");
   EXPECT_THAT(format, HasSubstr("name: print"));
   EXPECT_THAT(format, HasSubstr("field:char buf"));
@@ -157,7 +163,7 @@
 #define MAYBE_CanOpenTracePipeRaw DISABLED_CanOpenTracePipeRaw
 #endif
 TEST(FtraceProcfsIntegrationTest, MAYBE_CanOpenTracePipeRaw) {
-  FtraceProcfs ftrace(kTracingPath);
+  FtraceProcfs ftrace(GetFtracePath());
   EXPECT_TRUE(ftrace.OpenPipeForCpu(0));
 }
 
@@ -167,7 +173,7 @@
 #define MAYBE_Clock DISABLED_Clock
 #endif
 TEST(FtraceProcfsIntegrationTest, MAYBE_Clock) {
-  FtraceProcfs ftrace(kTracingPath);
+  FtraceProcfs ftrace(GetFtracePath());
   std::set<std::string> clocks = ftrace.AvailableClocks();
   EXPECT_THAT(clocks, Contains("local"));
   EXPECT_THAT(clocks, Contains("global"));
@@ -184,7 +190,7 @@
 #define MAYBE_CanSetBufferSize DISABLED_CanSetBufferSize
 #endif
 TEST(FtraceProcfsIntegrationTest, MAYBE_CanSetBufferSize) {
-  FtraceProcfs ftrace(kTracingPath);
+  FtraceProcfs ftrace(GetFtracePath());
   EXPECT_TRUE(ftrace.SetCpuBufferSizeInPages(4ul));
   EXPECT_EQ(ReadFile("buffer_size_kb"), "16\n");  // (4096 * 4) / 1024
   EXPECT_TRUE(ftrace.SetCpuBufferSizeInPages(5ul));
@@ -197,7 +203,7 @@
 #define MAYBE_FtraceControllerHardReset DISABLED_FtraceControllerHardReset
 #endif
 TEST(FtraceProcfsIntegrationTest, MAYBE_FtraceControllerHardReset) {
-  FtraceProcfs ftrace(kTracingPath);
+  FtraceProcfs ftrace(GetFtracePath());
   ResetFtrace(&ftrace);
 
   ftrace.SetCpuBufferSizeInPages(4ul);
diff --git a/src/traced/probes/ftrace/proto_translation_table.h b/src/traced/probes/ftrace/proto_translation_table.h
index 592629f..fe4abde 100644
--- a/src/traced/probes/ftrace/proto_translation_table.h
+++ b/src/traced/probes/ftrace/proto_translation_table.h
@@ -130,6 +130,14 @@
     return ftrace_page_header_spec_;
   }
 
+  // Returns the size in bytes of the "size" field in the ftrace header. This
+  // usually matches sizeof(void*) in the kernel (which can be != sizeof(void*)
+  // of user space on 32bit-user + 64-bit-kernel configurations).
+  inline uint16_t page_header_size_len() const {
+    // TODO(fmayer): Do kernel deepdive to double check this.
+    return ftrace_page_header_spec_.size.size;
+  }
+
   // Retrieves the ftrace event from the proto translation
   // table. If it does not exist, reads the format file and creates a
   // new event with the proto id set to generic. Virtual for testing.
diff --git a/src/tracing/core/shared_memory_arbiter_impl_unittest.cc b/src/tracing/core/shared_memory_arbiter_impl_unittest.cc
index c5497b9..833c60b 100644
--- a/src/tracing/core/shared_memory_arbiter_impl_unittest.cc
+++ b/src/tracing/core/shared_memory_arbiter_impl_unittest.cc
@@ -37,6 +37,8 @@
  public:
   void RegisterDataSource(const DataSourceDescriptor&) override {}
   void UnregisterDataSource(const std::string&) override {}
+  void RegisterTraceWriter(uint32_t, uint32_t) override {}
+  void UnregisterTraceWriter(uint32_t) override {}
   void NotifyFlushComplete(FlushRequestID) override {}
   void NotifyDataSourceStopped(DataSourceInstanceID) override {}
   SharedMemory* shared_memory() const override { return nullptr; }
diff --git a/src/tracing/core/trace_writer_impl_unittest.cc b/src/tracing/core/trace_writer_impl_unittest.cc
index e80730a..79e79be 100644
--- a/src/tracing/core/trace_writer_impl_unittest.cc
+++ b/src/tracing/core/trace_writer_impl_unittest.cc
@@ -34,6 +34,8 @@
 class FakeProducerEndpoint : public TracingService::ProducerEndpoint {
   void RegisterDataSource(const DataSourceDescriptor&) override {}
   void UnregisterDataSource(const std::string&) override {}
+  void RegisterTraceWriter(uint32_t, uint32_t) override {}
+  void UnregisterTraceWriter(uint32_t) override {}
   void CommitData(const CommitDataRequest&, CommitDataCallback) override {}
   void NotifyFlushComplete(FlushRequestID) override {}
   void NotifyDataSourceStopped(DataSourceInstanceID) override {}
diff --git a/src/tracing/core/tracing_service_impl.cc b/src/tracing/core/tracing_service_impl.cc
index 3a47e4d..015c52b 100644
--- a/src/tracing/core/tracing_service_impl.cc
+++ b/src/tracing/core/tracing_service_impl.cc
@@ -1492,6 +1492,20 @@
   service_->UnregisterDataSource(id_, name);
 }
 
+void TracingServiceImpl::ProducerEndpointImpl::RegisterTraceWriter(
+    uint32_t /*writer_id*/,
+    uint32_t /*target_buffer*/) {
+  PERFETTO_DCHECK_THREAD(thread_checker_);
+  // TODO(eseckler): Store association into a map. Make sure to verify that the
+  // target buffer is "allowed" when using this association later.
+}
+
+void TracingServiceImpl::ProducerEndpointImpl::UnregisterTraceWriter(
+    uint32_t /*writer_id*/) {
+  PERFETTO_DCHECK_THREAD(thread_checker_);
+  // TODO(eseckler): Remove association from the map.
+}
+
 void TracingServiceImpl::ProducerEndpointImpl::CommitData(
     const CommitDataRequest& req_untrusted,
     CommitDataCallback callback) {
diff --git a/src/tracing/core/tracing_service_impl.h b/src/tracing/core/tracing_service_impl.h
index 862cecd..d1a7e35 100644
--- a/src/tracing/core/tracing_service_impl.h
+++ b/src/tracing/core/tracing_service_impl.h
@@ -73,6 +73,9 @@
     // TracingService::ProducerEndpoint implementation.
     void RegisterDataSource(const DataSourceDescriptor&) override;
     void UnregisterDataSource(const std::string& name) override;
+    void RegisterTraceWriter(uint32_t writer_id,
+                             uint32_t target_buffer) override;
+    void UnregisterTraceWriter(uint32_t writer_id) override;
     void CommitData(const CommitDataRequest&, CommitDataCallback) override;
     void SetSharedMemory(std::unique_ptr<SharedMemory>);
     std::unique_ptr<TraceWriter> CreateTraceWriter(BufferID) override;
diff --git a/src/tracing/ipc/producer/producer_ipc_client_impl.cc b/src/tracing/ipc/producer/producer_ipc_client_impl.cc
index 420f2fa..3bd8fc6 100644
--- a/src/tracing/ipc/producer/producer_ipc_client_impl.cc
+++ b/src/tracing/ipc/producer/producer_ipc_client_impl.cc
@@ -207,6 +207,34 @@
       req, ipc::Deferred<protos::UnregisterDataSourceResponse>());
 }
 
+void ProducerIPCClientImpl::RegisterTraceWriter(uint32_t writer_id,
+                                                uint32_t target_buffer) {
+  PERFETTO_DCHECK_THREAD(thread_checker_);
+  if (!connected_) {
+    PERFETTO_DLOG(
+        "Cannot RegisterTraceWriter(), not connected to tracing service");
+    return;
+  }
+  protos::RegisterTraceWriterRequest req;
+  req.set_trace_writer_id(writer_id);
+  req.set_target_buffer(target_buffer);
+  producer_port_.RegisterTraceWriter(
+      req, ipc::Deferred<protos::RegisterTraceWriterResponse>());
+}
+
+void ProducerIPCClientImpl::UnregisterTraceWriter(uint32_t writer_id) {
+  PERFETTO_DCHECK_THREAD(thread_checker_);
+  if (!connected_) {
+    PERFETTO_DLOG(
+        "Cannot UnregisterTraceWriter(), not connected to tracing service");
+    return;
+  }
+  protos::UnregisterTraceWriterRequest req;
+  req.set_trace_writer_id(writer_id);
+  producer_port_.UnregisterTraceWriter(
+      req, ipc::Deferred<protos::UnregisterTraceWriterResponse>());
+}
+
 void ProducerIPCClientImpl::CommitData(const CommitDataRequest& req,
                                        CommitDataCallback callback) {
   PERFETTO_DCHECK_THREAD(thread_checker_);
diff --git a/src/tracing/ipc/producer/producer_ipc_client_impl.h b/src/tracing/ipc/producer/producer_ipc_client_impl.h
index ba749ad..23a1d31 100644
--- a/src/tracing/ipc/producer/producer_ipc_client_impl.h
+++ b/src/tracing/ipc/producer/producer_ipc_client_impl.h
@@ -63,6 +63,8 @@
   // tracing library, which know nothing about the IPC transport.
   void RegisterDataSource(const DataSourceDescriptor&) override;
   void UnregisterDataSource(const std::string& name) override;
+  void RegisterTraceWriter(uint32_t writer_id, uint32_t target_buffer) override;
+  void UnregisterTraceWriter(uint32_t writer_id) override;
   void CommitData(const CommitDataRequest&, CommitDataCallback) override;
   void NotifyDataSourceStopped(DataSourceInstanceID) override;
 
diff --git a/src/tracing/ipc/service/producer_ipc_service.cc b/src/tracing/ipc/service/producer_ipc_service.cc
index 95dcf67..ae28ef7 100644
--- a/src/tracing/ipc/service/producer_ipc_service.cc
+++ b/src/tracing/ipc/service/producer_ipc_service.cc
@@ -90,7 +90,9 @@
   if (!producer) {
     PERFETTO_DLOG(
         "Producer invoked RegisterDataSource() before InitializeConnection()");
-    return response.Reject();
+    if (response.IsBound())
+      response.Reject();
+    return;
   }
 
   DataSourceDescriptor dsd;
@@ -98,8 +100,10 @@
   GetProducerForCurrentRequest()->service_endpoint->RegisterDataSource(dsd);
 
   // RegisterDataSource doesn't expect any meaningful response.
-  response.Resolve(
-      ipc::AsyncResult<protos::RegisterDataSourceResponse>::Create());
+  if (response.IsBound()) {
+    response.Resolve(
+        ipc::AsyncResult<protos::RegisterDataSourceResponse>::Create());
+  }
 }
 
 // Called by the IPC layer.
@@ -122,13 +126,60 @@
     PERFETTO_DLOG(
         "Producer invoked UnregisterDataSource() before "
         "InitializeConnection()");
-    return response.Reject();
+    if (response.IsBound())
+      response.Reject();
+    return;
   }
   producer->service_endpoint->UnregisterDataSource(req.data_source_name());
 
   // UnregisterDataSource doesn't expect any meaningful response.
-  response.Resolve(
-      ipc::AsyncResult<protos::UnregisterDataSourceResponse>::Create());
+  if (response.IsBound()) {
+    response.Resolve(
+        ipc::AsyncResult<protos::UnregisterDataSourceResponse>::Create());
+  }
+}
+
+void ProducerIPCService::RegisterTraceWriter(
+    const protos::RegisterTraceWriterRequest& req,
+    DeferredRegisterTraceWriterResponse response) {
+  RemoteProducer* producer = GetProducerForCurrentRequest();
+  if (!producer) {
+    PERFETTO_DLOG(
+        "Producer invoked RegisterTraceWriter() before "
+        "InitializeConnection()");
+    if (response.IsBound())
+      response.Reject();
+    return;
+  }
+  producer->service_endpoint->RegisterTraceWriter(req.trace_writer_id(),
+                                                  req.target_buffer());
+
+  // RegisterTraceWriter doesn't expect any meaningful response.
+  if (response.IsBound()) {
+    response.Resolve(
+        ipc::AsyncResult<protos::RegisterTraceWriterResponse>::Create());
+  }
+}
+
+void ProducerIPCService::UnregisterTraceWriter(
+    const protos::UnregisterTraceWriterRequest& req,
+    DeferredUnregisterTraceWriterResponse response) {
+  RemoteProducer* producer = GetProducerForCurrentRequest();
+  if (!producer) {
+    PERFETTO_DLOG(
+        "Producer invoked UnregisterTraceWriter() before "
+        "InitializeConnection()");
+    if (response.IsBound())
+      response.Reject();
+    return;
+  }
+  producer->service_endpoint->UnregisterTraceWriter(req.trace_writer_id());
+
+  // UnregisterTraceWriter doesn't expect any meaningful response.
+  if (response.IsBound()) {
+    response.Resolve(
+        ipc::AsyncResult<protos::UnregisterTraceWriterResponse>::Create());
+  }
 }
 
 void ProducerIPCService::CommitData(const protos::CommitDataRequest& proto_req,
@@ -137,6 +188,8 @@
   if (!producer) {
     PERFETTO_DLOG(
         "Producer invoked CommitData() before InitializeConnection()");
+    if (resp.IsBound())
+      resp.Reject();
     return;
   }
   CommitDataRequest req;
@@ -167,6 +220,8 @@
     PERFETTO_DLOG(
         "Producer invoked NotifyDataSourceStopped() before "
         "InitializeConnection()");
+    if (response.IsBound())
+      response.Reject();
     return;
   }
   producer->service_endpoint->NotifyDataSourceStopped(request.data_source_id());
diff --git a/src/tracing/ipc/service/producer_ipc_service.h b/src/tracing/ipc/service/producer_ipc_service.h
index caa1978..d714f3f 100644
--- a/src/tracing/ipc/service/producer_ipc_service.h
+++ b/src/tracing/ipc/service/producer_ipc_service.h
@@ -49,6 +49,10 @@
                           DeferredRegisterDataSourceResponse) override;
   void UnregisterDataSource(const protos::UnregisterDataSourceRequest&,
                             DeferredUnregisterDataSourceResponse) override;
+  void RegisterTraceWriter(const protos::RegisterTraceWriterRequest&,
+                           DeferredRegisterTraceWriterResponse) override;
+  void UnregisterTraceWriter(const protos::UnregisterTraceWriterRequest&,
+                             DeferredUnregisterTraceWriterResponse) override;
   void CommitData(const protos::CommitDataRequest&,
                   DeferredCommitDataResponse) override;
   void NotifyDataSourceStopped(
diff --git a/src/tracing/test/mock_producer.cc b/src/tracing/test/mock_producer.cc
index 1294c1b..e7dfa7d 100644
--- a/src/tracing/test/mock_producer.cc
+++ b/src/tracing/test/mock_producer.cc
@@ -67,6 +67,15 @@
   service_endpoint_->UnregisterDataSource(name);
 }
 
+void MockProducer::RegisterTraceWriter(uint32_t writer_id,
+                                       uint32_t target_buffer) {
+  service_endpoint_->RegisterTraceWriter(writer_id, target_buffer);
+}
+
+void MockProducer::UnregisterTraceWriter(uint32_t writer_id) {
+  service_endpoint_->UnregisterTraceWriter(writer_id);
+}
+
 void MockProducer::WaitForTracingSetup() {
   static int i = 0;
   auto checkpoint_name =
diff --git a/src/tracing/test/mock_producer.h b/src/tracing/test/mock_producer.h
index be13084..6f5ab0d 100644
--- a/src/tracing/test/mock_producer.h
+++ b/src/tracing/test/mock_producer.h
@@ -49,6 +49,8 @@
                size_t shared_memory_size_hint_bytes = 0);
   void RegisterDataSource(const std::string& name, bool ack_stop = false);
   void UnregisterDataSource(const std::string& name);
+  void RegisterTraceWriter(uint32_t writer_id, uint32_t target_buffer);
+  void UnregisterTraceWriter(uint32_t writer_id);
   void WaitForTracingSetup();
   void WaitForDataSourceSetup(const std::string& name);
   void WaitForDataSourceStart(const std::string& name);
diff --git a/tools/heap_profile b/tools/heap_profile
new file mode 100755
index 0000000..5744477
--- /dev/null
+++ b/tools/heap_profile
@@ -0,0 +1,208 @@
+#!/usr/bin/env python
+
+# Copyright (C) 2017 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+from __future__ import absolute_import
+from __future__ import division
+from __future__ import print_function
+
+import argparse
+import atexit
+import hashlib
+import os
+import signal
+import subprocess
+import sys
+import tempfile
+import time
+import urllib
+
+TRACE_TO_TEXT_SHAS = {
+  'linux': 'c704cf765b9c5445c16a8e7ec5757cad364a8d92',
+  'mac': 'aed4ad02da526a3f1e4f9df47d4989ae9305b30e',
+}
+TRACE_TO_TEXT_PATH = tempfile.gettempdir()
+TRACE_TO_TEXT_BASE_URL = (
+    'https://storage.googleapis.com/perfetto/')
+
+def check_hash(file_name, sha_value):
+  with open(file_name, 'rb') as fd:
+    # TODO(fmayer): Chunking.
+    file_hash = hashlib.sha1(fd.read()).hexdigest()
+    return file_hash == sha_value
+
+
+def load_trace_to_text(platform):
+  sha_value = TRACE_TO_TEXT_SHAS[platform]
+  file_name = 'trace_to_text-' + platform + '-' + sha_value
+  local_file = os.path.join(TRACE_TO_TEXT_PATH, file_name)
+
+  if os.path.exists(local_file):
+    if not check_hash(local_file, sha_value):
+      os.remove(local_file)
+    else:
+      return local_file
+
+  url = TRACE_TO_TEXT_BASE_URL + file_name
+  urllib.urlretrieve(url, local_file)
+  if not check_hash(local_file, sha_value):
+    os.remove(local_file)
+    raise ValueError("Invalid signature.")
+  os.chmod(local_file, 0o755)
+  return local_file
+
+
+NULL = open('/dev/null', 'r')
+
+CFG_IDENT = '      '
+CFG='''buffers {{
+  size_kb: 32768
+}}
+
+data_sources {{
+  config {{
+    name: "android.heapprofd"
+    heapprofd_config {{
+
+      all: {all}
+      sampling_interval_bytes: {interval}
+{target_cfg}
+      continuous_dump_config {{
+        dump_phase_ms: 0
+        dump_interval_ms: 1000
+      }}
+    }}
+  }}
+}}
+
+duration_ms: {duration}
+'''
+
+PERFETTO_CMD=('CFG=\'{}\'; echo ${{CFG}} | '
+              'perfetto -t -c - -o /data/misc/perfetto-traces/profile -b')
+IS_INTERRUPTED = False
+def sigint_handler(sig, frame):
+  global IS_INTERRUPTED
+  IS_INTERRUPTED = True
+
+
+def on_exit(enforcing):
+  subprocess.check_output(['adb', 'shell', 'su root stop heapprofd'])
+  subprocess.check_output(
+      ['adb', 'shell', 'su root setenforce %s' % enforcing])
+
+
+def main(argv):
+  parser = argparse.ArgumentParser()
+  parser.add_argument("-i", "--interval", help="Sampling interval. "
+                      "Default 128000 (128kB)", type=int, default=128000)
+  parser.add_argument("-d", "--duration", help="Duration of profile (ms). "
+                      "Default 1 minute", type=int, default=60000)
+  parser.add_argument("-a", "--all", help="Profile the whole system",
+                      action='store_true')
+  parser.add_argument("-p", "--pid", help="PIDs to profile", nargs='+',
+                      type=int)
+  parser.add_argument("-n", "--name", help="Process names to profile",
+                      nargs='+')
+  parser.add_argument("-t", "--trace-to-text-binary",
+                      help="Path to local trace to text. For debugging.")
+
+  args = parser.parse_args()
+
+  fail = False
+  if args.all is None and args.pid is None and args.name is None:
+    print("FATAL: Neither --all nor PID nor NAME given.", file=sys.stderr)
+    fail = True
+  if args.duration is None:
+    print("FATAL: No duration given.", file=sys.stderr)
+    fail = True
+  if args.interval is None:
+    print("FATAL: No interval given.", file=sys.stderr)
+    fail = True
+  if fail:
+    parser.print_help()
+    return 1
+  target_cfg = ""
+  if args.pid:
+    for pid in args.pid:
+      target_cfg += '{}pid: {}\n'.format(CFG_IDENT, pid)
+  if args.name:
+    for name in args.name:
+      target_cfg += '{}process_cmdline: "{}"\n'.format(CFG_IDENT, name)
+
+  trace_to_text_binary = args.trace_to_text_binary
+  if trace_to_text_binary is None:
+    platform = None
+    if sys.platform.startswith('linux'):
+      platform = 'linux'
+    elif sys.platform.startswith('darwin'):
+      platform = 'mac'
+    else:
+      print("Invalid platform: {}".format(sys.platform), file=sys.stderr)
+      return 1
+
+    trace_to_text_binary = load_trace_to_text(platform)
+
+  cfg = CFG.format(all=str(args.all == True).lower(), interval=args.interval,
+                   duration=args.duration, target_cfg=target_cfg)
+
+  enforcing = subprocess.check_output(['adb', 'shell', 'getenforce'])
+  atexit.register(on_exit, enforcing)
+
+  subprocess.check_call(['adb', 'shell', 'su root setenforce 0'])
+  subprocess.check_call(['adb', 'shell', 'su root start heapprofd'])
+
+  perfetto_pid = subprocess.check_output(
+      ['adb', 'exec-out', PERFETTO_CMD.format(cfg)]).strip()
+
+  old_handler = signal.signal(signal.SIGINT, sigint_handler)
+  print("Profiling active. Press Ctrl+C to terminate.")
+  exists = True
+  while exists and not IS_INTERRUPTED:
+    exists = subprocess.call(
+        ['adb', 'shell', '[ -d /proc/{} ]'.format(perfetto_pid)]) == 0
+    time.sleep(1)
+  signal.signal(signal.SIGINT, old_handler)
+  if IS_INTERRUPTED:
+    # Not check_call because it could have existed in the meantime.
+    subprocess.call(['adb', 'shell', 'kill', '-INT', perfetto_pid])
+
+  subprocess.check_call(['adb', 'pull', '/data/misc/perfetto-traces/profile',
+                         '/tmp/profile'], stdout=NULL)
+  trace_to_text_output = subprocess.check_output(
+      [trace_to_text_binary, 'profile', '/tmp/profile'],
+      stderr=NULL)
+  profile_path = None
+  for word in trace_to_text_output.split():
+    if 'heap_profile-' in word:
+      profile_path = word
+  if profile_path is None:
+    print("Could not find trace_to_text output path.", file=sys.stderr)
+    return 1
+
+  profile_files = os.listdir(profile_path)
+  if not profile_files:
+    print("No profiles generated", file=sys.stderr)
+    return 1
+
+  subprocess.check_call(['gzip'] + [os.path.join(profile_path, x) for x in
+                                    os.listdir(profile_path)])
+  print("Wrote profiles to {}".format(profile_path))
+  print("These can be viewed using pprof. Googlers: head to pprof/ and "
+        "upload them.")
+
+
+if __name__ == '__main__':
+  sys.exit(main(sys.argv))
diff --git a/tools/profile b/tools/profile
deleted file mode 100755
index 78f0b19..0000000
--- a/tools/profile
+++ /dev/null
@@ -1,82 +0,0 @@
-#!/bin/bash
-
-# Copyright (C) 2018 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.
-
-set -euo pipefail
-
-TRACE_TO_TEXT=""
-
-while getopts ":i:d:p:n:t:" opt; do
-  case $opt in
-    i) INTERVAL=$OPTARG;;
-    d) DURATION=$OPTARG;;
-    p) PID=$OPTARG;;
-    n) NAME=$OPTARG;;
-    t) TRACE_TO_TEXT=$OPTARG;;
-    ?) echo "Usage: $0 -i SAMPLING_INTERVAL -d DURATION [-p PID]"\
-      "[-n PROCESS_NAME] [-t TRACE_TO_TEXT_PATH]"
-       exit;;
-  esac
-done
-
-if [[ -z "${TRACE_TO_TEXT}" ]]; then
-  SCRIPTDIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null && pwd )"
-  TRACE_TO_TEXT="$SCRIPTDIR/trace_to_text"
-fi
-
-ENFORCE_MODE=$(adb shell getenforce)
-function finish {
-  adb shell su root stop heapprofd
-  adb shell su root setenforce $ENFORCE_MODE
-}
-trap finish EXIT
-
-adb shell su root setenforce 0
-adb shell su root start heapprofd
-
-CFG='
-buffers {
-  size_kb: 32768
-}
-
-data_sources {
-  config {
-    name: "android.heapprofd"
-    heapprofd_config {
-      sampling_interval_bytes: '${INTERVAL}'
-      '${PID+"pid: $PID"}'
-      '${NAME+"process_cmdline: \"${NAME}\""}'
-      continuous_dump_config {
-        dump_phase_ms: 10000
-        dump_interval_ms: 1000
-      }
-    }
-  }
-}
-
-duration_ms: '${DURATION}
-
-PERFETTO_PID=$(adb exec-out 'CFG='"'${CFG}'
-"'echo ${CFG} | perfetto -t -c - -o /data/misc/perfetto-traces/profile -b'\
-  | grep -o '^pid: [0-9]*$' | awk '{ print $2 }')
-
-# TODO(fmayer): Allow to interrupt.
-adb exec-out "while [[ -d /proc/$PERFETTO_PID ]]; do sleep 1; done" | cat
-adb pull /data/misc/perfetto-traces/profile /tmp/profile > /dev/null
-
-OUTDIR=$($TRACE_TO_TEXT profile /tmp/profile | grep -o "[^ ]*heap_profile[^ ]*")
-gzip $OUTDIR/*.pb
-
-echo "Wrote profiles to $OUTDIR"