Merge "Reland: perfetto_cmd: add ability to compress trace packets"
diff --git a/Android.bp b/Android.bp
index 71646b3..b9dd77f 100644
--- a/Android.bp
+++ b/Android.bp
@@ -218,20 +218,6 @@
   ],
 }
 
-// GN target: //:idle_alloc
-cc_binary {
-  name: "idle_alloc",
-  srcs: [
-    "tools/idle_alloc.cc",
-  ],
-  shared_libs: [
-    "liblog",
-  ],
-  defaults: [
-    "perfetto_defaults",
-  ],
-}
-
 // GN target: //:libperfetto
 cc_library_shared {
   name: "libperfetto",
diff --git a/BUILD.gn b/BUILD.gn
index e28ab12..89c95be 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -378,8 +378,10 @@
   }
 }
 
-executable("idle_alloc") {
-  sources = [
-    "tools/idle_alloc.cc",
-  ]
+if (perfetto_build_standalone) {
+  executable("idle_alloc") {
+    sources = [
+      "tools/idle_alloc.cc",
+    ]
+  }
 }
diff --git a/docs/heapprofd.md b/docs/heapprofd.md
index c5ea5ed..4234686 100644
--- a/docs/heapprofd.md
+++ b/docs/heapprofd.md
@@ -189,7 +189,7 @@
 idle.
 4. Interact with your program.
 
-Once you are done interacting, `Ctrl-C` the invokation of
+Once you are done interacting, `Ctrl-C` the invocation of
 `tools/heap_profile`, and upload the `heap_dump.2.*.pb.gz` file to pprof.
 You can then see the memory that was idle in the `idle_space` tab.
 
diff --git a/src/profiling/memory/heapprofd_end_to_end_test.cc b/src/profiling/memory/heapprofd_end_to_end_test.cc
index c1b1cdc..bb9ee37 100644
--- a/src/profiling/memory/heapprofd_end_to_end_test.cc
+++ b/src/profiling/memory/heapprofd_end_to_end_test.cc
@@ -20,6 +20,7 @@
 
 #include <gmock/gmock.h>
 #include <gtest/gtest.h>
+
 #include "perfetto/base/build_config.h"
 #include "perfetto/ext/base/pipe.h"
 #include "perfetto/protozero/scattered_heap_buffer.h"
@@ -33,17 +34,6 @@
 #include <sys/system_properties.h>
 #endif
 
-// This test only works when run on Android using an Android Q version of
-// Bionic.
-// TODO(b/118428762): look into unwinding issues on x86.
-#if !PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) ||                        \
-    PERFETTO_BUILDFLAG(PERFETTO_START_DAEMONS) || defined(__i386__) || \
-    defined(__x86_64__)
-#define MAYBE_SKIP(x) DISABLED_##x
-#else
-#define MAYBE_SKIP(x) x
-#endif
-
 namespace perfetto {
 namespace profiling {
 namespace {
@@ -51,6 +41,7 @@
 constexpr useconds_t kMsToUs = 1000;
 
 using ::testing::AnyOf;
+using ::testing::Bool;
 using ::testing::Eq;
 
 class HeapprofdDelegate : public ThreadDelegate {
@@ -199,7 +190,7 @@
          "unwinding_time_us: " + FormatHistogram(stats.unwinding_time_us());
 }
 
-class HeapprofdEndToEnd : public ::testing::Test {
+class HeapprofdEndToEnd : public ::testing::TestWithParam<bool> {
  public:
   HeapprofdEndToEnd() {
     // This is not needed for correctness, but works around a init behavior that
@@ -207,10 +198,20 @@
     // and then set to 1 again too quickly, init decides that the service is
     // "restarting" and waits before restarting it.
     usleep(50000);
+    bool should_fork = GetParam();
+    if (should_fork) {
+      fork_prop_ = EnableFork();
+      PERFETTO_CHECK(ReadProperty(kHeapprofdModeProperty, "") == "fork");
+    } else {
+      fork_prop_ = DisableFork();
+      PERFETTO_CHECK(ReadProperty(kHeapprofdModeProperty, "") == "");
+    }
   }
 
  protected:
   base::TestTaskRunner task_runner;
+  base::ScopedResource<std::string*, SetModeProperty, nullptr> fork_prop_{
+      nullptr};
 
   std::unique_ptr<TestHelper> Trace(const TraceConfig& trace_config) {
     auto helper = GetHelper(&task_runner);
@@ -314,776 +315,655 @@
     }
     EXPECT_GT(dumps, 0);
   }
+};
 
-  void Smoke() {
-    constexpr size_t kAllocSize = 1024;
+TEST_P(HeapprofdEndToEnd, Smoke) {
+  constexpr size_t kAllocSize = 1024;
 
-    pid_t pid = ForkContinuousMalloc(kAllocSize);
+  pid_t pid = ForkContinuousMalloc(kAllocSize);
 
-    TraceConfig trace_config;
-    trace_config.add_buffers()->set_size_kb(10 * 1024);
-    trace_config.set_duration_ms(2000);
-    trace_config.set_flush_timeout_ms(10000);
+  TraceConfig trace_config;
+  trace_config.add_buffers()->set_size_kb(10 * 1024);
+  trace_config.set_duration_ms(2000);
+  trace_config.set_flush_timeout_ms(10000);
 
-    auto* ds_config = trace_config.add_data_sources()->mutable_config();
-    ds_config->set_name("android.heapprofd");
-    ds_config->set_target_buffer(0);
+  auto* ds_config = trace_config.add_data_sources()->mutable_config();
+  ds_config->set_name("android.heapprofd");
+  ds_config->set_target_buffer(0);
 
-    protozero::HeapBuffered<protos::pbzero::HeapprofdConfig> heapprofd_config;
-    heapprofd_config->set_sampling_interval_bytes(1);
-    heapprofd_config->add_pid(static_cast<uint64_t>(pid));
-    heapprofd_config->set_all(false);
-    auto* cont_config = heapprofd_config->set_continuous_dump_config();
-    cont_config->set_dump_phase_ms(0);
-    cont_config->set_dump_interval_ms(100);
-    ds_config->set_heapprofd_config_raw(heapprofd_config.SerializeAsString());
+  protozero::HeapBuffered<protos::pbzero::HeapprofdConfig> heapprofd_config;
+  heapprofd_config->set_sampling_interval_bytes(1);
+  heapprofd_config->add_pid(static_cast<uint64_t>(pid));
+  heapprofd_config->set_all(false);
+  auto* cont_config = heapprofd_config->set_continuous_dump_config();
+  cont_config->set_dump_phase_ms(0);
+  cont_config->set_dump_interval_ms(100);
+  ds_config->set_heapprofd_config_raw(heapprofd_config.SerializeAsString());
 
-    auto helper = Trace(trace_config);
-    PrintStats(helper.get());
-    ValidateHasSamples(helper.get(), static_cast<uint64_t>(pid));
-    ValidateOnlyPID(helper.get(), static_cast<uint64_t>(pid));
-    ValidateSampleSizes(helper.get(), static_cast<uint64_t>(pid), kAllocSize);
+  auto helper = Trace(trace_config);
+  PrintStats(helper.get());
+  ValidateHasSamples(helper.get(), static_cast<uint64_t>(pid));
+  ValidateOnlyPID(helper.get(), static_cast<uint64_t>(pid));
+  ValidateSampleSizes(helper.get(), static_cast<uint64_t>(pid), kAllocSize);
 
-    PERFETTO_CHECK(kill(pid, SIGKILL) == 0);
-    PERFETTO_CHECK(PERFETTO_EINTR(waitpid(pid, nullptr, 0)) == pid);
+  PERFETTO_CHECK(kill(pid, SIGKILL) == 0);
+  PERFETTO_CHECK(PERFETTO_EINTR(waitpid(pid, nullptr, 0)) == pid);
+}
+
+TEST_P(HeapprofdEndToEnd, TwoProcesses) {
+  constexpr size_t kAllocSize = 1024;
+  constexpr size_t kAllocSize2 = 7;
+
+  pid_t pid = ForkContinuousMalloc(kAllocSize);
+  pid_t pid2 = ForkContinuousMalloc(kAllocSize2);
+
+  TraceConfig trace_config;
+  trace_config.add_buffers()->set_size_kb(10 * 1024);
+  trace_config.set_duration_ms(2000);
+  trace_config.set_flush_timeout_ms(10000);
+
+  auto* ds_config = trace_config.add_data_sources()->mutable_config();
+  ds_config->set_name("android.heapprofd");
+  ds_config->set_target_buffer(0);
+
+  protozero::HeapBuffered<protos::pbzero::HeapprofdConfig> heapprofd_config;
+  heapprofd_config->set_sampling_interval_bytes(1);
+  heapprofd_config->add_pid(static_cast<uint64_t>(pid));
+  heapprofd_config->add_pid(static_cast<uint64_t>(pid2));
+  heapprofd_config->set_all(false);
+  ds_config->set_heapprofd_config_raw(heapprofd_config.SerializeAsString());
+
+  auto helper = Trace(trace_config);
+  PrintStats(helper.get());
+  ValidateHasSamples(helper.get(), static_cast<uint64_t>(pid));
+  ValidateSampleSizes(helper.get(), static_cast<uint64_t>(pid), kAllocSize);
+  ValidateHasSamples(helper.get(), static_cast<uint64_t>(pid2));
+  ValidateSampleSizes(helper.get(), static_cast<uint64_t>(pid2), kAllocSize2);
+
+  PERFETTO_CHECK(kill(pid, SIGKILL) == 0);
+  PERFETTO_CHECK(PERFETTO_EINTR(waitpid(pid, nullptr, 0)) == pid);
+  PERFETTO_CHECK(kill(pid2, SIGKILL) == 0);
+  PERFETTO_CHECK(PERFETTO_EINTR(waitpid(pid2, nullptr, 0)) == pid2);
+}
+
+TEST_P(HeapprofdEndToEnd, FinalFlush) {
+  constexpr size_t kAllocSize = 1024;
+
+  pid_t pid = ForkContinuousMalloc(kAllocSize);
+
+  TraceConfig trace_config;
+  trace_config.add_buffers()->set_size_kb(10 * 1024);
+  trace_config.set_duration_ms(2000);
+  trace_config.set_flush_timeout_ms(10000);
+
+  auto* ds_config = trace_config.add_data_sources()->mutable_config();
+  ds_config->set_name("android.heapprofd");
+  ds_config->set_target_buffer(0);
+
+  protozero::HeapBuffered<protos::pbzero::HeapprofdConfig> heapprofd_config;
+  heapprofd_config->set_sampling_interval_bytes(1);
+  heapprofd_config->add_pid(static_cast<uint64_t>(pid));
+  heapprofd_config->set_all(false);
+  ds_config->set_heapprofd_config_raw(heapprofd_config.SerializeAsString());
+
+  auto helper = Trace(trace_config);
+  PrintStats(helper.get());
+  ValidateHasSamples(helper.get(), static_cast<uint64_t>(pid));
+  ValidateOnlyPID(helper.get(), static_cast<uint64_t>(pid));
+  ValidateSampleSizes(helper.get(), static_cast<uint64_t>(pid), kAllocSize);
+
+  PERFETTO_CHECK(kill(pid, SIGKILL) == 0);
+  PERFETTO_CHECK(PERFETTO_EINTR(waitpid(pid, nullptr, 0)) == pid);
+}
+
+TEST_P(HeapprofdEndToEnd, NativeStartup) {
+  auto helper = GetHelper(&task_runner);
+
+  TraceConfig trace_config;
+  trace_config.add_buffers()->set_size_kb(10 * 1024);
+  trace_config.set_duration_ms(5000);
+  trace_config.set_flush_timeout_ms(10000);
+
+  auto* ds_config = trace_config.add_data_sources()->mutable_config();
+  ds_config->set_name("android.heapprofd");
+
+  protozero::HeapBuffered<protos::pbzero::HeapprofdConfig> heapprofd_config;
+  heapprofd_config->set_sampling_interval_bytes(1);
+  heapprofd_config->add_process_cmdline("heapprofd_continuous_malloc");
+  heapprofd_config->set_all(false);
+  ds_config->set_heapprofd_config_raw(heapprofd_config.SerializeAsString());
+
+  helper->StartTracing(trace_config);
+
+  // Wait to guarantee that the process forked below is hooked by the profiler
+  // by virtue of the startup check, and not by virtue of being seen as a
+  // running process. This sleep is here to prevent that, accidentally, the
+  // test gets to the fork()+exec() too soon, before the heap profiling daemon
+  // has received the trace config.
+  sleep(1);
+
+  // Make sure the forked process does not get reparented to init.
+  setsid();
+  pid_t pid = fork();
+  switch (pid) {
+    case -1:
+      PERFETTO_FATAL("Failed to fork.");
+    case 0: {
+      const char* envp[] = {"HEAPPROFD_TESTING_RUN_MALLOC=1", nullptr};
+      int null = open("/dev/null", O_RDWR);
+      dup2(null, STDIN_FILENO);
+      dup2(null, STDOUT_FILENO);
+      dup2(null, STDERR_FILENO);
+      PERFETTO_CHECK(execle("/proc/self/exe", "heapprofd_continuous_malloc",
+                            nullptr, envp) == 0);
+      break;
+    }
+    default:
+      break;
   }
 
-  void TwoProcesses() {
-    constexpr size_t kAllocSize = 1024;
-    constexpr size_t kAllocSize2 = 7;
+  helper->WaitForTracingDisabled(20000);
 
-    pid_t pid = ForkContinuousMalloc(kAllocSize);
-    pid_t pid2 = ForkContinuousMalloc(kAllocSize2);
+  helper->ReadData();
+  helper->WaitForReadData();
 
-    TraceConfig trace_config;
-    trace_config.add_buffers()->set_size_kb(10 * 1024);
-    trace_config.set_duration_ms(2000);
-    trace_config.set_flush_timeout_ms(10000);
+  PERFETTO_CHECK(kill(pid, SIGKILL) == 0);
+  PERFETTO_CHECK(PERFETTO_EINTR(waitpid(pid, nullptr, 0)) == pid);
 
-    auto* ds_config = trace_config.add_data_sources()->mutable_config();
-    ds_config->set_name("android.heapprofd");
-    ds_config->set_target_buffer(0);
+  const auto& packets = helper->trace();
+  ASSERT_GT(packets.size(), 0u);
+  size_t profile_packets = 0;
+  size_t samples = 0;
+  uint64_t total_allocated = 0;
+  uint64_t total_freed = 0;
+  for (const protos::TracePacket& packet : packets) {
+    if (packet.has_profile_packet() &&
+        packet.profile_packet().process_dumps().size() > 0) {
+      const auto& dumps = packet.profile_packet().process_dumps();
+      ASSERT_EQ(dumps.size(), 1);
+      const protos::ProfilePacket_ProcessHeapSamples& dump = dumps.Get(0);
+      EXPECT_EQ(dump.pid(), pid);
+      profile_packets++;
+      for (const auto& sample : dump.samples()) {
+        samples++;
+        total_allocated += sample.self_allocated();
+        total_freed += sample.self_freed();
+      }
+    }
+  }
+  EXPECT_EQ(profile_packets, 1);
+  EXPECT_GT(samples, 0);
+  EXPECT_GT(total_allocated, 0);
+  EXPECT_GT(total_freed, 0);
+}
 
-    protozero::HeapBuffered<protos::pbzero::HeapprofdConfig> heapprofd_config;
-    heapprofd_config->set_sampling_interval_bytes(1);
-    heapprofd_config->add_pid(static_cast<uint64_t>(pid));
-    heapprofd_config->add_pid(static_cast<uint64_t>(pid2));
-    heapprofd_config->set_all(false);
-    ds_config->set_heapprofd_config_raw(heapprofd_config.SerializeAsString());
+TEST_P(HeapprofdEndToEnd, NativeStartupDenormalizedCmdline) {
+  auto helper = GetHelper(&task_runner);
 
-    auto helper = Trace(trace_config);
-    PrintStats(helper.get());
-    ValidateHasSamples(helper.get(), static_cast<uint64_t>(pid));
-    ValidateSampleSizes(helper.get(), static_cast<uint64_t>(pid), kAllocSize);
-    ValidateHasSamples(helper.get(), static_cast<uint64_t>(pid2));
-    ValidateSampleSizes(helper.get(), static_cast<uint64_t>(pid2), kAllocSize2);
+  TraceConfig trace_config;
+  trace_config.add_buffers()->set_size_kb(10 * 1024);
+  trace_config.set_duration_ms(5000);
+  trace_config.set_flush_timeout_ms(10000);
 
-    PERFETTO_CHECK(kill(pid, SIGKILL) == 0);
-    PERFETTO_CHECK(PERFETTO_EINTR(waitpid(pid, nullptr, 0)) == pid);
-    PERFETTO_CHECK(kill(pid2, SIGKILL) == 0);
-    PERFETTO_CHECK(PERFETTO_EINTR(waitpid(pid2, nullptr, 0)) == pid2);
+  auto* ds_config = trace_config.add_data_sources()->mutable_config();
+  ds_config->set_name("android.heapprofd");
+
+  protozero::HeapBuffered<protos::pbzero::HeapprofdConfig> heapprofd_config;
+  heapprofd_config->set_sampling_interval_bytes(1);
+  heapprofd_config->add_process_cmdline("heapprofd_continuous_malloc@1.2.3");
+  heapprofd_config->set_all(false);
+  ds_config->set_heapprofd_config_raw(heapprofd_config.SerializeAsString());
+
+  helper->StartTracing(trace_config);
+
+  // Wait to guarantee that the process forked below is hooked by the profiler
+  // by virtue of the startup check, and not by virtue of being seen as a
+  // running process. This sleep is here to prevent that, accidentally, the
+  // test gets to the fork()+exec() too soon, before the heap profiling daemon
+  // has received the trace config.
+  sleep(1);
+
+  // Make sure the forked process does not get reparented to init.
+  setsid();
+  pid_t pid = fork();
+  switch (pid) {
+    case -1:
+      PERFETTO_FATAL("Failed to fork.");
+    case 0: {
+      const char* envp[] = {"HEAPPROFD_TESTING_RUN_MALLOC=1", nullptr};
+      int null = open("/dev/null", O_RDWR);
+      dup2(null, STDIN_FILENO);
+      dup2(null, STDOUT_FILENO);
+      dup2(null, STDERR_FILENO);
+      PERFETTO_CHECK(execle("/proc/self/exe", "heapprofd_continuous_malloc",
+                            nullptr, envp) == 0);
+      break;
+    }
+    default:
+      break;
   }
 
-  void FinalFlush() {
-    constexpr size_t kAllocSize = 1024;
+  helper->WaitForTracingDisabled(20000);
 
-    pid_t pid = ForkContinuousMalloc(kAllocSize);
+  helper->ReadData();
+  helper->WaitForReadData();
 
-    TraceConfig trace_config;
-    trace_config.add_buffers()->set_size_kb(10 * 1024);
-    trace_config.set_duration_ms(2000);
-    trace_config.set_flush_timeout_ms(10000);
+  PERFETTO_CHECK(kill(pid, SIGKILL) == 0);
+  PERFETTO_CHECK(PERFETTO_EINTR(waitpid(pid, nullptr, 0)) == pid);
 
-    auto* ds_config = trace_config.add_data_sources()->mutable_config();
-    ds_config->set_name("android.heapprofd");
-    ds_config->set_target_buffer(0);
+  const auto& packets = helper->trace();
+  ASSERT_GT(packets.size(), 0u);
+  size_t profile_packets = 0;
+  size_t samples = 0;
+  uint64_t total_allocated = 0;
+  uint64_t total_freed = 0;
+  for (const protos::TracePacket& packet : packets) {
+    if (packet.has_profile_packet() &&
+        packet.profile_packet().process_dumps().size() > 0) {
+      const auto& dumps = packet.profile_packet().process_dumps();
+      ASSERT_EQ(dumps.size(), 1);
+      const protos::ProfilePacket_ProcessHeapSamples& dump = dumps.Get(0);
+      EXPECT_EQ(dump.pid(), pid);
+      profile_packets++;
+      for (const auto& sample : dump.samples()) {
+        samples++;
+        total_allocated += sample.self_allocated();
+        total_freed += sample.self_freed();
+      }
+    }
+  }
+  EXPECT_EQ(profile_packets, 1);
+  EXPECT_GT(samples, 0);
+  EXPECT_GT(total_allocated, 0);
+  EXPECT_GT(total_freed, 0);
+}
 
-    protozero::HeapBuffered<protos::pbzero::HeapprofdConfig> heapprofd_config;
-    heapprofd_config->set_sampling_interval_bytes(1);
-    heapprofd_config->add_pid(static_cast<uint64_t>(pid));
-    heapprofd_config->set_all(false);
-    ds_config->set_heapprofd_config_raw(heapprofd_config.SerializeAsString());
+TEST_P(HeapprofdEndToEnd, DiscoverByName) {
+  auto helper = GetHelper(&task_runner);
 
-    auto helper = Trace(trace_config);
-    PrintStats(helper.get());
-    ValidateHasSamples(helper.get(), static_cast<uint64_t>(pid));
-    ValidateOnlyPID(helper.get(), static_cast<uint64_t>(pid));
-    ValidateSampleSizes(helper.get(), static_cast<uint64_t>(pid), kAllocSize);
+  TraceConfig trace_config;
+  trace_config.add_buffers()->set_size_kb(10 * 1024);
+  trace_config.set_duration_ms(5000);
+  trace_config.set_flush_timeout_ms(10000);
 
-    PERFETTO_CHECK(kill(pid, SIGKILL) == 0);
-    PERFETTO_CHECK(PERFETTO_EINTR(waitpid(pid, nullptr, 0)) == pid);
+  auto* ds_config = trace_config.add_data_sources()->mutable_config();
+  ds_config->set_name("android.heapprofd");
+
+  protozero::HeapBuffered<protos::pbzero::HeapprofdConfig> heapprofd_config;
+  heapprofd_config->set_sampling_interval_bytes(1);
+  heapprofd_config->add_process_cmdline("heapprofd_continuous_malloc");
+  heapprofd_config->set_all(false);
+  ds_config->set_heapprofd_config_raw(heapprofd_config.SerializeAsString());
+
+  // Make sure the forked process does not get reparented to init.
+  setsid();
+  pid_t pid = fork();
+  switch (pid) {
+    case -1:
+      PERFETTO_FATAL("Failed to fork.");
+    case 0: {
+      const char* envp[] = {"HEAPPROFD_TESTING_RUN_MALLOC=1", nullptr};
+      int null = open("/dev/null", O_RDWR);
+      dup2(null, STDIN_FILENO);
+      dup2(null, STDOUT_FILENO);
+      dup2(null, STDERR_FILENO);
+      PERFETTO_CHECK(execle("/proc/self/exe", "heapprofd_continuous_malloc",
+                            nullptr, envp) == 0);
+      break;
+    }
+    default:
+      break;
   }
 
-  void NativeStartup() {
-    auto helper = GetHelper(&task_runner);
+  // Wait to make sure process is fully initialized, so we do not accidentally
+  // match it by the startup logic.
+  sleep(1);
 
-    TraceConfig trace_config;
-    trace_config.add_buffers()->set_size_kb(10 * 1024);
-    trace_config.set_duration_ms(5000);
-    trace_config.set_flush_timeout_ms(10000);
+  helper->StartTracing(trace_config);
+  helper->WaitForTracingDisabled(20000);
 
-    auto* ds_config = trace_config.add_data_sources()->mutable_config();
-    ds_config->set_name("android.heapprofd");
+  helper->ReadData();
+  helper->WaitForReadData();
 
-    protozero::HeapBuffered<protos::pbzero::HeapprofdConfig> heapprofd_config;
-    heapprofd_config->set_sampling_interval_bytes(1);
-    heapprofd_config->add_process_cmdline("heapprofd_continuous_malloc");
-    heapprofd_config->set_all(false);
-    ds_config->set_heapprofd_config_raw(heapprofd_config.SerializeAsString());
+  PERFETTO_CHECK(kill(pid, SIGKILL) == 0);
+  PERFETTO_CHECK(PERFETTO_EINTR(waitpid(pid, nullptr, 0)) == pid);
 
-    helper->StartTracing(trace_config);
-
-    // Wait to guarantee that the process forked below is hooked by the profiler
-    // by virtue of the startup check, and not by virtue of being seen as a
-    // running process. This sleep is here to prevent that, accidentally, the
-    // test gets to the fork()+exec() too soon, before the heap profiling daemon
-    // has received the trace config.
-    sleep(1);
-
-    // Make sure the forked process does not get reparented to init.
-    setsid();
-    pid_t pid = fork();
-    switch (pid) {
-      case -1:
-        PERFETTO_FATAL("Failed to fork.");
-      case 0: {
-        const char* envp[] = {"HEAPPROFD_TESTING_RUN_MALLOC=1", nullptr};
-        int null = open("/dev/null", O_RDWR);
-        dup2(null, STDIN_FILENO);
-        dup2(null, STDOUT_FILENO);
-        dup2(null, STDERR_FILENO);
-        PERFETTO_CHECK(execle("/proc/self/exe", "heapprofd_continuous_malloc",
-                              nullptr, envp) == 0);
-        break;
-      }
-      default:
-        break;
-    }
-
-    helper->WaitForTracingDisabled(20000);
-
-    helper->ReadData();
-    helper->WaitForReadData();
-
-    PERFETTO_CHECK(kill(pid, SIGKILL) == 0);
-    PERFETTO_CHECK(PERFETTO_EINTR(waitpid(pid, nullptr, 0)) == pid);
-
-    const auto& packets = helper->trace();
-    ASSERT_GT(packets.size(), 0u);
-    size_t profile_packets = 0;
-    size_t samples = 0;
-    uint64_t total_allocated = 0;
-    uint64_t total_freed = 0;
-    for (const protos::TracePacket& packet : packets) {
-      if (packet.has_profile_packet() &&
-          packet.profile_packet().process_dumps().size() > 0) {
-        const auto& dumps = packet.profile_packet().process_dumps();
-        ASSERT_EQ(dumps.size(), 1);
-        const protos::ProfilePacket_ProcessHeapSamples& dump = dumps.Get(0);
-        EXPECT_EQ(dump.pid(), pid);
-        profile_packets++;
-        for (const auto& sample : dump.samples()) {
-          samples++;
-          total_allocated += sample.self_allocated();
-          total_freed += sample.self_freed();
-        }
+  const auto& packets = helper->trace();
+  ASSERT_GT(packets.size(), 0u);
+  size_t profile_packets = 0;
+  size_t samples = 0;
+  uint64_t total_allocated = 0;
+  uint64_t total_freed = 0;
+  for (const protos::TracePacket& packet : packets) {
+    if (packet.has_profile_packet() &&
+        packet.profile_packet().process_dumps().size() > 0) {
+      const auto& dumps = packet.profile_packet().process_dumps();
+      ASSERT_EQ(dumps.size(), 1);
+      const protos::ProfilePacket_ProcessHeapSamples& dump = dumps.Get(0);
+      EXPECT_EQ(dump.pid(), pid);
+      profile_packets++;
+      for (const auto& sample : dump.samples()) {
+        samples++;
+        total_allocated += sample.self_allocated();
+        total_freed += sample.self_freed();
       }
     }
-    EXPECT_EQ(profile_packets, 1);
-    EXPECT_GT(samples, 0);
-    EXPECT_GT(total_allocated, 0);
-    EXPECT_GT(total_freed, 0);
+  }
+  EXPECT_EQ(profile_packets, 1);
+  EXPECT_GT(samples, 0);
+  EXPECT_GT(total_allocated, 0);
+  EXPECT_GT(total_freed, 0);
+}
+
+TEST_P(HeapprofdEndToEnd, DiscoverByNameDenormalizedCmdline) {
+  auto helper = GetHelper(&task_runner);
+
+  TraceConfig trace_config;
+  trace_config.add_buffers()->set_size_kb(10 * 1024);
+  trace_config.set_duration_ms(5000);
+  trace_config.set_flush_timeout_ms(10000);
+
+  auto* ds_config = trace_config.add_data_sources()->mutable_config();
+  ds_config->set_name("android.heapprofd");
+
+  protozero::HeapBuffered<protos::pbzero::HeapprofdConfig> heapprofd_config;
+  heapprofd_config->set_sampling_interval_bytes(1);
+  heapprofd_config->add_process_cmdline("heapprofd_continuous_malloc@1.2.3");
+  heapprofd_config->set_all(false);
+  ds_config->set_heapprofd_config_raw(heapprofd_config.SerializeAsString());
+
+  // Make sure the forked process does not get reparented to init.
+  setsid();
+  pid_t pid = fork();
+  switch (pid) {
+    case -1:
+      PERFETTO_FATAL("Failed to fork.");
+    case 0: {
+      const char* envp[] = {"HEAPPROFD_TESTING_RUN_MALLOC=1", nullptr};
+      int null = open("/dev/null", O_RDWR);
+      dup2(null, STDIN_FILENO);
+      dup2(null, STDOUT_FILENO);
+      dup2(null, STDERR_FILENO);
+      PERFETTO_CHECK(execle("/proc/self/exe", "heapprofd_continuous_malloc",
+                            nullptr, envp) == 0);
+      break;
+    }
+    default:
+      break;
   }
 
-  void NativeStartupDenormalizedCmdline() {
-    auto helper = GetHelper(&task_runner);
+  // Wait to make sure process is fully initialized, so we do not accidentally
+  // match it by the startup logic.
+  sleep(1);
 
-    TraceConfig trace_config;
-    trace_config.add_buffers()->set_size_kb(10 * 1024);
-    trace_config.set_duration_ms(5000);
-    trace_config.set_flush_timeout_ms(10000);
+  helper->StartTracing(trace_config);
+  helper->WaitForTracingDisabled(20000);
 
-    auto* ds_config = trace_config.add_data_sources()->mutable_config();
-    ds_config->set_name("android.heapprofd");
+  helper->ReadData();
+  helper->WaitForReadData();
 
-    protozero::HeapBuffered<protos::pbzero::HeapprofdConfig> heapprofd_config;
-    heapprofd_config->set_sampling_interval_bytes(1);
-    heapprofd_config->add_process_cmdline("heapprofd_continuous_malloc@1.2.3");
-    heapprofd_config->set_all(false);
-    ds_config->set_heapprofd_config_raw(heapprofd_config.SerializeAsString());
+  PERFETTO_CHECK(kill(pid, SIGKILL) == 0);
+  PERFETTO_CHECK(PERFETTO_EINTR(waitpid(pid, nullptr, 0)) == pid);
 
-    helper->StartTracing(trace_config);
-
-    // Wait to guarantee that the process forked below is hooked by the profiler
-    // by virtue of the startup check, and not by virtue of being seen as a
-    // running process. This sleep is here to prevent that, accidentally, the
-    // test gets to the fork()+exec() too soon, before the heap profiling daemon
-    // has received the trace config.
-    sleep(1);
-
-    // Make sure the forked process does not get reparented to init.
-    setsid();
-    pid_t pid = fork();
-    switch (pid) {
-      case -1:
-        PERFETTO_FATAL("Failed to fork.");
-      case 0: {
-        const char* envp[] = {"HEAPPROFD_TESTING_RUN_MALLOC=1", nullptr};
-        int null = open("/dev/null", O_RDWR);
-        dup2(null, STDIN_FILENO);
-        dup2(null, STDOUT_FILENO);
-        dup2(null, STDERR_FILENO);
-        PERFETTO_CHECK(execle("/proc/self/exe", "heapprofd_continuous_malloc",
-                              nullptr, envp) == 0);
-        break;
-      }
-      default:
-        break;
-    }
-
-    helper->WaitForTracingDisabled(20000);
-
-    helper->ReadData();
-    helper->WaitForReadData();
-
-    PERFETTO_CHECK(kill(pid, SIGKILL) == 0);
-    PERFETTO_CHECK(PERFETTO_EINTR(waitpid(pid, nullptr, 0)) == pid);
-
-    const auto& packets = helper->trace();
-    ASSERT_GT(packets.size(), 0u);
-    size_t profile_packets = 0;
-    size_t samples = 0;
-    uint64_t total_allocated = 0;
-    uint64_t total_freed = 0;
-    for (const protos::TracePacket& packet : packets) {
-      if (packet.has_profile_packet() &&
-          packet.profile_packet().process_dumps().size() > 0) {
-        const auto& dumps = packet.profile_packet().process_dumps();
-        ASSERT_EQ(dumps.size(), 1);
-        const protos::ProfilePacket_ProcessHeapSamples& dump = dumps.Get(0);
-        EXPECT_EQ(dump.pid(), pid);
-        profile_packets++;
-        for (const auto& sample : dump.samples()) {
-          samples++;
-          total_allocated += sample.self_allocated();
-          total_freed += sample.self_freed();
-        }
+  const auto& packets = helper->trace();
+  ASSERT_GT(packets.size(), 0u);
+  size_t profile_packets = 0;
+  size_t samples = 0;
+  uint64_t total_allocated = 0;
+  uint64_t total_freed = 0;
+  for (const protos::TracePacket& packet : packets) {
+    if (packet.has_profile_packet() &&
+        packet.profile_packet().process_dumps().size() > 0) {
+      const auto& dumps = packet.profile_packet().process_dumps();
+      ASSERT_EQ(dumps.size(), 1);
+      const protos::ProfilePacket_ProcessHeapSamples& dump = dumps.Get(0);
+      EXPECT_EQ(dump.pid(), pid);
+      profile_packets++;
+      for (const auto& sample : dump.samples()) {
+        samples++;
+        total_allocated += sample.self_allocated();
+        total_freed += sample.self_freed();
       }
     }
-    EXPECT_EQ(profile_packets, 1);
-    EXPECT_GT(samples, 0);
-    EXPECT_GT(total_allocated, 0);
-    EXPECT_GT(total_freed, 0);
   }
+  EXPECT_EQ(profile_packets, 1);
+  EXPECT_GT(samples, 0);
+  EXPECT_GT(total_allocated, 0);
+  EXPECT_GT(total_freed, 0);
+}
 
-  void DiscoverByName() {
-    auto helper = GetHelper(&task_runner);
+TEST_P(HeapprofdEndToEnd, ReInit) {
+  constexpr uint64_t kFirstIterationBytes = 5;
+  constexpr uint64_t kSecondIterationBytes = 7;
 
-    TraceConfig trace_config;
-    trace_config.add_buffers()->set_size_kb(10 * 1024);
-    trace_config.set_duration_ms(5000);
-    trace_config.set_flush_timeout_ms(10000);
+  base::Pipe signal_pipe = base::Pipe::Create(base::Pipe::kBothNonBlock);
+  base::Pipe ack_pipe = base::Pipe::Create(base::Pipe::kBothBlock);
 
-    auto* ds_config = trace_config.add_data_sources()->mutable_config();
-    ds_config->set_name("android.heapprofd");
-
-    protozero::HeapBuffered<protos::pbzero::HeapprofdConfig> heapprofd_config;
-    heapprofd_config->set_sampling_interval_bytes(1);
-    heapprofd_config->add_process_cmdline("heapprofd_continuous_malloc");
-    heapprofd_config->set_all(false);
-    ds_config->set_heapprofd_config_raw(heapprofd_config.SerializeAsString());
-
-    // Make sure the forked process does not get reparented to init.
-    setsid();
-    pid_t pid = fork();
-    switch (pid) {
-      case -1:
-        PERFETTO_FATAL("Failed to fork.");
-      case 0: {
-        const char* envp[] = {"HEAPPROFD_TESTING_RUN_MALLOC=1", nullptr};
-        int null = open("/dev/null", O_RDWR);
-        dup2(null, STDIN_FILENO);
-        dup2(null, STDOUT_FILENO);
-        dup2(null, STDERR_FILENO);
-        PERFETTO_CHECK(execle("/proc/self/exe", "heapprofd_continuous_malloc",
-                              nullptr, envp) == 0);
-        break;
-      }
-      default:
-        break;
-    }
-
-    // Wait to make sure process is fully initialized, so we do not accidentally
-    // match it by the startup logic.
-    sleep(1);
-
-    helper->StartTracing(trace_config);
-    helper->WaitForTracingDisabled(20000);
-
-    helper->ReadData();
-    helper->WaitForReadData();
-
-    PERFETTO_CHECK(kill(pid, SIGKILL) == 0);
-    PERFETTO_CHECK(PERFETTO_EINTR(waitpid(pid, nullptr, 0)) == pid);
-
-    const auto& packets = helper->trace();
-    ASSERT_GT(packets.size(), 0u);
-    size_t profile_packets = 0;
-    size_t samples = 0;
-    uint64_t total_allocated = 0;
-    uint64_t total_freed = 0;
-    for (const protos::TracePacket& packet : packets) {
-      if (packet.has_profile_packet() &&
-          packet.profile_packet().process_dumps().size() > 0) {
-        const auto& dumps = packet.profile_packet().process_dumps();
-        ASSERT_EQ(dumps.size(), 1);
-        const protos::ProfilePacket_ProcessHeapSamples& dump = dumps.Get(0);
-        EXPECT_EQ(dump.pid(), pid);
-        profile_packets++;
-        for (const auto& sample : dump.samples()) {
-          samples++;
-          total_allocated += sample.self_allocated();
-          total_freed += sample.self_freed();
-        }
-      }
-    }
-    EXPECT_EQ(profile_packets, 1);
-    EXPECT_GT(samples, 0);
-    EXPECT_GT(total_allocated, 0);
-    EXPECT_GT(total_freed, 0);
-  }
-
-  void DiscoverByNameDenormalizedCmdline() {
-    auto helper = GetHelper(&task_runner);
-
-    TraceConfig trace_config;
-    trace_config.add_buffers()->set_size_kb(10 * 1024);
-    trace_config.set_duration_ms(5000);
-    trace_config.set_flush_timeout_ms(10000);
-
-    auto* ds_config = trace_config.add_data_sources()->mutable_config();
-    ds_config->set_name("android.heapprofd");
-
-    protozero::HeapBuffered<protos::pbzero::HeapprofdConfig> heapprofd_config;
-    heapprofd_config->set_sampling_interval_bytes(1);
-    heapprofd_config->add_process_cmdline("heapprofd_continuous_malloc@1.2.3");
-    heapprofd_config->set_all(false);
-    ds_config->set_heapprofd_config_raw(heapprofd_config.SerializeAsString());
-
-    // Make sure the forked process does not get reparented to init.
-    setsid();
-    pid_t pid = fork();
-    switch (pid) {
-      case -1:
-        PERFETTO_FATAL("Failed to fork.");
-      case 0: {
-        const char* envp[] = {"HEAPPROFD_TESTING_RUN_MALLOC=1", nullptr};
-        int null = open("/dev/null", O_RDWR);
-        dup2(null, STDIN_FILENO);
-        dup2(null, STDOUT_FILENO);
-        dup2(null, STDERR_FILENO);
-        PERFETTO_CHECK(execle("/proc/self/exe", "heapprofd_continuous_malloc",
-                              nullptr, envp) == 0);
-        break;
-      }
-      default:
-        break;
-    }
-
-    // Wait to make sure process is fully initialized, so we do not accidentally
-    // match it by the startup logic.
-    sleep(1);
-
-    helper->StartTracing(trace_config);
-    helper->WaitForTracingDisabled(20000);
-
-    helper->ReadData();
-    helper->WaitForReadData();
-
-    PERFETTO_CHECK(kill(pid, SIGKILL) == 0);
-    PERFETTO_CHECK(PERFETTO_EINTR(waitpid(pid, nullptr, 0)) == pid);
-
-    const auto& packets = helper->trace();
-    ASSERT_GT(packets.size(), 0u);
-    size_t profile_packets = 0;
-    size_t samples = 0;
-    uint64_t total_allocated = 0;
-    uint64_t total_freed = 0;
-    for (const protos::TracePacket& packet : packets) {
-      if (packet.has_profile_packet() &&
-          packet.profile_packet().process_dumps().size() > 0) {
-        const auto& dumps = packet.profile_packet().process_dumps();
-        ASSERT_EQ(dumps.size(), 1);
-        const protos::ProfilePacket_ProcessHeapSamples& dump = dumps.Get(0);
-        EXPECT_EQ(dump.pid(), pid);
-        profile_packets++;
-        for (const auto& sample : dump.samples()) {
-          samples++;
-          total_allocated += sample.self_allocated();
-          total_freed += sample.self_freed();
-        }
-      }
-    }
-    EXPECT_EQ(profile_packets, 1);
-    EXPECT_GT(samples, 0);
-    EXPECT_GT(total_allocated, 0);
-    EXPECT_GT(total_freed, 0);
-  }
-
-  void ReInit() {
-    constexpr uint64_t kFirstIterationBytes = 5;
-    constexpr uint64_t kSecondIterationBytes = 7;
-
-    base::Pipe signal_pipe = base::Pipe::Create(base::Pipe::kBothNonBlock);
-    base::Pipe ack_pipe = base::Pipe::Create(base::Pipe::kBothBlock);
-
-    setsid();
-    pid_t pid = fork();
-    switch (pid) {
-      case -1:
-        PERFETTO_FATAL("Failed to fork.");
-      case 0: {
-        uint64_t bytes = kFirstIterationBytes;
-        signal_pipe.wr.reset();
-        ack_pipe.rd.reset();
-        for (;;) {
+  setsid();
+  pid_t pid = fork();
+  switch (pid) {
+    case -1:
+      PERFETTO_FATAL("Failed to fork.");
+    case 0: {
+      uint64_t bytes = kFirstIterationBytes;
+      signal_pipe.wr.reset();
+      ack_pipe.rd.reset();
+      for (;;) {
+        AllocateAndFree(bytes);
+        char buf[1];
+        if (bool(signal_pipe.rd) &&
+            read(*signal_pipe.rd, buf, sizeof(buf)) == 0) {
+          // make sure the client has noticed that the session has stopped
           AllocateAndFree(bytes);
-          char buf[1];
-          if (bool(signal_pipe.rd) &&
-              read(*signal_pipe.rd, buf, sizeof(buf)) == 0) {
-            // make sure the client has noticed that the session has stopped
-            AllocateAndFree(bytes);
 
-            bytes = kSecondIterationBytes;
-            signal_pipe.rd.reset();
-            ack_pipe.wr.reset();
-          }
-          usleep(10 * kMsToUs);
-        }
-        PERFETTO_FATAL("Should be unreachable");
-      }
-      default:
-        break;
-    }
-
-    signal_pipe.rd.reset();
-    ack_pipe.wr.reset();
-
-    TraceConfig trace_config;
-    trace_config.add_buffers()->set_size_kb(10 * 1024);
-    trace_config.set_duration_ms(2000);
-    trace_config.set_flush_timeout_ms(10000);
-
-    auto* ds_config = trace_config.add_data_sources()->mutable_config();
-    ds_config->set_name("android.heapprofd");
-    ds_config->set_target_buffer(0);
-
-    protozero::HeapBuffered<protos::pbzero::HeapprofdConfig> heapprofd_config;
-    heapprofd_config->set_sampling_interval_bytes(1);
-    heapprofd_config->add_pid(static_cast<uint64_t>(pid));
-    heapprofd_config->set_all(false);
-    ds_config->set_heapprofd_config_raw(heapprofd_config.SerializeAsString());
-
-    auto helper = Trace(trace_config);
-    PrintStats(helper.get());
-    ValidateHasSamples(helper.get(), static_cast<uint64_t>(pid));
-    ValidateOnlyPID(helper.get(), static_cast<uint64_t>(pid));
-    ValidateSampleSizes(helper.get(), static_cast<uint64_t>(pid),
-                        kFirstIterationBytes);
-
-    signal_pipe.wr.reset();
-    char buf[1];
-    ASSERT_EQ(read(*ack_pipe.rd, buf, sizeof(buf)), 0);
-    ack_pipe.rd.reset();
-
-    // A brief sleep to allow the client to notice that the profiling session is
-    // to be torn down (as it rejects concurrent sessions).
-    usleep(500 * kMsToUs);
-
-    PERFETTO_LOG("HeapprofdEndToEnd::Reinit: Starting second");
-    helper = Trace(trace_config);
-    PrintStats(helper.get());
-    ValidateHasSamples(helper.get(), static_cast<uint64_t>(pid));
-    ValidateOnlyPID(helper.get(), static_cast<uint64_t>(pid));
-    ValidateSampleSizes(helper.get(), static_cast<uint64_t>(pid),
-                        kSecondIterationBytes);
-
-    PERFETTO_CHECK(kill(pid, SIGKILL) == 0);
-    PERFETTO_CHECK(PERFETTO_EINTR(waitpid(pid, nullptr, 0)) == pid);
-  }
-
-  void ConcurrentSession() {
-    constexpr size_t kAllocSize = 1024;
-
-    pid_t pid = ForkContinuousMalloc(kAllocSize);
-
-    TraceConfig trace_config;
-    trace_config.add_buffers()->set_size_kb(10 * 1024);
-    trace_config.set_duration_ms(5000);
-    trace_config.set_flush_timeout_ms(10000);
-
-    auto* ds_config = trace_config.add_data_sources()->mutable_config();
-    ds_config->set_name("android.heapprofd");
-    ds_config->set_target_buffer(0);
-
-    protozero::HeapBuffered<protos::pbzero::HeapprofdConfig> heapprofd_config;
-    heapprofd_config->set_sampling_interval_bytes(1);
-    heapprofd_config->add_pid(static_cast<uint64_t>(pid));
-    heapprofd_config->set_all(false);
-    auto* cont_config = heapprofd_config->set_continuous_dump_config();
-    cont_config->set_dump_phase_ms(0);
-    cont_config->set_dump_interval_ms(100);
-    ds_config->set_heapprofd_config_raw(heapprofd_config.SerializeAsString());
-
-    auto helper = GetHelper(&task_runner);
-    helper->StartTracing(trace_config);
-    sleep(1);
-    auto helper_concurrent = GetHelper(&task_runner);
-    helper_concurrent->StartTracing(trace_config);
-
-    helper->WaitForTracingDisabled(20000);
-    helper->ReadData();
-    helper->WaitForReadData();
-    PrintStats(helper.get());
-    ValidateHasSamples(helper.get(), static_cast<uint64_t>(pid));
-    ValidateOnlyPID(helper.get(), static_cast<uint64_t>(pid));
-    ValidateSampleSizes(helper.get(), static_cast<uint64_t>(pid), kAllocSize);
-    ValidateRejectedConcurrent(helper_concurrent.get(),
-                               static_cast<uint64_t>(pid), false);
-
-    helper_concurrent->WaitForTracingDisabled(20000);
-    helper_concurrent->ReadData();
-    helper_concurrent->WaitForReadData();
-    PrintStats(helper.get());
-    ValidateOnlyPID(helper_concurrent.get(), static_cast<uint64_t>(pid));
-    ValidateRejectedConcurrent(helper_concurrent.get(),
-                               static_cast<uint64_t>(pid), true);
-
-    PERFETTO_CHECK(kill(pid, SIGKILL) == 0);
-    PERFETTO_CHECK(PERFETTO_EINTR(waitpid(pid, nullptr, 0)) == pid);
-  }
-
-  // TODO(rsavitski): fold exit status assertions into existing tests where
-  // possible.
-  void NativeProfilingActiveAtProcessExit() {
-    constexpr uint64_t kTestAllocSize = 128;
-    base::Pipe start_pipe = base::Pipe::Create(base::Pipe::kBothBlock);
-
-    setsid();
-    pid_t pid = fork();
-    if (pid == 0) {  // child
-      start_pipe.rd.reset();
-      start_pipe.wr.reset();
-      for (int i = 0; i < 200; i++) {
-        // malloc and leak, otherwise the free batching will cause us to filter
-        // out the allocations (as we don't see the interleaved frees).
-        volatile char* x = static_cast<char*>(malloc(kTestAllocSize));
-        if (x) {
-          x[0] = 'x';
+          bytes = kSecondIterationBytes;
+          signal_pipe.rd.reset();
+          ack_pipe.wr.reset();
         }
         usleep(10 * kMsToUs);
       }
-      exit(0);
+      PERFETTO_FATAL("Should be unreachable");
     }
+    default:
+      break;
+  }
 
-    ASSERT_NE(pid, -1) << "Failed to fork.";
-    start_pipe.wr.reset();
+  signal_pipe.rd.reset();
+  ack_pipe.wr.reset();
 
-    // Construct tracing config (without starting profiling).
-    auto helper = GetHelper(&task_runner);
-    TraceConfig trace_config;
-    trace_config.add_buffers()->set_size_kb(10 * 1024);
-    trace_config.set_duration_ms(5000);
-    trace_config.set_flush_timeout_ms(10000);
+  TraceConfig trace_config;
+  trace_config.add_buffers()->set_size_kb(10 * 1024);
+  trace_config.set_duration_ms(2000);
+  trace_config.set_flush_timeout_ms(10000);
 
-    auto* ds_config = trace_config.add_data_sources()->mutable_config();
-    ds_config->set_name("android.heapprofd");
+  auto* ds_config = trace_config.add_data_sources()->mutable_config();
+  ds_config->set_name("android.heapprofd");
+  ds_config->set_target_buffer(0);
 
-    protozero::HeapBuffered<protos::pbzero::HeapprofdConfig> heapprofd_config;
-    heapprofd_config->set_sampling_interval_bytes(1);
-    heapprofd_config->add_pid(static_cast<uint64_t>(pid));
-    ds_config->set_heapprofd_config_raw(heapprofd_config.SerializeAsString());
+  protozero::HeapBuffered<protos::pbzero::HeapprofdConfig> heapprofd_config;
+  heapprofd_config->set_sampling_interval_bytes(1);
+  heapprofd_config->add_pid(static_cast<uint64_t>(pid));
+  heapprofd_config->set_all(false);
+  ds_config->set_heapprofd_config_raw(heapprofd_config.SerializeAsString());
 
-    // Wait for child to have been scheduled at least once.
-    char buf[1] = {};
-    ASSERT_EQ(PERFETTO_EINTR(read(*start_pipe.rd, buf, sizeof(buf))), 0);
+  auto helper = Trace(trace_config);
+  PrintStats(helper.get());
+  ValidateHasSamples(helper.get(), static_cast<uint64_t>(pid));
+  ValidateOnlyPID(helper.get(), static_cast<uint64_t>(pid));
+  ValidateSampleSizes(helper.get(), static_cast<uint64_t>(pid),
+                      kFirstIterationBytes);
+
+  signal_pipe.wr.reset();
+  char buf[1];
+  ASSERT_EQ(read(*ack_pipe.rd, buf, sizeof(buf)), 0);
+  ack_pipe.rd.reset();
+
+  // A brief sleep to allow the client to notice that the profiling session is
+  // to be torn down (as it rejects concurrent sessions).
+  usleep(500 * kMsToUs);
+
+  PERFETTO_LOG("HeapprofdEndToEnd::Reinit: Starting second");
+  helper = Trace(trace_config);
+  PrintStats(helper.get());
+  ValidateHasSamples(helper.get(), static_cast<uint64_t>(pid));
+  ValidateOnlyPID(helper.get(), static_cast<uint64_t>(pid));
+  ValidateSampleSizes(helper.get(), static_cast<uint64_t>(pid),
+                      kSecondIterationBytes);
+
+  PERFETTO_CHECK(kill(pid, SIGKILL) == 0);
+  PERFETTO_CHECK(PERFETTO_EINTR(waitpid(pid, nullptr, 0)) == pid);
+}
+
+TEST_P(HeapprofdEndToEnd, ConcurrentSession) {
+  constexpr size_t kAllocSize = 1024;
+
+  pid_t pid = ForkContinuousMalloc(kAllocSize);
+
+  TraceConfig trace_config;
+  trace_config.add_buffers()->set_size_kb(10 * 1024);
+  trace_config.set_duration_ms(5000);
+  trace_config.set_flush_timeout_ms(10000);
+
+  auto* ds_config = trace_config.add_data_sources()->mutable_config();
+  ds_config->set_name("android.heapprofd");
+  ds_config->set_target_buffer(0);
+
+  protozero::HeapBuffered<protos::pbzero::HeapprofdConfig> heapprofd_config;
+  heapprofd_config->set_sampling_interval_bytes(1);
+  heapprofd_config->add_pid(static_cast<uint64_t>(pid));
+  heapprofd_config->set_all(false);
+  auto* cont_config = heapprofd_config->set_continuous_dump_config();
+  cont_config->set_dump_phase_ms(0);
+  cont_config->set_dump_interval_ms(100);
+  ds_config->set_heapprofd_config_raw(heapprofd_config.SerializeAsString());
+
+  auto helper = GetHelper(&task_runner);
+  helper->StartTracing(trace_config);
+  sleep(1);
+  auto helper_concurrent = GetHelper(&task_runner);
+  helper_concurrent->StartTracing(trace_config);
+
+  helper->WaitForTracingDisabled(20000);
+  helper->ReadData();
+  helper->WaitForReadData();
+  PrintStats(helper.get());
+  ValidateHasSamples(helper.get(), static_cast<uint64_t>(pid));
+  ValidateOnlyPID(helper.get(), static_cast<uint64_t>(pid));
+  ValidateSampleSizes(helper.get(), static_cast<uint64_t>(pid), kAllocSize);
+  ValidateRejectedConcurrent(helper_concurrent.get(),
+                             static_cast<uint64_t>(pid), false);
+
+  helper_concurrent->WaitForTracingDisabled(20000);
+  helper_concurrent->ReadData();
+  helper_concurrent->WaitForReadData();
+  PrintStats(helper.get());
+  ValidateOnlyPID(helper_concurrent.get(), static_cast<uint64_t>(pid));
+  ValidateRejectedConcurrent(helper_concurrent.get(),
+                             static_cast<uint64_t>(pid), true);
+
+  PERFETTO_CHECK(kill(pid, SIGKILL) == 0);
+  PERFETTO_CHECK(PERFETTO_EINTR(waitpid(pid, nullptr, 0)) == pid);
+}
+
+// TODO(rsavitski): fold exit status assertions into existing tests where
+// possible.
+TEST_P(HeapprofdEndToEnd, NativeProfilingActiveAtProcessExit) {
+  constexpr uint64_t kTestAllocSize = 128;
+  base::Pipe start_pipe = base::Pipe::Create(base::Pipe::kBothBlock);
+
+  setsid();
+  pid_t pid = fork();
+  if (pid == 0) {  // child
     start_pipe.rd.reset();
+    start_pipe.wr.reset();
+    for (int i = 0; i < 200; i++) {
+      // malloc and leak, otherwise the free batching will cause us to filter
+      // out the allocations (as we don't see the interleaved frees).
+      volatile char* x = static_cast<char*>(malloc(kTestAllocSize));
+      if (x) {
+        x[0] = 'x';
+      }
+      usleep(10 * kMsToUs);
+    }
+    exit(0);
+  }
 
-    // Trace until child exits.
-    helper->StartTracing(trace_config);
+  ASSERT_NE(pid, -1) << "Failed to fork.";
+  start_pipe.wr.reset();
 
-    siginfo_t siginfo = {};
-    int wait_ret = PERFETTO_EINTR(
-        waitid(P_PID, static_cast<id_t>(pid), &siginfo, WEXITED));
-    ASSERT_FALSE(wait_ret) << "Failed to waitid.";
+  // Construct tracing config (without starting profiling).
+  auto helper = GetHelper(&task_runner);
+  TraceConfig trace_config;
+  trace_config.add_buffers()->set_size_kb(10 * 1024);
+  trace_config.set_duration_ms(5000);
+  trace_config.set_flush_timeout_ms(10000);
 
-    // Assert that the child exited successfully.
-    EXPECT_EQ(siginfo.si_code, CLD_EXITED) << "Child did not exit by itself.";
-    EXPECT_EQ(siginfo.si_status, 0) << "Child's exit status not successful.";
+  auto* ds_config = trace_config.add_data_sources()->mutable_config();
+  ds_config->set_name("android.heapprofd");
 
-    // Assert that we did profile the process.
-    helper->FlushAndWait(2000);
-    helper->DisableTracing();
-    helper->WaitForTracingDisabled(10000);
-    helper->ReadData();
-    helper->WaitForReadData();
+  protozero::HeapBuffered<protos::pbzero::HeapprofdConfig> heapprofd_config;
+  heapprofd_config->set_sampling_interval_bytes(1);
+  heapprofd_config->add_pid(static_cast<uint64_t>(pid));
+  ds_config->set_heapprofd_config_raw(heapprofd_config.SerializeAsString());
 
-    const auto& packets = helper->trace();
-    ASSERT_GT(packets.size(), 0u);
-    size_t profile_packets = 0;
-    size_t samples = 0;
-    uint64_t total_allocated = 0;
-    for (const protos::TracePacket& packet : packets) {
-      if (packet.has_profile_packet() &&
-          packet.profile_packet().process_dumps().size() > 0) {
-        const auto& dumps = packet.profile_packet().process_dumps();
-        ASSERT_EQ(dumps.size(), 1);
-        const protos::ProfilePacket_ProcessHeapSamples& dump = dumps.Get(0);
-        EXPECT_EQ(dump.pid(), pid);
-        profile_packets++;
-        for (const auto& sample : dump.samples()) {
-          samples++;
-          total_allocated += sample.self_allocated();
-        }
+  // Wait for child to have been scheduled at least once.
+  char buf[1] = {};
+  ASSERT_EQ(PERFETTO_EINTR(read(*start_pipe.rd, buf, sizeof(buf))), 0);
+  start_pipe.rd.reset();
+
+  // Trace until child exits.
+  helper->StartTracing(trace_config);
+
+  siginfo_t siginfo = {};
+  int wait_ret =
+      PERFETTO_EINTR(waitid(P_PID, static_cast<id_t>(pid), &siginfo, WEXITED));
+  ASSERT_FALSE(wait_ret) << "Failed to waitid.";
+
+  // Assert that the child exited successfully.
+  EXPECT_EQ(siginfo.si_code, CLD_EXITED) << "Child did not exit by itself.";
+  EXPECT_EQ(siginfo.si_status, 0) << "Child's exit status not successful.";
+
+  // Assert that we did profile the process.
+  helper->FlushAndWait(2000);
+  helper->DisableTracing();
+  helper->WaitForTracingDisabled(10000);
+  helper->ReadData();
+  helper->WaitForReadData();
+
+  const auto& packets = helper->trace();
+  ASSERT_GT(packets.size(), 0u);
+  size_t profile_packets = 0;
+  size_t samples = 0;
+  uint64_t total_allocated = 0;
+  for (const protos::TracePacket& packet : packets) {
+    if (packet.has_profile_packet() &&
+        packet.profile_packet().process_dumps().size() > 0) {
+      const auto& dumps = packet.profile_packet().process_dumps();
+      ASSERT_EQ(dumps.size(), 1);
+      const protos::ProfilePacket_ProcessHeapSamples& dump = dumps.Get(0);
+      EXPECT_EQ(dump.pid(), pid);
+      profile_packets++;
+      for (const auto& sample : dump.samples()) {
+        samples++;
+        total_allocated += sample.self_allocated();
       }
     }
-    EXPECT_EQ(profile_packets, 1);
-    EXPECT_GT(samples, 0);
-    EXPECT_GT(total_allocated, 0);
   }
-};
-
-TEST_F(HeapprofdEndToEnd, MAYBE_SKIP(Smoke_Central)) {
-  auto prop = DisableFork();
-  ASSERT_EQ(ReadProperty(kHeapprofdModeProperty, ""), "");
-  Smoke();
+  EXPECT_EQ(profile_packets, 1);
+  EXPECT_GT(samples, 0);
+  EXPECT_GT(total_allocated, 0);
 }
 
-TEST_F(HeapprofdEndToEnd, MAYBE_SKIP(TwoProcesses_Fork)) {
-  // RAII handle that resets to central mode when out of scope.
-  auto prop = EnableFork();
-  ASSERT_EQ(ReadProperty(kHeapprofdModeProperty, ""), "fork");
-  TwoProcesses();
-}
-
-TEST_F(HeapprofdEndToEnd, MAYBE_SKIP(TwoProcesses_Central)) {
-  auto prop = DisableFork();
-  ASSERT_EQ(ReadProperty(kHeapprofdModeProperty, ""), "");
-  TwoProcesses();
-}
-
-TEST_F(HeapprofdEndToEnd, MAYBE_SKIP(Smoke_Fork)) {
-  // RAII handle that resets to central mode when out of scope.
-  auto prop = EnableFork();
-  ASSERT_EQ(ReadProperty(kHeapprofdModeProperty, ""), "fork");
-  Smoke();
-}
-
-TEST_F(HeapprofdEndToEnd, MAYBE_SKIP(FinalFlush_Central)) {
-  auto prop = DisableFork();
-  ASSERT_EQ(ReadProperty(kHeapprofdModeProperty, ""), "");
-  FinalFlush();
-}
-
-TEST_F(HeapprofdEndToEnd, MAYBE_SKIP(FinalFlush_Fork)) {
-  // RAII handle that resets to central mode when out of scope.
-  auto prop = EnableFork();
-  ASSERT_EQ(ReadProperty(kHeapprofdModeProperty, ""), "fork");
-  FinalFlush();
-}
-
-TEST_F(HeapprofdEndToEnd, MAYBE_SKIP(NativeStartup_Central)) {
-  auto prop = DisableFork();
-  ASSERT_EQ(ReadProperty(kHeapprofdModeProperty, ""), "");
-  NativeStartup();
-}
-
-TEST_F(HeapprofdEndToEnd, MAYBE_SKIP(NativeStartup_Fork)) {
-  // RAII handle that resets to central mode when out of scope.
-  auto prop = EnableFork();
-  ASSERT_EQ(ReadProperty(kHeapprofdModeProperty, ""), "fork");
-  NativeStartup();
-}
-
-TEST_F(HeapprofdEndToEnd,
-       MAYBE_SKIP(NativeStartupDenormalizedCmdline_Central)) {
-  auto prop = DisableFork();
-  ASSERT_EQ(ReadProperty(kHeapprofdModeProperty, ""), "");
-  NativeStartupDenormalizedCmdline();
-}
-
-TEST_F(HeapprofdEndToEnd, MAYBE_SKIP(NativeStartupDenormalizedCmdline_Fork)) {
-  // RAII handle that resets to central mode when out of scope.
-  auto prop = EnableFork();
-  ASSERT_EQ(ReadProperty(kHeapprofdModeProperty, ""), "fork");
-  NativeStartupDenormalizedCmdline();
-}
-
-TEST_F(HeapprofdEndToEnd, MAYBE_SKIP(DiscoverByName_Central)) {
-  auto prop = DisableFork();
-  ASSERT_EQ(ReadProperty(kHeapprofdModeProperty, ""), "");
-  DiscoverByName();
-}
-
-TEST_F(HeapprofdEndToEnd, MAYBE_SKIP(DiscoverByName_Fork)) {
-  // RAII handle that resets to central mode when out of scope.
-  auto prop = EnableFork();
-  ASSERT_EQ(ReadProperty(kHeapprofdModeProperty, ""), "fork");
-  DiscoverByName();
-}
-
-TEST_F(HeapprofdEndToEnd,
-       MAYBE_SKIP(DiscoverByNameDenormalizedCmdline_Central)) {
-  auto prop = DisableFork();
-  ASSERT_EQ(ReadProperty(kHeapprofdModeProperty, ""), "");
-  DiscoverByNameDenormalizedCmdline();
-}
-
-TEST_F(HeapprofdEndToEnd, MAYBE_SKIP(DiscoverByNameDenormalizedCmdline_Fork)) {
-  auto prop = DisableFork();
-  ASSERT_EQ(ReadProperty(kHeapprofdModeProperty, ""), "");
-  DiscoverByNameDenormalizedCmdline();
-}
-
-TEST_F(HeapprofdEndToEnd, MAYBE_SKIP(ReInit_Central)) {
-  auto prop = DisableFork();
-  ASSERT_EQ(ReadProperty(kHeapprofdModeProperty, ""), "");
-  ReInit();
-}
-
-TEST_F(HeapprofdEndToEnd, MAYBE_SKIP(ReInit_Fork)) {
-  // RAII handle that resets to central mode when out of scope.
-  auto prop = EnableFork();
-  ASSERT_EQ(ReadProperty(kHeapprofdModeProperty, ""), "fork");
-  ReInit();
-}
-
-TEST_F(HeapprofdEndToEnd, MAYBE_SKIP(ConcurrentSession_Central)) {
-  auto prop = DisableFork();
-  ASSERT_EQ(ReadProperty(kHeapprofdModeProperty, ""), "");
-  ConcurrentSession();
-}
-
-TEST_F(HeapprofdEndToEnd, MAYBE_SKIP(ConcurrentSession_Fork)) {
-  // RAII handle that resets to central mode when out of scope.
-  auto prop = EnableFork();
-  ASSERT_EQ(ReadProperty(kHeapprofdModeProperty, ""), "fork");
-  ConcurrentSession();
-}
-
-TEST_F(HeapprofdEndToEnd,
-       MAYBE_SKIP(NativeProfilingActiveAtProcessExit_Central)) {
-  auto prop = DisableFork();
-  ASSERT_EQ(ReadProperty(kHeapprofdModeProperty, ""), "");
-  NativeProfilingActiveAtProcessExit();
-}
-
-TEST_F(HeapprofdEndToEnd, MAYBE_SKIP(NativeProfilingActiveAtProcessExit_Fork)) {
-  // RAII handle that resets to central mode when out of scope.
-  auto prop = EnableFork();
-  ASSERT_EQ(ReadProperty(kHeapprofdModeProperty, ""), "fork");
-  NativeProfilingActiveAtProcessExit();
-}
+// This test only works when run on Android using an Android Q version of
+// Bionic.
+// TODO(b/118428762): look into unwinding issues on x86.
+#if !PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) ||                        \
+    PERFETTO_BUILDFLAG(PERFETTO_START_DAEMONS) || defined(__i386__) || \
+    defined(__x86_64__)
+INSTANTIATE_TEST_CASE_P(DISABLED_ForkMode, HeapprofdEndToEnd, Bool());
+#else
+INSTANTIATE_TEST_CASE_P(ForkMode, HeapprofdEndToEnd, Bool());
+#endif
 
 }  // namespace
 }  // namespace profiling
diff --git a/tools/gen_android_bp b/tools/gen_android_bp
index 4024a51..7885567 100755
--- a/tools/gen_android_bp
+++ b/tools/gen_android_bp
@@ -48,7 +48,6 @@
     '//:heapprofd_client',
     '//:heapprofd',
     '//:trigger_perfetto',
-    '//:idle_alloc',
 ]
 
 # Defines a custom init_rc argument to be applied to the corresponding output