Merge "Add consumer API and --query cmdline to list data sources"
diff --git a/Android.bp b/Android.bp
index 6985687..79de486 100644
--- a/Android.bp
+++ b/Android.bp
@@ -215,6 +215,20 @@
   ],
 }
 
+// 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",
@@ -392,6 +406,149 @@
   ],
 }
 
+// GN target: //:libperfetto_client_experimental
+cc_library_static {
+  name: "libperfetto_client_experimental",
+  srcs: [
+    ":perfetto_protos_perfetto_common_lite_gen",
+    ":perfetto_protos_perfetto_common_zero_gen",
+    ":perfetto_protos_perfetto_config_lite_gen",
+    ":perfetto_protos_perfetto_config_zero_gen",
+    ":perfetto_protos_perfetto_ipc_ipc_gen",
+    ":perfetto_protos_perfetto_trace_android_zero_gen",
+    ":perfetto_protos_perfetto_trace_chrome_zero_gen",
+    ":perfetto_protos_perfetto_trace_filesystem_zero_gen",
+    ":perfetto_protos_perfetto_trace_ftrace_zero_gen",
+    ":perfetto_protos_perfetto_trace_interned_data_zero_gen",
+    ":perfetto_protos_perfetto_trace_minimal_lite_gen",
+    ":perfetto_protos_perfetto_trace_power_zero_gen",
+    ":perfetto_protos_perfetto_trace_profiling_zero_gen",
+    ":perfetto_protos_perfetto_trace_ps_zero_gen",
+    ":perfetto_protos_perfetto_trace_sys_stats_zero_gen",
+    ":perfetto_protos_perfetto_trace_track_event_zero_gen",
+    ":perfetto_protos_perfetto_trace_trusted_lite_gen",
+    ":perfetto_protos_perfetto_trace_zero_gen",
+    ":perfetto_src_ipc_wire_protocol_gen",
+    "src/base/event.cc",
+    "src/base/file_utils.cc",
+    "src/base/metatrace.cc",
+    "src/base/paged_memory.cc",
+    "src/base/pipe.cc",
+    "src/base/string_splitter.cc",
+    "src/base/string_utils.cc",
+    "src/base/string_view.cc",
+    "src/base/temp_file.cc",
+    "src/base/thread_checker.cc",
+    "src/base/thread_task_runner.cc",
+    "src/base/time.cc",
+    "src/base/unix_socket.cc",
+    "src/base/unix_task_runner.cc",
+    "src/base/virtual_destructors.cc",
+    "src/base/watchdog_posix.cc",
+    "src/ipc/buffered_frame_deserializer.cc",
+    "src/ipc/client_impl.cc",
+    "src/ipc/deferred.cc",
+    "src/ipc/host_impl.cc",
+    "src/ipc/service_proxy.cc",
+    "src/ipc/virtual_destructors.cc",
+    "src/protozero/message.cc",
+    "src/protozero/message_handle.cc",
+    "src/protozero/proto_decoder.cc",
+    "src/protozero/scattered_heap_buffer.cc",
+    "src/protozero/scattered_stream_null_delegate.cc",
+    "src/protozero/scattered_stream_writer.cc",
+    "src/tracing/core/chrome_config.cc",
+    "src/tracing/core/commit_data_request.cc",
+    "src/tracing/core/data_source_config.cc",
+    "src/tracing/core/data_source_descriptor.cc",
+    "src/tracing/core/id_allocator.cc",
+    "src/tracing/core/null_trace_writer.cc",
+    "src/tracing/core/observable_events.cc",
+    "src/tracing/core/packet_stream_validator.cc",
+    "src/tracing/core/shared_memory_abi.cc",
+    "src/tracing/core/shared_memory_arbiter_impl.cc",
+    "src/tracing/core/sliced_protobuf_input_stream.cc",
+    "src/tracing/core/startup_trace_writer.cc",
+    "src/tracing/core/startup_trace_writer_registry.cc",
+    "src/tracing/core/test_config.cc",
+    "src/tracing/core/trace_buffer.cc",
+    "src/tracing/core/trace_config.cc",
+    "src/tracing/core/trace_packet.cc",
+    "src/tracing/core/trace_stats.cc",
+    "src/tracing/core/trace_writer_impl.cc",
+    "src/tracing/core/tracing_service_impl.cc",
+    "src/tracing/core/virtual_destructors.cc",
+    "src/tracing/data_source.cc",
+    "src/tracing/internal/in_process_tracing_backend.cc",
+    "src/tracing/internal/system_tracing_backend.cc",
+    "src/tracing/internal/tracing_muxer_impl.cc",
+    "src/tracing/platform.cc",
+    "src/tracing/platform_posix.cc",
+    "src/tracing/trace_writer_base.cc",
+    "src/tracing/tracing.cc",
+    "src/tracing/virtual_destructors.cc",
+  ],
+  shared_libs: [
+    "libprotobuf-cpp-lite",
+  ],
+  static_libs: [
+    "perfetto_src_tracing_ipc",
+  ],
+  export_include_dirs: [
+    "include",
+  ],
+  generated_headers: [
+    "perfetto_protos_perfetto_common_lite_gen_headers",
+    "perfetto_protos_perfetto_common_zero_gen_headers",
+    "perfetto_protos_perfetto_config_lite_gen_headers",
+    "perfetto_protos_perfetto_config_zero_gen_headers",
+    "perfetto_protos_perfetto_ipc_ipc_gen_headers",
+    "perfetto_protos_perfetto_trace_android_zero_gen_headers",
+    "perfetto_protos_perfetto_trace_chrome_zero_gen_headers",
+    "perfetto_protos_perfetto_trace_filesystem_zero_gen_headers",
+    "perfetto_protos_perfetto_trace_ftrace_zero_gen_headers",
+    "perfetto_protos_perfetto_trace_interned_data_zero_gen_headers",
+    "perfetto_protos_perfetto_trace_minimal_lite_gen_headers",
+    "perfetto_protos_perfetto_trace_power_zero_gen_headers",
+    "perfetto_protos_perfetto_trace_profiling_zero_gen_headers",
+    "perfetto_protos_perfetto_trace_ps_zero_gen_headers",
+    "perfetto_protos_perfetto_trace_sys_stats_zero_gen_headers",
+    "perfetto_protos_perfetto_trace_track_event_zero_gen_headers",
+    "perfetto_protos_perfetto_trace_trusted_lite_gen_headers",
+    "perfetto_protos_perfetto_trace_zero_gen_headers",
+    "perfetto_src_ipc_wire_protocol_gen_headers",
+  ],
+  export_generated_headers: [
+    "perfetto_protos_perfetto_common_lite_gen_headers",
+    "perfetto_protos_perfetto_common_zero_gen_headers",
+    "perfetto_protos_perfetto_config_lite_gen_headers",
+    "perfetto_protos_perfetto_config_zero_gen_headers",
+    "perfetto_protos_perfetto_ipc_ipc_gen_headers",
+    "perfetto_protos_perfetto_trace_android_zero_gen_headers",
+    "perfetto_protos_perfetto_trace_chrome_zero_gen_headers",
+    "perfetto_protos_perfetto_trace_filesystem_zero_gen_headers",
+    "perfetto_protos_perfetto_trace_ftrace_zero_gen_headers",
+    "perfetto_protos_perfetto_trace_interned_data_zero_gen_headers",
+    "perfetto_protos_perfetto_trace_minimal_lite_gen_headers",
+    "perfetto_protos_perfetto_trace_power_zero_gen_headers",
+    "perfetto_protos_perfetto_trace_profiling_zero_gen_headers",
+    "perfetto_protos_perfetto_trace_ps_zero_gen_headers",
+    "perfetto_protos_perfetto_trace_sys_stats_zero_gen_headers",
+    "perfetto_protos_perfetto_trace_track_event_zero_gen_headers",
+    "perfetto_protos_perfetto_trace_trusted_lite_gen_headers",
+    "perfetto_protos_perfetto_trace_zero_gen_headers",
+    "perfetto_src_ipc_wire_protocol_gen_headers",
+  ],
+  defaults: [
+    "perfetto_defaults",
+  ],
+  cflags: [
+    "-DGOOGLE_PROTOBUF_NO_RTTI",
+    "-DGOOGLE_PROTOBUF_NO_STATIC_INITIALIZER",
+    "-DPERFETTO_BUILD_WITH_ANDROID",
+  ],
+}
+
 // GN target: //:perfetto
 cc_binary {
   name: "perfetto",
@@ -3599,4 +3756,30 @@
   srcs: [
     "protos/perfetto/config/perfetto_config.proto",
   ],
+}
+
+// This sample target shows how to use the perfetto client API from within the
+// Android tree.
+cc_binary {
+  name: "libperfetto_client_example",
+  srcs: [
+    "test/android_client_api_example.cc",
+  ],
+  static_libs: [
+    "libperfetto_client_experimental",
+    "perfetto_src_tracing_ipc",
+    "perfetto_trace_protos",
+  ],
+  shared_libs: [
+    "libprotobuf-cpp-lite",
+    "liblog",
+  ],
+  local_include_dirs: [
+    "include",
+  ],
+  cflags: [
+    "-DGOOGLE_PROTOBUF_NO_RTTI",
+    "-DGOOGLE_PROTOBUF_NO_STATIC_INITIALIZER",
+    "-DPERFETTO_BUILD_WITH_ANDROID",
+  ],
 }
\ No newline at end of file
diff --git a/Android.bp.extras b/Android.bp.extras
index 84c0f10..54de0c9 100644
--- a/Android.bp.extras
+++ b/Android.bp.extras
@@ -64,3 +64,29 @@
     "protos/perfetto/config/perfetto_config.proto",
   ],
 }
+
+// This sample target shows how to use the perfetto client API from within the
+// Android tree.
+cc_binary {
+  name: "libperfetto_client_example",
+  srcs: [
+    "test/android_client_api_example.cc",
+  ],
+  static_libs: [
+    "libperfetto_client_experimental",
+    "perfetto_src_tracing_ipc",
+    "perfetto_trace_protos",
+  ],
+  shared_libs: [
+    "libprotobuf-cpp-lite",
+    "liblog",
+  ],
+  local_include_dirs: [
+    "include",
+  ],
+  cflags: [
+    "-DGOOGLE_PROTOBUF_NO_RTTI",
+    "-DGOOGLE_PROTOBUF_NO_STATIC_INITIALIZER",
+    "-DPERFETTO_BUILD_WITH_ANDROID",
+  ],
+}
diff --git a/BUILD.gn b/BUILD.gn
index 367b8b6..e28ab12 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -271,6 +271,19 @@
         "src/android_internal",
       ]
     }
+
+    # Client library target for the Android tree.
+    # Still in experimental stage and not API stable yet.
+    # See "libperfetto_client_example" (in Android.bp.extras) for an example
+    # on how to use the Perfetto Client API from the android tree.
+    static_library("libperfetto_client_experimental") {
+      complete_static_lib = true
+      deps = [
+        "gn:default_deps",
+        "src/tracing:client_api",
+        "src/tracing:platform_posix",
+      ]
+    }
   }  # if (perfetto_build_with_android)
 }  # if (perfetto_build_standalone || perfetto_build_with_android)
 
@@ -364,3 +377,9 @@
     ]
   }
 }
+
+executable("idle_alloc") {
+  sources = [
+    "tools/idle_alloc.cc",
+  ]
+}
diff --git a/heapprofd.rc b/heapprofd.rc
index 2e69f60..86b8c74 100644
--- a/heapprofd.rc
+++ b/heapprofd.rc
@@ -24,7 +24,10 @@
     # DAC_READ_SEARCH is denied by SELinux on user builds because the SELinux
     # permission is userdebug_or_eng only.
     # This is fine as this is not needed for central heapprofd in fork mode.
-    capabilities KILL DAC_READ_SEARCH
+    # SYS_ADMIN and SYS_PTRACE will be denied unless SELinux enforcement is
+    # off. They are needed to implement the idle page tracking feature, which
+    # only works without SELinux enforcement on.
+    capabilities KILL DAC_READ_SEARCH DAC_OVERRIDE SYS_ADMIN SYS_PTRACE
 
 on property:persist.heapprofd.enable=1
     start heapprofd
diff --git a/include/perfetto/ext/base/string_writer.h b/include/perfetto/ext/base/string_writer.h
index 10e92a0..6bc7fad 100644
--- a/include/perfetto/ext/base/string_writer.h
+++ b/include/perfetto/ext/base/string_writer.h
@@ -68,34 +68,20 @@
   // digits of the integer is less than |padding|.
   template <char padchar, uint64_t padding>
   void AppendPaddedInt(int64_t sign_value) {
-    // Need to add 2 to the number of digits to account for minus sign and
-    // rounding down of digits10.
-    constexpr auto kMaxDigits = std::numeric_limits<uint64_t>::digits10 + 2;
-    constexpr auto kSizeNeeded = kMaxDigits > padding ? kMaxDigits : padding;
-    PERFETTO_DCHECK(pos_ + kSizeNeeded <= size_);
-
-    char data[kSizeNeeded];
     const bool negate = signbit(static_cast<double>(sign_value));
-    uint64_t value = static_cast<uint64_t>(std::abs(sign_value));
+    uint64_t absolute_value = static_cast<uint64_t>(std::abs(sign_value));
+    AppendPaddedInt<padchar, padding>(absolute_value, negate);
+  }
 
-    size_t idx;
-    for (idx = kSizeNeeded - 1; value >= 10;) {
-      char digit = value % 10;
-      value /= 10;
-      data[idx--] = digit + '0';
-    }
-    data[idx--] = static_cast<char>(value) + '0';
+  void AppendUnsignedInt(uint64_t value) {
+    AppendPaddedUnsignedInt<'0', 0>(value);
+  }
 
-    if (padding > 0) {
-      size_t num_digits = kSizeNeeded - 1 - idx;
-      for (size_t i = num_digits; i < padding; i++) {
-        data[idx--] = padchar;
-      }
-    }
-
-    if (negate)
-      buffer_[pos_++] = '-';
-    AppendString(&data[idx + 1], kSizeNeeded - idx - 1);
+  // Appends an unsigned integer to the buffer, padding with |padchar| if the
+  // number of digits of the integer is less than |padding|.
+  template <char padchar, uint64_t padding>
+  void AppendPaddedUnsignedInt(uint64_t value) {
+    AppendPaddedInt<padchar, padding>(value, false);
   }
 
   // Appends a hex integer to the buffer.
@@ -118,6 +104,14 @@
     pos_ += res;
   }
 
+  void AppendBool(bool value) {
+    if (value) {
+      AppendLiteral("true");
+      return;
+    }
+    AppendLiteral("false");
+  }
+
   StringView GetStringView() {
     PERFETTO_DCHECK(pos_ <= size_);
     return StringView(buffer_, pos_);
@@ -137,6 +131,36 @@
   void reset() { pos_ = 0; }
 
  private:
+  template <char padchar, uint64_t padding>
+  void AppendPaddedInt(uint64_t absolute_value, bool negate) {
+    // Need to add 2 to the number of digits to account for minus sign and
+    // rounding down of digits10.
+    constexpr auto kMaxDigits = std::numeric_limits<uint64_t>::digits10 + 2;
+    constexpr auto kSizeNeeded = kMaxDigits > padding ? kMaxDigits : padding;
+    PERFETTO_DCHECK(pos_ + kSizeNeeded <= size_);
+
+    char data[kSizeNeeded];
+
+    size_t idx;
+    for (idx = kSizeNeeded - 1; absolute_value >= 10;) {
+      char digit = absolute_value % 10;
+      absolute_value /= 10;
+      data[idx--] = digit + '0';
+    }
+    data[idx--] = static_cast<char>(absolute_value) + '0';
+
+    if (padding > 0) {
+      size_t num_digits = kSizeNeeded - 1 - idx;
+      for (size_t i = num_digits; i < padding; i++) {
+        data[idx--] = padchar;
+      }
+    }
+
+    if (negate)
+      buffer_[pos_++] = '-';
+    AppendString(&data[idx + 1], kSizeNeeded - idx - 1);
+  }
+
   char* buffer_ = nullptr;
   size_t size_ = 0;
   size_t pos_ = 0;
diff --git a/include/perfetto/tracing.h b/include/perfetto/tracing.h
index 6e0abef..3dfb88a 100644
--- a/include/perfetto/tracing.h
+++ b/include/perfetto/tracing.h
@@ -28,4 +28,9 @@
 #include "perfetto/tracing/tracing.h"
 #include "perfetto/tracing/tracing_backend.h"
 
+// TODO(primiano): move these generated classes from /ext/ into public. Right
+// now these are a layering violation.
+#include "perfetto/ext/tracing/core/data_source_descriptor.h"  // nogncheck
+#include "perfetto/ext/tracing/core/trace_config.h"            // nogncheck
+
 #endif  // INCLUDE_PERFETTO_TRACING_H_
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index fb15d07..e404c75 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -3132,29 +3132,29 @@
   optional int32 legacy_sort_index = 3;
 
   enum ChromeThreadType {
-    THREAD_TYPE_UNSPECIFIED = 0;
+    CHROME_THREAD_UNSPECIFIED = 0;
 
-    THREAD_TYPE_MAIN = 1;
-    THREAD_TYPE_IO = 2;
+    CHROME_THREAD_MAIN = 1;
+    CHROME_THREAD_IO = 2;
 
     // Scheduler:
-    THREAD_TYPE_THREAD_POOL_BG_WORKER = 3;
-    THREAD_TYPE_THREAD_POOL_FG_WORKER = 4;
-    THREAD_TYPE_THREAD_POOL_FB_BLOCKING = 5;
-    THREAD_TYPE_THREAD_POOL_BG_BLOCKING = 6;
-    THREAD_TYPE_THREAD_POOL_SERVICE = 7;
+    CHROME_THREAD_POOL_BG_WORKER = 3;
+    CHROME_THREAD_POOL_FG_WORKER = 4;
+    CHROME_THREAD_POOL_FB_BLOCKING = 5;
+    CHROME_THREAD_POOL_BG_BLOCKING = 6;
+    CHROME_THREAD_POOL_SERVICE = 7;
 
     // Compositor:
-    THREAD_TYPE_COMPOSITOR = 8;
-    THREAD_TYPE_VIZ_COMPOSITOR = 9;
-    THREAD_TYPE_COMPOSITOR_WORKER = 10;
+    CHROME_THREAD_COMPOSITOR = 8;
+    CHROME_THREAD_VIZ_COMPOSITOR = 9;
+    CHROME_THREAD_COMPOSITOR_WORKER = 10;
 
     // Renderer:
-    THREAD_TYPE_SERVICE_WORKER = 11;
+    CHROME_THREAD_SERVICE_WORKER = 11;
 
     // Tracing related threads:
-    THREAD_TYPE_MEMORY_INFRA = 50;
-    THREAD_TYPE_SAMPLING_PROFILER = 51;
+    CHROME_THREAD_MEMORY_INFRA = 50;
+    CHROME_THREAD_SAMPLING_PROFILER = 51;
   };
   optional ChromeThreadType chrome_thread_type = 4;
 
diff --git a/protos/perfetto/trace/track_event/thread_descriptor.proto b/protos/perfetto/trace/track_event/thread_descriptor.proto
index 85a5824..78afc36 100644
--- a/protos/perfetto/trace/track_event/thread_descriptor.proto
+++ b/protos/perfetto/trace/track_event/thread_descriptor.proto
@@ -32,29 +32,29 @@
   optional int32 legacy_sort_index = 3;
 
   enum ChromeThreadType {
-    THREAD_TYPE_UNSPECIFIED = 0;
+    CHROME_THREAD_UNSPECIFIED = 0;
 
-    THREAD_TYPE_MAIN = 1;
-    THREAD_TYPE_IO = 2;
+    CHROME_THREAD_MAIN = 1;
+    CHROME_THREAD_IO = 2;
 
     // Scheduler:
-    THREAD_TYPE_THREAD_POOL_BG_WORKER = 3;
-    THREAD_TYPE_THREAD_POOL_FG_WORKER = 4;
-    THREAD_TYPE_THREAD_POOL_FB_BLOCKING = 5;
-    THREAD_TYPE_THREAD_POOL_BG_BLOCKING = 6;
-    THREAD_TYPE_THREAD_POOL_SERVICE = 7;
+    CHROME_THREAD_POOL_BG_WORKER = 3;
+    CHROME_THREAD_POOL_FG_WORKER = 4;
+    CHROME_THREAD_POOL_FB_BLOCKING = 5;
+    CHROME_THREAD_POOL_BG_BLOCKING = 6;
+    CHROME_THREAD_POOL_SERVICE = 7;
 
     // Compositor:
-    THREAD_TYPE_COMPOSITOR = 8;
-    THREAD_TYPE_VIZ_COMPOSITOR = 9;
-    THREAD_TYPE_COMPOSITOR_WORKER = 10;
+    CHROME_THREAD_COMPOSITOR = 8;
+    CHROME_THREAD_VIZ_COMPOSITOR = 9;
+    CHROME_THREAD_COMPOSITOR_WORKER = 10;
 
     // Renderer:
-    THREAD_TYPE_SERVICE_WORKER = 11;
+    CHROME_THREAD_SERVICE_WORKER = 11;
 
     // Tracing related threads:
-    THREAD_TYPE_MEMORY_INFRA = 50;
-    THREAD_TYPE_SAMPLING_PROFILER = 51;
+    CHROME_THREAD_MEMORY_INFRA = 50;
+    CHROME_THREAD_SAMPLING_PROFILER = 51;
   };
   optional ChromeThreadType chrome_thread_type = 4;
 
diff --git a/src/base/string_writer_unittest.cc b/src/base/string_writer_unittest.cc
index 8d83dcc..12a7872 100644
--- a/src/base/string_writer_unittest.cc
+++ b/src/base/string_writer_unittest.cc
@@ -37,6 +37,11 @@
   }
   {
     base::StringWriter writer(buffer, sizeof(buffer));
+    writer.AppendUnsignedInt(523);
+    ASSERT_EQ(writer.GetStringView().ToStdString(), "523");
+  }
+  {
+    base::StringWriter writer(buffer, sizeof(buffer));
     writer.AppendPaddedInt<'0', 3>(0);
     ASSERT_EQ(writer.GetStringView().ToStdString(), "000");
   }
@@ -62,6 +67,11 @@
   }
   {
     base::StringWriter writer(buffer, sizeof(buffer));
+    writer.AppendPaddedUnsignedInt<' ', 5>(123);
+    ASSERT_EQ(writer.GetStringView().ToStdString(), "  123");
+  }
+  {
+    base::StringWriter writer(buffer, sizeof(buffer));
     writer.AppendDouble(123.25);
     ASSERT_EQ(writer.GetStringView().ToStdString(), "123.250000");
   }
@@ -75,6 +85,21 @@
     writer.AppendInt(std::numeric_limits<int64_t>::max());
     ASSERT_EQ(writer.GetStringView().ToStdString(), "9223372036854775807");
   }
+  {
+    base::StringWriter writer(buffer, sizeof(buffer));
+    writer.AppendUnsignedInt(std::numeric_limits<uint64_t>::max());
+    ASSERT_EQ(writer.GetStringView().ToStdString(), "18446744073709551615");
+  }
+  {
+    base::StringWriter writer(buffer, sizeof(buffer));
+    writer.AppendBool(true);
+    ASSERT_EQ(writer.GetStringView().ToStdString(), "true");
+  }
+  {
+    base::StringWriter writer(buffer, sizeof(buffer));
+    writer.AppendBool(false);
+    ASSERT_EQ(writer.GetStringView().ToStdString(), "false");
+  }
 
   constexpr char kTestStr[] = "test";
   {
@@ -105,13 +130,16 @@
   base::StringWriter writer(buffer, sizeof(buffer));
   writer.AppendChar('0');
   writer.AppendInt(132545);
+  writer.AppendUnsignedInt(523);
   writer.AppendPaddedInt<'0', 0>(1);
   writer.AppendPaddedInt<'0', 3>(0);
   writer.AppendPaddedInt<'0', 1>(1);
   writer.AppendPaddedInt<'0', 2>(1);
   writer.AppendPaddedInt<'0', 3>(1);
   writer.AppendPaddedInt<' ', 5>(123);
+  writer.AppendPaddedUnsignedInt<' ', 5>(456);
   writer.AppendDouble(123.25);
+  writer.AppendBool(true);
 
   constexpr char kTestStr[] = "test";
   writer.AppendLiteral(kTestStr);
@@ -119,7 +147,7 @@
   writer.AppendString(kTestStr);
 
   ASSERT_EQ(writer.GetStringView().ToStdString(),
-            "01325451000101001  123123.250000testtesttest");
+            "01325455231000101001  123  456123.250000truetesttesttest");
 }
 
 }  // namespace
diff --git a/src/profiling/memory/client.cc b/src/profiling/memory/client.cc
index 7ea4528..86550b2 100644
--- a/src/profiling/memory/client.cc
+++ b/src/profiling/memory/client.cc
@@ -152,11 +152,6 @@
     PERFETTO_DFATAL_OR_ELOG("Failed to open /proc/self/mem");
     return nullptr;
   }
-  base::ScopedFile pagemap(base::OpenFile("/proc/self/pagemap", O_RDONLY));
-  if (!pagemap) {
-    PERFETTO_DFATAL_OR_ELOG("Failed to open /proc/self/pagemap");
-    return nullptr;
-  }
 
   // Restore original dumpability value if we overrode it.
   unset_dumpable.reset();
@@ -164,7 +159,6 @@
   int fds[kHandshakeSize];
   fds[kHandshakeMaps] = *maps;
   fds[kHandshakeMem] = *mem;
-  fds[kHandshakePageMap] = *pagemap;
 
   // Send an empty record to transfer fds for /proc/self/maps and
   // /proc/self/mem.
diff --git a/src/profiling/memory/heapprofd_producer.cc b/src/profiling/memory/heapprofd_producer.cc
index 20782ff..f55306d 100644
--- a/src/profiling/memory/heapprofd_producer.cc
+++ b/src/profiling/memory/heapprofd_producer.cc
@@ -87,6 +87,22 @@
   return i;
 }
 
+base::Optional<PageIdleChecker> MakePageIdleChecker(base::ScopedFile pagemap) {
+  base::Optional<PageIdleChecker> res;
+  if (!pagemap) {
+    PERFETTO_PLOG("Invalid pagemap.");
+    return res;
+  }
+  base::ScopedFile bitmap(
+      base::OpenFile("/sys/kernel/mm/page_idle/bitmap", O_RDWR));
+  if (!bitmap) {
+    PERFETTO_PLOG("Failed to open /sys/kernel/mm/page_idle/bitmap.");
+    return res;
+  }
+  res = PageIdleChecker(std::move(pagemap), std::move(bitmap));
+  return res;
+}
+
 }  // namespace
 
 const uint64_t LogHistogram::kMaxBucket = 0;
@@ -590,6 +606,8 @@
         [&dump_state](const HeapTracker::CallstackAllocations& alloc) {
           dump_state.WriteAllocation(alloc);
         });
+    if (process_state.page_idle_checker)
+      process_state.page_idle_checker->MarkPagesIdle();
   }
 
   dump_state.DumpCallstacks(&callsites_);
@@ -679,8 +697,8 @@
   char buf[1];
   self->Receive(buf, sizeof(buf), fds, base::ArraySize(fds));
 
-  static_assert(kHandshakeSize == 3, "change if and else if below.");
-  if (fds[kHandshakeMaps] && fds[kHandshakeMem] && fds[kHandshakePageMap]) {
+  static_assert(kHandshakeSize == 2, "change if and else if below.");
+  if (fds[kHandshakeMaps] && fds[kHandshakeMem]) {
     auto ds_it =
         producer_->data_sources_.find(pending_process.data_source_instance_id);
     if (ds_it == producer_->data_sources_.end()) {
@@ -694,13 +712,17 @@
 
     ProcessState& process_state = it_and_inserted.first->second;
     if (data_source.config.idle_allocations()) {
-      base::ScopedFile kpageflags(base::OpenFile("/proc/kpageflags", O_RDONLY));
-      if (kpageflags) {
-        process_state.page_idle_checker = PageIdleChecker(
-            std::move(fds[kHandshakePageMap]), std::move(kpageflags));
-      } else {
-        PERFETTO_DFATAL_OR_ELOG("Failed to open /proc/kpageflags");
-      }
+      // We have to open this here, because reading the PFN requires
+      // the process that opened the file to have CAP_SYS_ADMIN. We can work
+      // around this by making this a setenforce 0 only feature, giving
+      // heapprofd very broad capabilities (CAP_SYS_ADMIN and CAP_SYS_PTRACE)
+      // which will get rejected by SELinux on real builds.
+      std::string procfs_path =
+          "/proc/" + std::to_string(self->peer_pid()) + "/pagemap";
+      base::ScopedFile pagemap_fd(
+          base::OpenFile(procfs_path.c_str(), O_RDONLY));
+      process_state.page_idle_checker =
+          MakePageIdleChecker(std::move(pagemap_fd));
     }
 
     PERFETTO_DLOG("%d: Received FDs.", self->peer_pid());
@@ -722,8 +744,7 @@
     producer_->UnwinderForPID(self->peer_pid())
         .PostHandoffSocket(std::move(handoff_data));
     producer_->pending_processes_.erase(it);
-  } else if (fds[kHandshakeMaps] || fds[kHandshakeMem] ||
-             fds[kHandshakePageMap]) {
+  } else if (fds[kHandshakeMaps] || fds[kHandshakeMem]) {
     PERFETTO_DFATAL_OR_ELOG("%d: Received partial FDs.", self->peer_pid());
     producer_->pending_processes_.erase(it);
   } else {
diff --git a/src/profiling/memory/page_idle_checker.cc b/src/profiling/memory/page_idle_checker.cc
index d018b6b..10cd1fe 100644
--- a/src/profiling/memory/page_idle_checker.cc
+++ b/src/profiling/memory/page_idle_checker.cc
@@ -18,6 +18,7 @@
 #include "perfetto/ext/base/utils.h"
 #include "src/profiling/memory/utils.h"
 
+#include <inttypes.h>
 #include <vector>
 
 namespace perfetto {
@@ -27,57 +28,103 @@
 constexpr uint64_t kIsInRam = 1ULL << 63;
 constexpr uint64_t kRamPhysicalPageMask = ~(~0ULL << 55);
 
-constexpr uint64_t kPhysPageReferenced = 1ULL << 2;
-
 }  // namespace
 
 int64_t PageIdleChecker::OnIdlePage(uint64_t addr, size_t size) {
   uint64_t page_nr = addr / base::kPageSize;
-  uint64_t page_aligned_addr = page_nr * base::kPageSize;
   uint64_t end_page_nr = (addr + size) / base::kPageSize;
   // The trailing division will have rounded down, unless the end is at a page
   // boundary. Add one page if we rounded down.
-  if (addr + size % base::kPageSize != 0)
+  if ((addr + size) % base::kPageSize != 0)
     end_page_nr++;
-  uint64_t page_aligned_end_addr = base::kPageSize * end_page_nr;
 
-  size_t pages = (page_aligned_end_addr - page_aligned_addr) / base::kPageSize;
+  size_t pages = end_page_nr - page_nr;
   std::vector<uint64_t> virt_page_infos(pages);
 
   off64_t virt_off = static_cast<off64_t>(page_nr * sizeof(virt_page_infos[0]));
   size_t virt_rd_size = pages * sizeof(virt_page_infos[0]);
-  if (ReadAtOffsetClobberSeekPos(*pagemap_fd_, &(virt_page_infos[0]),
-                                 virt_rd_size, virt_off) !=
-      static_cast<ssize_t>(virt_rd_size)) {
+  ssize_t rd = ReadAtOffsetClobberSeekPos(*pagemap_fd_, &(virt_page_infos[0]),
+                                          virt_rd_size, virt_off);
+  if (rd != static_cast<ssize_t>(virt_rd_size)) {
+    PERFETTO_ELOG("Invalid read from pagemap: %zd", rd);
     return -1;
   }
 
   int64_t idle_mem = 0;
 
   for (size_t i = 0; i < pages; ++i) {
-    if (!(virt_page_infos[i] & kIsInRam))
+    if (!virt_page_infos[i]) {
+      PERFETTO_DLOG("Empty pageinfo.");
       continue;
-    uint64_t phys_page_nr = virt_page_infos[i] & kRamPhysicalPageMask;
-    uint64_t phys_page_info;
-    off64_t phys_off =
-        static_cast<off64_t>(phys_page_nr * sizeof(phys_page_info));
-    if (ReadAtOffsetClobberSeekPos(*kpageflags_fd_, &phys_page_info,
-                                   sizeof(phys_page_info),
-                                   phys_off) != sizeof(phys_page_info)) {
-      return -1;
     }
-    if (!(phys_page_info & kPhysPageReferenced)) {
+
+    if (!(virt_page_infos[i] & kIsInRam)) {
+      PERFETTO_DLOG("Page is not in RAM.");
+      continue;
+    }
+
+    uint64_t phys_page_nr = virt_page_infos[i] & kRamPhysicalPageMask;
+    if (!phys_page_nr) {
+      PERFETTO_ELOG("Failed to get physical page number.");
+      continue;
+    }
+
+    int idle = IsPageIdle(phys_page_nr);
+    if (idle == -1)
+      continue;
+
+    if (idle) {
       if (i == 0)
         idle_mem += GetFirstPageShare(addr, size);
       else if (i == pages - 1)
         idle_mem += GetLastPageShare(addr, size);
       else
         idle_mem += base::kPageSize;
+    } else {
+      touched_phys_page_nrs_.emplace(phys_page_nr);
     }
   }
   return idle_mem;
 }
 
+void PageIdleChecker::MarkPagesIdle() {
+  for (uint64_t phys_page_nr : touched_phys_page_nrs_)
+    MarkPageIdle(phys_page_nr);
+  touched_phys_page_nrs_.clear();
+}
+
+void PageIdleChecker::MarkPageIdle(uint64_t phys_page_nr) {
+  // The file implements a bitmap where each bit corresponds to a memory page.
+  // The bitmap is represented by an array of 8-byte integers, and the page at
+  // PFN #i is mapped to bit #i%64 of array element #i/64, byte order i
+  // native. When a bit is set, the corresponding page is idle.
+  //
+  // The kernel ORs the value written with the existing bitmap, so we do not
+  // override previously written values.
+  // See https://www.kernel.org/doc/Documentation/vm/idle_page_tracking.txt
+  off64_t offset = 8 * (phys_page_nr / 64);
+  size_t bit_offset = phys_page_nr % 64;
+  uint64_t bit_pattern = 1 << bit_offset;
+  if (WriteAtOffsetClobberSeekPos(*bitmap_fd_, &bit_pattern,
+                                  sizeof(bit_pattern), offset) !=
+      static_cast<ssize_t>(sizeof(bit_pattern))) {
+    PERFETTO_PLOG("Failed to write bit pattern at %" PRIi64 ".", offset);
+  }
+}
+
+int PageIdleChecker::IsPageIdle(uint64_t phys_page_nr) {
+  off64_t offset = 8 * (phys_page_nr / 64);
+  size_t bit_offset = phys_page_nr % 64;
+  uint64_t bit_pattern;
+  if (ReadAtOffsetClobberSeekPos(*bitmap_fd_, &bit_pattern, sizeof(bit_pattern),
+                                 offset) !=
+      static_cast<ssize_t>(sizeof(bit_pattern))) {
+    PERFETTO_PLOG("Failed to read bit pattern at %" PRIi64 ".", offset);
+    return -1;
+  }
+  return static_cast<int>(bit_pattern & (1 << bit_offset));
+}
+
 uint64_t GetFirstPageShare(uint64_t addr, size_t size) {
   // Our allocation is xxxx in this illustration:
   //         +----------------------------------------------+
diff --git a/src/profiling/memory/page_idle_checker.h b/src/profiling/memory/page_idle_checker.h
index 2a9ab7b..529c097 100644
--- a/src/profiling/memory/page_idle_checker.h
+++ b/src/profiling/memory/page_idle_checker.h
@@ -17,6 +17,8 @@
 #ifndef SRC_PROFILING_MEMORY_PAGE_IDLE_CHECKER_H_
 #define SRC_PROFILING_MEMORY_PAGE_IDLE_CHECKER_H_
 
+#include <set>
+
 #include <stddef.h>
 #include <stdint.h>
 
@@ -30,18 +32,25 @@
 
 class PageIdleChecker {
  public:
-  PageIdleChecker(base::ScopedFile pagemap_fd, base::ScopedFile kpageflags_fd)
-      : pagemap_fd_(std::move(pagemap_fd)),
-        kpageflags_fd_(std::move(kpageflags_fd)) {}
+  PageIdleChecker(base::ScopedFile pagemap_fd, base::ScopedFile bitmap_fd)
+      : pagemap_fd_(std::move(pagemap_fd)), bitmap_fd_(std::move(bitmap_fd)) {}
 
   // Return number of bytes of allocation of size bytes starting at alloc that
   // are on unreferenced pages.
   // Return -1 on error.
   int64_t OnIdlePage(uint64_t addr, size_t size);
 
+  void MarkPagesIdle();
+
  private:
+  void MarkPageIdle(uint64_t phys_page_nr);
+  // Return 1 if page is idle, 0 if it is not idle, or -1 on error.
+  int IsPageIdle(uint64_t phys_page_nr);
+
+  std::set<uint64_t> touched_phys_page_nrs_;
+
   base::ScopedFile pagemap_fd_;
-  base::ScopedFile kpageflags_fd_;
+  base::ScopedFile bitmap_fd_;
 };
 
 }  // namespace profiling
diff --git a/src/profiling/memory/utils.cc b/src/profiling/memory/utils.cc
index 3e2e78d..b424192 100644
--- a/src/profiling/memory/utils.cc
+++ b/src/profiling/memory/utils.cc
@@ -35,5 +35,21 @@
 #endif
 }
 
+// Behaves as a pread64, emulating it if not already exposed by the standard
+// library.
+// Clobbers the |fd| seek position if emulating.
+ssize_t WriteAtOffsetClobberSeekPos(int fd,
+                                    void* buf,
+                                    size_t count,
+                                    off64_t addr) {
+#ifdef __BIONIC__
+  return pwrite64(fd, buf, count, addr);
+#else
+  if (lseek64(fd, addr, SEEK_SET) == -1)
+    return -1;
+  return write(fd, buf, count);
+#endif
+}
+
 }  // namespace profiling
 }  // namespace perfetto
diff --git a/src/profiling/memory/utils.h b/src/profiling/memory/utils.h
index d338678..5e375c9 100644
--- a/src/profiling/memory/utils.h
+++ b/src/profiling/memory/utils.h
@@ -26,6 +26,11 @@
                                    void* buf,
                                    size_t count,
                                    off64_t addr);
+
+ssize_t WriteAtOffsetClobberSeekPos(int fd,
+                                    void* buf,
+                                    size_t count,
+                                    off64_t addr);
 }
 }  // namespace perfetto
 
diff --git a/src/profiling/memory/wire_protocol.h b/src/profiling/memory/wire_protocol.h
index b970dd4..9de58d6 100644
--- a/src/profiling/memory/wire_protocol.h
+++ b/src/profiling/memory/wire_protocol.h
@@ -119,9 +119,8 @@
 
 enum HandshakeFDs : size_t {
   kHandshakeMaps = 0,
-  kHandshakeMem = 1,
-  kHandshakePageMap = 2,
-  kHandshakeSize = 3,
+  kHandshakeMem,
+  kHandshakeSize,
 };
 
 struct WireMessage {
diff --git a/src/trace_processor/args_table.cc b/src/trace_processor/args_table.cc
index 8b781f2..bc89ef0 100644
--- a/src/trace_processor/args_table.cc
+++ b/src/trace_processor/args_table.cc
@@ -79,14 +79,30 @@
     case Variadic::Type::kInt:
       sqlite_utils::ReportSqliteResult(ctx, value.int_value);
       break;
-    case Variadic::Type::kReal:
-      sqlite_utils::ReportSqliteResult(ctx, value.real_value);
+    case Variadic::Type::kUint:
+      // BEWARE: uint64 is handled as signed int64 for SQLite operations.
+      sqlite_utils::ReportSqliteResult(ctx,
+                                       static_cast<int64_t>(value.uint_value));
       break;
     case Variadic::Type::kString: {
       const char* str = storage_->GetString(value.string_value).c_str();
       sqlite3_result_text(ctx, str, -1, sqlite_utils::kSqliteStatic);
       break;
     }
+    case Variadic::Type::kReal:
+      sqlite_utils::ReportSqliteResult(ctx, value.real_value);
+      break;
+    case Variadic::Type::kPointer:
+      // BEWARE: pointers are handled as signed int64 for SQLite operations.
+      sqlite_utils::ReportSqliteResult(
+          ctx, static_cast<int64_t>(value.pointer_value));
+      break;
+    case Variadic::Type::kBool:
+      sqlite_utils::ReportSqliteResult(ctx, value.bool_value);
+      break;
+    case Variadic::Type::kJson:
+      sqlite_utils::ReportSqliteResult(ctx, value.json_value);
+      break;
   }
 }
 
@@ -110,13 +126,16 @@
           });
       break;
     }
-    case Variadic::Type::kReal: {
+    case Variadic::Type::kUint: {
       bool op_is_null = sqlite_utils::IsOpIsNull(op);
-      auto predicate = sqlite_utils::CreateNumericPredicate<double>(op, value);
+      // BEWARE: uint64 is handled as signed int64 for SQLite operations.
+      auto predicate = sqlite_utils::CreateNumericPredicate<int64_t>(op, value);
       index->FilterRows(
           [this, predicate, op_is_null](uint32_t row) PERFETTO_ALWAYS_INLINE {
             const auto& arg = storage_->args().arg_values()[row];
-            return arg.type == type_ ? predicate(arg.real_value) : op_is_null;
+            return arg.type == type_
+                       ? predicate(static_cast<int64_t>(arg.uint_value))
+                       : op_is_null;
           });
       break;
     }
@@ -131,6 +150,50 @@
       });
       break;
     }
+    case Variadic::Type::kReal: {
+      bool op_is_null = sqlite_utils::IsOpIsNull(op);
+      auto predicate = sqlite_utils::CreateNumericPredicate<double>(op, value);
+      index->FilterRows(
+          [this, predicate, op_is_null](uint32_t row) PERFETTO_ALWAYS_INLINE {
+            const auto& arg = storage_->args().arg_values()[row];
+            return arg.type == type_ ? predicate(arg.real_value) : op_is_null;
+          });
+      break;
+    }
+    case Variadic::Type::kPointer: {
+      bool op_is_null = sqlite_utils::IsOpIsNull(op);
+      // BEWARE: pointers are handled as signed int64 for SQLite operations.
+      auto predicate = sqlite_utils::CreateNumericPredicate<int64_t>(op, value);
+      index->FilterRows(
+          [this, predicate, op_is_null](uint32_t row) PERFETTO_ALWAYS_INLINE {
+            const auto& arg = storage_->args().arg_values()[row];
+            return arg.type == type_
+                       ? predicate(static_cast<int64_t>(arg.pointer_value))
+                       : op_is_null;
+          });
+      break;
+    }
+    case Variadic::Type::kBool: {
+      bool op_is_null = sqlite_utils::IsOpIsNull(op);
+      auto predicate = sqlite_utils::CreateNumericPredicate<bool>(op, value);
+      index->FilterRows(
+          [this, predicate, op_is_null](uint32_t row) PERFETTO_ALWAYS_INLINE {
+            const auto& arg = storage_->args().arg_values()[row];
+            return arg.type == type_ ? predicate(arg.bool_value) : op_is_null;
+          });
+      break;
+    }
+    case Variadic::Type::kJson: {
+      auto predicate = sqlite_utils::CreateStringPredicate(op, value);
+      index->FilterRows([this,
+                         &predicate](uint32_t row) PERFETTO_ALWAYS_INLINE {
+        const auto& arg = storage_->args().arg_values()[row];
+        return arg.type == type_
+                   ? predicate(storage_->GetString(arg.json_value).c_str())
+                   : predicate(nullptr);
+      });
+      break;
+    }
   }
 }
 
@@ -150,14 +213,32 @@
     switch (type_) {
       case Variadic::Type::kInt:
         return sqlite_utils::CompareValuesAsc(arg_f.int_value, arg_s.int_value);
-      case Variadic::Type::kReal:
-        return sqlite_utils::CompareValuesAsc(arg_f.real_value,
-                                              arg_s.real_value);
+      case Variadic::Type::kUint:
+        // BEWARE: uint64 is handled as signed int64 for SQLite operations.
+        return sqlite_utils::CompareValuesAsc(
+            static_cast<int64_t>(arg_f.uint_value),
+            static_cast<int64_t>(arg_s.uint_value));
       case Variadic::Type::kString: {
         const auto& f_str = storage_->GetString(arg_f.string_value);
         const auto& s_str = storage_->GetString(arg_s.string_value);
         return sqlite_utils::CompareValuesAsc(f_str, s_str);
       }
+      case Variadic::Type::kReal:
+        return sqlite_utils::CompareValuesAsc(arg_f.real_value,
+                                              arg_s.real_value);
+      case Variadic::Type::kPointer:
+        // BEWARE: pointers are handled as signed int64 for SQLite operations.
+        return sqlite_utils::CompareValuesAsc(
+            static_cast<int64_t>(arg_f.pointer_value),
+            static_cast<int64_t>(arg_s.pointer_value));
+      case Variadic::Type::kBool:
+        return sqlite_utils::CompareValuesAsc(arg_f.bool_value,
+                                              arg_s.bool_value);
+      case Variadic::Type::kJson: {
+        const auto& f_str = storage_->GetString(arg_f.json_value);
+        const auto& s_str = storage_->GetString(arg_s.json_value);
+        return sqlite_utils::CompareValuesAsc(f_str, s_str);
+      }
     }
   } else if (arg_s.type == type_) {
     return -1;
diff --git a/src/trace_processor/args_table.h b/src/trace_processor/args_table.h
index 0d9fd4b..3327e57 100644
--- a/src/trace_processor/args_table.h
+++ b/src/trace_processor/args_table.h
@@ -56,9 +56,17 @@
       switch (type_) {
         case Variadic::Type::kInt:
           return Table::ColumnType::kLong;
+        case Variadic::Type::kUint:
+          return Table::ColumnType::kLong;
+        case Variadic::Type::kString:
+          return Table::ColumnType::kString;
         case Variadic::Type::kReal:
           return Table::ColumnType::kDouble;
-        case Variadic::Type::kString:
+        case Variadic::Type::kPointer:
+          return Table::ColumnType::kLong;
+        case Variadic::Type::kBool:
+          return Table::ColumnType::kBool;
+        case Variadic::Type::kJson:
           return Table::ColumnType::kString;
       }
       PERFETTO_FATAL("Not reached");  // For gcc
diff --git a/src/trace_processor/args_tracker.h b/src/trace_processor/args_tracker.h
index b966dc9..7eb27d7 100644
--- a/src/trace_processor/args_tracker.h
+++ b/src/trace_processor/args_tracker.h
@@ -32,10 +32,12 @@
   virtual ~ArgsTracker();
 
   // Adds a arg for this row id with the given key and value.
-  void AddArg(RowId row_id, StringId flat_key, StringId key, Variadic);
+  // Virtual for testing.
+  virtual void AddArg(RowId row_id, StringId flat_key, StringId key, Variadic);
 
   // Commits the added args to storage.
-  void Flush();
+  // Virtual for testing.
+  virtual void Flush();
 
  private:
   std::vector<TraceStorage::Args::Arg> args_;
diff --git a/src/trace_processor/proto_incremental_state.h b/src/trace_processor/proto_incremental_state.h
index 38f4abd..b2dc1de 100644
--- a/src/trace_processor/proto_incremental_state.h
+++ b/src/trace_processor/proto_incremental_state.h
@@ -77,7 +77,7 @@
   struct InternedDataView {
     InternedDataView(TraceBlobView msg) : message(std::move(msg)) {}
 
-    typename MessageType::Decoder CreateDecoder() {
+    typename MessageType::Decoder CreateDecoder() const {
       return typename MessageType::Decoder(message.data(), message.length());
     }
 
diff --git a/src/trace_processor/proto_trace_parser.cc b/src/trace_processor/proto_trace_parser.cc
index ae94745..3355766 100644
--- a/src/trace_processor/proto_trace_parser.cc
+++ b/src/trace_processor/proto_trace_parser.cc
@@ -118,7 +118,11 @@
       oom_score_adj_id_(context->storage->InternString("oom_score_adj")),
       ion_total_unknown_id_(context->storage->InternString("mem.ion.unknown")),
       ion_change_unknown_id_(
-          context->storage->InternString("mem.ion_change.unknown")) {
+          context->storage->InternString("mem.ion_change.unknown")),
+      task_file_name_args_key_id_(
+          context->storage->InternString("task.posted_from.file_name")),
+      task_function_name_args_key_id_(
+          context->storage->InternString("task.posted_from.function_name")) {
   for (const auto& name : BuildMeminfoCounterNames()) {
     meminfo_strs_id_.emplace_back(context->storage->InternString(name));
   }
@@ -1436,21 +1440,35 @@
   // TODO(eseckler): Handle thread timestamp/duration, debug annotations, task
   // souce locations, legacy event attributes, ...
 
+  auto args_callback = [this, &event, &sequence_state](
+                           ArgsTracker* args_tracker, RowId row) {
+    for (auto it = event.debug_annotations(); it; ++it) {
+      ParseDebugAnnotationArgs(it->as_bytes(), sequence_state, args_tracker,
+                               row);
+    }
+
+    if (event.has_task_execution()) {
+      ParseTaskExecutionArgs(event.task_execution(), sequence_state,
+                             args_tracker, row);
+    }
+  };
+
   int32_t phase = legacy_event.phase();
   switch (static_cast<char>(phase)) {
     case 'B': {  // TRACE_EVENT_PHASE_BEGIN.
-      slice_tracker->Begin(ts, utid, category_id, name_id);
+      slice_tracker->Begin(ts, utid, category_id, name_id, args_callback);
       break;
     }
     case 'E': {  // TRACE_EVENT_PHASE_END.
-      slice_tracker->End(ts, utid, category_id, name_id);
+      slice_tracker->End(ts, utid, category_id, name_id, args_callback);
       break;
     }
     case 'X': {  // TRACE_EVENT_PHASE_COMPLETE.
       auto duration_ns = legacy_event.duration_us() * 1000;
       if (duration_ns < 0)
         return;
-      slice_tracker->Scoped(ts, utid, category_id, name_id, duration_ns);
+      slice_tracker->Scoped(ts, utid, category_id, name_id, duration_ns,
+                            args_callback);
       break;
     }
     case 'M': {  // TRACE_EVENT_PHASE_METADATA (process and thread names).
@@ -1486,6 +1504,176 @@
   }
 }
 
+void ProtoTraceParser::ParseDebugAnnotationArgs(
+    ConstBytes debug_annotation,
+    ProtoIncrementalState::PacketSequenceState* sequence_state,
+    ArgsTracker* args_tracker,
+    RowId row) {
+  protos::pbzero::DebugAnnotation::Decoder annotation(debug_annotation.data,
+                                                      debug_annotation.size);
+  uint32_t iid = annotation.name_iid();
+  if (!iid)
+    return;
+
+  auto* map =
+      sequence_state->GetInternedDataMap<protos::pbzero::DebugAnnotationName>();
+  auto name_view_it = map->find(iid);
+  if (name_view_it == map->end()) {
+    PERFETTO_ELOG(
+        "Could not find debug annotation name interning entry for ID %u", iid);
+    return;
+  }
+
+  TraceStorage* storage = context_->storage.get();
+
+  StringId name_id = 0;
+
+  // If the name is already in the pool, no need to decode it again.
+  if (name_view_it->second.storage_refs) {
+    name_id = name_view_it->second.storage_refs->name_id;
+  } else {
+    auto name = name_view_it->second.CreateDecoder();
+    std::string name_prefixed = "debug." + name.name().ToStdString();
+    name_id = storage->InternString(base::StringView(name_prefixed));
+    // Avoid having to decode & look up the name again in the future.
+    name_view_it->second.storage_refs =
+        ProtoIncrementalState::StorageReferences<
+            protos::pbzero::DebugAnnotationName>{name_id};
+  }
+
+  if (annotation.has_bool_value()) {
+    args_tracker->AddArg(row, name_id, name_id,
+                         Variadic::Boolean(annotation.bool_value()));
+  } else if (annotation.has_uint_value()) {
+    args_tracker->AddArg(row, name_id, name_id,
+                         Variadic::UnsignedInteger(annotation.uint_value()));
+  } else if (annotation.has_int_value()) {
+    args_tracker->AddArg(row, name_id, name_id,
+                         Variadic::Integer(annotation.int_value()));
+  } else if (annotation.has_double_value()) {
+    args_tracker->AddArg(row, name_id, name_id,
+                         Variadic::Real(annotation.double_value()));
+  } else if (annotation.has_string_value()) {
+    args_tracker->AddArg(
+        row, name_id, name_id,
+        Variadic::String(storage->InternString(annotation.string_value())));
+  } else if (annotation.has_pointer_value()) {
+    args_tracker->AddArg(row, name_id, name_id,
+                         Variadic::Pointer(annotation.pointer_value()));
+  } else if (annotation.has_legacy_json_value()) {
+    args_tracker->AddArg(row, name_id, name_id,
+                         Variadic::String(storage->InternString(
+                             annotation.legacy_json_value())));
+  } else if (annotation.has_nested_value()) {
+    auto name = storage->GetString(name_id);
+    ParseNestedValueArgs(annotation.nested_value(), name, name, args_tracker,
+                         row);
+  }
+}
+
+void ProtoTraceParser::ParseNestedValueArgs(ConstBytes nested_value,
+                                            base::StringView flat_key,
+                                            base::StringView key,
+                                            ArgsTracker* args_tracker,
+                                            RowId row) {
+  protos::pbzero::DebugAnnotation::NestedValue::Decoder value(
+      nested_value.data, nested_value.size);
+  switch (value.nested_type()) {
+    case protos::pbzero::DebugAnnotation::NestedValue::UNSPECIFIED: {
+      auto flat_key_id = context_->storage->InternString(flat_key);
+      auto key_id = context_->storage->InternString(key);
+      // Leaf value.
+      if (value.has_bool_value()) {
+        args_tracker->AddArg(row, flat_key_id, key_id,
+                             Variadic::Boolean(value.bool_value()));
+      } else if (value.has_int_value()) {
+        args_tracker->AddArg(row, flat_key_id, key_id,
+                             Variadic::Integer(value.int_value()));
+      } else if (value.has_double_value()) {
+        args_tracker->AddArg(row, flat_key_id, key_id,
+                             Variadic::Real(value.double_value()));
+      } else if (value.has_string_value()) {
+        args_tracker->AddArg(row, flat_key_id, key_id,
+                             Variadic::String(context_->storage->InternString(
+                                 value.string_value())));
+      }
+      break;
+    }
+    case protos::pbzero::DebugAnnotation::NestedValue::DICT: {
+      auto key_it = value.dict_keys();
+      auto value_it = value.dict_values();
+      for (; key_it && value_it; ++key_it, ++value_it) {
+        std::string child_name = key_it->as_std_string();
+        std::string child_flat_key = flat_key.ToStdString() + "." + child_name;
+        std::string child_key = key.ToStdString() + "." + child_name;
+        ParseNestedValueArgs(value_it->as_bytes(),
+                             base::StringView(child_flat_key),
+                             base::StringView(child_key), args_tracker, row);
+      }
+      break;
+    }
+    case protos::pbzero::DebugAnnotation::NestedValue::ARRAY: {
+      int child_index = 0;
+      std::string child_flat_key = flat_key.ToStdString();
+      for (auto value_it = value.array_values(); value_it;
+           ++value_it, ++child_index) {
+        std::string child_key =
+            key.ToStdString() + "[" + std::to_string(child_index) + "]";
+        ParseNestedValueArgs(value_it->as_bytes(),
+                             base::StringView(child_flat_key),
+                             base::StringView(child_key), args_tracker, row);
+      }
+      break;
+    }
+  }
+}
+
+void ProtoTraceParser::ParseTaskExecutionArgs(
+    ConstBytes task_execution,
+    ProtoIncrementalState::PacketSequenceState* sequence_state,
+    ArgsTracker* args_tracker,
+    RowId row) {
+  protos::pbzero::TaskExecution::Decoder task(task_execution.data,
+                                              task_execution.size);
+  uint32_t iid = task.posted_from_iid();
+  if (!iid)
+    return;
+
+  auto* map =
+      sequence_state->GetInternedDataMap<protos::pbzero::SourceLocation>();
+  auto location_view_it = map->find(iid);
+  if (location_view_it == map->end()) {
+    PERFETTO_ELOG("Could not find source location interning entry for ID %u",
+                  iid);
+    return;
+  }
+
+  StringId file_name_id = 0;
+  StringId function_name_id = 0;
+
+  // If the names are already in the pool, no need to decode them again.
+  if (location_view_it->second.storage_refs) {
+    file_name_id = location_view_it->second.storage_refs->file_name_id;
+    function_name_id = location_view_it->second.storage_refs->function_name_id;
+  } else {
+    TraceStorage* storage = context_->storage.get();
+    auto location = location_view_it->second.CreateDecoder();
+    file_name_id = storage->InternString(location.file_name());
+    function_name_id = storage->InternString(location.function_name());
+    // Avoid having to decode & look up the names again in the future.
+    location_view_it->second.storage_refs =
+        ProtoIncrementalState::StorageReferences<
+            protos::pbzero::SourceLocation>{file_name_id, function_name_id};
+  }
+
+  args_tracker->AddArg(row, task_file_name_args_key_id_,
+                       task_file_name_args_key_id_,
+                       Variadic::String(file_name_id));
+  args_tracker->AddArg(row, task_function_name_args_key_id_,
+                       task_function_name_args_key_id_,
+                       Variadic::String(function_name_id));
+}
+
 void ProtoTraceParser::ParseChromeBenchmarkMetadata(ConstBytes blob) {
   TraceStorage* storage = context_->storage.get();
   protos::pbzero::ChromeBenchmarkMetadata::Decoder packet(blob.data, blob.size);
diff --git a/src/trace_processor/proto_trace_parser.h b/src/trace_processor/proto_trace_parser.h
index a66573d..0fbe329 100644
--- a/src/trace_processor/proto_trace_parser.h
+++ b/src/trace_processor/proto_trace_parser.h
@@ -33,6 +33,7 @@
 namespace perfetto {
 namespace trace_processor {
 
+class ArgsTracker;
 class TraceProcessorContext;
 
 class ProtoTraceParser : public TraceParser {
@@ -92,6 +93,21 @@
                        int64_t tts,
                        ProtoIncrementalState::PacketSequenceState*,
                        ConstBytes);
+  void ParseDebugAnnotationArgs(
+      ConstBytes debug_annotation,
+      ProtoIncrementalState::PacketSequenceState* sequence_state,
+      ArgsTracker* args_tracker,
+      RowId row);
+  void ParseNestedValueArgs(ConstBytes nested_value,
+                            base::StringView flat_key,
+                            base::StringView key,
+                            ArgsTracker* args_tracker,
+                            RowId row);
+  void ParseTaskExecutionArgs(
+      ConstBytes task_execution,
+      ProtoIncrementalState::PacketSequenceState* sequence_state,
+      ArgsTracker* args_tracker,
+      RowId row);
   void ParseChromeBenchmarkMetadata(ConstBytes);
 
  private:
@@ -123,6 +139,8 @@
   const StringId oom_score_adj_id_;
   const StringId ion_total_unknown_id_;
   const StringId ion_change_unknown_id_;
+  const StringId task_file_name_args_key_id_;
+  const StringId task_function_name_args_key_id_;
   std::vector<StringId> meminfo_strs_id_;
   std::vector<StringId> vmstat_strs_id_;
   std::vector<StringId> rss_members_;
diff --git a/src/trace_processor/proto_trace_parser_unittest.cc b/src/trace_processor/proto_trace_parser_unittest.cc
index 6107f4f..a40868d 100644
--- a/src/trace_processor/proto_trace_parser_unittest.cc
+++ b/src/trace_processor/proto_trace_parser_unittest.cc
@@ -104,6 +104,7 @@
   MockTraceStorage() : TraceStorage() {}
 
   MOCK_METHOD1(InternString, StringId(base::StringView));
+  MOCK_CONST_METHOD1(GetString, NullTermStringView(StringId));
   MOCK_METHOD2(SetMetadata, void(size_t, Variadic));
   MOCK_METHOD2(AppendMetadata, void(size_t, Variadic));
 };
@@ -147,8 +148,7 @@
   ProtoTraceParserTest() {
     nice_storage_ = new NiceMock<MockTraceStorage>();
     context_.storage.reset(nice_storage_);
-    args_ = new MockArgsTracker(&context_);
-    context_.args_tracker.reset(args_);
+    context_.args_tracker.reset(new ArgsTracker(&context_));
     event_ = new MockEventTracker(&context_);
     context_.event_tracker.reset(event_);
     process_ = new MockProcessTracker(&context_);
@@ -189,7 +189,6 @@
   std::unique_ptr<protozero::ScatteredStreamWriter> stream_writer_;
   protos::pbzero::Trace trace_;
   TraceProcessorContext context_;
-  MockArgsTracker* args_;
   MockEventTracker* event_;
   MockProcessTracker* process_;
   MockSliceTracker* slice_;
@@ -976,6 +975,264 @@
   context_.sorter->ExtractEventsForced();
 }
 
+TEST_F(ProtoTraceParserTest, TrackEventWithDebugAnnotations) {
+  InitStorage();
+  context_.sorter.reset(new TraceSorter(
+      &context_, std::numeric_limits<int64_t>::max() /*window size*/));
+  MockArgsTracker args(&context_);
+
+  {
+    auto* packet = trace_.add_packet();
+    packet->set_trusted_packet_sequence_id(1);
+    packet->set_incremental_state_cleared(true);
+    auto* thread_desc = packet->set_thread_descriptor();
+    thread_desc->set_pid(15);
+    thread_desc->set_tid(16);
+    thread_desc->set_reference_timestamp_us(1000);
+    thread_desc->set_reference_thread_time_us(2000);
+  }
+  {
+    auto* packet = trace_.add_packet();
+    packet->set_trusted_packet_sequence_id(1);
+    auto* event = packet->set_track_event();
+    event->set_timestamp_delta_us(10);   // absolute: 1010.
+    event->set_thread_time_delta_us(5);  // absolute: 2005.
+    event->add_category_iids(1);
+    auto* annotation1 = event->add_debug_annotations();
+    annotation1->set_name_iid(1);
+    annotation1->set_uint_value(10u);
+    auto* annotation2 = event->add_debug_annotations();
+    annotation2->set_name_iid(2);
+    auto* nested = annotation2->set_nested_value();
+    nested->set_nested_type(protos::pbzero::DebugAnnotation::NestedValue::DICT);
+    nested->add_dict_keys("child1");
+    nested->add_dict_keys("child2");
+    auto* child1 = nested->add_dict_values();
+    child1->set_nested_type(
+        protos::pbzero::DebugAnnotation::NestedValue::UNSPECIFIED);
+    child1->set_bool_value(true);
+    auto* child2 = nested->add_dict_values();
+    child2->set_nested_type(
+        protos::pbzero::DebugAnnotation::NestedValue::ARRAY);
+    auto* child21 = child2->add_array_values();
+    child21->set_nested_type(
+        protos::pbzero::DebugAnnotation::NestedValue::UNSPECIFIED);
+    child21->set_string_value("child21");
+    auto* child22 = child2->add_array_values();
+    child22->set_nested_type(
+        protos::pbzero::DebugAnnotation::NestedValue::UNSPECIFIED);
+    child22->set_double_value(2.2);
+    auto* child23 = child2->add_array_values();
+    child23->set_nested_type(
+        protos::pbzero::DebugAnnotation::NestedValue::UNSPECIFIED);
+    child23->set_int_value(23);
+    auto* legacy_event = event->set_legacy_event();
+    legacy_event->set_name_iid(1);
+    legacy_event->set_phase('B');
+
+    auto* interned_data = packet->set_interned_data();
+    auto cat1 = interned_data->add_event_categories();
+    cat1->set_iid(1);
+    cat1->set_name("cat1");
+    auto ev1 = interned_data->add_legacy_event_names();
+    ev1->set_iid(1);
+    ev1->set_name("ev1");
+    auto an1 = interned_data->add_debug_annotation_names();
+    an1->set_iid(1);
+    an1->set_name("an1");
+    auto an2 = interned_data->add_debug_annotation_names();
+    an2->set_iid(2);
+    an2->set_name("an2");
+  }
+  {
+    auto* packet = trace_.add_packet();
+    packet->set_trusted_packet_sequence_id(1);
+    auto* event = packet->set_track_event();
+    event->set_timestamp_delta_us(10);   // absolute: 1020.
+    event->set_thread_time_delta_us(5);  // absolute: 2010.
+    event->add_category_iids(1);
+    auto* annotation3 = event->add_debug_annotations();
+    annotation3->set_name_iid(3);
+    annotation3->set_int_value(-3);
+    auto* annotation4 = event->add_debug_annotations();
+    annotation4->set_name_iid(4);
+    annotation4->set_bool_value(true);
+    auto* annotation5 = event->add_debug_annotations();
+    annotation5->set_name_iid(5);
+    annotation5->set_double_value(-5.5);
+    auto* annotation6 = event->add_debug_annotations();
+    annotation6->set_name_iid(6);
+    annotation6->set_pointer_value(20u);
+    auto* annotation7 = event->add_debug_annotations();
+    annotation7->set_name_iid(7);
+    annotation7->set_string_value("val7");
+    auto* annotation8 = event->add_debug_annotations();
+    annotation8->set_name_iid(8);
+    annotation8->set_legacy_json_value("val8");
+    auto* legacy_event = event->set_legacy_event();
+    legacy_event->set_name_iid(1);
+    legacy_event->set_phase('E');
+
+    auto* interned_data = packet->set_interned_data();
+    auto an3 = interned_data->add_debug_annotation_names();
+    an3->set_iid(3);
+    an3->set_name("an3");
+    auto an4 = interned_data->add_debug_annotation_names();
+    an4->set_iid(4);
+    an4->set_name("an4");
+    auto an5 = interned_data->add_debug_annotation_names();
+    an5->set_iid(5);
+    an5->set_name("an5");
+    auto an6 = interned_data->add_debug_annotation_names();
+    an6->set_iid(6);
+    an6->set_name("an6");
+    auto an7 = interned_data->add_debug_annotation_names();
+    an7->set_iid(7);
+    an7->set_name("an7");
+    auto an8 = interned_data->add_debug_annotation_names();
+    an8->set_iid(8);
+    an8->set_name("an8");
+  }
+
+  Tokenize();
+
+  EXPECT_CALL(*process_, UpdateThread(16, 15))
+      .Times(2)
+      .WillRepeatedly(Return(1));
+
+  InSequence in_sequence;  // Below slices should be sorted by timestamp.
+
+  EXPECT_CALL(*storage_, InternString(base::StringView("cat1")))
+      .WillOnce(Return(1));
+  EXPECT_CALL(*storage_, InternString(base::StringView("ev1")))
+      .WillOnce(Return(2));
+  EXPECT_CALL(*slice_, Begin(1010000, 1, 1, 2, _))
+      .WillOnce(testing::InvokeArgument<4>(&args, 1u));
+  EXPECT_CALL(*storage_, InternString(base::StringView("debug.an1")))
+      .WillOnce(Return(3));
+  EXPECT_CALL(args, AddArg(1u, 3, 3, Variadic::UnsignedInteger(10u)));
+
+  EXPECT_CALL(*storage_, InternString(base::StringView("debug.an2")))
+      .WillOnce(Return(4));
+  EXPECT_CALL(*storage_, GetString(4)).WillOnce(Return("debug.an2"));
+  EXPECT_CALL(*storage_, InternString(base::StringView("debug.an2.child1")))
+      .Times(2)
+      .WillRepeatedly(Return(5));
+  EXPECT_CALL(args, AddArg(1u, 5, 5, Variadic::Boolean(true)));
+
+  EXPECT_CALL(*storage_, InternString(base::StringView("debug.an2.child2")))
+      .WillOnce(Return(6));
+  EXPECT_CALL(*storage_, InternString(base::StringView("debug.an2.child2[0]")))
+      .WillOnce(Return(7));
+  EXPECT_CALL(*storage_, InternString(base::StringView("child21")))
+      .WillOnce(Return(8));
+  EXPECT_CALL(args, AddArg(1u, 6, 7, Variadic::String(8)));
+
+  EXPECT_CALL(*storage_, InternString(base::StringView("debug.an2.child2")))
+      .WillOnce(Return(6));
+  EXPECT_CALL(*storage_, InternString(base::StringView("debug.an2.child2[1]")))
+      .WillOnce(Return(9));
+  EXPECT_CALL(args, AddArg(1u, 6, 9, Variadic::Real(2.2)));
+
+  EXPECT_CALL(*storage_, InternString(base::StringView("debug.an2.child2")))
+      .WillOnce(Return(6));
+  EXPECT_CALL(*storage_, InternString(base::StringView("debug.an2.child2[2]")))
+      .WillOnce(Return(10));
+  EXPECT_CALL(args, AddArg(1u, 6, 10, Variadic::Integer(23)));
+
+  EXPECT_CALL(*slice_, End(1020000, 1, 1, 2, _))
+      .WillOnce(testing::InvokeArgument<4>(&args, 1u));
+
+  EXPECT_CALL(*storage_, InternString(base::StringView("debug.an3")))
+      .WillOnce(Return(11));
+  EXPECT_CALL(args, AddArg(1u, 11, 11, Variadic::Integer(-3)));
+  EXPECT_CALL(*storage_, InternString(base::StringView("debug.an4")))
+      .WillOnce(Return(12));
+  EXPECT_CALL(args, AddArg(1u, 12, 12, Variadic::Boolean(true)));
+  EXPECT_CALL(*storage_, InternString(base::StringView("debug.an5")))
+      .WillOnce(Return(13));
+  EXPECT_CALL(args, AddArg(1u, 13, 13, Variadic::Real(-5.5)));
+  EXPECT_CALL(*storage_, InternString(base::StringView("debug.an6")))
+      .WillOnce(Return(14));
+  EXPECT_CALL(args, AddArg(1u, 14, 14, Variadic::Pointer(20u)));
+  EXPECT_CALL(*storage_, InternString(base::StringView("debug.an7")))
+      .WillOnce(Return(15));
+  EXPECT_CALL(*storage_, InternString(base::StringView("val7")))
+      .WillOnce(Return(16));
+  EXPECT_CALL(args, AddArg(1u, 15, 15, Variadic::String(16)));
+  EXPECT_CALL(*storage_, InternString(base::StringView("debug.an8")))
+      .WillOnce(Return(17));
+  EXPECT_CALL(*storage_, InternString(base::StringView("val8")))
+      .WillOnce(Return(18));
+  EXPECT_CALL(args, AddArg(1u, 17, 17, Variadic::String(18)));
+
+  context_.sorter->ExtractEventsForced();
+}
+
+TEST_F(ProtoTraceParserTest, TrackEventWithTaskExecution) {
+  InitStorage();
+  context_.sorter.reset(new TraceSorter(
+      &context_, std::numeric_limits<int64_t>::max() /*window size*/));
+  MockArgsTracker args(&context_);
+
+  {
+    auto* packet = trace_.add_packet();
+    packet->set_trusted_packet_sequence_id(1);
+    packet->set_incremental_state_cleared(true);
+    auto* thread_desc = packet->set_thread_descriptor();
+    thread_desc->set_pid(15);
+    thread_desc->set_tid(16);
+    thread_desc->set_reference_timestamp_us(1000);
+    thread_desc->set_reference_thread_time_us(2000);
+  }
+  {
+    auto* packet = trace_.add_packet();
+    packet->set_trusted_packet_sequence_id(1);
+    auto* event = packet->set_track_event();
+    event->set_timestamp_delta_us(10);   // absolute: 1010.
+    event->set_thread_time_delta_us(5);  // absolute: 2005.
+    event->add_category_iids(1);
+    auto* task_execution = event->set_task_execution();
+    task_execution->set_posted_from_iid(1);
+    auto* legacy_event = event->set_legacy_event();
+    legacy_event->set_name_iid(1);
+    legacy_event->set_phase('B');
+
+    auto* interned_data = packet->set_interned_data();
+    auto cat1 = interned_data->add_event_categories();
+    cat1->set_iid(1);
+    cat1->set_name("cat1");
+    auto ev1 = interned_data->add_legacy_event_names();
+    ev1->set_iid(1);
+    ev1->set_name("ev1");
+    auto loc1 = interned_data->add_source_locations();
+    loc1->set_iid(1);
+    loc1->set_file_name("file1");
+    loc1->set_function_name("func1");
+  }
+
+  Tokenize();
+
+  EXPECT_CALL(*process_, UpdateThread(16, 15)).WillOnce(Return(1));
+
+  InSequence in_sequence;  // Below slices should be sorted by timestamp.
+
+  EXPECT_CALL(*storage_, InternString(base::StringView("cat1")))
+      .WillOnce(Return(1));
+  EXPECT_CALL(*storage_, InternString(base::StringView("ev1")))
+      .WillOnce(Return(2));
+  EXPECT_CALL(*slice_, Begin(1010000, 1, 1, 2, _))
+      .WillOnce(testing::InvokeArgument<4>(&args, 1u));
+  EXPECT_CALL(*storage_, InternString(base::StringView("file1")))
+      .WillOnce(Return(3));
+  EXPECT_CALL(*storage_, InternString(base::StringView("func1")))
+      .WillOnce(Return(4));
+  EXPECT_CALL(args, AddArg(1u, _, _, Variadic::String(3)));
+  EXPECT_CALL(args, AddArg(1u, _, _, Variadic::String(4)));
+
+  context_.sorter->ExtractEventsForced();
+}
+
 TEST_F(ProtoTraceParserTest, LoadChromeBenchmarkMetadata) {
   static const char kName[] = "name";
   static const char kTag2[] = "tag1";
diff --git a/src/trace_processor/raw_table.cc b/src/trace_processor/raw_table.cc
index 1f9196e..d7dcffe 100644
--- a/src/trace_processor/raw_table.cc
+++ b/src/trace_processor/raw_table.cc
@@ -90,12 +90,27 @@
       case Variadic::kInt:
         writer->AppendInt(value.int_value);
         break;
-      case Variadic::kReal:
-        writer->AppendDouble(value.real_value);
+      case Variadic::kUint:
+        writer->AppendUnsignedInt(value.uint_value);
         break;
       case Variadic::kString: {
         const auto& str = storage_->GetString(value.string_value);
         writer->AppendString(str.c_str(), str.size());
+        break;
+      }
+      case Variadic::kReal:
+        writer->AppendDouble(value.real_value);
+        break;
+      case Variadic::kPointer:
+        writer->AppendUnsignedInt(value.pointer_value);
+        break;
+      case Variadic::kBool:
+        writer->AppendBool(value.bool_value);
+        break;
+      case Variadic::kJson: {
+        const auto& str = storage_->GetString(value.json_value);
+        writer->AppendString(str.c_str(), str.size());
+        break;
       }
     }
   };
diff --git a/src/trace_processor/sqlite_utils.h b/src/trace_processor/sqlite_utils.h
index 60d095f..64ad4fb 100644
--- a/src/trace_processor/sqlite_utils.h
+++ b/src/trace_processor/sqlite_utils.h
@@ -132,6 +132,13 @@
   return sqlite3_value_double(value);
 }
 
+template <>
+inline bool ExtractSqliteValue(sqlite3_value* value) {
+  auto type = sqlite3_value_type(value);
+  PERFETTO_DCHECK(type == SQLITE_INTEGER);
+  return static_cast<bool>(sqlite3_value_int(value));
+}
+
 // Do not add a uint64_t version of ExtractSqliteValue. You should not be using
 // uint64_t at all given that SQLite doesn't support it.
 
@@ -362,6 +369,11 @@
 }
 
 template <>
+inline void ReportSqliteResult(sqlite3_context* ctx, bool value) {
+  sqlite3_result_int(ctx, value);
+}
+
+template <>
 inline void ReportSqliteResult(sqlite3_context* ctx, double value) {
   sqlite3_result_double(ctx, value);
 }
@@ -430,6 +442,8 @@
       type = Table::ColumnType::kString;
     } else if (strcmp(raw_type, "DOUBLE") == 0) {
       type = Table::ColumnType::kDouble;
+    } else if (strcmp(raw_type, "BOOLEAN") == 0) {
+      type = Table::ColumnType::kBool;
     } else if (!*raw_type) {
       PERFETTO_DLOG("Unknown column type for %s %s", raw_table_name.c_str(),
                     name);
diff --git a/src/trace_processor/table.cc b/src/trace_processor/table.cc
index 1218b5a..1110d6f 100644
--- a/src/trace_processor/table.cc
+++ b/src/trace_processor/table.cc
@@ -39,6 +39,8 @@
       return "INT";
     case Table::ColumnType::kDouble:
       return "DOUBLE";
+    case Table::ColumnType::kBool:
+      return "BOOLEAN";
     case Table::ColumnType::kUnknown:
       PERFETTO_FATAL("Cannot map unknown column type");
   }
diff --git a/src/trace_processor/table.h b/src/trace_processor/table.h
index 0c8e638..c89f002 100644
--- a/src/trace_processor/table.h
+++ b/src/trace_processor/table.h
@@ -49,7 +49,8 @@
     kLong = 3,
     kInt = 4,
     kDouble = 5,
-    kUnknown = 6,
+    kBool = 6,
+    kUnknown = 7,
   };
 
   // Describes a column of this table.
diff --git a/src/trace_processor/trace_storage.h b/src/trace_processor/trace_storage.h
index 45ffed3..de4dfa5 100644
--- a/src/trace_processor/trace_storage.h
+++ b/src/trace_processor/trace_storage.h
@@ -131,12 +131,24 @@
           case Variadic::Type::kInt:
             hash.Update(arg.value.int_value);
             break;
+          case Variadic::Type::kUint:
+            hash.Update(arg.value.uint_value);
+            break;
           case Variadic::Type::kString:
             hash.Update(arg.value.string_value);
             break;
           case Variadic::Type::kReal:
             hash.Update(arg.value.real_value);
             break;
+          case Variadic::Type::kPointer:
+            hash.Update(arg.value.pointer_value);
+            break;
+          case Variadic::Type::kBool:
+            hash.Update(arg.value.bool_value);
+            break;
+          case Variadic::Type::kJson:
+            hash.Update(arg.value.json_value);
+            break;
         }
         return hash.digest();
       }
@@ -832,7 +844,8 @@
   }
 
   // Reading methods.
-  NullTermStringView GetString(StringId id) const {
+  // Virtual for testing.
+  virtual NullTermStringView GetString(StringId id) const {
     return string_pool_.Get(id);
   }
 
diff --git a/src/trace_processor/variadic.h b/src/trace_processor/variadic.h
index 761aea3..6df011e 100644
--- a/src/trace_processor/variadic.h
+++ b/src/trace_processor/variadic.h
@@ -24,7 +24,7 @@
 
 // Variadic type representing value of different possible types.
 struct Variadic {
-  enum Type { kInt, kString, kReal };
+  enum Type { kInt, kUint, kString, kReal, kPointer, kBool, kJson };
 
   static Variadic Integer(int64_t int_value) {
     Variadic variadic;
@@ -33,6 +33,17 @@
     return variadic;
   }
 
+  // BEWARE: Unsigned 64-bit integers will be handled as signed integers by
+  // SQLite for built-in SQL operators. This variadic type is used to
+  // distinguish between int64 and uint64 for correct JSON export of TrackEvent
+  // arguments.
+  static Variadic UnsignedInteger(uint64_t uint_value) {
+    Variadic variadic;
+    variadic.type = Type::kUint;
+    variadic.uint_value = uint_value;
+    return variadic;
+  }
+
   static Variadic String(StringPool::Id string_id) {
     Variadic variadic;
     variadic.type = Type::kString;
@@ -47,16 +58,49 @@
     return variadic;
   }
 
+  // This variadic type is used to distinguish between integers and pointer
+  // values for correct JSON export of TrackEvent arguments.
+  static Variadic Pointer(uint64_t pointer_value) {
+    Variadic variadic;
+    variadic.type = Type::kPointer;
+    variadic.pointer_value = pointer_value;
+    return variadic;
+  }
+
+  static Variadic Boolean(bool bool_value) {
+    Variadic variadic;
+    variadic.type = Type::kBool;
+    variadic.bool_value = bool_value;
+    return variadic;
+  }
+
+  // This variadic type is used to distinguish between regular string and JSON
+  // string values for correct JSON export of TrackEvent arguments.
+  static Variadic Json(StringPool::Id json_value) {
+    Variadic variadic;
+    variadic.type = Type::kJson;
+    variadic.json_value = json_value;
+    return variadic;
+  }
+
   // Used in tests.
   bool operator==(const Variadic& other) const {
     if (type == other.type) {
       switch (type) {
         case kInt:
           return int_value == other.int_value;
+        case kUint:
+          return uint_value == other.uint_value;
         case kString:
           return string_value == other.string_value;
         case kReal:
           return std::equal_to<double>()(real_value, other.real_value);
+        case kPointer:
+          return pointer_value == other.pointer_value;
+        case kBool:
+          return bool_value == other.bool_value;
+        case kJson:
+          return json_value == other.json_value;
       }
     }
     return false;
@@ -65,8 +109,12 @@
   Type type;
   union {
     int64_t int_value;
+    uint64_t uint_value;
     StringPool::Id string_value;
     double real_value;
+    uint64_t pointer_value;
+    bool bool_value;
+    StringPool::Id json_value;
   };
 };
 
diff --git a/test/android_client_api_example.cc b/test/android_client_api_example.cc
new file mode 100644
index 0000000..459dd5f
--- /dev/null
+++ b/test/android_client_api_example.cc
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#include "perfetto/tracing.h"
+
+#include "perfetto/trace/test_event.pbzero.h"
+#include "perfetto/trace/trace.pb.h"
+#include "perfetto/trace/trace_packet.pbzero.h"
+
+// Deliberately not pulling any non-public perfetto header to spot accidental
+// header public -> non-public dependency while building this file.
+
+class MyDataSource : public perfetto::DataSource<MyDataSource> {
+ public:
+  void OnSetup(const SetupArgs& args) override {
+    // This can be used to access the domain-specific DataSourceConfig, via
+    // args.config->xxx_config_raw().
+    PERFETTO_ILOG("OnSetup called, name: %s", args.config->name().c_str());
+  }
+
+  void OnStart(const StartArgs&) override { PERFETTO_ILOG("OnStart called"); }
+
+  void OnStop(const StopArgs&) override { PERFETTO_ILOG("OnStop called"); }
+};
+
+PERFETTO_DEFINE_DATA_SOURCE_STATIC_MEMBERS(MyDataSource);
+
+int main() {
+  perfetto::TracingInitArgs args;
+  args.backends = perfetto::kSystemBackend;
+  perfetto::Tracing::Initialize(args);
+
+  // DataSourceDescriptor can be used to advertise domain-specific features.
+  perfetto::DataSourceDescriptor dsd;
+  dsd.set_name("com.example.mytrace");
+  MyDataSource::Register(dsd);
+
+  for (;;) {
+    MyDataSource::Trace([](MyDataSource::TraceContext ctx) {
+      PERFETTO_LOG("Tracing lambda called");
+      auto packet = ctx.NewTracePacket();
+      packet->set_timestamp(42);
+      packet->set_for_testing()->set_str("event 1");
+    });
+    sleep(1);
+  }
+}
diff --git a/tools/check_include_violations b/tools/check_include_violations
index 5e05b86..82e36ab 100755
--- a/tools/check_include_violations
+++ b/tools/check_include_violations
@@ -26,16 +26,6 @@
 
 ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
 
-# TODO(primiano): fix violation from protozero -> base/logging.h .
-WHITELIST = [('include/perfetto/protozero/.*', 'perfetto/ext/base/logging.h')]
-
-
-def whitelisted(rel_path, incl):
-  for path_regex, incl_regex in WHITELIST:
-    if re.match(path_regex, rel_path) and re.match(incl_regex, incl):
-      return True
-  return False
-
 
 def main():
   errors = 0
@@ -69,10 +59,13 @@
             errors += 1
             continue
 
+          # Ignore lines marked with nogncheck.
+          if '// nogncheck' in line:
+            continue
+
           # Public (non-/ext/) headers cannot include /ext/ headers.
           if (not rel_path.startswith('include/perfetto/ext/') and
-                  incl.startswith('perfetto/ext/') and
-                  not whitelisted(rel_path, incl)):
+                  incl.startswith('perfetto/ext/')):
             sys.stderr.write(('Public header %s cannot include the non-public' +
                               '/ext/ header %s.\n') % (rel_path, incl))
             errors += 1
diff --git a/tools/gen_android_bp b/tools/gen_android_bp
index f34f55a..45b7ff3 100755
--- a/tools/gen_android_bp
+++ b/tools/gen_android_bp
@@ -36,6 +36,7 @@
 # Default targets to translate to the blueprint file.
 default_targets = [
     '//:libperfetto',
+    '//:libperfetto_client_experimental',
     '//:libperfetto_android_internal',
     '//:perfetto_integrationtests',
     '//:perfetto_trace_protos',
@@ -47,6 +48,7 @@
     '//:heapprofd_client',
     '//:heapprofd',
     '//:trigger_perfetto',
+    '//:idle_alloc',
 ]
 
 # Defines a custom init_rc argument to be applied to the corresponding output
diff --git a/tools/heap_profile b/tools/heap_profile
index 7f9395a..40debf1 100755
--- a/tools/heap_profile
+++ b/tools/heap_profile
@@ -30,8 +30,8 @@
 import urllib
 
 TRACE_TO_TEXT_SHAS = {
-  'linux': 'a8171d85c5964ccafe457142dbb7df68ca8da543',
-  'mac': '268c2fc096039566979d16c1a7a99eabef0d9682',
+  'linux': '2ef44a2ce2f9cfdccd3a21c80299fa6c0e6b4e38',
+  'mac': '32f29cf000e1e4638f8b96c7d004586a74a72f72',
 }
 TRACE_TO_TEXT_PATH = tempfile.gettempdir()
 TRACE_TO_TEXT_BASE_URL = (
diff --git a/tools/idle_alloc.cc b/tools/idle_alloc.cc
new file mode 100644
index 0000000..1a1dd5a
--- /dev/null
+++ b/tools/idle_alloc.cc
@@ -0,0 +1,228 @@
+/*
+ * Copyright (C) 2019 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.
+ */
+
+#include <memory>
+
+#include <errno.h>
+#include <fcntl.h>
+#include <inttypes.h>
+#include <signal.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+namespace {
+
+constexpr auto kIdleSize = 10000 * 4096;
+constexpr auto kNoIdleSize = 1000 * 4096;
+
+static bool interrupted = false;
+
+volatile unsigned char* __attribute__((noinline)) AllocIdle(size_t bytes);
+volatile unsigned char* __attribute__((noinline)) AllocIdle(size_t bytes) {
+  // This volatile is needed to prevent the compiler from trying to be
+  // helpful and compiling a "useless" malloc + free into a noop.
+  volatile unsigned char* x = static_cast<unsigned char*>(malloc(bytes));
+  if (x) {
+    x[1] = 'x';
+  }
+  return x;
+}
+
+volatile unsigned char* __attribute__((noinline)) AllocNoIdle(size_t bytes);
+volatile unsigned char* __attribute__((noinline)) AllocNoIdle(size_t bytes) {
+  // This volatile is needed to prevent the compiler from trying to be
+  // helpful and compiling a "useless" malloc + free into a noop.
+  volatile unsigned char* x = static_cast<unsigned char*>(malloc(bytes));
+  if (x) {
+    x[0] = 'x';
+  }
+  return x;
+}
+
+class MemoryToucher {
+ public:
+  virtual void Touch(volatile unsigned char* nonidle) = 0;
+  virtual ~MemoryToucher() = default;
+};
+
+class ReadDevZeroChunks : public MemoryToucher {
+ public:
+  ReadDevZeroChunks(size_t chunk_size)
+      : chunk_size_(chunk_size), fd_(open("/dev/zero", O_RDONLY)) {
+    if (fd_ == -1) {
+      fprintf(stderr, "Failed to open: %s", strerror(errno));
+      abort();
+    }
+  }
+
+  ~ReadDevZeroChunks() override = default;
+
+  void Touch(volatile unsigned char* nonidle) override {
+    size_t total_rd = 0;
+    while (total_rd < kNoIdleSize) {
+      size_t chunk = chunk_size_;
+      if (chunk > kNoIdleSize - total_rd)
+        chunk = kNoIdleSize - total_rd;
+
+      ssize_t rd =
+          read(fd_, const_cast<unsigned char*>(nonidle) + total_rd, chunk);
+      if (rd == -1) {
+        fprintf(stderr, "Failed to write: %s.", strerror(errno));
+        abort();
+      }
+      total_rd += static_cast<size_t>(rd);
+    }
+  }
+
+ private:
+  size_t chunk_size_;
+  int fd_;
+};
+
+class ReadDevZeroChunksAndSleep : public ReadDevZeroChunks {
+ public:
+  ReadDevZeroChunksAndSleep(size_t chunk_size)
+      : ReadDevZeroChunks(chunk_size) {}
+  void Touch(volatile unsigned char* nonidle) override {
+    ReadDevZeroChunks::Touch(nonidle);
+    sleep(1);
+  }
+};
+
+class SumUp : public MemoryToucher {
+ public:
+  SumUp()
+      : sum_(const_cast<volatile uint64_t*>(
+            static_cast<uint64_t*>(malloc(sizeof(uint64_t))))) {}
+  ~SumUp() override = default;
+
+  void Touch(volatile unsigned char* nonidle) override {
+    for (size_t i = 0; i < kNoIdleSize; ++i)
+      *sum_ += nonidle[i];
+  }
+
+ private:
+  volatile uint64_t* sum_;
+};
+
+class ReadDevZeroChunksAndSum : public ReadDevZeroChunks {
+ public:
+  ReadDevZeroChunksAndSum(size_t chunk_size) : ReadDevZeroChunks(chunk_size) {}
+  void Touch(volatile unsigned char* nonidle) override {
+    ReadDevZeroChunks::Touch(nonidle);
+    sum_up_.Touch(nonidle);
+  }
+
+ private:
+  SumUp sum_up_;
+};
+
+class AssignValues : public MemoryToucher {
+ public:
+  ~AssignValues() override = default;
+
+  void Touch(volatile unsigned char* nonidle) override {
+    for (size_t i = 0; i < kNoIdleSize; ++i)
+      nonidle[i] = static_cast<unsigned char>(i % 256);
+  }
+};
+
+}  // namespace
+
+int main(int argc, char** argv) {
+  volatile auto* idle = AllocIdle(kIdleSize);
+  volatile auto* nonidle = AllocNoIdle(kNoIdleSize);
+
+  printf("Own PID: %" PRIdMAX "\n", static_cast<intmax_t>(getpid()));
+  printf("Idle: %p\n", static_cast<void*>(const_cast<unsigned char*>(idle)));
+  printf("Nonidle: %p\n",
+         static_cast<void*>(const_cast<unsigned char*>(nonidle)));
+
+  for (size_t i = 0; i < kIdleSize; ++i)
+    idle[i] = static_cast<unsigned char>(i % 256);
+  for (size_t i = 0; i < kNoIdleSize; ++i)
+    nonidle[i] = static_cast<unsigned char>(i % 256);
+
+  printf("Allocated everything.\n");
+
+  struct sigaction action = {};
+  action.sa_handler = [](int) { interrupted = true; };
+  if (sigaction(SIGUSR1, &action, nullptr) != 0) {
+    fprintf(stderr, "Failed to register signal handler.\n");
+    abort();
+  }
+
+  if (argc < 2) {
+    fprintf(stderr,
+            "Specifiy one of AssignValues / SumUp / ReadDevZeroChunks\n");
+    abort();
+  }
+
+  std::unique_ptr<MemoryToucher> toucher;
+  if (strcmp(argv[1], "AssignValues") == 0) {
+    toucher.reset(new AssignValues());
+    printf("Using AssignValues.\n");
+  } else if (strcmp(argv[1], "SumUp") == 0) {
+    toucher.reset(new SumUp());
+    printf("Using SumUp.\n");
+  } else if (strcmp(argv[1], "ReadDevZeroChunks") == 0 ||
+             strcmp(argv[1], "ReadDevZeroChunksAndSleep") == 0 ||
+             strcmp(argv[1], "ReadDevZeroChunksAndSum") == 0) {
+    if (argc < 3) {
+      fprintf(stderr, "Specify chunk size.\n");
+      abort();
+    }
+    char* end;
+    long long chunk_arg = strtoll(argv[2], &end, 10);
+    if (*end != '\0' || *argv[2] == '\0') {
+      fprintf(stderr, "Invalid chunk size: %s\n", argv[2]);
+      abort();
+    }
+    if (strcmp(argv[1], "ReadDevZeroChunksAndSleep") == 0) {
+      printf("Using ReadDevZeroChunksAndSleep.\n");
+      toucher.reset(
+          new ReadDevZeroChunksAndSleep(static_cast<size_t>(chunk_arg)));
+    } else if (strcmp(argv[1], "ReadDevZeroChunksAndSum") == 0) {
+      printf("Using ReadDevZeroChunksAndSum.\n");
+      toucher.reset(
+          new ReadDevZeroChunksAndSum(static_cast<size_t>(chunk_arg)));
+    } else {
+      printf("Using ReadDevZeroChunks.\n");
+      toucher.reset(new ReadDevZeroChunks(static_cast<size_t>(chunk_arg)));
+    }
+  } else {
+    fprintf(stderr, "Invalid input.\n");
+    abort();
+  }
+
+  while (true) {
+    bool report = interrupted;
+    if (report) {
+      printf("Waiting to finish touching everything.\n");
+      interrupted = false;
+      sleep(2);
+    }
+
+    toucher->Touch(nonidle);
+
+    if (report)
+      printf("Touched everything.\n");
+  }
+}