Merge "TraceProcessor: add /websocket endpoint"
diff --git a/Android.bp b/Android.bp
index afc122e..3cb4471 100644
--- a/Android.bp
+++ b/Android.bp
@@ -92,6 +92,7 @@
         ":perfetto_src_tracing_common",
         ":perfetto_src_tracing_core_core",
         ":perfetto_src_tracing_ipc_common",
+        ":perfetto_src_tracing_ipc_default_socket",
         ":perfetto_src_tracing_ipc_producer_producer",
         "src/profiling/memory/main.cc",
     ],
@@ -347,6 +348,7 @@
         ":perfetto_src_tracing_common",
         ":perfetto_src_tracing_core_core",
         ":perfetto_src_tracing_ipc_common",
+        ":perfetto_src_tracing_ipc_default_socket",
         ":perfetto_src_tracing_ipc_producer_producer",
     ],
     shared_libs: [
@@ -548,6 +550,7 @@
         ":perfetto_src_tracing_core_service",
         ":perfetto_src_tracing_ipc_common",
         ":perfetto_src_tracing_ipc_consumer_consumer",
+        ":perfetto_src_tracing_ipc_default_socket",
         ":perfetto_src_tracing_ipc_producer_producer",
         ":perfetto_src_tracing_ipc_service_service",
     ],
@@ -732,6 +735,7 @@
         ":perfetto_src_tracing_in_process_backend",
         ":perfetto_src_tracing_ipc_common",
         ":perfetto_src_tracing_ipc_consumer_consumer",
+        ":perfetto_src_tracing_ipc_default_socket",
         ":perfetto_src_tracing_ipc_producer_producer",
         ":perfetto_src_tracing_ipc_service_service",
         ":perfetto_src_tracing_platform_impl",
@@ -924,6 +928,7 @@
         ":perfetto_src_tracing_core_core",
         ":perfetto_src_tracing_ipc_common",
         ":perfetto_src_tracing_ipc_consumer_consumer",
+        ":perfetto_src_tracing_ipc_default_socket",
         ":perfetto_src_tracing_ipc_producer_producer",
         "src/perfetto_cmd/main.cc",
     ],
@@ -1099,6 +1104,7 @@
         ":perfetto_src_tracing_core_service",
         ":perfetto_src_tracing_ipc_common",
         ":perfetto_src_tracing_ipc_consumer_consumer",
+        ":perfetto_src_tracing_ipc_default_socket",
         ":perfetto_src_tracing_ipc_producer_producer",
         ":perfetto_src_tracing_ipc_service_service",
         ":perfetto_test_end_to_end_integrationtests",
@@ -1355,6 +1361,7 @@
         ":perfetto_src_tracing_core_service",
         ":perfetto_src_tracing_ipc_common",
         ":perfetto_src_tracing_ipc_consumer_consumer",
+        ":perfetto_src_tracing_ipc_default_socket",
         ":perfetto_src_tracing_ipc_producer_producer",
         ":perfetto_src_tracing_ipc_service_service",
         ":perfetto_test_test_helper",
@@ -1796,6 +1803,7 @@
         ":perfetto_src_tracing_in_process_backend",
         ":perfetto_src_tracing_ipc_common",
         ":perfetto_src_tracing_ipc_consumer_consumer",
+        ":perfetto_src_tracing_ipc_default_socket",
         ":perfetto_src_tracing_ipc_producer_producer",
         ":perfetto_src_tracing_ipc_service_service",
         ":perfetto_src_tracing_platform_impl",
@@ -8923,7 +8931,6 @@
 filegroup {
     name: "perfetto_src_tracing_ipc_common",
     srcs: [
-        "src/tracing/ipc/default_socket.cc",
         "src/tracing/ipc/memfd.cc",
         "src/tracing/ipc/posix_shared_memory.cc",
         "src/tracing/ipc/shared_memory_windows.cc",
@@ -8938,6 +8945,14 @@
     ],
 }
 
+// GN: //src/tracing/ipc:default_socket
+filegroup {
+    name: "perfetto_src_tracing_ipc_default_socket",
+    srcs: [
+        "src/tracing/ipc/default_socket.cc",
+    ],
+}
+
 // GN: //src/tracing/ipc/producer:producer
 filegroup {
     name: "perfetto_src_tracing_ipc_producer_producer",
@@ -9463,6 +9478,7 @@
         ":perfetto_src_tracing_core_unittests",
         ":perfetto_src_tracing_ipc_common",
         ":perfetto_src_tracing_ipc_consumer_consumer",
+        ":perfetto_src_tracing_ipc_default_socket",
         ":perfetto_src_tracing_ipc_producer_producer",
         ":perfetto_src_tracing_ipc_service_service",
         ":perfetto_src_tracing_ipc_unittests",
@@ -10037,6 +10053,7 @@
         ":perfetto_src_tracing_core_core",
         ":perfetto_src_tracing_core_service",
         ":perfetto_src_tracing_ipc_common",
+        ":perfetto_src_tracing_ipc_default_socket",
         ":perfetto_src_tracing_ipc_producer_producer",
         "src/profiling/perf/main.cc",
     ],
@@ -10201,6 +10218,7 @@
         ":perfetto_src_tracing_common",
         ":perfetto_src_tracing_core_core",
         ":perfetto_src_tracing_ipc_common",
+        ":perfetto_src_tracing_ipc_default_socket",
         ":perfetto_src_tracing_ipc_producer_producer",
         "src/perfetto_cmd/trigger_perfetto_main.cc",
     ],
diff --git a/BUILD b/BUILD
index a0d0884..de1511e 100644
--- a/BUILD
+++ b/BUILD
@@ -244,6 +244,7 @@
         ":src_tracing_core_service",
         ":src_tracing_ipc_common",
         ":src_tracing_ipc_consumer_consumer",
+        ":src_tracing_ipc_default_socket",
         ":src_tracing_ipc_producer_producer",
         ":src_tracing_ipc_service_service",
     ],
@@ -1810,7 +1811,6 @@
 perfetto_filegroup(
     name = "src_tracing_ipc_common",
     srcs = [
-        "src/tracing/ipc/default_socket.cc",
         "src/tracing/ipc/memfd.cc",
         "src/tracing/ipc/memfd.h",
         "src/tracing/ipc/posix_shared_memory.cc",
@@ -1820,6 +1820,14 @@
     ],
 )
 
+# GN target: //src/tracing/ipc:default_socket
+perfetto_filegroup(
+    name = "src_tracing_ipc_default_socket",
+    srcs = [
+        "src/tracing/ipc/default_socket.cc",
+    ],
+)
+
 # GN target: //src/tracing:client_api_without_backends
 perfetto_filegroup(
     name = "src_tracing_client_api_without_backends",
@@ -3559,6 +3567,7 @@
         ":src_tracing_in_process_backend",
         ":src_tracing_ipc_common",
         ":src_tracing_ipc_consumer_consumer",
+        ":src_tracing_ipc_default_socket",
         ":src_tracing_ipc_producer_producer",
         ":src_tracing_ipc_service_service",
         ":src_tracing_platform_impl",
@@ -3651,6 +3660,7 @@
         ":src_tracing_core_core",
         ":src_tracing_ipc_common",
         ":src_tracing_ipc_consumer_consumer",
+        ":src_tracing_ipc_default_socket",
         ":src_tracing_ipc_producer_producer",
         "src/perfetto_cmd/main.cc",
     ],
diff --git a/BUILD.gn b/BUILD.gn
index fe44ed2..b36d588 100644
--- a/BUILD.gn
+++ b/BUILD.gn
@@ -68,7 +68,10 @@
 }
 
 if (enable_perfetto_tools) {
-  all_targets += [ "tools" ]
+  all_targets += [
+    "tools",
+    "src/websocket_bridge",
+  ]
 }
 
 if (enable_perfetto_unittests) {
diff --git a/CHANGELOG b/CHANGELOG
index e316684..3bf07a9 100644
--- a/CHANGELOG
+++ b/CHANGELOG
@@ -4,6 +4,9 @@
     * Changed compiler flags. Assume recent x64 CPUs (-msse4.2 -mavx -mpopcnt).
       This behavior affects only standalone builds and can be changed by setting
       enable_perfetto_x64_cpu_opt=false in the GN args.
+    * The java heap profiler now rescans all the processes every time for
+      continous_dump_config. The scan_pids_only_on_start can be used to restore
+      the old behavior.
   Trace Processor:
     * Changed LIKE comparisions to be case-senstive. This may break existing
       queries but was a necessary from a performance perspective.
diff --git a/protos/perfetto/config/perfetto_config.proto b/protos/perfetto/config/perfetto_config.proto
index 3e1caf8..52f256a 100644
--- a/protos/perfetto/config/perfetto_config.proto
+++ b/protos/perfetto/config/perfetto_config.proto
@@ -785,6 +785,13 @@
     optional uint32 dump_phase_ms = 1;
     // ms to wait between following dumps.
     optional uint32 dump_interval_ms = 2;
+    // If true, scans all the processes to find `process_cmdline` and filter by
+    // `min_anonymous_memory_kb` only at data source start. Default on Android
+    // S-.
+    //
+    // If false, rescans all the processes to find on every dump. Default on
+    // Android T+.
+    optional bool scan_pids_only_on_start = 3;
   }
 
   // This input is normalized in the following way: if it contains slashes,
diff --git a/protos/perfetto/config/profiling/java_hprof_config.proto b/protos/perfetto/config/profiling/java_hprof_config.proto
index d504678..9245ee6 100644
--- a/protos/perfetto/config/profiling/java_hprof_config.proto
+++ b/protos/perfetto/config/profiling/java_hprof_config.proto
@@ -27,6 +27,13 @@
     optional uint32 dump_phase_ms = 1;
     // ms to wait between following dumps.
     optional uint32 dump_interval_ms = 2;
+    // If true, scans all the processes to find `process_cmdline` and filter by
+    // `min_anonymous_memory_kb` only at data source start. Default on Android
+    // S-.
+    //
+    // If false, rescans all the processes to find on every dump. Default on
+    // Android T+.
+    optional bool scan_pids_only_on_start = 3;
   }
 
   // This input is normalized in the following way: if it contains slashes,
diff --git a/protos/perfetto/trace/perfetto_trace.proto b/protos/perfetto/trace/perfetto_trace.proto
index f8f8f94..c4eb417 100644
--- a/protos/perfetto/trace/perfetto_trace.proto
+++ b/protos/perfetto/trace/perfetto_trace.proto
@@ -785,6 +785,13 @@
     optional uint32 dump_phase_ms = 1;
     // ms to wait between following dumps.
     optional uint32 dump_interval_ms = 2;
+    // If true, scans all the processes to find `process_cmdline` and filter by
+    // `min_anonymous_memory_kb` only at data source start. Default on Android
+    // S-.
+    //
+    // If false, rescans all the processes to find on every dump. Default on
+    // Android T+.
+    optional bool scan_pids_only_on_start = 3;
   }
 
   // This input is normalized in the following way: if it contains slashes,
diff --git a/src/profiling/memory/java_hprof_producer.cc b/src/profiling/memory/java_hprof_producer.cc
index aef9b5b..6c68995 100644
--- a/src/profiling/memory/java_hprof_producer.cc
+++ b/src/profiling/memory/java_hprof_producer.cc
@@ -40,8 +40,11 @@
   auto it = data_sources_.find(id);
   if (it == data_sources_.end())
     return;
-  const DataSource& ds = it->second;
-  SignalDataSource(ds);
+  DataSource& ds = it->second;
+  if (!ds.config().continuous_dump_config().scan_pids_only_on_start()) {
+    ds.CollectPids();
+  }
+  ds.SendSignal();
   auto weak_producer = weak_factory_.GetWeakPtr();
   task_runner_->PostDelayedTask(
       [weak_producer, id, dump_interval] {
@@ -52,10 +55,16 @@
       dump_interval);
 }
 
-// static
-void JavaHprofProducer::SignalDataSource(const DataSource& ds) {
-  const std::set<pid_t>& pids = ds.pids;
-  for (pid_t pid : pids) {
+JavaHprofProducer::DataSource::DataSource(
+    DataSourceConfig ds_config,
+    JavaHprofConfig config,
+    std::vector<std::string> normalized_cmdlines)
+    : ds_config_(ds_config),
+      config_(config),
+      normalized_cmdlines_(normalized_cmdlines) {}
+
+void JavaHprofProducer::DataSource::SendSignal() const {
+  for (pid_t pid : pids_) {
     auto opt_status = ReadStatus(pid);
     if (!opt_status) {
       PERFETTO_PLOG("Failed to read /proc/%d/status. Not signalling.", pid);
@@ -69,23 +78,32 @@
           pid);
       continue;
     }
-    if (!CanProfile(ds.ds_config, uids->effective,
-                    ds.config.target_installed_by())) {
+    if (!CanProfile(ds_config_, uids->effective,
+                    config_.target_installed_by())) {
       PERFETTO_ELOG("%d (UID %" PRIu64 ") not profileable.", pid,
                     uids->effective);
       continue;
     }
     PERFETTO_DLOG("Sending %d to %d", kJavaHeapprofdSignal, pid);
     union sigval signal_value;
-    signal_value.sival_int =
-        static_cast<int32_t>(ds.ds_config.tracing_session_id() %
-                             std::numeric_limits<int32_t>::max());
+    signal_value.sival_int = static_cast<int32_t>(
+        ds_config_.tracing_session_id() % std::numeric_limits<int32_t>::max());
     if (sigqueue(pid, kJavaHeapprofdSignal, signal_value) != 0) {
       PERFETTO_DPLOG("sigqueue");
     }
   }
 }
 
+void JavaHprofProducer::DataSource::CollectPids() {
+  pids_.clear();
+  for (uint64_t pid : config_.pid()) {
+    pids_.emplace(static_cast<pid_t>(pid));
+  }
+  FindPidsForCmdlines(normalized_cmdlines_, &pids_);
+  if (config_.min_anonymous_memory_kb() > 0)
+    RemoveUnderAnonThreshold(config_.min_anonymous_memory_kb(), &pids_);
+}
+
 void JavaHprofProducer::IncreaseConnectionBackoff() {
   connection_backoff_ms_ *= 2;
   if (connection_backoff_ms_ > kMaxConnectionBackoffMs)
@@ -104,23 +122,15 @@
   }
   JavaHprofConfig config;
   config.ParseFromString(ds_config.java_hprof_config_raw());
-  DataSource ds;
-  ds.id = id;
-  for (uint64_t pid : config.pid())
-    ds.pids.emplace(static_cast<pid_t>(pid));
   base::Optional<std::vector<std::string>> normalized_cmdlines =
       NormalizeCmdlines(config.process_cmdline());
   if (!normalized_cmdlines.has_value()) {
     PERFETTO_ELOG("Rejecting data source due to invalid cmdline in config.");
     return;
   }
-  FindPidsForCmdlines(normalized_cmdlines.value(), &ds.pids);
-  if (config.min_anonymous_memory_kb() > 0)
-    RemoveUnderAnonThreshold(config.min_anonymous_memory_kb(), &ds.pids);
-
-  ds.config = std::move(config);
-  ds.ds_config = std::move(ds_config);
-  data_sources_.emplace(id, std::move(ds));
+  DataSource ds(ds_config, std::move(config), std::move(*normalized_cmdlines));
+  ds.CollectPids();
+  data_sources_.emplace(id, ds);
 }
 
 void JavaHprofProducer::StartDataSource(DataSourceInstanceID id,
@@ -131,7 +141,7 @@
     return;
   }
   const DataSource& ds = it->second;
-  const auto continuous_dump_config = ds.config.continuous_dump_config();
+  const auto& continuous_dump_config = ds.config().continuous_dump_config();
   uint32_t dump_interval = continuous_dump_config.dump_interval_ms();
   if (dump_interval) {
     auto weak_producer = weak_factory_.GetWeakPtr();
@@ -143,7 +153,7 @@
         },
         continuous_dump_config.dump_phase_ms());
   }
-  SignalDataSource(ds);
+  ds.SendSignal();
 }
 
 void JavaHprofProducer::StopDataSource(DataSourceInstanceID id) {
diff --git a/src/profiling/memory/java_hprof_producer.h b/src/profiling/memory/java_hprof_producer.h
index 97ac07d..056c60a 100644
--- a/src/profiling/memory/java_hprof_producer.h
+++ b/src/profiling/memory/java_hprof_producer.h
@@ -69,11 +69,23 @@
     kConnected,
   };
 
-  struct DataSource {
-    DataSourceInstanceID id;
-    std::set<pid_t> pids;
-    JavaHprofConfig config;
-    DataSourceConfig ds_config;
+  class DataSource {
+   public:
+    DataSource(DataSourceConfig ds_config,
+               JavaHprofConfig config,
+               std::vector<std::string> normalized_cmdlines);
+    void CollectPids();
+    void SendSignal() const;
+
+    const JavaHprofConfig& config() const { return config_; }
+    const DataSourceConfig& ds_config() const { return ds_config_; }
+
+   private:
+    DataSourceConfig ds_config_;
+    JavaHprofConfig config_;
+    std::vector<std::string> normalized_cmdlines_;
+
+    std::set<pid_t> pids_;
   };
 
   void ConnectService();
@@ -82,7 +94,6 @@
   void IncreaseConnectionBackoff();
 
   void DoContinuousDump(DataSourceInstanceID id, uint32_t dump_interval);
-  static void SignalDataSource(const DataSource& ds);
 
   // State of connection to the tracing service.
   State state_ = kNotStarted;
diff --git a/src/tracebox/BUILD.gn b/src/tracebox/BUILD.gn
index 647ad5e..9000732 100644
--- a/src/tracebox/BUILD.gn
+++ b/src/tracebox/BUILD.gn
@@ -24,6 +24,7 @@
     "../perfetto_cmd:trigger_perfetto_cmd",
     "../traced/probes",
     "../traced/service",
+    "../websocket_bridge:lib",
   ]
   sources = [ "tracebox.cc" ]
 }
diff --git a/src/tracebox/tracebox.cc b/src/tracebox/tracebox.cc
index ed75b31..a421b97 100644
--- a/src/tracebox/tracebox.cc
+++ b/src/tracebox/tracebox.cc
@@ -20,6 +20,7 @@
 #include "perfetto/ext/base/utils.h"
 #include "perfetto/ext/traced/traced.h"
 #include "src/perfetto_cmd/perfetto_cmd.h"
+#include "src/websocket_bridge/websocket_bridge.h"
 
 #include <stdio.h>
 
@@ -39,6 +40,7 @@
     {"traced_probes", ProbesMain},
     {"perfetto", PerfettoCmdMain},
     {"trigger_perfetto", TriggerPerfettoMain},
+    {"websocket_bridge", WebsocketBridgeMain},
 };
 
 void PrintUsage() {
diff --git a/src/tracing/ipc/BUILD.gn b/src/tracing/ipc/BUILD.gn
index f86aeb5..dfd0eda 100644
--- a/src/tracing/ipc/BUILD.gn
+++ b/src/tracing/ipc/BUILD.gn
@@ -27,7 +27,6 @@
     "../../../include/perfetto/ext/tracing/ipc",
   ]
   sources = [
-    "default_socket.cc",
     "memfd.cc",
     "memfd.h",
     "posix_shared_memory.cc",
@@ -36,6 +35,7 @@
     "shared_memory_windows.h",
   ]
   deps = [
+    ":default_socket",
     "../../../gn:default_deps",
     "../../../include/perfetto/ext/ipc",
     "../../base",
@@ -43,6 +43,17 @@
   ]
 }
 
+source_set("default_socket") {
+  sources = [ "default_socket.cc" ]
+  public_deps = [ "../../../include/perfetto/ext/tracing/ipc" ]
+  deps = [
+    "../../../gn:default_deps",
+    "../../../include/perfetto/ext/ipc",
+    "../../../include/perfetto/ext/tracing/core",
+    "../../base",
+  ]
+}
+
 perfetto_unittest_source_set("unittests") {
   testonly = true
   deps = [
diff --git a/src/websocket_bridge/BUILD.gn b/src/websocket_bridge/BUILD.gn
new file mode 100644
index 0000000..38f3e66
--- /dev/null
+++ b/src/websocket_bridge/BUILD.gn
@@ -0,0 +1,35 @@
+# 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.
+
+source_set("lib") {
+  deps = [
+    "../../gn:default_deps",
+    "../base",
+    "../base:unix_socket",
+    "../base/http",
+    "../tracing/ipc:default_socket",
+  ]
+  sources = [
+    "websocket_bridge.cc",
+    "websocket_bridge.h",
+  ]
+}
+
+executable("websocket_bridge") {
+  deps = [
+    ":lib",
+    "../../gn:default_deps",
+  ]
+  sources = [ "websocket_bridge_main.cc" ]
+}
diff --git a/src/websocket_bridge/websocket_bridge.cc b/src/websocket_bridge/websocket_bridge.cc
new file mode 100644
index 0000000..eb26506
--- /dev/null
+++ b/src/websocket_bridge/websocket_bridge.cc
@@ -0,0 +1,190 @@
+/*
+ * 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 "src/websocket_bridge/websocket_bridge.h"
+
+#include <stdint.h>
+
+#include <map>
+#include <memory>
+#include <vector>
+
+#include "perfetto/ext/base/http/http_server.h"
+#include "perfetto/ext/base/unix_socket.h"
+#include "perfetto/ext/base/unix_task_runner.h"
+#include "perfetto/ext/tracing/ipc/default_socket.h"
+
+namespace perfetto {
+namespace {
+
+constexpr int kWebsocketPort = 8037;
+
+struct Endpoint {
+  const char* uri;
+  const char* endpoint;
+  base::SockFamily family;
+};
+
+class WSBridge : public base::HttpRequestHandler,
+                 public base::UnixSocket::EventListener {
+ public:
+  void Main(int argc, char** argv);
+
+  // base::HttpRequestHandler implementation.
+  void OnHttpRequest(const base::HttpRequest&) override;
+  void OnWebsocketMessage(const base::WebsocketMessage&) override;
+  void OnHttpConnectionClosed(base::HttpServerConnection*) override;
+
+  // base::UnixSocket::EventListener implementation.
+  void OnNewIncomingConnection(base::UnixSocket*,
+                               std::unique_ptr<base::UnixSocket>) override;
+  void OnConnect(base::UnixSocket*, bool) override;
+  void OnDisconnect(base::UnixSocket*) override;
+  void OnDataAvailable(base::UnixSocket* self) override;
+
+ private:
+  base::HttpServerConnection* GetWebsocket(base::UnixSocket*);
+
+  base::UnixTaskRunner task_runner_;
+  std::vector<Endpoint> endpoints_;
+  std::map<base::HttpServerConnection*, std::unique_ptr<base::UnixSocket>>
+      conns_;
+};
+
+void WSBridge::Main(int, char**) {
+#if PERFETTO_BUILDFLAG(PERFETTO_OS_WIN)
+  // On Windows traced used a TCP socket.
+  const auto kTracedFamily = base::SockFamily::kInet;
+#else
+  const auto kTracedFamily = base::SockFamily::kUnix;
+#endif
+  endpoints_.push_back({"/traced", GetConsumerSocket(), kTracedFamily});
+  endpoints_.push_back({"/adb", "127.0.0.1:5037", base::SockFamily::kInet});
+
+  base::HttpServer srv(&task_runner_, this);
+  srv.AddAllowedOrigin("http://localhost:10000");
+  srv.AddAllowedOrigin("http://127.0.0.1:10000");
+  srv.AddAllowedOrigin("https://ui.perfetto.dev");
+
+  srv.Start(kWebsocketPort);
+  PERFETTO_LOG("[WSBridge] Listening on 127.0.0.1:%d", kWebsocketPort);
+  task_runner_.Run();
+}
+
+void WSBridge::OnHttpRequest(const base::HttpRequest& req) {
+  for (const auto& ep : endpoints_) {
+    if (req.uri != ep.uri || !req.is_websocket_handshake)
+      continue;
+
+    // Connect to the endpoint in blocking mode.
+    auto sock_raw =
+        base::UnixSocketRaw::CreateMayFail(ep.family, base::SockType::kStream);
+    if (!sock_raw) {
+      PERFETTO_PLOG("socket() failed");
+      req.conn->SendResponseAndClose("500 Server Error");
+      return;
+    }
+    PERFETTO_LOG("[WSBridge] New connection from \"%.*s\"",
+                 static_cast<int>(req.origin.size()), req.origin.data());
+    sock_raw.SetTxTimeout(3000);
+    sock_raw.SetBlocking(true);
+
+    if (!sock_raw.Connect(ep.endpoint)) {
+      PERFETTO_ELOG("[WSBridge] Connection to %s failed", ep.endpoint);
+      req.conn->SendResponseAndClose("503 Service Unavailable");
+      return;
+    }
+    sock_raw.SetBlocking(false);
+
+    PERFETTO_DLOG("[WSBridge] Connected to %s", ep.endpoint);
+    conns_[req.conn] = base::UnixSocket::AdoptConnected(
+        sock_raw.ReleaseFd(), this, &task_runner_, ep.family,
+        base::SockType::kStream);
+
+    req.conn->UpgradeToWebsocket(req);
+    return;
+  }  // for (endpoint)
+  req.conn->SendResponseAndClose("404 Not Found");
+}
+
+// Called when an inbound websocket message is received from the browser.
+void WSBridge::OnWebsocketMessage(const base::WebsocketMessage& msg) {
+  auto it = conns_.find(msg.conn);
+  PERFETTO_CHECK(it != conns_.end());
+  // Pass through the websocket message onto the endpoint TCP socket.
+  base::UnixSocket& sock = *it->second;
+  sock.Send(msg.data.data(), msg.data.size());
+}
+
+// Called when a TCP message is received from the endpoint.
+void WSBridge::OnDataAvailable(base::UnixSocket* sock) {
+  base::HttpServerConnection* websocket = GetWebsocket(sock);
+  PERFETTO_CHECK(websocket);
+
+  char buf[8192];
+  auto rsize = sock->Receive(buf, sizeof(buf));
+  if (rsize > 0) {
+    websocket->SendWebsocketMessage(buf, static_cast<size_t>(rsize));
+  } else {
+    // Connection closed or errored.
+    sock->Shutdown(/*notify=*/true);  // Will trigger OnDisconnect().
+    websocket->Close();
+  }
+}
+
+// Called when the browser terminates the websocket connection.
+void WSBridge::OnHttpConnectionClosed(base::HttpServerConnection* websocket) {
+  PERFETTO_DLOG("[WSBridge] Websocket connection closed");
+  auto it = conns_.find(websocket);
+  if (it == conns_.end())
+    return;  // Can happen if ADB closed first.
+  base::UnixSocket& sock = *it->second;
+  sock.Shutdown(/*notify=*/true);
+  conns_.erase(websocket);
+}
+
+void WSBridge::OnDisconnect(base::UnixSocket* sock) {
+  base::HttpServerConnection* websocket = GetWebsocket(sock);
+  if (!websocket)
+    return;
+  websocket->Close();
+  sock->Shutdown(/*notify=*/false);
+  conns_.erase(websocket);
+  PERFETTO_DLOG("[WSBridge] Socket connection closed");
+}
+
+base::HttpServerConnection* WSBridge::GetWebsocket(base::UnixSocket* sock) {
+  for (const auto& it : conns_) {
+    if (it.second.get() == sock) {
+      return it.first;
+    }
+  }
+  return nullptr;
+}
+
+void WSBridge::OnConnect(base::UnixSocket*, bool) {}
+void WSBridge::OnNewIncomingConnection(base::UnixSocket*,
+                                       std::unique_ptr<base::UnixSocket>) {}
+
+}  // namespace
+
+int PERFETTO_EXPORT_ENTRYPOINT WebsocketBridgeMain(int argc, char** argv) {
+  perfetto::WSBridge ws_bridge;
+  ws_bridge.Main(argc, argv);
+  return 0;
+}
+
+}  // namespace perfetto
diff --git a/src/websocket_bridge/websocket_bridge.h b/src/websocket_bridge/websocket_bridge.h
new file mode 100644
index 0000000..d93bd61
--- /dev/null
+++ b/src/websocket_bridge/websocket_bridge.h
@@ -0,0 +1,26 @@
+/*
+ * 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 SRC_WEBSOCKET_BRIDGE_WEBSOCKET_BRIDGE_H_
+#define SRC_WEBSOCKET_BRIDGE_WEBSOCKET_BRIDGE_H_
+
+#include "perfetto/base/compiler.h"
+
+namespace perfetto {
+int WebsocketBridgeMain(int argc, char** argv);
+}
+
+#endif  // SRC_WEBSOCKET_BRIDGE_WEBSOCKET_BRIDGE_H_
diff --git a/src/websocket_bridge/websocket_bridge_main.cc b/src/websocket_bridge/websocket_bridge_main.cc
new file mode 100644
index 0000000..480522b
--- /dev/null
+++ b/src/websocket_bridge/websocket_bridge_main.cc
@@ -0,0 +1,23 @@
+/*
+ * 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 "src/websocket_bridge/websocket_bridge.h"
+
+// This is split in a dedicated translation unit so that tracebox can refer to
+// WebsocketBridgeMain() without pulling in a main() symbol.
+int main(int argc, char** argv) {
+  return perfetto::WebsocketBridgeMain(argc, argv);
+}