feat(info): report all rustfs features (#6722)

* feat(info): report all rustfs features

Co-Authored-By: heihutu <heihutu@gmail.com>

* chore(deps): update s3s revision

Co-Authored-By: heihutu <heihutu@gmail.com>

* fix(obs): adapt dial9 telemetry API

Co-Authored-By: heihutu <heihutu@gmail.com>

---------

Co-authored-by: heihutu <heihutu@gmail.com>
This commit is contained in:
houseme
2026-08-27 14:45:50 +08:00
committed by GitHub
parent 9a1a15ca58
commit c006f84461
11 changed files with 344 additions and 143 deletions
Generated
+153 -76
View File
@@ -1623,6 +1623,22 @@ dependencies = [
"digest 0.11.3", "digest 0.11.3",
] ]
[[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 = "block-buffer" name = "block-buffer"
version = "0.10.4" version = "0.10.4"
@@ -1806,6 +1822,12 @@ dependencies = [
"libbz2-rs-sys", "libbz2-rs-sys",
] ]
[[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.5" version = "1.2.5"
@@ -2300,6 +2322,15 @@ version = "0.8.7"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b"
[[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 = "cpubits" name = "cpubits"
version = "0.1.1" version = "0.1.1"
@@ -3453,25 +3484,23 @@ dependencies = [
[[package]] [[package]]
name = "deadpool" name = "deadpool"
version = "0.12.3" version = "0.13.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0be2b1d1d6ec8d846f05e137292d0b89133caf95ef33695424c09568bdd39b1b" checksum = "3e98a7e119cd347f4201e1159b19831029e203e2d8b790547708e8157b4acf1e"
dependencies = [ dependencies = [
"deadpool-runtime", "deadpool-runtime",
"lazy_static",
"num_cpus",
"tokio", "tokio",
] ]
[[package]] [[package]]
name = "deadpool-postgres" name = "deadpool-postgres"
version = "0.14.1" version = "0.14.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "3d697d376cbfa018c23eb4caab1fd1883dd9c906a8c034e8d9a3cb06a7e0bef9" checksum = "65a536565624b97fc19f758cd01b15d12908d3344425066efc8162236fbd3749"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"deadpool", "deadpool",
"getrandom 0.2.17", "getrandom 0.4.3",
"tokio", "tokio",
"tokio-postgres", "tokio-postgres",
"tracing", "tracing",
@@ -3479,9 +3508,9 @@ dependencies = [
[[package]] [[package]]
name = "deadpool-runtime" name = "deadpool-runtime"
version = "0.1.4" version = "0.3.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "092966b41edc516079bdf31ec78a2e0588d1d0c08f78b91d8307215928642b2b" checksum = "2657f61fb1dd8bf37a8d51093cc7cee4e77125b22f7753f49b289f831bec2bae"
dependencies = [ dependencies = [
"tokio", "tokio",
] ]
@@ -3685,35 +3714,61 @@ dependencies = [
] ]
[[package]] [[package]]
name = "dial9-macro" name = "dial9-core"
version = "0.3.7" 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 = "1a7e31f073f2e14e5a9d338c543a0601aeaf7c43fc428cd59ce417230d0db37d" checksum = "9e8cbbc8955394be626249a3b52ddd6bf664373661eaeae70d87eb12bf6f20b6"
dependencies = [
"proc-macro2",
"quote",
"syn 2.0.119",
]
[[package]]
name = "dial9-tokio-telemetry"
version = "0.3.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b511dfd54f5191f7eb86856fe19d8e3c8f71673bd256e1bfa1c8128fbbc0cdb"
dependencies = [ dependencies = [
"arc-swap", "arc-swap",
"bon", "bon",
"bytes", "bytes",
"crossbeam-queue", "crossbeam-queue",
"dial9-macro", "dial9-trace-format",
"flate2",
"futures-util",
"libc",
"metrique",
"metrique-timesource",
"tokio",
"tokio-util",
"tracing",
"ulid",
]
[[package]]
name = "dial9-perf-self-profile"
version = "0.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a8f65948455c504bf08576c7b5cfe93bfcaa486d661dc4ee85c2a6309ab89629"
dependencies = [
"blazesym",
"bon",
"bytes",
"crossbeam-utils",
"dial9-core",
"dial9-trace-format",
"libc",
"perf-event-data",
"perf-event-open-sys2",
"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", "dial9-trace-format",
"flate2", "flate2",
"futures-util", "futures-util",
"hostname", "hostname",
"libc", "libc",
"metrique",
"metrique-timesource", "metrique-timesource",
"metrique-writer",
"pin-project-lite", "pin-project-lite",
"serde", "serde",
"serde_json", "serde_json",
@@ -3725,20 +3780,22 @@ dependencies = [
[[package]] [[package]]
name = "dial9-trace-format" name = "dial9-trace-format"
version = "0.4.1" 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 = "b3636d6ec60d94840cc414dcd6a95c77b3ba0a7b86d43fd035b89662eeb5cfa7" checksum = "86083b7240114b2d0da4a7e9d041571d80ca3b1f92ae0ec794f1be21709daa6a"
dependencies = [ dependencies = [
"dial9-trace-format-derive", "dial9-trace-format-derive",
"serde", "serde",
"typeid",
] ]
[[package]] [[package]]
name = "dial9-trace-format-derive" name = "dial9-trace-format-derive"
version = "0.4.1" 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 = "fff7c2855b73d0de34bc31d6dc7afbf0f6ce230a668403ac2b57b21d1ffe3928" checksum = "9309248f12e414d88bcc9505f78b0c9ed47f79c5b62db611493e19a612cdc9d6"
dependencies = [ dependencies = [
"proc-macro-crate",
"proc-macro2", "proc-macro2",
"quote", "quote",
"syn 2.0.119", "syn 2.0.119",
@@ -4558,6 +4615,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",
] ]
@@ -4581,20 +4641,20 @@ dependencies = [
[[package]] [[package]]
name = "google-cloud-auth" name = "google-cloud-auth"
version = "1.15.0" version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f54aab44c16b8463ae11b165a87c3d484780231f157bb1ed65843d591beb5abd" checksum = "ff461519b1a948200f163574be072753bcfb462a323f0eb426629d89872dd685"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"aws-lc-rs", "aws-lc-rs",
"base64 0.22.1", "base64 0.23.1",
"bytes", "bytes",
"chrono",
"google-cloud-gax", "google-cloud-gax",
"hex", "hex",
"hmac 0.13.0", "hmac 0.13.0",
"http 1.5.0", "http 1.5.0",
"jsonwebtoken 10.4.0", "jiff",
"jsonwebtoken",
"reqwest", "reqwest",
"rustc_version", "rustc_version",
"rustls", "rustls",
@@ -4610,9 +4670,9 @@ dependencies = [
[[package]] [[package]]
name = "google-cloud-gax" name = "google-cloud-gax"
version = "1.13.0" version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b9a46dd0fd026bbc4a5d84e6ab0c941cee6e3b057976a0bb107fdb5238ce598f" checksum = "c5615cff28ee59cfe52fbb4c11b8b1e77f650296e2ea4f4c2b7757ac6b19e752"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures", "futures",
@@ -4625,13 +4685,14 @@ dependencies = [
"serde_json", "serde_json",
"thiserror 2.0.20", "thiserror 2.0.20",
"tokio", "tokio",
"tokio-stream",
] ]
[[package]] [[package]]
name = "google-cloud-gax-internal" name = "google-cloud-gax-internal"
version = "0.7.16" version = "0.7.18"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "fb04c54317ace06d489213f761797240b3046142a9b7ce6b9a82a9d134e193d1" checksum = "d2766757d877a7a8ac23da9884cb0e3f10ed9b75a0ce59801ce6b19bf9d5819e"
dependencies = [ dependencies = [
"bytes", "bytes",
"futures", "futures",
@@ -4668,9 +4729,9 @@ dependencies = [
[[package]] [[package]]
name = "google-cloud-iam-v1" name = "google-cloud-iam-v1"
version = "1.11.0" version = "1.12.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34cdf5acc7ef946ee2db7a7f62bd436d8395a6543b4beef110cdc061fcf578bb" checksum = "5f962b40234b1531e6ef73f7558871c96e117231e962086c98804323fb8d2c82"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"bytes", "bytes",
@@ -4686,9 +4747,9 @@ dependencies = [
[[package]] [[package]]
name = "google-cloud-longrunning" name = "google-cloud-longrunning"
version = "1.12.0" version = "1.13.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1e6ce05df0aea2c08472983ce2bbbed9483cbb637b89ff69a7c4ef94371fe4f2" checksum = "1c0363c5389ffda2b55cd8a86eef4b19a3481a48467c91dc5d77f626a9572766"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"bytes", "bytes",
@@ -4704,9 +4765,9 @@ dependencies = [
[[package]] [[package]]
name = "google-cloud-lro" name = "google-cloud-lro"
version = "1.9.0" version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cd7cca2b991d619525d72a170ca7f413cb520872702442da22ac9af650a8e786" checksum = "47af3deef75c14a2983c430898d960c765bddbcc9f9188ca0563108e9227cfe7"
dependencies = [ dependencies = [
"google-cloud-gax", "google-cloud-gax",
"google-cloud-gax-internal", "google-cloud-gax-internal",
@@ -4733,14 +4794,13 @@ dependencies = [
[[package]] [[package]]
name = "google-cloud-storage" name = "google-cloud-storage"
version = "1.17.0" version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9227f65175fa91a6e41f246797917697efdadfe09dd8ea84ad8b737a71efbd28" checksum = "973399251b245c63f1d02d0768772833dcf1372ee358f593fb9159de8fe9c7d4"
dependencies = [ dependencies = [
"async-trait", "async-trait",
"base64 0.22.1", "base64 0.23.1",
"bytes", "bytes",
"chrono",
"crc32c", "crc32c",
"futures", "futures",
"google-cloud-auth", "google-cloud-auth",
@@ -4755,6 +4815,7 @@ dependencies = [
"hex", "hex",
"http 1.5.0", "http 1.5.0",
"http-body 1.1.0", "http-body 1.1.0",
"jiff",
"md5", "md5",
"percent-encoding", "percent-encoding",
"prost 0.14.4", "prost 0.14.4",
@@ -5784,22 +5845,6 @@ dependencies = [
"wasm-bindgen", "wasm-bindgen",
] ]
[[package]]
name = "jsonwebtoken"
version = "10.4.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eba32bfb4ffdeaca3e34431072faf01745c9b26d25504aa7a6cf5684334fc4fc"
dependencies = [
"aws-lc-rs",
"base64 0.22.1",
"getrandom 0.2.17",
"js-sys",
"serde",
"serde_json",
"signature 2.2.0",
"zeroize",
]
[[package]] [[package]]
name = "jsonwebtoken" name = "jsonwebtoken"
version = "11.0.0" version = "11.0.0"
@@ -6357,7 +6402,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2b55bfa39e6f5e44a37a59a794915ce36d04371471cf62ae365cd9703f58a5e0" checksum = "2b55bfa39e6f5e44a37a59a794915ce36d04371471cf62ae365cd9703f58a5e0"
dependencies = [ dependencies = [
"itoa", "itoa",
"jiff",
"metrique-core", "metrique-core",
"metrique-macro", "metrique-macro",
"metrique-service-metrics", "metrique-service-metrics",
@@ -6366,7 +6410,6 @@ dependencies = [
"metrique-writer-core", "metrique-writer-core",
"metrique-writer-macro", "metrique-writer-macro",
"ryu", "ryu",
"serde_json",
"tokio", "tokio",
] ]
@@ -7611,6 +7654,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.13.1",
"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",
]
[[package]] [[package]]
name = "petgraph" name = "petgraph"
version = "0.7.1" version = "0.7.1"
@@ -9443,7 +9507,6 @@ dependencies = [
name = "rustfs-audit" name = "rustfs-audit"
version = "1.0.0-rc.4" version = "1.0.0-rc.4"
dependencies = [ dependencies = [
"async-trait",
"const-str", "const-str",
"futures", "futures",
"hashbrown 0.17.1", "hashbrown 0.17.1",
@@ -9537,7 +9600,7 @@ dependencies = [
"base64-simd", "base64-simd",
"chacha20poly1305", "chacha20poly1305",
"hotpath", "hotpath",
"jsonwebtoken 11.0.0", "jsonwebtoken",
"pbkdf2 0.13.0", "pbkdf2 0.13.0",
"rand 0.10.2", "rand 0.10.2",
"rsa 0.10.0-rc.18", "rsa 0.10.0-rc.18",
@@ -9582,7 +9645,6 @@ dependencies = [
"flatbuffers", "flatbuffers",
"futures", "futures",
"futures-util", "futures-util",
"glob",
"google-cloud-auth", "google-cloud-auth",
"google-cloud-storage", "google-cloud-storage",
"hex-simd", "hex-simd",
@@ -9789,7 +9851,7 @@ dependencies = [
"hmac 0.13.0", "hmac 0.13.0",
"hotpath", "hotpath",
"http 1.5.0", "http 1.5.0",
"jsonwebtoken 11.0.0", "jsonwebtoken",
"moka", "moka",
"openidconnect", "openidconnect",
"pollster", "pollster",
@@ -10220,7 +10282,7 @@ dependencies = [
"hotpath", "hotpath",
"ipnetwork", "ipnetwork",
"jiff", "jiff",
"jsonwebtoken 11.0.0", "jsonwebtoken",
"moka", "moka",
"pollster", "pollster",
"proptest", "proptest",
@@ -10441,7 +10503,6 @@ dependencies = [
"s3s", "s3s",
"serde", "serde",
"serde_json", "serde_json",
"sha1 0.11.0",
"sha2 0.11.0", "sha2 0.11.0",
"thiserror 2.0.20", "thiserror 2.0.20",
"time", "time",
@@ -10981,7 +11042,7 @@ checksum = "9774ba4a74de5f7b1c1451ed6cd5285a32eddb5cccb8cc655a4e50009e06477f"
[[package]] [[package]]
name = "s3s" name = "s3s"
version = "0.15.0" version = "0.15.0"
source = "git+https://github.com/rustfs/s3s.git?rev=f4dedc905ec621fa85a4686df6304190b55375f6#f4dedc905ec621fa85a4686df6304190b55375f6" source = "git+https://github.com/rustfs/s3s.git?rev=0f6f83d98b37fd9edcaa3be573db4aa8f568e088#0f6f83d98b37fd9edcaa3be573db4aa8f568e088"
dependencies = [ dependencies = [
"arc-swap", "arc-swap",
"arrayvec", "arrayvec",
@@ -12813,12 +12874,28 @@ version = "0.12.3"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e" checksum = "8e28f89b80c87b8fb0cf04ab448d5dd0dd0ade2f8891bae878de66a75a28600e"
[[package]]
name = "typeid"
version = "1.0.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "bc7d623258602320d5c55d1bc22793b57daff0ec7efc270ea7d55ce1d5f5471c"
[[package]] [[package]]
name = "typenum" name = "typenum"
version = "1.20.1" version = "1.20.1"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20" checksum = "b6f5e870be6c3b371b77fe0ee0bafb859fa4964b4404c27de1d380043c4dda20"
[[package]]
name = "ulid"
version = "1.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "470dbf6591da1b39d43c14523b2b469c86879a53e8b758c8e090a470fe7b1fbe"
dependencies = [
"rand 0.9.5",
"web-time",
]
[[package]] [[package]]
name = "unarray" name = "unarray"
version = "0.1.4" version = "0.1.4"
@@ -12953,9 +13030,9 @@ checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821"
[[package]] [[package]]
name = "uuid" name = "uuid"
version = "1.25.0" version = "1.26.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f053576934f05a761a402421fbbe3d425d9366f75f978806a037b3ca481abecc" checksum = "b5772d71c9be8a8a6ac2117d949c5b224c1b72241bb611d9a3012edcf8af7812"
dependencies = [ dependencies = [
"getrandom 0.4.3", "getrandom 0.4.3",
"js-sys", "js-sys",
+5 -5
View File
@@ -259,8 +259,8 @@ enumset = "1.1.14"
faster-hex = "0.10.0" faster-hex = "0.10.0"
flate2 = "1.1.9" flate2 = "1.1.9"
glob = "0.3.4" glob = "0.3.4"
google-cloud-storage = "1.17.0" google-cloud-storage = "1.18.0"
google-cloud-auth = "1.15.0" google-cloud-auth = "1.16.0"
hashbrown = { version = "0.17.1" } hashbrown = { version = "0.17.1" }
# Base32 for RFC 6238 TOTP shared secrets (RFC 4648 unpadded, the alphabet # Base32 for RFC 6238 TOTP shared secrets (RFC 4648 unpadded, the alphabet
# every authenticator app expects). Already in the graph transitively. # every authenticator app expects). Already in the graph transitively.
@@ -304,7 +304,7 @@ rustify = { version = "0.7", default-features = false }
rustix = { version = "1.1.4" } rustix = { version = "1.1.4" }
rust-embed = { version = "8.12.0" } rust-embed = { version = "8.12.0" }
rustc-hash = { version = "2.1.3" } rustc-hash = { version = "2.1.3" }
s3s = { git = "https://github.com/rustfs/s3s.git", rev = "f4dedc905ec621fa85a4686df6304190b55375f6", version = "0.15.0", features = ["minio"] } s3s = { git = "https://github.com/rustfs/s3s.git", rev = "0f6f83d98b37fd9edcaa3be573db4aa8f568e088", version = "0.15.0", features = ["minio"] }
serial_test = "4.0.1" serial_test = "4.0.1"
shadow-rs = { default-features = false, version = "2.0.0" } shadow-rs = { default-features = false, version = "2.0.0" }
siphasher = "1.0.3" siphasher = "1.0.3"
@@ -327,7 +327,7 @@ tracing-subscriber = { version = "0.3.23" }
transform-stream = "0.3.1" transform-stream = "0.3.1"
url = "2.5.8" url = "2.5.8"
urlencoding = "2.1.3" urlencoding = "2.1.3"
uuid = { version = "1.25.0" } uuid = { version = "1.26.0" }
vaultrs = { version = "0.8.0" } vaultrs = { version = "0.8.0" }
tar = "0.4.46" tar = "0.4.46"
walkdir = "2.5.0" walkdir = "2.5.0"
@@ -341,7 +341,7 @@ zstd = "0.13.3"
# Observability and Metrics # Observability and Metrics
metrics = "0.24.6" metrics = "0.24.6"
metrics-util = "0.20" metrics-util = "0.20"
dial9-tokio-telemetry = "0.3" dial9-tokio-telemetry = "0.5.0"
opentelemetry = { version = "0.32.0" } opentelemetry = { version = "0.32.0" }
opentelemetry-appender-tracing = { version = "0.32.0" } opentelemetry-appender-tracing = { version = "0.32.0" }
opentelemetry-otlp = { version = "0.32.0" } opentelemetry-otlp = { version = "0.32.0" }
-1
View File
@@ -68,7 +68,6 @@ tracing = { workspace = true, features = ["std", "attributes"] }
[dev-dependencies] [dev-dependencies]
rustfs-targets = { workspace = true, features = ["test-support"] } rustfs-targets = { workspace = true, features = ["test-support"] }
async-trait = { workspace = true }
temp-env = { workspace = true } temp-env = { workspace = true }
url = { workspace = true } url = { workspace = true }
-1
View File
@@ -146,7 +146,6 @@ bytes = { workspace = true, features = ["serde"] }
byteorder = { workspace = true } byteorder = { workspace = true }
chrono = { workspace = true, features = ["serde"] } chrono = { workspace = true, features = ["serde"] }
jiff = { workspace = true, features = ["serde"] } jiff = { workspace = true, features = ["serde"] }
glob = { workspace = true }
thiserror.workspace = true thiserror.workspace = true
flatbuffers.workspace = true flatbuffers.workspace = true
futures.workspace = true futures.workspace = true
+1 -1
View File
@@ -74,7 +74,7 @@ hotpath-cpu = [
# Tokio runtime-level telemetry. Requires a `--cfg tokio_unstable` build; the # Tokio runtime-level telemetry. Requires a `--cfg tokio_unstable` build; the
# build script fails the compile when that flag is missing. Off by default so # build script fails the compile when that flag is missing. Off by default so
# ordinary builds neither pay for nor depend on Tokio's unstable API. # ordinary builds neither pay for nor depend on Tokio's unstable API.
dial9 = ["dep:dial9-tokio-telemetry"] dial9 = ["dep:dial9-tokio-telemetry", "dial9-tokio-telemetry/process-resource"]
# #
# NOTE: there is deliberately no `dial9-taskdump` feature. dial9 only captures a # NOTE: there is deliberately no `dial9-taskdump` feature. dial9 only captures a
# task dump for futures it wrapped itself, i.e. those spawned via # task dump for futures it wrapped itself, i.e. those spawned via
+2 -2
View File
@@ -76,7 +76,7 @@ pub struct Dial9Config {
/// Directory where trace files are written /// Directory where trace files are written
pub output_dir: String, pub output_dir: String,
/// Prefix for trace file names /// Trace family name under the output directory
pub file_prefix: String, pub file_prefix: String,
/// Maximum size of each trace file in bytes /// Maximum size of each trace file in bytes
@@ -158,7 +158,7 @@ impl Dial9Config {
} }
} }
/// Get the base path for trace files. /// Get the trace family directory for rotating trace segments.
pub fn base_path(&self) -> PathBuf { pub fn base_path(&self) -> PathBuf {
PathBuf::from(&self.output_dir).join(&self.file_prefix) PathBuf::from(&self.output_dir).join(&self.file_prefix)
} }
+54 -32
View File
@@ -23,15 +23,22 @@ use super::config::Dial9Config;
use super::state::{dial9_runtime_state, measure_disk_usage_bytes}; use super::state::{dial9_runtime_state, measure_disk_usage_bytes};
use super::{EVENT_DIAL9_STATE, LOG_COMPONENT_OBS, LOG_SUBSYSTEM_DIAL9}; use super::{EVENT_DIAL9_STATE, LOG_COMPONENT_OBS, LOG_SUBSYSTEM_DIAL9};
use crate::TelemetryError; use crate::TelemetryError;
use dial9_tokio_telemetry::telemetry::{ProcessResourceUsageConfig, RotatingWriter, TracedRuntime}; use dial9_tokio_telemetry::telemetry::{
Dial9Handle, Dial9HandleTokioExt, DiskBuffer, ProcessResourceUsageConfig, RecorderPerfExt, TokioAttachOptions, recorder,
};
use std::time::Duration; use std::time::Duration;
use tracing::{info, warn}; use tracing::{info, warn};
pub use dial9_tokio_telemetry::telemetry::TelemetryGuard; pub type TelemetryGuard = Dial9Handle;
type ShutdownRecorder = Box<dyn FnOnce() + Send + 'static>;
/// How often the background refresher restates trace-file disk usage. /// How often the background refresher restates trace-file disk usage.
const DISK_USAGE_REFRESH_INTERVAL: Duration = Duration::from_secs(60); const DISK_USAGE_REFRESH_INTERVAL: Duration = Duration::from_secs(60);
/// Maximum time spent flushing the recorder during graceful shutdown.
const SHUTDOWN_TIMEOUT: Duration = Duration::from_secs(5);
/// Name recorded in segment metadata so the trace viewer can label workers. /// Name recorded in segment metadata so the trace viewer can label workers.
const RUNTIME_NAME: &str = "rustfs-worker"; const RUNTIME_NAME: &str = "rustfs-worker";
@@ -43,13 +50,14 @@ const RUNTIME_NAME: &str = "rustfs-worker";
/// are lost. /// are lost.
pub struct Dial9SessionGuard { pub struct Dial9SessionGuard {
guard: TelemetryGuard, guard: TelemetryGuard,
shutdown: Option<ShutdownRecorder>,
config: Dial9Config, config: Dial9Config,
} }
impl Dial9SessionGuard { impl Dial9SessionGuard {
/// Whether the underlying telemetry session is recording. /// Whether the underlying telemetry session is recording.
pub fn is_active(&self) -> bool { pub fn is_active(&self) -> bool {
self.guard.is_enabled() self.guard.is_enabled() && self.guard.is_connected() && !self.guard.is_stopped()
} }
} }
@@ -72,8 +80,10 @@ impl Drop for Dial9SessionGuard {
state = "flushed", state = "flushed",
"dial9 state changed" "dial9 state changed"
); );
// `TelemetryGuard`'s own `Drop` flushes buffered events and seals the
// active segment; it runs immediately after this body. if let Some(shutdown) = self.shutdown.take() {
shutdown();
}
} }
} }
@@ -97,53 +107,58 @@ pub fn build_traced_runtime(
TelemetryError::Io(format!("Failed to create dial9 output directory '{}': {e}", config.output_dir)) TelemetryError::Io(format!("Failed to create dial9 output directory '{}': {e}", config.output_dir))
})?; })?;
let writer = RotatingWriter::new(config.base_path(), config.max_file_size, config.total_disk_budget()).map_err(|e| { let writer = DiskBuffer::builder()
dial9_runtime_state().record_runtime_error(&config); .base_path(config.base_path())
TelemetryError::Io(format!("Failed to create dial9 RotatingWriter: {e}")) .max_file_size(config.max_file_size)
})?; .max_total_size(config.total_disk_budget())
.build()
.map_err(|e| {
dial9_runtime_state().record_runtime_error(&config);
TelemetryError::Io(format!("Failed to create dial9 DiskBuffer: {e}"))
})?;
// `with_trace_path` transitions the builder into the state that spawns the let recorder = recorder(writer)
// background worker, which drives the segment pipeline. .with_process_resource_usage(ProcessResourceUsageConfig::default())
let traced = TracedRuntime::builder() .build();
.with_trace_path(&config.output_dir) let guard = recorder.handle().clone();
.with_task_tracking(true) let shutdown: ShutdownRecorder = Box::new(move || recorder.graceful_shutdown(SHUTDOWN_TIMEOUT));
.with_runtime_name(RUNTIME_NAME)
.with_process_resource_usage(ProcessResourceUsageConfig::default());
// `build_and_start` rather than `build`: `build` returns a live guard that let attached = guard
// never records, writing segments that contain only a header. .attach_tokio_runtime(
// builder,
// No `with_task_dumps` here. dial9 captures a task dump only for futures it TokioAttachOptions::builder()
.runtime_name(RUNTIME_NAME)
.task_tracking_enabled(true)
.build(),
)
.map(|runtime| (runtime, guard, shutdown));
// No task dumps here. dial9 captures a task dump only for futures it
// wrapped itself, i.e. those spawned via `dial9_tokio_telemetry::spawn`; // wrapped itself, i.e. those spawned via `dial9_tokio_telemetry::spawn`;
// `tokio::spawn` gets no wrapper. RustFS spawns with `tokio::spawn` // `tokio::spawn` gets no wrapper. RustFS spawns with `tokio::spawn`
// throughout, so calling `with_task_dumps` records nothing. Measured on an // throughout, so enabling task dumps records nothing. Measured on an
// identical workload: 0 dumps via `tokio::spawn`, 14709 via `dial9::spawn`. // identical workload: 0 dumps via `tokio::spawn`, 14709 via `dial9::spawn`.
// See rustfs/backlog#1157 (D9-16) and dial9-rs/dial9#477. // See rustfs/backlog#1157 (D9-16) and dial9-rs/dial9#477.
// //
// No `with_s3_uploader` here: dial9's `worker-s3` feature carries a // No `with_s3_uploader` here: dial9's `worker-s3` feature carries a
// vulnerable TLS stack. See the note in `crates/obs/Cargo.toml`. // vulnerable TLS stack. See the note in `crates/obs/Cargo.toml`.
finish_traced_runtime(traced.build_and_start(builder, writer), config) finish_traced_runtime(attached, config)
} }
/// Publish the outcome of a traced-runtime build and start the background /// Publish the outcome of a traced-runtime build and start the background
/// disk-usage refresher. /// disk-usage refresher.
fn finish_traced_runtime( fn finish_traced_runtime(
started: std::io::Result<(tokio::runtime::Runtime, TelemetryGuard)>, started: std::io::Result<(tokio::runtime::Runtime, TelemetryGuard, ShutdownRecorder)>,
config: Dial9Config, config: Dial9Config,
) -> Result<(tokio::runtime::Runtime, Dial9SessionGuard), TelemetryError> { ) -> Result<(tokio::runtime::Runtime, Dial9SessionGuard), TelemetryError> {
let (runtime, guard) = started.map_err(|e| { let (runtime, guard, shutdown) = started.map_err(|e| {
dial9_runtime_state().record_runtime_error(&config); dial9_runtime_state().record_runtime_error(&config);
TelemetryError::Io(format!("Failed to build dial9 TracedRuntime: {e}")) TelemetryError::Io(format!("Failed to attach dial9 runtime telemetry: {e}"))
})?; })?;
// `is_enabled` distinguishes a live guard from the inert one a lenient
// config produces after a build failure. It does NOT mean recording has
// started — a guard from `build` (rather than `build_and_start`) reports
// `true` while writing segments that contain only a header. Recording is
// guaranteed by the `build_and_start` call above, not by this check.
if !guard.is_enabled() { if !guard.is_enabled() {
dial9_runtime_state().record_runtime_error(&config); dial9_runtime_state().record_runtime_error(&config);
return Err(TelemetryError::Io("dial9 TracedRuntime built with telemetry disabled".to_string())); return Err(TelemetryError::Io("dial9 runtime telemetry attached with recording disabled".to_string()));
} }
dial9_runtime_state().record_runtime_started(&config); dial9_runtime_state().record_runtime_started(&config);
@@ -160,7 +175,14 @@ fn finish_traced_runtime(
"dial9 state changed" "dial9 state changed"
); );
Ok((runtime, Dial9SessionGuard { guard, config })) Ok((
runtime,
Dial9SessionGuard {
guard,
shutdown: Some(shutdown),
config,
},
))
} }
/// Periodically restate trace-file disk usage so the metrics collector can read /// Periodically restate trace-file disk usage so the metrics collector can read
+7 -7
View File
@@ -49,13 +49,13 @@
//! //!
//! # Known observability gap //! # Known observability gap
//! //!
//! `dial9`'s `RotatingWriter` stops accepting writes (its internal `Finished` //! `dial9`'s `DiskBuffer` stops accepting writes when the output directory
//! state) when the output directory disappears or a segment cannot be sealed, //! disappears or a segment cannot be sealed, and it exposes no way to observe
//! and it exposes no way to observe that from outside. `TelemetryGuard::is_enabled` //! that from outside. `Dial9Handle::is_enabled` reports whether the recorder is
//! reports how the session was *built*, not whether it is still writing. There //! connected and unpaused, not whether the disk writer is still making progress.
//! is therefore no `writer_healthy` metric: it could only ever be hard-coded to //! There is therefore no `writer_healthy` metric: it could only ever be
//! `1`. Watch `rustfs_dial9_disk_usage_bytes` — a session that is recording but //! hard-coded to `1`. Watch `rustfs_dial9_disk_usage_bytes` — a session that is
//! whose disk usage stops growing has most likely hit this state. //! recording but whose disk usage stops growing has most likely hit this state.
//! Reported upstream as dial9-rs/dial9#658. //! Reported upstream as dial9-rs/dial9#658.
mod config; mod config;
+8 -5
View File
@@ -27,6 +27,9 @@ use std::sync::OnceLock;
use std::sync::RwLock; use std::sync::RwLock;
use std::sync::atomic::{AtomicU64, Ordering}; use std::sync::atomic::{AtomicU64, Ordering};
/// Segment filename stem used by `dial9` rotating disk buffers.
const DIAL9_SEGMENT_STEM: &str = "trace";
/// Point-in-time view of dial9 runtime state. /// Point-in-time view of dial9 runtime state.
#[derive(Debug, Clone, Default)] #[derive(Debug, Clone, Default)]
pub(crate) struct Dial9RuntimeSnapshot { pub(crate) struct Dial9RuntimeSnapshot {
@@ -66,8 +69,8 @@ impl Dial9RuntimeState {
pub(super) fn record_config(&self, config: &Dial9Config) { pub(super) fn record_config(&self, config: &Dial9Config) {
*self.trace_dir.write().expect("dial9 trace_dir lock should not be poisoned") = Some(TraceLocation { *self.trace_dir.write().expect("dial9 trace_dir lock should not be poisoned") = Some(TraceLocation {
output_dir: PathBuf::from(&config.output_dir), output_dir: config.base_path(),
file_prefix: config.file_prefix.clone(), file_prefix: DIAL9_SEGMENT_STEM.to_string(),
}); });
if !config.enabled { if !config.enabled {
self.active_sessions.store(0, Ordering::Relaxed); self.active_sessions.store(0, Ordering::Relaxed);
@@ -168,11 +171,11 @@ mod tests {
#[test] #[test]
fn measure_disk_usage_sums_only_matching_prefix() { fn measure_disk_usage_sums_only_matching_prefix() {
let dir = tempdir().expect("create temp dir"); let dir = tempdir().expect("create temp dir");
std::fs::write(dir.path().join("rustfs-tokio.0.bin"), vec![0_u8; 128]).expect("write segment"); std::fs::write(dir.path().join("trace.0.bin"), vec![0_u8; 128]).expect("write segment");
std::fs::write(dir.path().join("rustfs-tokio.1.bin"), vec![0_u8; 64]).expect("write segment"); std::fs::write(dir.path().join("trace.1.bin"), vec![0_u8; 64]).expect("write segment");
std::fs::write(dir.path().join("unrelated.log"), vec![0_u8; 4096]).expect("write unrelated"); std::fs::write(dir.path().join("unrelated.log"), vec![0_u8; 4096]).expect("write unrelated");
assert_eq!(measure_disk_usage_bytes(dir.path(), "rustfs-tokio"), 192); assert_eq!(measure_disk_usage_bytes(dir.path(), DIAL9_SEGMENT_STEM), 192);
} }
#[test] #[test]
-1
View File
@@ -54,7 +54,6 @@ rustls-pki-types.workspace = true
s3s = { workspace = true, features = ["minio"] } s3s = { workspace = true, features = ["minio"] }
serde = { workspace = true, features = ["derive"] } serde = { workspace = true, features = ["derive"] }
serde_json = { workspace = true, features = ["raw_value"] } serde_json = { workspace = true, features = ["raw_value"] }
sha1 = { workspace = true }
sha2 = { workspace = true } sha2 = { workspace = true }
thiserror.workspace = true thiserror.workspace = true
time = { workspace = true, features = ["parsing", "formatting", "macros", "serde"] } time = { workspace = true, features = ["parsing", "formatting", "macros", "serde"] }
+114 -12
View File
@@ -591,12 +591,19 @@ struct FeatureSpec {
default_enabled: bool, default_enabled: bool,
} }
fn feature_specs() -> [FeatureSpec; 7] { fn feature_specs() -> &'static [FeatureSpec] {
[ &[
FeatureSpec {
name: "default",
enabled: cfg!(feature = "default"),
description: "Default feature set",
dependencies: "ftps + webdav",
default_enabled: true,
},
FeatureSpec { FeatureSpec {
name: "metrics-gpu", name: "metrics-gpu",
enabled: cfg!(feature = "metrics-gpu"), enabled: cfg!(feature = "metrics-gpu"),
description: "Metrics GPU support", description: "GPU metrics support",
dependencies: "rustfs-obs/gpu", dependencies: "rustfs-obs/gpu",
default_enabled: false, default_enabled: false,
}, },
@@ -610,7 +617,7 @@ fn feature_specs() -> [FeatureSpec; 7] {
FeatureSpec { FeatureSpec {
name: "swift", name: "swift",
enabled: cfg!(feature = "swift"), enabled: cfg!(feature = "swift"),
description: "Swift storage backend", description: "OpenStack Swift protocol support",
dependencies: "rustfs-protocols/swift", dependencies: "rustfs-protocols/swift",
default_enabled: false, default_enabled: false,
}, },
@@ -621,6 +628,13 @@ fn feature_specs() -> [FeatureSpec; 7] {
dependencies: "rustfs-protocols/webdav", dependencies: "rustfs-protocols/webdav",
default_enabled: true, default_enabled: true,
}, },
FeatureSpec {
name: "sftp",
enabled: cfg!(feature = "sftp"),
description: "SFTP protocol support",
dependencies: "rustfs-protocols/sftp",
default_enabled: false,
},
FeatureSpec { FeatureSpec {
name: "license", name: "license",
enabled: cfg!(feature = "license"), enabled: cfg!(feature = "license"),
@@ -635,11 +649,81 @@ fn feature_specs() -> [FeatureSpec; 7] {
dependencies: "(none)", dependencies: "(none)",
default_enabled: false, default_enabled: false,
}, },
FeatureSpec {
name: "tracing-chunk-debug",
enabled: cfg!(feature = "tracing-chunk-debug"),
description: "Per-chunk data-plane tracing",
dependencies: "(none)",
default_enabled: false,
},
FeatureSpec { FeatureSpec {
name: "full", name: "full",
enabled: cfg!(feature = "full"), enabled: cfg!(feature = "full"),
description: "All features enabled", description: "Full protocol and observability bundle",
dependencies: "metrics-gpu + ftps + swift + webdav", dependencies: "metrics-gpu + ftps + swift + webdav + sftp + pyroscope",
default_enabled: false,
},
FeatureSpec {
name: "e2e-test-hooks",
enabled: cfg!(feature = "e2e-test-hooks"),
description: "End-to-end test hooks",
dependencies: "(none)",
default_enabled: false,
},
FeatureSpec {
name: "connect-e2e-short-credentials",
enabled: cfg!(feature = "connect-e2e-short-credentials"),
description: "Short-lived Connect credentials for debug E2E builds",
dependencies: "(none)",
default_enabled: false,
},
FeatureSpec {
name: "offline-enrollment-e2e-root",
enabled: cfg!(feature = "offline-enrollment-e2e-root"),
description: "Dedicated offline enrollment E2E root",
dependencies: "(none)",
default_enabled: false,
},
FeatureSpec {
name: "rio-v2",
enabled: cfg!(feature = "rio-v2"),
description: "RIO v2 storage path support",
dependencies: "rustfs-ecstore/rio-v2",
default_enabled: false,
},
FeatureSpec {
name: "pyroscope",
enabled: cfg!(feature = "pyroscope"),
description: "Pyroscope profiling support",
dependencies: "rustfs-obs/pyroscope",
default_enabled: false,
},
FeatureSpec {
name: "dial9",
enabled: cfg!(feature = "dial9"),
description: "Tokio runtime telemetry",
dependencies: "rustfs-obs/dial9",
default_enabled: false,
},
FeatureSpec {
name: "hotpath",
enabled: cfg!(feature = "hotpath"),
description: "Hotpath instrumentation",
dependencies: "hotpath + RustFS crate hotpath features",
default_enabled: false,
},
FeatureSpec {
name: "hotpath-alloc",
enabled: cfg!(feature = "hotpath-alloc"),
description: "Hotpath allocation diagnostics",
dependencies: "hotpath + hotpath/hotpath-alloc + RustFS crate hotpath-alloc features",
default_enabled: false,
},
FeatureSpec {
name: "hotpath-cpu",
enabled: cfg!(feature = "hotpath-cpu"),
description: "Hotpath CPU attribution",
dependencies: "hotpath + hotpath/hotpath-cpu + RustFS crate hotpath-cpu features",
default_enabled: false, default_enabled: false,
}, },
] ]
@@ -655,7 +739,7 @@ struct DepsInfoJson {
fn collect_deps_info_json() -> DepsInfoJson { fn collect_deps_info_json() -> DepsInfoJson {
let features: Vec<FeatureInfoJson> = feature_specs() let features: Vec<FeatureInfoJson> = feature_specs()
.into_iter() .iter()
.map(|feature| FeatureInfoJson { .map(|feature| FeatureInfoJson {
name: feature.name, name: feature.name,
enabled: feature.enabled, enabled: feature.enabled,
@@ -901,7 +985,7 @@ fn format_deps_info() -> String {
output.push_str("### Feature Status\n\n"); output.push_str("### Feature Status\n\n");
output.push_str("| Feature | Status | Description |\n"); output.push_str("| Feature | Status | Description |\n");
output.push_str("|---------|--------|-------------|\n"); output.push_str("|---------|--------|-------------|\n");
for feature in &features { for feature in features {
let status = if feature.enabled { "" } else { "" }; let status = if feature.enabled { "" } else { "" };
output.push_str(&format!("| {} | {} | {} |\n", feature.name, status, feature.description)); output.push_str(&format!("| {} | {} | {} |\n", feature.name, status, feature.description));
} }
@@ -916,7 +1000,7 @@ fn format_deps_info() -> String {
output.push_str("\n### Feature Dependencies\n\n"); output.push_str("\n### Feature Dependencies\n\n");
output.push_str("| Feature | Dependencies |\n"); output.push_str("| Feature | Dependencies |\n");
output.push_str("|---------|-------------|\n"); output.push_str("|---------|-------------|\n");
for feature in &features { for feature in features {
output.push_str(&format!("| {} | {} |\n", feature.name, feature.dependencies)); output.push_str(&format!("| {} | {} |\n", feature.name, feature.dependencies));
} }
@@ -1001,10 +1085,22 @@ mod tests {
let info = collect_deps_info_json(); let info = collect_deps_info_json();
let feature_names: Vec<_> = info.features.iter().map(|feature| feature.name).collect(); let feature_names: Vec<_> = info.features.iter().map(|feature| feature.name).collect();
assert_eq!(info.total_count, 7); assert_eq!(info.total_count, 19);
assert_eq!(info.features.len(), 7); assert_eq!(info.features.len(), 19);
assert!(feature_names.contains(&"default"));
assert!(feature_names.contains(&"metrics-gpu")); assert!(feature_names.contains(&"metrics-gpu"));
assert!(feature_names.contains(&"sftp"));
assert!(feature_names.contains(&"io-scheduler-debug")); assert!(feature_names.contains(&"io-scheduler-debug"));
assert!(feature_names.contains(&"tracing-chunk-debug"));
assert!(feature_names.contains(&"e2e-test-hooks"));
assert!(feature_names.contains(&"connect-e2e-short-credentials"));
assert!(feature_names.contains(&"offline-enrollment-e2e-root"));
assert!(feature_names.contains(&"rio-v2"));
assert!(feature_names.contains(&"pyroscope"));
assert!(feature_names.contains(&"dial9"));
assert!(feature_names.contains(&"hotpath"));
assert!(feature_names.contains(&"hotpath-alloc"));
assert!(feature_names.contains(&"hotpath-cpu"));
assert!(!feature_names.contains(&"manual-test-runners")); assert!(!feature_names.contains(&"manual-test-runners"));
assert!(!feature_names.contains(&"metrics")); assert!(!feature_names.contains(&"metrics"));
assert!(!feature_names.contains(&"direct-io")); assert!(!feature_names.contains(&"direct-io"));
@@ -1016,10 +1112,16 @@ mod tests {
assert!(output.contains("| metrics-gpu |")); assert!(output.contains("| metrics-gpu |"));
assert!(output.contains("| io-scheduler-debug |")); assert!(output.contains("| io-scheduler-debug |"));
assert!(output.contains("| tracing-chunk-debug |"));
assert!(output.contains("| sftp |"));
assert!(output.contains("| rio-v2 |"));
assert!(output.contains("| dial9 |"));
assert!(output.contains("| hotpath-cpu |"));
assert!(output.contains("| default | enabled by default |"));
assert!(!output.contains("| manual-test-runners |")); assert!(!output.contains("| manual-test-runners |"));
assert!(output.contains("| ftps | enabled by default |")); assert!(output.contains("| ftps | enabled by default |"));
assert!(output.contains("| webdav | enabled by default |")); assert!(output.contains("| webdav | enabled by default |"));
assert!(output.contains("| full | metrics-gpu + ftps + swift + webdav |")); assert!(output.contains("| full | metrics-gpu + ftps + swift + webdav + sftp + pyroscope |"));
assert!(!output.contains("| direct-io |")); assert!(!output.contains("| direct-io |"));
} }