Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .bazelrc
Original file line number Diff line number Diff line change
Expand Up @@ -89,8 +89,13 @@ build:macos --copt=-UDEBUG
# Keep workerd and V8's Perfetto build configurations in sync.
build:perfetto --//src/workerd/util:use_perfetto=True
build:perfetto --@v8//:v8_use_perfetto=True
# Perfetto trace points in Rust (//src/rust/perfetto). The flag defaults to off so that embedders
# that don't read this file (and may not provide the Perfetto C ABI and Rust SDK) get the no-op
# facade; see the comment on the flag in src/workerd/util/BUILD.bazel.
build:perfetto --//src/workerd/util:use_rust_perfetto=True
build:no-perfetto --//src/workerd/util:use_perfetto=False
build:no-perfetto --@v8//:v8_use_perfetto=False
build:no-perfetto --//src/workerd/util:use_rust_perfetto=False
build:unix --config=perfetto

# Increasing the optimization level of V8 significantly speeds up tests using V8 a lot, especially
Expand Down
1 change: 1 addition & 0 deletions AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,7 @@ Be aware that workerd uses tcmalloc for memory allocation in the typical case. W
| JS benchmarks | `src/workerd/api/bench.{h,c++}`, `build/wd_bench.bzl` | `bench` handler; runner `BenchController`; report schema `io/bench.capnp`; driver `server/server/bench.rs` |
| TypeScript types | `types/` | Extracted from C++ RTTI + hand-written `defines/*.d.ts`; see `types/AGENTS.md` for detailed typings guidance |
| V8 patches | `patches/v8/` | 33 patches; see `docs/v8-updates.md` |
| Perfetto trace points | `src/workerd/util/perfetto-tracing.h` (C++), `src/rust/perfetto/` (Rust) | Rust backend behind `--//src/workerd/util:use_rust_perfetto`; see `src/rust/AGENTS.md` PERFETTO TRACING |

## Coding Conventions

Expand Down
3 changes: 2 additions & 1 deletion build/deps/deps.jsonc
Original file line number Diff line number Diff line change
Expand Up @@ -116,7 +116,8 @@
"patches": [
"//:patches/perfetto/0001-Don-t-attempt-to-use-rules_android.patch",
"//:patches/perfetto/0002-disable-info-level-logging-re2.patch",
"//:patches/perfetto/0003-Load-rules-removed-from-Bazel-9-builtins.patch"
"//:patches/perfetto/0003-Load-rules-removed-from-Bazel-9-builtins.patch",
"//:patches/perfetto/0004-Add-C-ABI-library-over-client-and-expose-Rust-SDK-sources.patch"
]
},
{
Expand Down
1 change: 1 addition & 0 deletions build/deps/gen/deps.MODULE.bazel
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ archive_override(
"//:patches/perfetto/0001-Don-t-attempt-to-use-rules_android.patch",
"//:patches/perfetto/0002-disable-info-level-logging-re2.patch",
"//:patches/perfetto/0003-Load-rules-removed-from-Bazel-9-builtins.patch",
"//:patches/perfetto/0004-Add-C-ABI-library-over-client-and-expose-Rust-SDK-sources.patch",
],
sha256 = "4fcc98fe4754269a70e297f6caba429420039c89a1c6cb561dffb4057de63abb",
strip_prefix = "google-perfetto-9e9bdaf",
Expand Down
4 changes: 2 additions & 2 deletions build/wd_rust_crate.bzl
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ def wd_rust_crate(
cxx_bridge_features = [],
cxx_bridge_visibility = [],
testonly = False,
crate_features = [],
visibility = None):
"""Define rust crate.

Expand Down Expand Up @@ -112,6 +113,7 @@ def wd_rust_crate(
another package that includes a bridge's header (it must also link the crate).
testonly: True for a crate that only tests depend on (a test harness, the Rust half of a
C++ test). Like other test code, it is not held to //build/rust:lints.
crate_features: cargo-style features to enable for the crate and its test; may be a select().
"""
if srcs == None:
srcs = native.glob(["**/*.rs"])
Expand Down Expand Up @@ -165,8 +167,6 @@ def wd_rust_crate(
for bridge_src in cxx_bridge_srcs:
link_deps = link_deps + [bridge_src + "@cxx"]

crate_features = []

library_kwargs = {}
if crate_root != None:
library_kwargs["crate_root"] = crate_root
Expand Down
8 changes: 8 additions & 0 deletions deps/rust/Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

5 changes: 5 additions & 0 deletions deps/rust/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,10 @@ async-trait = { version = "0", default-features = false }
# kj-hyper's WebSocket handshake: Sec-WebSocket-Key / -Accept (RFC 6455 section 4).
base64 = { version = "0", default-features = false, features = ["std"] }
sha1 = { version = "0", default-features = false }
# bitflags and pastey are dependencies of the Perfetto Rust SDK, which is built from the
# @perfetto sources by //src/rust/perfetto/sdk. pastey is a maintained drop-in replacement for the
# archived `paste` crate that the SDK imports; it's aliased to `paste` there.
bitflags = "2"
bytes = { version = "1", default-features = false }
capnp = "0.27"
capnpc = "0.27"
Expand Down Expand Up @@ -70,6 +74,7 @@ hashlink = "0"
# kj-rs-io: `fs` for fcntl (KJ's fd flags), `signal` for ignoring SIGPIPE once per process like
# kj::UnixEventPort does.
nix = { version = "0", features = ["fs", "signal"] }
pastey = "0"
pico-args = "0"
# kj-hyper: WebSocket keys and frame masks. Every Cargo.toml merged into the crate_universe
# workspace (downstream repos include this one alongside their own) must request the same
Expand Down
5 changes: 5 additions & 0 deletions docs/v8-updates.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,11 @@ from the V8 directory.
figure out what version of the github code V8 depends on. Instead, it should be safe
to just bump these dependencies to the latest version on github.

When bumping `perfetto`, rebase `patches/perfetto/0004-*.patch`. It adds
`:libperfetto_c_over_client`, whose `srcs` and `deps` follow upstream's `:libperfetto_c`,
and exposes the Rust SDK sources. Then run `bazel test //src/rust/perfetto/...` to check
the Rust SDK still builds against the new C ABI and interoperates with the C++ SDK.

11. Check workerd's tests pass with the updated V8.

```sh
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
From: James M Snell <jsnell@cloudflare.com>
Date: Sat, 26 Sep 2026 00:00:00 +0000
Subject: Add C ABI library over the client library and expose Rust SDK sources


diff --git a/BUILD b/BUILD
index e531db5..9c57994 100644
--- a/BUILD
+++ b/BUILD
@@ -12275,5 +12275,48 @@ perfetto_py_binary(

exports_files(
["ui/src/assets/favicon.png"],
visibility = PERFETTO_CONFIG.public_visibility,
)
+
+# workerd: the Perfetto C ABI (src/shared_lib) layered on top of
+# :libperfetto_client_experimental instead of beside it. :libperfetto_c compiles its own copy of
+# the whole client/service stack, so linking it next to the C++ client would duplicate symbols and
+# produce a second perfetto::Tracing singleton. This target only adds the C ABI implementation, so
+# C ABI users (e.g. the Rust SDK in contrib/rust-sdk) and C++ SDK users share one Tracing instance
+# and one tracing session. Linked statically, so PERFETTO_SDK_SHLIB_IMPLEMENTATION is not defined.
+perfetto_cc_library(
+ name = "libperfetto_c_over_client",
+ srcs = [
+ "src/tracing/internal/tracing_muxer_impl.h",
+ ":src_shared_lib_for_testing",
+ ":src_shared_lib_shared_lib",
+ ":src_shared_lib_track_event_intern_map",
+ ":src_shared_lib_track_event_track_event",
+ ],
+ hdrs = [
+ ":include_perfetto_public_abi_base",
+ ":include_perfetto_public_abi_public",
+ ":include_perfetto_public_base",
+ ":include_perfetto_public_protos_protos",
+ ":include_perfetto_public_protozero",
+ ":include_perfetto_public_public",
+ ],
+ visibility = PERFETTO_CONFIG.public_visibility,
+ deps = [
+ ":libperfetto_client_experimental",
+ ":protos_perfetto_common_cpp",
+ ":protos_perfetto_common_zero",
+ ":protos_perfetto_config_cpp",
+ ":protos_perfetto_config_track_event_cpp",
+ ":protos_perfetto_config_track_event_zero",
+ ":protos_perfetto_config_zero",
+ ":protos_perfetto_trace_interned_data_zero",
+ ":protos_perfetto_trace_minimal_zero",
+ ":protos_perfetto_trace_non_minimal_zero",
+ ":protos_perfetto_trace_track_event_cpp",
+ ":protos_perfetto_trace_track_event_zero",
+ ":protozero",
+ ":src_base_base",
+ ],
+ linkstatic = True,
+)
diff --git a/contrib/rust-sdk/BUILD.bazel b/contrib/rust-sdk/BUILD.bazel
new file mode 100644
index 0000000..301b71c
--- /dev/null
+++ b/contrib/rust-sdk/BUILD.bazel
@@ -0,0 +1,21 @@
+# workerd: exposes the Rust SDK sources so embedders can define their own rust_library targets
+# (this repository does not depend on rules_rust). perfetto-sys is used with its checked-in
+# bindings.rs and linked against //:libperfetto_c_over_client; its build.rs is not used.
+
+package(default_visibility = ["//visibility:public"])
+
+exports_files([
+ "perfetto-sys/src/lib.rs",
+ "perfetto/src/lib.rs",
+ "perfetto/README.md",
+])
+
+filegroup(
+ name = "perfetto_sys_srcs",
+ srcs = glob(["perfetto-sys/src/**/*.rs"]),
+)
+
+filegroup(
+ name = "perfetto_srcs",
+ srcs = glob(["perfetto/src/**/*.rs"]),
+)
17 changes: 17 additions & 0 deletions src/rust/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,9 @@ _Snapshot — the set drifts as crates come and go; `bazel query //src/rust/...`
| `transpiler/` | TS type stripping via SWC (`ts_strip()`, `StripOnly` mode) |
| `python-parser/` | Python import extraction via `ruff_python_parser`; **namespace: `edgeworker::rust::`** |
| `gen-compile-cache/` | Binary crate — V8 bytecode cache generator; calls C++ `compile()` via CXX |
| `perfetto/` | Crate `perfetto`: Perfetto trace point macros (`trace_event!` etc.) writing into the C++-owned session; no-op unless built with Rust Perfetto (see PERFETTO TRACING) |
| `perfetto/sdk/` | `rust_library` targets for the Perfetto Rust SDK built from the `@perfetto` archive; only `perfetto/` may depend on them |
| `perfetto/test/` | Non-production crate; its test checks Rust and C++ events share one session with matching flow/track/counter IDs, in every build configuration |

## CONVENTIONS

Expand Down Expand Up @@ -73,6 +76,20 @@ mod tests;
- `wd_rust_crate`, `wd_rust_binary` and `wd_rust_proc_macro` take the `*-test.rs` files out of the crate's `srcs` and compile them only into its `<name>_test` target (`split_rust_test_srcs` in `build/wd_rust_test.bzl`). A crate defined with `rust_library` directly does the same by hand: `exclude = ["*-test.rs"]` in the library's `srcs` glob, and `compile_data = glob(["*-test.rs"])` on its `wd_rust_test` (a `rust_test` built from a `crate` rejects `srcs`). An unconditional `mod` declaration of a test file therefore fails the production build.
- Crates that are test code throughout are exempt: a test-support crate in a `tests/` directory (e.g. `cxx/kj-rs/tests/`), and a `wd_rust_test` built from its own `srcs` (e.g. `tsan/`).

## PERFETTO TRACING

Rust trace points use the `perfetto` crate (`//src/rust/perfetto`); the crate docs in `lib.rs` cover the macro API and its C++ equivalents.

- **Backend**: the Perfetto Rust SDK, built from `@perfetto//contrib/rust-sdk` by `perfetto/sdk/` and linked against `@perfetto//:libperfetto_c_over_client` (added by `patches/perfetto/0004-*.patch`). That library layers Perfetto's C ABI over the C++ client library, so Rust, C++ and V8 events share one `perfetto::Tracing` instance and one session. Don't depend on upstream `:libperfetto_c`: it bundles a second copy of the client.
- **Build flag**: the backend is compiled in (crate feature `perfetto`) only under `//src/workerd/util:really_use_rust_perfetto` = `really_use_perfetto` && `--//src/workerd/util:use_rust_perfetto`. The flag defaults to off and workerd's `.bazelrc` turns it on in `build:perfetto`, because embedders don't read that file and may have no C ABI or SDK targets in their `@perfetto`. Keep every reference to those targets inside a `select()` on `really_use_rust_perfetto`; the `perfetto/sdk/` targets are incompatible otherwise.
- **Both configurations must build**: callers compile against either `enabled.rs` or `disabled.rs`. The public types (`Flow`, `Track`, `EventContext`, `ScopedEvent`, …) are shared, and static assertions pin their auto traits. `disabled.rs` type-checks closures behind a constant `false` and never runs them. When changing the crate, lint and test both ways: `just clippy perfetto`, then the same with `--//src/workerd/util:use_rust_perfetto=False`. For tests, also try `--config=no-perfetto`.
- **Categories**: listed in `define_categories!` in `lib.rs`, which generates both `CATEGORIES` and the SDK's category table in the same order, so indices match. Macros resolve category names at compile time, so a typo is a compile error. A name shared with a C++ category (currently `workerd`) is enabled for both languages by the same trace config.
- **Registration**: C++ owns Perfetto initialization. `PerfettoSession::registerWorkerdTracks()` (`src/workerd/util/perfetto-tracing.c++`) calls `workerd::rust::perfetto::register_track_events()` under `WORKERD_USE_RUST_PERFETTO`; that runs `TrackEvent::init()` (the C ABI's `PerfettoTeInit`) and registers the categories. Until then every Rust category reads as disabled.
- **IDs**: `Flow::from_ref`/`from_ptr`, `Track::from_ref`/`from_ptr` and `trace_counter!` compute IDs the same way as C++ `PERFETTO_FLOW_FROM_POINTER`, `PERFETTO_TRACK_FROM_POINTER` and `perfetto::CounterTrack(name)`, all relative to the process track UUID.
- **Cost**: a disabled trace point costs two atomic loads. An enabled one takes a process-wide mutex in the SDK's generated `emit()` (it guards a `static mut` category table because `TrackEventCategory::emit` takes `&mut self`) and allocates the event's extras. Fixing that means patching the SDK.
- **Crate dependencies**: the SDK needs `bitflags`, `thiserror` and `paste`. `paste` is archived upstream, so `perfetto/sdk/` aliases the drop-in `pastey` to `paste`. Embedders that splice `deps/rust/Cargo.toml` into their own crate repository must repin when it changes.
- **Async**: `trace_event!` is scope-based on the thread track; don't hold it across `.await`. Use `trace_event_begin!`/`trace_event_end!` on a `Track` instead.

## CXX BRIDGE: ASYNC AND ERROR HANDLING

### C++ calling async Rust (`extern "Rust"`)
Expand Down
28 changes: 28 additions & 0 deletions src/rust/perfetto/BUILD.bazel
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
load("//:build/wd_rust_crate.bzl", "wd_rust_crate")

# Perfetto trace points for Rust. The SDK backend is only compiled in (feature "perfetto") under
# //src/workerd/util:really_use_rust_perfetto; otherwise every macro compiles to a no-op and this
# crate has no dependency on Perfetto. Keep all references to Perfetto targets inside these
# selects so embedders that don't provide them can still build this crate.
wd_rust_crate(
name = "perfetto",
srcs = [
"disabled.rs",
"enabled.rs",
"ffi.rs",
"lib.rs",
"lib-test.rs",
],
crate_features = select({
"//src/workerd/util:really_use_rust_perfetto": ["perfetto"],
"//conditions:default": [],
}),
cxx_bridge_src = "ffi.rs",
visibility = ["//visibility:public"],
deps = [
"@crates_vendor//:static_assertions",
] + select({
"//src/workerd/util:really_use_rust_perfetto": ["//src/rust/perfetto/sdk:perfetto_sdk"],
"//conditions:default": [],
}),
)
79 changes: 79 additions & 0 deletions src/rust/perfetto/disabled.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
//! Backend used when Perfetto is not compiled in. Every function has the same signature as in
//! `enabled.rs`. Closures are still type-checked (behind `if false`), so code that builds in one
//! configuration builds in the other, but they're never called and no event is emitted.

use std::ffi::CStr;
use std::marker::PhantomData;

use crate::CounterValue;
use crate::DebugArg;
use crate::Flow;
use crate::Track;

pub const fn register_track_events() {}

pub const fn process_track_uuid() -> u64 {
0
}

#[inline]
pub const fn is_enabled(_category: usize) -> bool {
false
}

pub struct EventContext<'a> {
_marker: PhantomData<&'a mut ()>,
}

impl EventContext<'_> {
#[expect(clippy::unused_self, reason = "mirrors enabled.rs")]
pub const fn add_arg(&self, _name: &str, _value: DebugArg<'_>) {}

#[expect(clippy::unused_self, reason = "mirrors enabled.rs")]
pub const fn set_track(&self, _track: Track) {}

#[expect(clippy::unused_self, reason = "mirrors enabled.rs")]
pub const fn set_flow(&self, _flow: Flow) {}

#[expect(clippy::unused_self, reason = "mirrors enabled.rs")]
pub const fn set_terminating_flow(&self, _flow: Flow) {}
}

// Guards code that must type-check but never run.
const TYPE_CHECK_ONLY: bool = false;

#[inline]
fn type_check_only(f: impl FnOnce(EventContext<'_>)) {
if TYPE_CHECK_ONLY {
f(EventContext {
_marker: PhantomData,
});
}
}

#[inline]
pub fn emit_instant(_category: usize, _name: &'static CStr, f: impl FnOnce(EventContext<'_>)) {
type_check_only(f);
}

#[inline]
pub fn emit_begin(
_category: usize,
_name: &'static CStr,
f: impl FnOnce(EventContext<'_>),
) -> bool {
type_check_only(f);
false
}

#[inline]
pub fn emit_end(_category: usize, f: impl FnOnce(EventContext<'_>)) {
type_check_only(f);
}

#[inline]
pub fn emit_counter(_category: usize, _name: &'static str, value: impl FnOnce() -> CounterValue) {
if TYPE_CHECK_ONLY {
let _ = value();
}
}
Loading
Loading