Merge "trace_processor: make diff test more Win-friendly"
diff --git a/Android.bp b/Android.bp
index 3376ba3..046da6e 100644
--- a/Android.bp
+++ b/Android.bp
@@ -6402,6 +6402,7 @@
 filegroup {
   name: "perfetto_src_base_base",
   srcs: [
+    "src/base/ctrl_c_handler.cc",
     "src/base/event_fd.cc",
     "src/base/file_utils.cc",
     "src/base/logging.cc",
diff --git a/BUILD b/BUILD
index 8d3c5ea..d91e53f 100644
--- a/BUILD
+++ b/BUILD
@@ -291,6 +291,7 @@
     srcs = [
         "include/perfetto/ext/base/circular_queue.h",
         "include/perfetto/ext/base/container_annotations.h",
+        "include/perfetto/ext/base/ctrl_c_handler.h",
         "include/perfetto/ext/base/endian.h",
         "include/perfetto/ext/base/event_fd.h",
         "include/perfetto/ext/base/file_utils.h",
@@ -574,6 +575,7 @@
 perfetto_cc_library(
     name = "src_base_base",
     srcs = [
+        "src/base/ctrl_c_handler.cc",
         "src/base/event_fd.cc",
         "src/base/file_utils.cc",
         "src/base/logging.cc",
diff --git a/examples/sdk/CMakeLists.txt b/examples/sdk/CMakeLists.txt
index 1e1c6e2..a2f74bd 100644
--- a/examples/sdk/CMakeLists.txt
+++ b/examples/sdk/CMakeLists.txt
@@ -47,4 +47,25 @@
   target_link_libraries(example_system_wide log)
   target_link_libraries(example_custom_data_source log)
   target_link_libraries(example_console log)
-endif (ANDROID)
\ No newline at end of file
+endif (ANDROID)
+
+if (WIN32)
+  # The perfetto library contains many symbols, so it needs the big object
+  # format.
+  target_compile_options(perfetto PRIVATE "/bigobj")
+  # Disable legacy features in windows.h.
+  add_definitions(-DWIN32_LEAN_AND_MEAN -DNOMINMAX)
+  # On Windows we should link to WinSock2.
+  target_link_libraries(example ws2_32)
+  target_link_libraries(example_system_wide ws2_32)
+  target_link_libraries(example_custom_data_source ws2_32)
+  target_link_libraries(example_console ws2_32)
+endif (WIN32)
+
+# Enable standards-compliant mode when using the Visual Studio compiler.
+if (MSVC)
+  target_compile_options(example PRIVATE "/permissive-")
+  target_compile_options(example_system_wide PRIVATE "/permissive-")
+  target_compile_options(example_custom_data_source PRIVATE "/permissive-")
+  target_compile_options(example_console PRIVATE "/permissive-")
+endif (MSVC)
\ No newline at end of file
diff --git a/include/perfetto/ext/base/BUILD.gn b/include/perfetto/ext/base/BUILD.gn
index 694d23d..15e1799 100644
--- a/include/perfetto/ext/base/BUILD.gn
+++ b/include/perfetto/ext/base/BUILD.gn
@@ -18,6 +18,7 @@
   sources = [
     "circular_queue.h",
     "container_annotations.h",
+    "ctrl_c_handler.h",
     "endian.h",
     "event_fd.h",
     "file_utils.h",
diff --git a/include/perfetto/ext/base/ctrl_c_handler.h b/include/perfetto/ext/base/ctrl_c_handler.h
new file mode 100644
index 0000000..6c7b34c
--- /dev/null
+++ b/include/perfetto/ext/base/ctrl_c_handler.h
@@ -0,0 +1,32 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef INCLUDE_PERFETTO_EXT_BASE_CTRL_C_HANDLER_H_
+#define INCLUDE_PERFETTO_EXT_BASE_CTRL_C_HANDLER_H_
+
+namespace perfetto {
+namespace base {
+
+// On Linux/Android/Mac: installs SIGINT + SIGTERM signal handlers.
+// On Windows: installs a SetConsoleCtrlHandler() handler.
+// The passed handler must be async safe.
+using CtrlCHandlerFunction = void (*)();
+void InstallCtrCHandler(CtrlCHandlerFunction);
+
+}  // namespace base
+}  // namespace perfetto
+
+#endif  // INCLUDE_PERFETTO_EXT_BASE_CTRL_C_HANDLER_H_
diff --git a/include/perfetto/profiling/memory/heap_profile.h b/include/perfetto/profiling/memory/heap_profile.h
index 1a780d1..d788e46 100644
--- a/include/perfetto/profiling/memory/heap_profile.h
+++ b/include/perfetto/profiling/memory/heap_profile.h
@@ -137,12 +137,6 @@
 // Takes ownership of |info|.
 uint32_t AHeapProfile_registerHeap(AHeapInfo* _Nullable info);
 
-// Called by libc upon receipt of the profiling signal.
-// DO NOT CALL EXCEPT FROM LIBC!
-// TODO(fmayer): Maybe move this out of this header.
-bool AHeapProfile_initSession(void* _Nullable (*_Nonnull malloc_fn)(size_t),
-                              void (*_Nonnull free_fn)(void* _Nullable));
-
 // Reports an allocation of |size| on the given |heap_id|.
 //
 // If a profiling session is active, this function decides whether the reported
diff --git a/src/base/BUILD.gn b/src/base/BUILD.gn
index 52003ce..76921cf 100644
--- a/src/base/BUILD.gn
+++ b/src/base/BUILD.gn
@@ -19,6 +19,10 @@
 import("../../gn/test.gni")
 import("../../gn/wasm.gni")
 
+# On standalone builds this is all the OSes we support. On chromium builds,
+# though, this really means !is_fuchsia && !is_nacl.
+_subprocess_supported = is_linux || is_android || is_mac || is_win
+
 perfetto_component("base") {
   deps = [ "../../gn:default_deps" ]
   public_deps = [
@@ -26,6 +30,7 @@
     "../../include/perfetto/ext/base",
   ]
   sources = [
+    "ctrl_c_handler.cc",
     "event_fd.cc",
     "file_utils.cc",
     "logging.cc",
@@ -53,11 +58,14 @@
   }
 
   if (!is_nacl) {
+    sources += [ "unix_task_runner.cc" ]
+  }
+
+  if (_subprocess_supported) {
     sources += [
       "subprocess.cc",
       "subprocess_posix.cc",
       "subprocess_windows.cc",
-      "unix_task_runner.cc",
     ]
   }
 
@@ -160,7 +168,7 @@
     "uuid_unittest.cc",
     "weak_ptr_unittest.cc",
   ]
-  if (is_linux || is_android || is_mac || is_win) {
+  if (_subprocess_supported) {
     # Don't run on Fuchsia, NaCL. They pretend to be POSIX but then give up on
     # execve(2).
     sources += [ "subprocess_unittest.cc" ]
diff --git a/src/base/ctrl_c_handler.cc b/src/base/ctrl_c_handler.cc
new file mode 100644
index 0000000..72b60a8
--- /dev/null
+++ b/src/base/ctrl_c_handler.cc
@@ -0,0 +1,75 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#include "perfetto/ext/base/ctrl_c_handler.h"
+
+#include "perfetto/base/build_config.h"
+#include "perfetto/base/compiler.h"
+#include "perfetto/base/logging.h"
+
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+#include <Windows.h>
+#include <consoleapi.h>
+#include <io.h>
+#else
+#include <signal.h>
+#include <unistd.h>
+#endif
+
+namespace perfetto {
+namespace base {
+
+namespace {
+CtrlCHandlerFunction g_handler = nullptr;
+}
+
+void InstallCtrCHandler(CtrlCHandlerFunction handler) {
+  PERFETTO_CHECK(g_handler == nullptr);
+  g_handler = handler;
+
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+  auto trampoline = [](DWORD type) -> int {
+    if (type == CTRL_C_EVENT) {
+      g_handler();
+      return true;
+    }
+    return false;
+  };
+  ::SetConsoleCtrlHandler(trampoline, true);
+#elif PERFETTO_BUILDFLAG(PERFETTO_OS_LINUX) || \
+    PERFETTO_BUILDFLAG(PERFETTO_OS_ANDROID) || \
+    PERFETTO_BUILDFLAG(PERFETTO_OS_APPLE)
+  // Setup signal handler.
+  struct sigaction sa {};
+
+// Glibc headers for sa_sigaction trigger this.
+#pragma GCC diagnostic push
+#if defined(__clang__)
+#pragma GCC diagnostic ignored "-Wdisabled-macro-expansion"
+#endif
+  sa.sa_handler = [](int) { g_handler(); };
+  sa.sa_flags = static_cast<decltype(sa.sa_flags)>(SA_RESETHAND | SA_RESTART);
+#pragma GCC diagnostic pop
+  sigaction(SIGINT, &sa, nullptr);
+  sigaction(SIGTERM, &sa, nullptr);
+#else
+  // Do nothing on NaCL and Fuchsia.
+  ignore_result(handler);
+#endif
+}
+
+}  // namespace base
+}  // namespace perfetto
diff --git a/src/perfetto_cmd/perfetto_cmd.cc b/src/perfetto_cmd/perfetto_cmd.cc
index b8e332c..2610154 100644
--- a/src/perfetto_cmd/perfetto_cmd.cc
+++ b/src/perfetto_cmd/perfetto_cmd.cc
@@ -20,7 +20,6 @@
 
 #include <fcntl.h>
 #include <getopt.h>
-#include <signal.h>
 #include <stdio.h>
 #include <sys/stat.h>
 #include <sys/types.h>
@@ -45,6 +44,7 @@
 #include "perfetto/base/compiler.h"
 #include "perfetto/base/logging.h"
 #include "perfetto/base/time.h"
+#include "perfetto/ext/base/ctrl_c_handler.h"
 #include "perfetto/ext/base/file_utils.h"
 #include "perfetto/ext/base/string_view.h"
 #include "perfetto/ext/base/thread_utils.h"
@@ -920,20 +920,7 @@
 }
 
 void PerfettoCmd::SetupCtrlCSignalHandler() {
-  // Setup signal handler.
-  struct sigaction sa {};
-
-// Glibc headers for sa_sigaction trigger this.
-#pragma GCC diagnostic push
-#if defined(__clang__)
-#pragma GCC diagnostic ignored "-Wdisabled-macro-expansion"
-#endif
-  sa.sa_handler = [](int) { g_consumer_cmd->SignalCtrlC(); };
-  sa.sa_flags = static_cast<decltype(sa.sa_flags)>(SA_RESETHAND | SA_RESTART);
-#pragma GCC diagnostic pop
-  sigaction(SIGINT, &sa, nullptr);
-  sigaction(SIGTERM, &sa, nullptr);
-
+  base::InstallCtrCHandler([] { g_consumer_cmd->SignalCtrlC(); });
   task_runner_.AddFileDescriptorWatch(ctrl_c_evt_.fd(), [this] {
     PERFETTO_LOG("SIGINT/SIGTERM received: disabling tracing.");
     ctrl_c_evt_.Clear();
diff --git a/src/profiling/memory/client_api.cc b/src/profiling/memory/client_api.cc
index f508522..5c911d2 100644
--- a/src/profiling/memory/client_api.cc
+++ b/src/profiling/memory/client_api.cc
@@ -15,6 +15,7 @@
  */
 
 #include "perfetto/profiling/memory/heap_profile.h"
+#include "src/profiling/memory/heap_profile_internal.h"
 
 #include <inttypes.h>
 #include <malloc.h>
diff --git a/src/profiling/memory/client_api_factory_standalone.cc b/src/profiling/memory/client_api_factory_standalone.cc
index fa7c0c9..6b96f7a 100644
--- a/src/profiling/memory/client_api_factory_standalone.cc
+++ b/src/profiling/memory/client_api_factory_standalone.cc
@@ -25,6 +25,7 @@
 #include "perfetto/profiling/memory/heap_profile.h"
 #include "src/profiling/common/proc_utils.h"
 #include "src/profiling/memory/client.h"
+#include "src/profiling/memory/heap_profile_internal.h"
 #include "src/profiling/memory/heapprofd_producer.h"
 
 #include <string>
diff --git a/src/profiling/memory/client_api_unittest.cc b/src/profiling/memory/client_api_unittest.cc
index 7301d03..17edb31 100644
--- a/src/profiling/memory/client_api_unittest.cc
+++ b/src/profiling/memory/client_api_unittest.cc
@@ -17,6 +17,7 @@
 #include "perfetto/ext/base/unix_socket.h"
 #include "perfetto/ext/base/unix_task_runner.h"
 #include "perfetto/profiling/memory/heap_profile.h"
+#include "src/profiling/memory/heap_profile_internal.h"
 
 #include "src/profiling/memory/client.h"
 #include "src/profiling/memory/client_api_factory.h"
diff --git a/src/profiling/memory/heap_profile_internal.h b/src/profiling/memory/heap_profile_internal.h
new file mode 100644
index 0000000..8e9a980
--- /dev/null
+++ b/src/profiling/memory/heap_profile_internal.h
@@ -0,0 +1,47 @@
+/*
+ * Copyright (C) 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#ifndef SRC_PROFILING_MEMORY_HEAP_PROFILE_INTERNAL_H_
+#define SRC_PROFILING_MEMORY_HEAP_PROFILE_INTERNAL_H_
+
+#include <inttypes.h>
+#include <stdlib.h>
+
+#pragma GCC diagnostic push
+
+#if defined(__clang__)
+#pragma GCC diagnostic ignored "-Wnullability-extension"
+#else
+#define _Nullable
+#define _Nonnull
+#endif
+
+#ifdef __cplusplus
+extern "C" {
+#endif
+
+// Called by the standalone client or libc upon receipt of the profiling
+// signal.
+bool AHeapProfile_initSession(void* _Nullable (*_Nonnull malloc_fn)(size_t),
+                              void (*_Nonnull free_fn)(void* _Nullable));
+
+#ifdef __cplusplus
+}
+#endif
+
+#pragma GCC diagnostic pop
+
+#endif  // SRC_PROFILING_MEMORY_HEAP_PROFILE_INTERNAL_H_
diff --git a/src/profiling/memory/heapprofd_end_to_end_test.cc b/src/profiling/memory/heapprofd_end_to_end_test.cc
index 9233b42..5f27236 100644
--- a/src/profiling/memory/heapprofd_end_to_end_test.cc
+++ b/src/profiling/memory/heapprofd_end_to_end_test.cc
@@ -318,6 +318,42 @@
   }
 }
 
+void __attribute__((constructor(1024))) RunAccurateSample() {
+  const char* a0 = getenv("HEAPPROFD_TESTING_RUN_ACCURATE_SAMPLE");
+  if (a0 == nullptr)
+    return;
+
+  static std::atomic<bool> initialized{false};
+  static uint32_t heap_id =
+      AHeapProfile_registerHeap(AHeapInfo_setEnabledCallback(
+          AHeapInfo_create("test"),
+          [](void*, const AHeapProfileEnableCallbackInfo*) {
+            initialized = true;
+          },
+          nullptr));
+
+  ChildFinishHandshake();
+
+  // heapprofd_client needs malloc to see the signal.
+  while (!initialized)
+    AllocateAndFree(1);
+  // We call the callback before setting enabled=true on the heap, so we
+  // wait a bit for the assignment to happen.
+  usleep(100000);
+  if (!AHeapProfile_reportSample(heap_id, 0x1, 10u))
+    PERFETTO_FATAL("Expected allocation to be sampled.");
+  AHeapProfile_reportFree(heap_id, 0x1);
+  if (!AHeapProfile_reportSample(heap_id, 0x2, 15u))
+    PERFETTO_FATAL("Expected allocation to be sampled.");
+  if (!AHeapProfile_reportSample(heap_id, 0x3, 15u))
+    PERFETTO_FATAL("Expected allocation to be sampled.");
+  AHeapProfile_reportFree(heap_id, 0x2);
+
+  // Wait around so we can verify it did't crash.
+  for (;;) {
+  }
+}
+
 void __attribute__((constructor(1024))) RunReInit() {
   const char* a0 = getenv("HEAPPROFD_TESTING_RUN_REINIT_ARG0");
   if (a0 == nullptr)
@@ -353,6 +389,57 @@
   PERFETTO_FATAL("Should be unreachable");
 }
 
+void __attribute__((constructor(1024))) RunCustomLifetime() {
+  const char* a0 = getenv("HEAPPROFD_TESTING_RUN_LIFETIME_ARG0");
+  const char* a1 = getenv("HEAPPROFD_TESTING_RUN_LIFETIME_ARG1");
+  if (a0 == nullptr)
+    return;
+  uint64_t arg0 = a0 ? base::StringToUInt64(a0).value() : 0;
+  uint64_t arg1 = a0 ? base::StringToUInt64(a1).value() : 0;
+
+  PERFETTO_CHECK(arg1);
+
+  static std::atomic<bool> initialized{false};
+  static std::atomic<bool> disabled{false};
+  static std::atomic<uint64_t> sampling_interval;
+
+  auto enabled_callback = [](void*,
+                             const AHeapProfileEnableCallbackInfo* info) {
+    sampling_interval =
+        AHeapProfileEnableCallbackInfo_getSamplingInterval(info);
+    initialized = true;
+  };
+  auto disabled_callback = [](void*, const AHeapProfileDisableCallbackInfo*) {
+    disabled = true;
+  };
+  static uint32_t heap_id =
+      AHeapProfile_registerHeap(AHeapInfo_setDisabledCallback(
+          AHeapInfo_setEnabledCallback(AHeapInfo_create("test"),
+                                       enabled_callback, nullptr),
+          disabled_callback, nullptr));
+
+  ChildFinishHandshake();
+
+  // heapprofd_client needs malloc to see the signal.
+  while (!initialized)
+    AllocateAndFree(1);
+
+  if (sampling_interval.load() != arg0) {
+    PERFETTO_FATAL("%" PRIu64 " != %" PRIu64, sampling_interval.load(), arg0);
+  }
+
+  while (!disabled)
+    AHeapProfile_reportFree(heap_id, 0x2);
+
+  char x = 'x';
+  PERFETTO_CHECK(base::WriteAll(static_cast<int>(arg1), &x, sizeof(x)) == 1);
+  close(static_cast<int>(arg1));
+
+  // Wait around so we can verify it didn't crash.
+  for (;;) {
+  }
+}
+
 std::unique_ptr<TestHelper> GetHelper(base::TestTaskRunner* task_runner) {
   std::unique_ptr<TestHelper> helper(new TestHelper(task_runner));
   helper->StartServiceIfRequired();
@@ -728,7 +815,7 @@
   ValidateSampleSizes(helper.get(), pid, kAllocSize, allocator_name());
 }
 
-TEST_P(HeapprofdEndToEnd, AccurateCustom) {
+TEST_P(HeapprofdEndToEnd, AccurateCustomReportAllocation) {
   if (allocator_mode() != AllocatorMode::kCustom)
     GTEST_SKIP();
 
@@ -768,6 +855,46 @@
   EXPECT_EQ(total_freed, 25u);
 }
 
+TEST_P(HeapprofdEndToEnd, AccurateCustomReportSample) {
+  if (allocator_mode() != AllocatorMode::kCustom)
+    GTEST_SKIP();
+
+  base::Subprocess child({"/proc/self/exe"});
+  child.args.posix_argv0_override_for_testing = "heapprofd_continuous_malloc";
+  child.args.stdout_mode = base::Subprocess::kDevNull;
+  child.args.stderr_mode = base::Subprocess::kDevNull;
+  child.args.env.push_back("HEAPPROFD_TESTING_RUN_ACCURATE_SAMPLE=1");
+  StartAndWaitForHandshake(&child);
+
+  const uint64_t pid = static_cast<uint64_t>(child.pid());
+
+  TraceConfig trace_config = MakeTraceConfig([pid](HeapprofdConfig* cfg) {
+    cfg->set_sampling_interval_bytes(1000000);
+    cfg->add_pid(pid);
+    cfg->add_heaps("test");
+  });
+
+  auto helper = Trace(trace_config);
+  WRITE_TRACE(helper->full_trace());
+  PrintStats(helper.get());
+  KillAssertRunning(&child);
+
+  ValidateOnlyPID(helper.get(), pid);
+
+  size_t total_alloc = 0;
+  size_t total_freed = 0;
+  for (const protos::gen::TracePacket& packet : helper->trace()) {
+    for (const auto& dump : packet.profile_packet().process_dumps()) {
+      for (const auto& sample : dump.samples()) {
+        total_alloc += sample.self_allocated();
+        total_freed += sample.self_freed();
+      }
+    }
+  }
+  EXPECT_EQ(total_alloc, 40u);
+  EXPECT_EQ(total_freed, 25u);
+}
+
 TEST_P(HeapprofdEndToEnd, AccurateDumpAtMaxCustom) {
   if (allocator_mode() != AllocatorMode::kCustom)
     GTEST_SKIP();
@@ -809,6 +936,47 @@
   EXPECT_EQ(total_count, 2u);
 }
 
+TEST_P(HeapprofdEndToEnd, CustomLifetime) {
+  if (allocator_mode() != AllocatorMode::kCustom)
+    GTEST_SKIP();
+
+  int disabled_pipe[2];
+  PERFETTO_CHECK(pipe(disabled_pipe) == 0);  // NOLINT(android-cloexec-pipe)
+
+  int disabled_pipe_rd = disabled_pipe[0];
+  int disabled_pipe_wr = disabled_pipe[1];
+
+  base::Subprocess child({"/proc/self/exe"});
+  child.args.posix_argv0_override_for_testing = "heapprofd_continuous_malloc";
+  child.args.stdout_mode = base::Subprocess::kDevNull;
+  child.args.stderr_mode = base::Subprocess::kDevNull;
+  child.args.env.push_back("HEAPPROFD_TESTING_RUN_LIFETIME_ARG0=1000000");
+  child.args.env.push_back("HEAPPROFD_TESTING_RUN_LIFETIME_ARG1=" +
+                           std::to_string(disabled_pipe_wr));
+  child.args.preserve_fds.push_back(disabled_pipe_wr);
+  StartAndWaitForHandshake(&child);
+  close(disabled_pipe_wr);
+
+  const uint64_t pid = static_cast<uint64_t>(child.pid());
+
+  TraceConfig trace_config = MakeTraceConfig([pid](HeapprofdConfig* cfg) {
+    cfg->set_sampling_interval_bytes(1000000);
+    cfg->add_pid(pid);
+    cfg->add_heaps("test");
+  });
+
+  auto helper = Trace(trace_config);
+  WRITE_TRACE(helper->full_trace());
+  PrintStats(helper.get());
+  // Give client some time to notice the disconnect.
+  sleep(2);
+  KillAssertRunning(&child);
+
+  char x;
+  EXPECT_EQ(base::Read(disabled_pipe_rd, &x, sizeof(x)), 1);
+  close(disabled_pipe_rd);
+}
+
 TEST_P(HeapprofdEndToEnd, TwoProcesses) {
   constexpr size_t kAllocSize = 1024;
   constexpr size_t kAllocSize2 = 7;
diff --git a/src/profiling/memory/heapprofd_standalone_client_example.cc b/src/profiling/memory/heapprofd_standalone_client_example.cc
index 5e18f4b..5032b4c 100644
--- a/src/profiling/memory/heapprofd_standalone_client_example.cc
+++ b/src/profiling/memory/heapprofd_standalone_client_example.cc
@@ -18,10 +18,18 @@
 
 #include <unistd.h>
 
+namespace {
+
+void OtherFn(uint32_t heap_id, uint64_t i) {
+  AHeapProfile_reportAllocation(heap_id, i, i);
+}
+
+}  // namespace
+
 int main(int, char**) {
   uint32_t heap_id = AHeapProfile_registerHeap(AHeapInfo_create("test"));
   for (uint64_t i = 0; i < 100000; ++i) {
-    AHeapProfile_reportAllocation(heap_id, i, i);
+    OtherFn(heap_id, i);
     sleep(1);
   }
 }
diff --git a/src/profiling/memory/malloc_hooks.cc b/src/profiling/memory/malloc_hooks.cc
index 6ba7e79..132f3a8 100644
--- a/src/profiling/memory/malloc_hooks.cc
+++ b/src/profiling/memory/malloc_hooks.cc
@@ -24,6 +24,7 @@
 #include "perfetto/base/logging.h"
 #include "perfetto/ext/base/utils.h"
 #include "perfetto/profiling/memory/heap_profile.h"
+#include "src/profiling/memory/heap_profile_internal.h"
 
 #include "src/profiling/memory/wrap_allocators.h"
 
diff --git a/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc b/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc
index 462c451..ae32ff7 100644
--- a/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc
+++ b/src/trace_processor/importers/proto/proto_trace_parser_unittest.cc
@@ -75,6 +75,7 @@
 using ::testing::_;
 using ::testing::Args;
 using ::testing::AtLeast;
+using ::testing::DoAll;
 using ::testing::ElementsAreArray;
 using ::testing::Eq;
 using ::testing::HasSubstr;
diff --git a/test/gtest_logcat_printer.cc b/test/gtest_logcat_printer.cc
index f88e01b..a78fa88 100644
--- a/test/gtest_logcat_printer.cc
+++ b/test/gtest_logcat_printer.cc
@@ -58,8 +58,14 @@
 void LogcatPrinter::OnTestEnd(const testing::TestInfo& test_info) {
   const auto* result = test_info.result();
   const char* state = "N/A";
-  if (result)
-    state = result->Passed() ? "PASS" : "FAIL";
+  if (result) {
+    if (result->Passed())
+      state = "PASS";
+    else if (result->Skipped())
+      state = "SKIPPED";
+    else if (result->Failed())
+      state = "FAIL";
+  }
   PERFETTO_TEST_LOG("Test end: %s.%s [%s]", test_info.test_case_name(),
                     test_info.name(), state);
 }
diff --git a/test/stress_test/stress_test.cc b/test/stress_test/stress_test.cc
index ac53bd3..4fa0d2d 100644
--- a/test/stress_test/stress_test.cc
+++ b/test/stress_test/stress_test.cc
@@ -14,10 +14,7 @@
  * limitations under the License.
  */
 
-#include <signal.h>
 #include <stdarg.h>
-#include <sys/mman.h>
-#include <sys/stat.h>
 
 #include <chrono>
 #include <list>
@@ -28,7 +25,9 @@
 #include <thread>
 #include <vector>
 
+#include "perfetto/base/build_config.h"
 #include "perfetto/base/compiler.h"
+#include "perfetto/ext/base/ctrl_c_handler.h"
 #include "perfetto/ext/base/file_utils.h"
 #include "perfetto/ext/base/scoped_file.h"
 #include "perfetto/ext/base/subprocess.h"
@@ -44,6 +43,12 @@
 #include "protos/perfetto/trace/test_event.pbzero.h"
 #include "protos/perfetto/trace/trace_packet.pbzero.h"
 
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+#include <Windows.h>
+#else
+#include <signal.h>
+#endif
+
 // Generated by gen_configs_blob.py. It defines the kStressTestConfigs array,
 // which contains a proto-encoded StressTestConfig message for each .cfg file
 // listed in /test/stress_test/configs/BUILD.gn.
@@ -52,6 +57,19 @@
 namespace perfetto {
 namespace {
 
+// TODO(primiano): We need a base::File to get around the awkwardness of
+// files on Windows being a mix of int and HANDLE (and open() vs CreateFile())
+// in our codebase.
+base::ScopedPlatformHandle OpenLogFile(const std::string& path) {
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+  return base::ScopedPlatformHandle(::CreateFileA(
+      path.c_str(), GENERIC_READ | GENERIC_WRITE,
+      FILE_SHARE_DELETE | FILE_SHARE_READ, nullptr, CREATE_ALWAYS, 0, nullptr));
+#else
+  return base::OpenFile(path, O_RDWR | O_CREAT | O_TRUNC, 0644);
+#endif
+}
+
 using StressTestConfig = protos::gen::StressTestConfig;
 
 struct SigHandlerCtx {
@@ -105,7 +123,11 @@
 
 TestHarness::TestHarness() {
   results_dir_ = base::GetSysTempDir() + "/perfetto-stress-test";
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+  system(("rmdir \"" + results_dir_ + "\" /s /q").c_str());
+#else
   system(("rm -r -- \"" + results_dir_ + "\"").c_str());
+#endif
   PERFETTO_CHECK(base::Mkdir(results_dir_));
   PERFETTO_LOG("Saving test results in %s", results_dir_.c_str());
 }
@@ -125,7 +147,8 @@
     log_msg[res++] = '\n';
     log_msg[res++] = '\0';
   }
-  base::ignore_result(write(*error_log_, log_msg, static_cast<size_t>(res)));
+
+  base::WriteAll(*error_log_, log_msg, static_cast<size_t>(res));
 }
 
 void TestHarness::RunConfig(const char* cfg_name,
@@ -154,8 +177,7 @@
   base::Subprocess traced({bin_dir + "/traced"});
   traced.args.env = env_;
   if (!verbose) {
-    traced.args.out_fd = base::OpenFile(result_dir + "/traced.log",
-                                        O_RDWR | O_CREAT | O_TRUNC, 0644);
+    traced.args.out_fd = OpenLogFile(result_dir + "/traced.log");
     traced.args.stderr_mode = traced.args.stdout_mode = base::Subprocess::kFd;
   }
   traced.Start();
@@ -171,8 +193,7 @@
     producer.args.input = cfg.SerializeAsString();
     if (!verbose) {
       producer.args.out_fd =
-          base::OpenFile(result_dir + "/producer." + std::to_string(i) + ".log",
-                         O_RDWR | O_CREAT | O_TRUNC, 0644);
+          OpenLogFile(result_dir + "/producer." + std::to_string(i) + ".log");
       producer.args.stderr_mode = producer.args.stdout_mode =
           base::Subprocess::kFd;
     }
@@ -190,12 +211,11 @@
   consumer.args.env = env_;
   consumer.args.input = cfg.trace_config().SerializeAsString();
   if (!verbose) {
-    consumer.args.out_fd = base::OpenFile(result_dir + "/perfetto.log",
-                                          O_RDWR | O_CREAT | O_TRUNC, 0644);
+    consumer.args.out_fd = OpenLogFile(result_dir + "/perfetto.log");
     consumer.args.stderr_mode = consumer.args.stdout_mode =
         base::Subprocess::kFd;
   }
-  unlink(trace_file_path.c_str());
+  remove(trace_file_path.c_str());
   consumer.Start();
   int64_t t_start = base::GetBootTimeNs().count();
   g_sig->pids_to_kill.emplace_back(consumer.pid());
@@ -209,8 +229,6 @@
     consumer.KillAndWaitForTermination();
   }
 
-  // Stop
-  consumer.KillAndWaitForTermination(SIGTERM);
   int64_t t_end = base::GetBootTimeNs().count();
 
   for (auto& producer : producers) {
@@ -260,12 +278,14 @@
   if (!fd)
     return AddFailure("Trace file does not exist");
   const off_t file_size = lseek(*fd, 0, SEEK_END);
+  lseek(*fd, 0, SEEK_SET);
   if (file_size <= 0)
     return AddFailure("Trace file is empty");
+
   test_result.trace_size_kb = static_cast<uint32_t>(file_size / 1000);
-  const uint8_t* const start = static_cast<const uint8_t*>(mmap(
-      nullptr, static_cast<size_t>(file_size), PROT_READ, MAP_PRIVATE, *fd, 0));
-  PERFETTO_CHECK(start != MAP_FAILED);
+  std::string trace_data;
+  PERFETTO_CHECK(base::ReadFileDescriptor(*fd, &trace_data));
+  const auto* const start = reinterpret_cast<const uint8_t*>(trace_data.data());
   const uint8_t* const end = start + file_size;
 
   constexpr uint8_t kTracePacketTag = MakeTagLengthDelimited(1);
@@ -401,11 +421,17 @@
   }
 }
 
-void CtrlCHandler(int) {
+void CtrlCHandler() {
   g_sig->aborted.store(true);
   for (auto it = g_sig->pids_to_kill.rbegin(); it != g_sig->pids_to_kill.rend();
        it++) {
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+    base::ScopedPlatformHandle proc_handle(
+        ::OpenProcess(PROCESS_TERMINATE, false, *it));
+    ::TerminateProcess(*proc_handle, STATUS_CONTROL_C_EXIT);
+#else
     kill(*it, SIGKILL);
+#endif
   }
 }
 
@@ -425,7 +451,7 @@
   }
 
   g_sig = new SigHandlerCtx();
-  signal(SIGINT, CtrlCHandler);
+  base::InstallCtrCHandler(&CtrlCHandler);
 
   for (size_t i = 0; i < base::ArraySize(kStressTestConfigs) && !g_sig->aborted;
        ++i) {
diff --git a/test/trace_processor/profiling/index b/test/trace_processor/profiling/index
index d216ac5..ddb7ab5 100644
--- a/test/trace_processor/profiling/index
+++ b/test/trace_processor/profiling/index
@@ -45,3 +45,7 @@
 
 # perf_sample table (traced_perf trace as an input).
 ../../data/perf_sample.pb perf_sample.sql perf_sample_perf_sample.out
+
+# this uses llvm-symbolizer to test the offline symbolization built into
+# trace_processor_shell.
+../../data/heapprofd_standalone_client_example-trace stack_profile_symbols.sql stack_profile_symbols.out
diff --git a/test/trace_processor/profiling/stack_profile_symbols.out b/test/trace_processor/profiling/stack_profile_symbols.out
new file mode 100644
index 0000000..64e7397
--- /dev/null
+++ b/test/trace_processor/profiling/stack_profile_symbols.out
@@ -0,0 +1,5 @@
+"name","source_file","line_number"
+"[NULL]","[NULL]",0
+"_start","??",0
+"(anonymous namespace)::OtherFn(unsigned int, unsigned long)","/builds/master/experiment/external/perfetto/out/linux_clang_release/../../src/profiling/memory/heapprofd_standalone_client_example.cc",24
+"main","/builds/master/experiment/external/perfetto/out/linux_clang_release/../../src/profiling/memory/heapprofd_standalone_client_example.cc",32
diff --git a/test/trace_processor/profiling/stack_profile_symbols.sql b/test/trace_processor/profiling/stack_profile_symbols.sql
new file mode 100644
index 0000000..e57f84d
--- /dev/null
+++ b/test/trace_processor/profiling/stack_profile_symbols.sql
@@ -0,0 +1 @@
+select name, source_file, line_number from stack_profile_symbol
diff --git a/tools/diff_test_trace_processor.py b/tools/diff_test_trace_processor.py
index f7a5dd6..ccc3119 100755
--- a/tools/diff_test_trace_processor.py
+++ b/tools/diff_test_trace_processor.py
@@ -35,6 +35,18 @@
 from proto_utils import create_message_factory, serialize_textproto_trace, serialize_python_trace
 
 ROOT_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
+ENV = {
+    'PERFETTO_BINARY_PATH': os.path.join(ROOT_DIR, 'test', 'data'),
+}
+if sys.platform.startswith('linux'):
+  ENV['PATH'] = os.path.join(ROOT_DIR, 'buildtools', 'linux64', 'clang', 'bin')
+elif sys.platform.startswith('dawin'):
+  # Sadly, on macOS we need to check out the Android deps to get
+  # llvm symbolizer.
+  ENV['PATH'] = os.path.join(ROOT_DIR, 'buildtools', 'ndk', 'toolchains',
+                             'llvm', 'prebuilt', 'darwin-x86_64', 'bin')
+elif sys.platform.startswith('win32'):
+  ENV['PATH'] = os.path.join(ROOT_DIR, 'buildtools', 'win', 'clang', 'bin')
 
 
 class Test(object):
@@ -100,7 +112,8 @@
       perf_path,
       gen_trace_path,
   ]
-  tp = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+  tp = subprocess.Popen(
+      cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=ENV)
   (stdout, stderr) = tp.communicate()
 
   if json_output:
@@ -139,7 +152,8 @@
       gen_trace_path,
   ]
 
-  tp = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
+  tp = subprocess.Popen(
+      cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE, env=ENV)
   (stdout, stderr) = tp.communicate()
   return TestResult('query', query_path, gen_trace_path, cmd, expected,
                     stdout.decode('utf8'), stderr.decode('utf8'), tp.returncode)
diff --git a/tools/install-build-deps b/tools/install-build-deps
index f16263a..d56bba9 100755
--- a/tools/install-build-deps
+++ b/tools/install-build-deps
@@ -218,8 +218,8 @@
     # Example traces for regression tests.
     Dependency(
         'test/data.zip',
-        'https://storage.googleapis.com/perfetto/test-data-20201221-112454.zip',
-        'bdb45847b3bfc3f12f10be69e669187e114944ca1ea386a455b0f31d3b1b2c1c',
+        'https://storage.googleapis.com/perfetto/test-data-20210105-174747.zip',
+        'e4fe27c8aad5bf0c447b2e853302d5431d0f134e368b73b68e5f56a293945d52',
         'all',
     ),
 
diff --git a/ui/src/frontend/keyboard_event_handler.ts b/ui/src/frontend/keyboard_event_handler.ts
index 064994d..2b0f12d 100644
--- a/ui/src/frontend/keyboard_event_handler.ts
+++ b/ui/src/frontend/keyboard_event_handler.ts
@@ -24,6 +24,7 @@
 } from './scroll_helper';
 import {executeSearch} from './search_handler';
 
+const INSTANT_FOCUS_DURATION_S = 1 / 100;
 type Direction = 'Forward'|'Backward';
 
 // Handles all key events than are not handled by the
@@ -174,6 +175,10 @@
       if (slice.ts && slice.dur) {
         startTs = slice.ts + globals.state.traceTime.startSec;
         endTs = startTs + slice.dur;
+      } else if (slice.ts) {
+        startTs = slice.ts - INSTANT_FOCUS_DURATION_S / 2 +
+            globals.state.traceTime.startSec;
+        endTs = startTs + INSTANT_FOCUS_DURATION_S;
       }
     } else if (selection.kind === 'THREAD_STATE') {
       const threadState = globals.threadStateDetails;