feat: optional dial9 Tokio telemetry behind a dial9 feature (#10442)

* Add optional dial9 Tokio telemetry behind a `dial9` feature

Integrate dial9 so storage runtimes can emit production-friendly Tokio
traces. Recording is off unless the crate is built with `--features dial9`
and DIAL9_ENABLED=true is set at runtime; with the feature off, runtime
construction is byte-for-byte unchanged.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y7X6MkjY3P7wpP2MfHdTDY

* Enable dial9 CPU and schedule profiling

Turn on cpu-profiling and sched events behind the same `dial9` feature,
add the DIAL9_CPU_* / DIAL9_SCHEDULE_* env knobs, and document the frame
pointer rustflags the stack unwinder needs.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y7X6MkjY3P7wpP2MfHdTDY

* Harden dial9 env parsing and the writer-failure path

- Reset Cargo.lock to the branch point and re-resolve, so the diff is
  additive instead of re-resolving unrelated packages. This drops the
  heck 0.5.0 -> 0.4.1 downgrade, which sat in the default build graph and
  would have changed proto codegen identifier casing. The remaining
  non-additive entry, toml_parser 1.0.9 -> 1.1.3, is forced by
  proc-macro-crate via dial9-trace-format-derive.
- Parse DIAL9_* booleans the way dial9 does, accepting 1/y/yes/on and
  0/n/no/off and warning on anything else. `str::parse::<bool>` took only
  exact lowercase true/false, so DIAL9_CPU_PROFILE_ENABLED=0 silently left
  99 Hz sampling on and DIAL9_ENABLED=1 silently left recording off.
- Require the numeric knobs to be positive. A zero disk budget made dial9
  evict everything and stop recording within seconds while the log still
  reported telemetry enabled.
- Treat a set-but-empty DIAL9_TRACE_DIR as unset. It skipped the /tmp
  fallback and wrote up to the full budget into the working directory,
  which is /qdrant next to storage/ in the official image.
- Return a disabled guard as soon as the trace writer fails, before
  with_cpu_profiling and with_sched_events run. Those start their profilers
  eagerly, opening a perf event per thread and installing a process-global
  signal handler that build() would then discard.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y7X6MkjY3P7wpP2MfHdTDY

* Correct the dial9 docs and give them their own section

- `--cfg tokio_unstable` is required for any task data at all, not merely
  for fuller coverage: dial9's poll, spawn and terminate hooks are all
  `#[cfg(tokio_unstable)]`, and nothing in the repo sets the flag. Without
  it there is no task timeline and DIAL9_TASK_TRACKING_ENABLED does nothing.
- Document `-C debuginfo=2`. `[profile.perf]` inherits `release` and sets no
  `debug` key, so the documented build symbolized off the ELF symtab with
  inlined callees collapsed and no file or line, unlike `[profile.bench]`
  which sets `debug = true` for this reason.
- Move the dial9 material out from between the feature list and the prose
  that belongs to it. Those paragraphs describe `tracing` instrumentation
  and read as dial9's when the example is wedged in front of them, which
  points readers at `#[tracing::instrument]` for a tool that records Tokio
  runtime events and no tracing spans.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Y7X6MkjY3P7wpP2MfHdTDY

* Use cfg_select!

---------

Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-authored-by: timvisee <tim@visee.me>
This commit is contained in:
Arnaud Gourlay
2026-09-03 12:46:01 +02:00
committed by timvisee
co-authored by Claude Opus 5 timvisee
parent 943b9c3a0c
commit 4d1570c03d
8 changed files with 871 additions and 33 deletions
Generated
+565 -23
View File
@@ -2,6 +2,16 @@
# It is not intended for manual editing. # It is not intended for manual editing.
version = 4 version = 4
[[package]]
name = "Inflector"
version = "0.11.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fe438c63458706e03479442743baae6c88256498e6431708f6dfc520a26515d3"
dependencies = [
"lazy_static",
"regex",
]
[[package]] [[package]]
name = "actix-codec" name = "actix-codec"
version = "0.5.2" version = "0.5.2"
@@ -139,7 +149,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8720bceaa6797fd8b2deab968d52e1120b2a8c30950939f6c8cdb42a910bc885" checksum = "8720bceaa6797fd8b2deab968d52e1120b2a8c30950939f6c8cdb42a910bc885"
dependencies = [ dependencies = [
"bytesize", "bytesize",
"darling", "darling 0.23.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.119", "syn 2.0.119",
@@ -845,6 +855,22 @@ dependencies = [
"wyz", "wyz",
] ]
[[package]]
name = "blazesym"
version = "0.2.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "847a0a95b041ad5aae1bdc44f2bd54743f76eb0065c4f86b139f81343c287eaf"
dependencies = [
"cpp_demangle",
"crc32fast",
"flate2",
"gimli 0.33.0",
"libc",
"memmap2",
"rustc-demangle",
"tempfile",
]
[[package]] [[package]]
name = "blink-alloc" name = "blink-alloc"
version = "0.4.0" version = "0.4.0"
@@ -922,6 +948,29 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "bon"
version = "3.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e3fac94a66da67200398458a25412bcc3f9b6443b5119a6cad9cf3ccfcd8cc6"
dependencies = [
"bon-macros",
]
[[package]]
name = "bon-macros"
version = "3.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4654961ad0494e4774c5c60b4cb4cd0ae9b9d92d039d901638b1dba97ebebf5"
dependencies = [
"darling 0.24.1",
"ident_case",
"prettyplease 0.3.0",
"proc-macro2",
"quote",
"syn 3.0.4",
]
[[package]] [[package]]
name = "brotli" name = "brotli"
version = "8.0.2" version = "8.0.2"
@@ -1022,6 +1071,12 @@ dependencies = [
"bytes", "bytes",
] ]
[[package]]
name = "c-enum"
version = "0.2.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd17eb909a8c6a894926bfcc3400a4bb0e732f5a57d37b1f14e8b29e329bace8"
[[package]] [[package]]
name = "camino" name = "camino"
version = "1.2.4" version = "1.2.4"
@@ -1331,7 +1386,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c" checksum = "117725a109d387c937a1533ce01b450cbde6b88abceea8473c4d7a85853cda3c"
dependencies = [ dependencies = [
"lazy_static", "lazy_static",
"windows-sys 0.52.0", "windows-sys 0.59.0",
] ]
[[package]] [[package]]
@@ -1340,7 +1395,7 @@ version = "3.1.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34"
dependencies = [ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.61.2",
] ]
[[package]] [[package]]
@@ -1547,6 +1602,15 @@ dependencies = [
"siphasher 1.0.3", "siphasher 1.0.3",
] ]
[[package]]
name = "cpp_demangle"
version = "0.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0667304c32ea56cb4cd6d2d7c0cfe9a2f8041229db8c033af7f8d69492429def"
dependencies = [
"cfg-if",
]
[[package]] [[package]]
name = "cpufeatures" name = "cpufeatures"
version = "0.2.17" version = "0.2.17"
@@ -1671,6 +1735,15 @@ dependencies = [
"crossbeam-utils", "crossbeam-utils",
] ]
[[package]]
name = "crossbeam-queue"
version = "0.3.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "803d13fb3b09d88be9f4dbc29062c66b19bf7170867ceb746d2a8689bf6c7a26"
dependencies = [
"crossbeam-utils",
]
[[package]] [[package]]
name = "crossbeam-utils" name = "crossbeam-utils"
version = "0.8.21" version = "0.8.21"
@@ -1785,8 +1858,18 @@ version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d" checksum = "25ae13da2f202d56bd7f91c25fba009e7717a1e4a1cc98a76d844b65ae912e9d"
dependencies = [ dependencies = [
"darling_core", "darling_core 0.23.0",
"darling_macro", "darling_macro 0.23.0",
]
[[package]]
name = "darling"
version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed17f5901b6630b993ca003def43f2f8ef4014fc13b047b57aad617ff32bc2ec"
dependencies = [
"darling_core 0.24.1",
"darling_macro 0.24.1",
] ]
[[package]] [[package]]
@@ -1802,17 +1885,41 @@ dependencies = [
"syn 2.0.119", "syn 2.0.119",
] ]
[[package]]
name = "darling_core"
version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6837e2cf7485aaae18f86181d2f0e9a7ed297a025e220aeabf63fdebd3a2ddff"
dependencies = [
"ident_case",
"proc-macro2",
"quote",
"strsim 0.11.1",
"syn 3.0.4",
]
[[package]] [[package]]
name = "darling_macro" name = "darling_macro"
version = "0.23.0" version = "0.23.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d" checksum = "ac3984ec7bd6cfa798e62b4a642426a5be0e68f9401cfc2a01e3fa9ea2fcdb8d"
dependencies = [ dependencies = [
"darling_core", "darling_core 0.23.0",
"quote", "quote",
"syn 2.0.119", "syn 2.0.119",
] ]
[[package]]
name = "darling_macro"
version = "0.24.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2ac7135c3ef02b2f7833bbeb1be5ba7f966dcde8a87c6b87f65a778d71a02785"
dependencies = [
"darling_core 0.24.1",
"quote",
"syn 3.0.4",
]
[[package]] [[package]]
name = "dary_heap" name = "dary_heap"
version = "0.3.8" version = "0.3.8"
@@ -1881,6 +1988,17 @@ dependencies = [
"powerfmt", "powerfmt",
] ]
[[package]]
name = "derive-where"
version = "1.6.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d08b3a0bcc0d079199cd476b2cae8435016ec11d1c0986c6901c5ac223041534"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]] [[package]]
name = "derive_more" name = "derive_more"
version = "2.1.1" version = "2.1.1"
@@ -1904,6 +2022,136 @@ dependencies = [
"unicode-xid", "unicode-xid",
] ]
[[package]]
name = "dial9"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "49b11bce55173f77d9003cce8f129b4af889a4639005c53c3498cef933bc895c"
dependencies = [
"bon",
"dial9-core",
"dial9-macro",
"dial9-metrique",
"dial9-perf-self-profile",
"dial9-tokio-telemetry",
"dial9-trace-format",
"tokio",
"tracing",
]
[[package]]
name = "dial9-core"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9e8cbbc8955394be626249a3b52ddd6bf664373661eaeae70d87eb12bf6f20b6"
dependencies = [
"arc-swap",
"bon",
"bytes",
"crossbeam-queue",
"dial9-trace-format",
"flate2",
"futures-util",
"libc",
"metrique",
"metrique-timesource",
"tokio",
"tokio-util",
"tracing",
"ulid",
]
[[package]]
name = "dial9-macro"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4177b8f8bf95d8e3769c1aad596f5d6fb231953a45c4c3d2a5946fe8e4ec327b"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]]
name = "dial9-metrique"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d42fe0e3e14e8780c4ecf9ee14c183dbcb9b1ae3612cc9a689e7dc83b13adef1"
dependencies = [
"dial9-core",
"dial9-trace-format",
"metrique",
"metrique-writer",
"tokio",
"tracing",
]
[[package]]
name = "dial9-perf-self-profile"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f65948455c504bf08576c7b5cfe93bfcaa486d661dc4ee85c2a6309ab89629"
dependencies = [
"blazesym",
"bytes",
"crossbeam-utils",
"dial9-core",
"dial9-trace-format",
"libc",
"perf-event-data",
"perf-event-open-sys2",
"tokio",
"tracing",
]
[[package]]
name = "dial9-tokio-telemetry"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af1244091367c805b5d98a6a590f8ab992efda06e6d2560a6967ef898ffd30a2"
dependencies = [
"bon",
"bytes",
"dial9-core",
"dial9-perf-self-profile",
"dial9-trace-format",
"flate2",
"futures-util",
"hostname",
"libc",
"metrique-timesource",
"pin-project-lite",
"serde",
"serde_json",
"smallvec",
"tokio",
"tokio-util",
"tracing",
]
[[package]]
name = "dial9-trace-format"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "86083b7240114b2d0da4a7e9d041571d80ca3b1f92ae0ec794f1be21709daa6a"
dependencies = [
"dial9-trace-format-derive",
"serde",
"typeid",
]
[[package]]
name = "dial9-trace-format-derive"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9309248f12e414d88bcc9505f78b0c9ed47f79c5b62db611493e19a612cdc9d6"
dependencies = [
"proc-macro-crate",
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]] [[package]]
name = "digest" name = "digest"
version = "0.10.7" version = "0.10.7"
@@ -2166,6 +2414,12 @@ dependencies = [
"cfg-if", "cfg-if",
] ]
[[package]]
name = "endian-type"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d"
[[package]] [[package]]
name = "env_filter" name = "env_filter"
version = "2.0.0" version = "2.0.0"
@@ -2252,7 +2506,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb"
dependencies = [ dependencies = [
"libc", "libc",
"windows-sys 0.52.0", "windows-sys 0.61.2",
] ]
[[package]] [[package]]
@@ -2842,6 +3096,9 @@ version = "0.33.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bf7f043f89559805f8c7cacc432749b2fa0d0a0a9ee46ce47164ed5ba7f126c" checksum = "0bf7f043f89559805f8c7cacc432749b2fa0d0a0a9ee46ce47164ed5ba7f126c"
dependencies = [ dependencies = [
"fnv",
"hashbrown 0.16.1",
"indexmap 2.14.0",
"stable_deref_trait", "stable_deref_trait",
] ]
@@ -3109,6 +3366,17 @@ dependencies = [
"windows-sys 0.61.2", "windows-sys 0.61.2",
] ]
[[package]]
name = "hostname"
version = "0.4.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "617aaa3557aef3810a6369d0a99fac8a080891b68bd9f9812a1eeda0c0730cbd"
dependencies = [
"cfg-if",
"libc",
"windows-link",
]
[[package]] [[package]]
name = "http" name = "http"
version = "0.2.12" version = "0.2.12"
@@ -3654,7 +3922,7 @@ checksum = "3640c1c38b8e4e43584d8df18be5fc6b0aa314ce6ebf51b53313d4306cca8e46"
dependencies = [ dependencies = [
"hermit-abi", "hermit-abi",
"libc", "libc",
"windows-sys 0.52.0", "windows-sys 0.61.2",
] ]
[[package]] [[package]]
@@ -4177,6 +4445,141 @@ dependencies = [
"autocfg", "autocfg",
] ]
[[package]]
name = "metrics"
version = "0.24.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "89550ee9f79e88fef3119de263694973a8adb26c21d75322164fb8c493039fe2"
dependencies = [
"portable-atomic",
"rapidhash",
]
[[package]]
name = "metrics-util"
version = "0.20.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "96f8722f8562635f92f8ed992f26df0532266eb03d5202607c20c0d7e9745e13"
dependencies = [
"aho-corasick",
"crossbeam-epoch",
"crossbeam-utils",
"hashbrown 0.16.1",
"indexmap 2.14.0",
"metrics",
"ordered-float 5.5.0",
"quanta",
"radix_trie",
"rand 0.9.2",
"rand_xoshiro",
"rapidhash",
"sketches-ddsketch",
]
[[package]]
name = "metrique"
version = "0.1.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b55bfa39e6f5e44a37a59a794915ce36d04371471cf62ae365cd9703f58a5e0"
dependencies = [
"itoa",
"metrique-core",
"metrique-macro",
"metrique-service-metrics",
"metrique-timesource",
"metrique-writer",
"metrique-writer-core",
"metrique-writer-macro",
"ryu",
"tokio",
]
[[package]]
name = "metrique-core"
version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8bd60ab3f5d8e13e36aa1d1dd9cc4920012dd21a25bd7311ae3c39810648e068"
dependencies = [
"itertools 0.14.0",
"metrique-writer-core",
]
[[package]]
name = "metrique-macro"
version = "0.1.22"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7605c49951713cce25af6bee77ed4432dde0839c5d9935bcb6f9643e6e82c7c3"
dependencies = [
"Inflector",
"darling 0.23.0",
"metrique-core",
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]]
name = "metrique-service-metrics"
version = "0.1.23"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "24ee17caa35f83b730a83c8fa8f7a9aaa61a0becc7a71feb5bfb854bc75440d7"
dependencies = [
"metrique-writer",
]
[[package]]
name = "metrique-timesource"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2faca4e4480069ff02b1763b3b79f5cec7e8628e24d9dc5b6073f53d2577a4d9"
[[package]]
name = "metrique-writer"
version = "0.1.27"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bb97854dd4e2a92fe54b30e6e19976dcc0b2b7d39da3e84e86e2ce1e2ed8ce0d"
dependencies = [
"ahash",
"crossbeam-queue",
"crossbeam-utils",
"metrics",
"metrics-util",
"metrique-core",
"metrique-writer-core",
"metrique-writer-macro",
"rand 0.9.2",
"smallvec",
"tokio",
"tracing",
"tracing-subscriber",
]
[[package]]
name = "metrique-writer-core"
version = "0.1.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "71c9fc241cff4eedc2b6c3e2edece7f3edab2920e7531f62aac864ce933cdbb6"
dependencies = [
"derive-where",
"itertools 0.14.0",
"serde",
"smallvec",
]
[[package]]
name = "metrique-writer-macro"
version = "0.1.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1eef190f855405b9df4166ee0495925162fb2a7985bac62c6ae44f40061fac00"
dependencies = [
"darling 0.23.0",
"proc-macro2",
"quote",
"str_inflector",
"syn 2.0.119",
"synstructure",
]
[[package]] [[package]]
name = "mime" name = "mime"
version = "0.3.17" version = "0.3.17"
@@ -4310,6 +4713,15 @@ version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ab250442c86f1850815b5d268639dff018c0627022bc1940eb2d642ca1ce12f0" checksum = "ab250442c86f1850815b5d268639dff018c0627022bc1940eb2d642ca1ce12f0"
[[package]]
name = "nibble_vec"
version = "0.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43"
dependencies = [
"smallvec",
]
[[package]] [[package]]
name = "nix" name = "nix"
version = "0.23.2" version = "0.23.2"
@@ -4791,6 +5203,27 @@ version = "2.3.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220"
[[package]]
name = "perf-event-data"
version = "0.1.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "575828d9d7d205188048eb1508560607a03d21eafdbba47b8cade1736c1c28e1"
dependencies = [
"bitflags 2.11.0",
"c-enum",
"perf-event-open-sys2",
]
[[package]]
name = "perf-event-open-sys2"
version = "5.0.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9c25955321465255e437600b54296983fab1feac2cd0c38958adeb26dbae49e"
dependencies = [
"libc",
"memoffset 0.9.1",
]
[[package]] [[package]]
name = "pest" name = "pest"
version = "2.8.6" version = "2.8.6"
@@ -5129,6 +5562,15 @@ dependencies = [
"elliptic-curve", "elliptic-curve",
] ]
[[package]]
name = "proc-macro-crate"
version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "e67ba7e9b2b56446f1d419b1d807906278ffa1a658a8a5d8a39dcb1f5a78614f"
dependencies = [
"toml_edit",
]
[[package]] [[package]]
name = "proc-macro-error-attr3" name = "proc-macro-error-attr3"
version = "3.0.2" version = "3.0.2"
@@ -5290,7 +5732,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "03da047801ff44bb6a4d407d4860c05fd70bb81714e6b2f3812603d5b145b042" checksum = "03da047801ff44bb6a4d407d4860c05fd70bb81714e6b2f3812603d5b145b042"
dependencies = [ dependencies = [
"heck 0.5.0", "heck 0.5.0",
"itertools 0.10.5", "itertools 0.14.0",
"log", "log",
"multimap", "multimap",
"petgraph 0.8.3", "petgraph 0.8.3",
@@ -5337,7 +5779,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf" checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"itertools 0.10.5", "itertools 0.14.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.119", "syn 2.0.119",
@@ -5701,6 +6143,21 @@ dependencies = [
"serde_derive", "serde_derive",
] ]
[[package]]
name = "quanta"
version = "0.12.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f3ab5a9d756f0d97bdc89019bd2e4ea098cf9cde50ee7564dde6b81ccc8f06c7"
dependencies = [
"crossbeam-utils",
"libc",
"once_cell",
"raw-cpuid",
"wasi",
"web-sys",
"winapi",
]
[[package]] [[package]]
name = "quantization" name = "quantization"
version = "0.1.0" version = "0.1.0"
@@ -5828,7 +6285,7 @@ dependencies = [
"once_cell", "once_cell",
"socket2", "socket2",
"tracing", "tracing",
"windows-sys 0.52.0", "windows-sys 0.59.0",
] ]
[[package]] [[package]]
@@ -5858,6 +6315,16 @@ version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09"
[[package]]
name = "radix_trie"
version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd"
dependencies = [
"endian-type",
"nibble_vec",
]
[[package]] [[package]]
name = "raft" name = "raft"
version = "0.7.0" version = "0.7.0"
@@ -5987,6 +6454,33 @@ dependencies = [
"rand_core 0.9.5", "rand_core 0.9.5",
] ]
[[package]]
name = "rand_xoshiro"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f703f4665700daf5512dcca5f43afa6af89f09db47fb56be587f80636bda2d41"
dependencies = [
"rand_core 0.9.5",
]
[[package]]
name = "rapidhash"
version = "4.5.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5da7e78a036ce858e8d55b7e7dc8ba3a88b78350fd2155d3591bbd966b58589e"
dependencies = [
"rustversion",
]
[[package]]
name = "raw-cpuid"
version = "11.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186"
dependencies = [
"bitflags 2.11.0",
]
[[package]] [[package]]
name = "rawpointer" name = "rawpointer"
version = "0.2.1" version = "0.2.1"
@@ -6322,7 +6816,7 @@ dependencies = [
"errno", "errno",
"libc", "libc",
"linux-raw-sys 0.4.15", "linux-raw-sys 0.4.15",
"windows-sys 0.52.0", "windows-sys 0.59.0",
] ]
[[package]] [[package]]
@@ -6335,7 +6829,7 @@ dependencies = [
"errno", "errno",
"libc", "libc",
"linux-raw-sys 0.12.1", "linux-raw-sys 0.12.1",
"windows-sys 0.52.0", "windows-sys 0.61.2",
] ]
[[package]] [[package]]
@@ -6403,7 +6897,7 @@ dependencies = [
"security-framework", "security-framework",
"security-framework-sys", "security-framework-sys",
"webpki-root-certs", "webpki-root-certs",
"windows-sys 0.52.0", "windows-sys 0.61.2",
] ]
[[package]] [[package]]
@@ -6901,7 +7395,7 @@ version = "3.22.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8705578779c2b6bd90d84d66eb2e206b708b1a4d7b9f17641b293545bf1c7e46" checksum = "8705578779c2b6bd90d84d66eb2e206b708b1a4d7b9f17641b293545bf1c7e46"
dependencies = [ dependencies = [
"darling", "darling 0.23.0",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.119", "syn 2.0.119",
@@ -7102,6 +7596,12 @@ version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649" checksum = "8ee5873ec9cce0195efcb7a4e9507a04cd49aec9c83d0389df45b1ef7ba2e649"
[[package]]
name = "sketches-ddsketch"
version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c6f73aeb92d671e0cc4dca167e59b2deb6387c375391bc99ee743f326994a2b"
[[package]] [[package]]
name = "slab" name = "slab"
version = "0.4.12" version = "0.4.12"
@@ -7269,6 +7769,7 @@ dependencies = [
"collection", "collection",
"common", "common",
"dashmap", "dashmap",
"dial9",
"env_logger", "env_logger",
"fs-err", "fs-err",
"futures", "futures",
@@ -7307,6 +7808,16 @@ dependencies = [
"wal", "wal",
] ]
[[package]]
name = "str_inflector"
version = "0.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ec0b848d5a7695b33ad1be00f84a3c079fe85c9278a325ff9159e6c99cef4ef7"
dependencies = [
"lazy_static",
"regex",
]
[[package]] [[package]]
name = "str_stack" name = "str_stack"
version = "0.1.0" version = "0.1.0"
@@ -7533,7 +8044,7 @@ dependencies = [
"getrandom 0.4.2", "getrandom 0.4.2",
"once_cell", "once_cell",
"rustix 1.1.4", "rustix 1.1.4",
"windows-sys 0.52.0", "windows-sys 0.61.2",
] ]
[[package]] [[package]]
@@ -7788,7 +8299,7 @@ dependencies = [
"indexmap 2.14.0", "indexmap 2.14.0",
"serde_core", "serde_core",
"serde_spanned", "serde_spanned",
"toml_datetime", "toml_datetime 0.7.5+spec-1.1.0",
"toml_parser", "toml_parser",
"toml_writer", "toml_writer",
"winnow 0.7.15", "winnow 0.7.15",
@@ -7804,12 +8315,33 @@ dependencies = [
] ]
[[package]] [[package]]
name = "toml_parser" name = "toml_datetime"
version = "1.0.9+spec-1.1.0" version = "1.1.1+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "702d4415e08923e7e1ef96cd5727c0dfed80b4d2fa25db9647fe5eb6f7c5a4c4" checksum = "3165f65f62e28e0115a00b2ebdd37eb6f3b641855f9d636d3cd4103767159ad7"
dependencies = [ dependencies = [
"winnow 0.7.15", "serde_core",
]
[[package]]
name = "toml_edit"
version = "0.25.13+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6975367e4d2ef766d86af01ffad14b622fecc8d4357a998fbc4deb6e9bacaf9b"
dependencies = [
"indexmap 2.14.0",
"toml_datetime 1.1.1+spec-1.1.0",
"toml_parser",
"winnow 1.0.0",
]
[[package]]
name = "toml_parser"
version = "1.1.3+spec-1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1d38ac1cf9b95face32296c0a3ede1fdc270627c9d9c02a7274dd6d960dc4d56"
dependencies = [
"winnow 1.0.0",
] ]
[[package]] [[package]]
@@ -8162,6 +8694,16 @@ dependencies = [
"tonic-prost-build", "tonic-prost-build",
] ]
[[package]]
name = "ulid"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "470dbf6591da1b39d43c14523b2b469c86879a53e8b758c8e090a470fe7b1fbe"
dependencies = [
"rand 0.9.2",
"web-time",
]
[[package]] [[package]]
name = "unarray" name = "unarray"
version = "0.1.4" version = "0.1.4"
@@ -8459,7 +9001,7 @@ version = "0.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "240e4b81c20a1d6d50d1d7265c658dfbd204e8b9ac4d80f3c931f39462196335" checksum = "240e4b81c20a1d6d50d1d7265c658dfbd204e8b9ac4d80f3c931f39462196335"
dependencies = [ dependencies = [
"darling", "darling 0.23.0",
"proc-macro-error3", "proc-macro-error3",
"proc-macro2", "proc-macro2",
"quote", "quote",
@@ -8768,7 +9310,7 @@ version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22" checksum = "c2a7b1c03c876122aa43f3020e6c3c3ee5c05081c9a00739faf7503aeba10d22"
dependencies = [ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.61.2",
] ]
[[package]] [[package]]
+1
View File
@@ -31,6 +31,7 @@ console-subscriber = ["tracing", "dep:console-subscriber"]
tracy = ["tracing-tracy"] tracy = ["tracing-tracy"]
tracing-tracy = ["tracing", "dep:tracing-tracy"] tracing-tracy = ["tracing", "dep:tracing-tracy"]
tokio-tracing = ["tokio/tracing"] tokio-tracing = ["tokio/tracing"]
dial9 = ["storage/dial9"]
stacktrace = ["rstack-self"] stacktrace = ["rstack-self"]
chaos-testing = [] chaos-testing = []
data-consistency-check = ["collection/data-consistency-check"] data-consistency-check = ["collection/data-consistency-check"]
+47
View File
@@ -245,6 +245,8 @@ that can be enabled with optional features.
- note, that you'll also have to [pass `--cfg tokio_unstable` arguments to `rustc`][tokio-tracing] to enable this feature - note, that you'll also have to [pass `--cfg tokio_unstable` arguments to `rustc`][tokio-tracing] to enable this feature
- this is required (and enabled automatically) by the `console` feature - this is required (and enabled automatically) by the `console` feature
- but you can enable it explicitly with the `tracy` feature, to see Tokio traces in [`Tracy`] profiler - but you can enable it explicitly with the `tracy` feature, to see Tokio traces in [`Tracy`] profiler
- `dial9` feature enables [dial9](https://github.com/dial9-rs/dial9) Tokio runtime telemetry,
see [its own section](#dial9-tokio-runtime-telemetry) below
Qdrant code is **not** instrumented by default, so you'll have to manually add `#[tracing::instrument]` attributes Qdrant code is **not** instrumented by default, so you'll have to manually add `#[tracing::instrument]` attributes
on functions and methods that you want to profile. on functions and methods that you want to profile.
@@ -279,6 +281,51 @@ fn some_other_function() {
[`log`]: https://docs.rs/log/latest/log/ [`log`]: https://docs.rs/log/latest/log/
[tracing-log-warning]: https://docs.rs/tracing-log/latest/tracing_log/#caution-mixing-both-conversions [tracing-log-warning]: https://docs.rs/tracing-log/latest/tracing_log/#caution-mixing-both-conversions
### dial9 (Tokio runtime telemetry)
The `dial9` feature attaches [dial9](https://github.com/dial9-rs/dial9) hooks to Qdrant's storage
Tokio runtimes (`general`, `update`, `search-cpu`, `search-io`) and records worker, task and poll
events, with optional CPU and schedule profiling. Unlike [`tracing`] it needs no `#[instrument]`
attributes: it observes the runtime rather than your code, and it does *not* pick up [`tracing`]
spans.
Recording needs both the feature at build time and `DIAL9_ENABLED=true` at run time. With the
feature off, runtime construction is unchanged.
Three rustflags matter, and none of them is set anywhere in the repo:
- `--cfg tokio_unstable` is **required for any task data at all**, not just for fuller coverage.
dial9's poll, spawn and terminate hooks are all `#[cfg(tokio_unstable)]`, so without it a trace
carries worker park/unpark and runtime metrics only, with no task timeline, and
`DIAL9_TASK_TRACKING_ENABLED` is a silent no-op.
- `-C force-frame-pointers=yes` lets the CPU and schedule profilers unwind.
- `-C debuginfo=2` lets those frames symbolize. `[profile.perf]` inherits `release` and sets no
`debug` key, so without it the symbolizer falls back to the ELF symtab: inlined callees collapse
into their caller and frames carry no file or line.
```console
# Build with dial9 support (perf profile: release opts, no LTO)
$ RUSTFLAGS='--cfg tokio_unstable -C force-frame-pointers=yes -C debuginfo=2' \
cargo build --profile perf --features dial9
# Run with telemetry + CPU/schedule profiling
$ DIAL9_ENABLED=true DIAL9_TRACE_DIR=/tmp/qdrant-dial9 ./target/perf/qdrant
```
Env knobs: `DIAL9_TRACE_DIR` (default `/tmp/dial9-traces`), `DIAL9_MAX_DISK_USAGE_MB`,
`DIAL9_ROTATION_SECS`, `DIAL9_TASK_TRACKING_ENABLED`, `DIAL9_CPU_PROFILE_ENABLED`,
`DIAL9_SCHEDULE_PROFILE_ENABLED`, `DIAL9_CPU_SAMPLE_HZ`. Booleans accept `1/true/yes/on` and
`0/false/no/off`; blank or unrecognized values log a warning and keep the default.
Use a fresh `DIAL9_TRACE_DIR` per run: segments from separate runs in one directory are merged into
a single trace spanning the gap between them.
View traces with the hosted viewer at <https://dial9-tokio-telemetry.netlify.app/>, or install the
CLI with `cargo install --locked dial9 --features cli` and run
`dial9 serve --local-dir $DIAL9_TRACE_DIR`. In the browser, the **Browse** tab is built for dial9's
S3 key layout and stays empty for a local directory; use the **Raw** tab with an empty prefix, or
open a segment directly at `viewer.html?trace=trace.0.bin.gz`.
### Pyroscope (continuous CPU profiling) ### Pyroscope (continuous CPU profiling)
Qdrant has built-in [Pyroscope](https://grafana.com/oss/pyroscope/) integration for continuous CPU profiling. Linux only. Qdrant has built-in [Pyroscope](https://grafana.com/oss/pyroscope/) integration for continuous CPU profiling. Linux only.
+7
View File
@@ -14,6 +14,7 @@ workspace = true
[features] [features]
tracing = ["dep:tracing", "api/tracing", "collection/tracing", "segment/tracing"] tracing = ["dep:tracing", "api/tracing", "collection/tracing", "segment/tracing"]
staging = ["collection/staging"] staging = ["collection/staging"]
dial9 = ["dep:dial9"]
[dev-dependencies] [dev-dependencies]
collection = { path = "../collection", features = ["testing"] } collection = { path = "../collection", features = ["testing"] }
@@ -70,3 +71,9 @@ async-trait = { workspace = true }
tracing = { workspace = true, optional = true } tracing = { workspace = true, optional = true }
tracing-appender = "0.2" tracing-appender = "0.2"
# Optional Tokio telemetry (https://github.com/dial9-rs/dial9)
dial9 = { version = "0.5.0", optional = true, default-features = false, features = [
"tokio",
"cpu-profiling",
] }
+25 -10
View File
@@ -19,13 +19,26 @@ use tokio::runtime::{self, Runtime};
/// heavy work runs on the blocking pool, not the async workers. /// heavy work runs on the blocking pool, not the async workers.
const SEARCH_ASYNC_WORKERS: usize = 1; const SEARCH_ASYNC_WORKERS: usize = 1;
/// Finish building a runtime, optionally attaching dial9 telemetry hooks.
fn finish_runtime(builder: runtime::Builder, runtime_name: &str) -> io::Result<Runtime> {
cfg_select! {
feature = "dial9" => crate::dial9_telemetry::build_runtime(builder, runtime_name),
not(feature = "dial9") => {
let _ = runtime_name;
let mut builder = builder;
builder.build()
}
}
}
/// Build the CPU-friendly search runtime. Sized for steady-state CPU /// Build the CPU-friendly search runtime. Sized for steady-state CPU
/// saturation: one blocking thread per CPU, so concurrent searches don't /// saturation: one blocking thread per CPU, so concurrent searches don't
/// thrash. /// thrash.
pub(super) fn create_high_cpu_search_runtime(max_search_threads: usize) -> io::Result<Runtime> { pub(super) fn create_high_cpu_search_runtime(max_search_threads: usize) -> io::Result<Runtime> {
let blocking_threads = high_cpu_blocking_threads(max_search_threads); let blocking_threads = high_cpu_blocking_threads(max_search_threads);
let async_workers = SEARCH_ASYNC_WORKERS.min(blocking_threads.max(1)); let async_workers = SEARCH_ASYNC_WORKERS.min(blocking_threads.max(1));
runtime::Builder::new_multi_thread() let mut builder = runtime::Builder::new_multi_thread();
builder
.worker_threads(async_workers) .worker_threads(async_workers)
.max_blocking_threads(blocking_threads) .max_blocking_threads(blocking_threads)
.enable_all() .enable_all()
@@ -33,8 +46,8 @@ pub(super) fn create_high_cpu_search_runtime(max_search_threads: usize) -> io::R
static ATOMIC_ID: AtomicUsize = AtomicUsize::new(0); static ATOMIC_ID: AtomicUsize = AtomicUsize::new(0);
let id = ATOMIC_ID.fetch_add(1, Ordering::SeqCst); let id = ATOMIC_ID.fetch_add(1, Ordering::SeqCst);
format!("search-cpu-{id}") format!("search-cpu-{id}")
}) });
.build() finish_runtime(builder, "search-cpu")
} }
/// Build the IO-friendly search runtime. Blocking pool size comes from /// Build the IO-friendly search runtime. Blocking pool size comes from
@@ -43,7 +56,8 @@ pub(super) fn create_high_cpu_search_runtime(max_search_threads: usize) -> io::R
pub(super) fn create_high_io_search_runtime(max_search_threads: usize) -> io::Result<Runtime> { pub(super) fn create_high_io_search_runtime(max_search_threads: usize) -> io::Result<Runtime> {
let blocking_threads = high_io_blocking_threads(max_search_threads); let blocking_threads = high_io_blocking_threads(max_search_threads);
let async_workers = SEARCH_ASYNC_WORKERS.min(blocking_threads.max(1)); let async_workers = SEARCH_ASYNC_WORKERS.min(blocking_threads.max(1));
runtime::Builder::new_multi_thread() let mut builder = runtime::Builder::new_multi_thread();
builder
.worker_threads(async_workers) .worker_threads(async_workers)
.max_blocking_threads(blocking_threads) .max_blocking_threads(blocking_threads)
.enable_all() .enable_all()
@@ -51,8 +65,8 @@ pub(super) fn create_high_io_search_runtime(max_search_threads: usize) -> io::Re
static ATOMIC_ID: AtomicUsize = AtomicUsize::new(0); static ATOMIC_ID: AtomicUsize = AtomicUsize::new(0);
let id = ATOMIC_ID.fetch_add(1, Ordering::SeqCst); let id = ATOMIC_ID.fetch_add(1, Ordering::SeqCst);
format!("search-io-{id}") format!("search-io-{id}")
}) });
.build() finish_runtime(builder, "search-io")
} }
pub(super) fn create_update_runtime(max_optimization_threads: usize) -> io::Result<Runtime> { pub(super) fn create_update_runtime(max_optimization_threads: usize) -> io::Result<Runtime> {
@@ -70,11 +84,12 @@ pub(super) fn create_update_runtime(max_optimization_threads: usize) -> io::Resu
if max_optimization_threads > 0 { if max_optimization_threads > 0 {
builder.max_blocking_threads(max_optimization_threads); builder.max_blocking_threads(max_optimization_threads);
} }
builder.build() finish_runtime(builder, "update")
} }
pub(super) fn create_general_purpose_runtime() -> io::Result<Runtime> { pub(super) fn create_general_purpose_runtime() -> io::Result<Runtime> {
runtime::Builder::new_multi_thread() let mut builder = runtime::Builder::new_multi_thread();
builder
.enable_time() .enable_time()
.enable_io() .enable_io()
.worker_threads(max(common::cpu::get_num_cpus(), 2)) .worker_threads(max(common::cpu::get_num_cpus(), 2))
@@ -82,8 +97,8 @@ pub(super) fn create_general_purpose_runtime() -> io::Result<Runtime> {
static ATOMIC_ID: AtomicUsize = AtomicUsize::new(0); static ATOMIC_ID: AtomicUsize = AtomicUsize::new(0);
let id = ATOMIC_ID.fetch_add(1, Ordering::SeqCst); let id = ATOMIC_ID.fetch_add(1, Ordering::SeqCst);
format!("general-{id}") format!("general-{id}")
}) });
.build() finish_runtime(builder, "general")
} }
/// Blocking-thread count for the high-CPU search pool. /// Blocking-thread count for the high-CPU search pool.
+216
View File
@@ -0,0 +1,216 @@
//! Optional [dial9](https://github.com/dial9-rs/dial9) Tokio telemetry.
//!
//! Enabled at compile time with the `dial9` feature and at runtime with
//! `DIAL9_ENABLED=true`. When disabled (feature off, or env unset/false),
//! runtime construction is unchanged and recording is a no-op.
use std::sync::OnceLock;
use std::time::Duration;
use std::{env, io};
use dial9::cpu::{CpuProfilingConfig, SchedEventConfig};
use dial9::{
Dial9Handle, Dial9HandleTokioExt, DiskBuffer, Recorder, RecorderPerfExt, TokioAttachOptions,
recorder_disabled, recorder_or_disabled,
};
use tokio::runtime::{Builder, Runtime};
/// Process-wide dial9 handle for attaching Tokio runtimes.
static HANDLE: OnceLock<Dial9Handle> = OnceLock::new();
/// Holds the dial9 recorder for the process lifetime.
///
/// Constructed by [`init`]. Dropping it (at process shutdown) flushes and
/// stops the recorder.
#[derive(Debug)]
pub struct Dial9Guard {
recorder: Option<Recorder>,
}
impl Drop for Dial9Guard {
fn drop(&mut self) {
if let Some(recorder) = self.recorder.take() {
recorder.graceful_shutdown(Duration::from_secs(5));
}
}
}
/// Read a boolean `DIAL9_*` variable.
///
/// Matches dial9's own env parsing: trimmed, case-insensitive, and accepting
/// the usual spellings rather than only `str::parse::<bool>`. A blank or
/// unrecognized value warns and falls back to `default`, so an operator who
/// writes `DIAL9_CPU_PROFILE_ENABLED=0` is not silently left with profiling on.
fn env_bool(name: &str, default: bool) -> bool {
let Ok(raw) = env::var(name) else {
return default;
};
match raw.trim().to_ascii_lowercase().as_str() {
"" => default,
"1" | "t" | "true" | "y" | "yes" | "on" => true,
"0" | "f" | "false" | "n" | "no" | "off" => false,
other => {
log::warn!("dial9: ignoring invalid {name}={other:?}, using {default}");
default
}
}
}
/// Read a `DIAL9_*` variable that dial9 requires to be non-zero.
///
/// Zero is rejected rather than passed through: dial9 treats a zero disk budget
/// as "evict everything" (killing recording seconds after it starts) and fails
/// outright on a zero rotation period.
fn env_positive_u64(name: &str, default: u64) -> u64 {
let Ok(raw) = env::var(name) else {
return default;
};
let raw = raw.trim();
if raw.is_empty() {
return default;
}
match raw.parse::<u64>() {
Ok(value) if value > 0 => value,
_ => {
log::warn!("dial9: ignoring invalid {name}={raw:?}, using {default}");
default
}
}
}
/// Read a path-valued `DIAL9_*` variable, treating a blank value as unset.
///
/// `env::var` returns `Ok("")` for a variable that is set but empty (a common
/// shape from `docker run -e VAR` and unset compose interpolations), which dial9
/// would otherwise resolve to relative paths in the working directory.
fn env_path(name: &str, default: &str) -> String {
match env::var(name) {
Ok(raw) if !raw.trim().is_empty() => raw.trim().to_string(),
_ => default.to_string(),
}
}
/// Initialize dial9 from `DIAL9_*` environment variables.
///
/// Safe to call once at process start, before any Tokio runtimes are built.
/// Returns a guard that must be kept alive until shutdown.
///
/// `application_version` is recorded into every trace segment (e.g. the qdrant
/// binary version).
///
/// Recognized variables (subset of dial9's env surface). Booleans accept
/// `1/t/true/y/yes/on` and `0/f/false/n/no/off`; blank or unrecognized values
/// warn and keep the default:
///
/// - `DIAL9_ENABLED`: record at all (default: off)
/// - `DIAL9_TRACE_DIR`: trace directory (default: `/tmp/dial9-traces`)
/// - `DIAL9_MAX_DISK_USAGE_MB`: total on-disk budget, must be > 0 (default: `1024`)
/// - `DIAL9_ROTATION_SECS`: segment rotation period, must be > 0 (default: `60`)
/// - `DIAL9_CPU_PROFILE_ENABLED`: CPU stack sampling (default: on, Linux)
/// - `DIAL9_CPU_SAMPLE_HZ`: sampling frequency, must be > 0 (default: `99`)
/// - `DIAL9_SCHEDULE_PROFILE_ENABLED`: sched-switch capture (default: on, Linux)
pub fn init(application_version: &str) -> Dial9Guard {
let enabled = env_bool("DIAL9_ENABLED", false);
if !enabled {
log::info!("dial9 telemetry disabled (set DIAL9_ENABLED=true to enable)");
let recorder = recorder_disabled();
let _ = HANDLE.set(recorder.handle().clone());
return Dial9Guard {
recorder: Some(recorder),
};
}
let trace_dir = env_path("DIAL9_TRACE_DIR", "/tmp/dial9-traces");
let max_disk_mb = env_positive_u64("DIAL9_MAX_DISK_USAGE_MB", 1024);
let rotation_secs = env_positive_u64("DIAL9_ROTATION_SECS", 60);
let cpu_profile_enabled = env_bool("DIAL9_CPU_PROFILE_ENABLED", cfg!(target_os = "linux"));
let schedule_profile_enabled =
env_bool("DIAL9_SCHEDULE_PROFILE_ENABLED", cfg!(target_os = "linux"));
let cpu_sample_hz = env_positive_u64("DIAL9_CPU_SAMPLE_HZ", 99);
let writer = DiskBuffer::builder()
.base_path(&trace_dir)
.max_total_size(max_disk_mb.saturating_mul(1024 * 1024))
.rotation_period(Duration::from_secs(rotation_secs))
.build();
if let Err(ref err) = writer {
// Return before the profiler setup below. `with_cpu_profiling` and
// `with_sched_events` start their profilers immediately (opening a perf
// event per thread, and installing a process-global signal handler on
// the fallback path), and `build()` would only discard them.
log::error!(
"dial9: failed to create trace writer at {trace_dir}: {err}; telemetry disabled"
);
let recorder = recorder_disabled();
let _ = HANDLE.set(recorder.handle().clone());
return Dial9Guard {
recorder: Some(recorder),
};
}
let mut builder = recorder_or_disabled(writer).segment_metadata([
("service".to_string(), "qdrant".to_string()),
(
"application.version".to_string(),
application_version.to_string(),
),
]);
if cpu_profile_enabled {
builder =
builder.with_cpu_profiling(CpuProfilingConfig::default().frequency_hz(cpu_sample_hz));
}
if schedule_profile_enabled {
builder = builder.with_sched_events(SchedEventConfig::default());
}
let recorder = builder.build();
if recorder.handle().is_connected() {
if let Err(err) = recorder.install_global_handle() {
log::warn!("dial9: could not install global handle: {err}");
}
log::info!(
"dial9 telemetry enabled; writing traces to {trace_dir} \
(max {max_disk_mb} MiB, rotate every {rotation_secs}s, \
cpu_profile={cpu_profile_enabled}, sched_profile={schedule_profile_enabled}, \
sample_hz={cpu_sample_hz})"
);
} else {
log::error!(
"dial9: recorder not connected after init (trace_dir={trace_dir}); \
check DiskBuffer / cpu-profiling permissions; telemetry inactive"
);
}
let _ = HANDLE.set(recorder.handle().clone());
Dial9Guard {
recorder: Some(recorder),
}
}
fn handle() -> Dial9Handle {
HANDLE.get().cloned().unwrap_or_else(Dial9Handle::disabled)
}
/// Build a Tokio runtime, attaching dial9 hooks when telemetry is active.
pub fn build_runtime(mut builder: Builder, runtime_name: &str) -> io::Result<Runtime> {
let handle = handle();
if !handle.is_connected() {
return builder.build();
}
let task_tracking = env_bool("DIAL9_TASK_TRACKING_ENABLED", true);
let options = TokioAttachOptions::builder()
.runtime_name(runtime_name.to_string())
.task_tracking_enabled(task_tracking)
.build();
handle.attach_tokio_runtime(builder, options)
}
+2
View File
@@ -14,6 +14,8 @@ pub mod audit;
pub mod audit_reader; pub mod audit_reader;
mod common; mod common;
pub mod content_manager; pub mod content_manager;
#[cfg(feature = "dial9")]
pub mod dial9_telemetry;
pub mod dispatcher; pub mod dispatcher;
pub mod issues_subscribers; pub mod issues_subscribers;
pub mod quota; pub mod quota;
+8
View File
@@ -426,6 +426,14 @@ fn main() -> anyhow::Result<()> {
welcome(&settings); welcome(&settings);
//
// Optional dial9 Tokio telemetry (compile with `--features dial9`,
// enable at runtime with DIAL9_ENABLED=true). Must run before any
// TableOfContent Tokio runtimes are constructed.
//
#[cfg(feature = "dial9")]
let _dial9_guard = storage::dial9_telemetry::init(env!("CARGO_PKG_VERSION"));
// //
// Audit logging // Audit logging
// //