diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index e8708a8f..de12fb36 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -95,6 +95,10 @@ jobs: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - name: Check cospeed MSRV run: cargo msrv --path crates/codspeed verify -- cargo check --all-features --config codspeed=true + - name: Check cospeed MSRV in telemetry builds + run: cargo msrv --path crates/codspeed verify -- cargo check --all-features --config codspeed=true + env: + RUSTFLAGS: --cfg codspeed_telemetry - name: Check bencher_compat MSRV run: cargo msrv --path crates/bencher_compat verify -- cargo check --all-features --config codspeed=true - name: Check criterion_compat MSRV @@ -114,6 +118,10 @@ jobs: - run: rustup toolchain install nightly --profile minimal - run: cargo +nightly update -Zminimal-versions -p codspeed --recursive - run: cargo check -p codspeed --all-features --config codspeed=true + - name: Check in telemetry builds + run: cargo check -p codspeed --all-features --config codspeed=true + env: + RUSTFLAGS: --cfg codspeed_telemetry tests-without-cargo-codspeed: runs-on: ubuntu-latest @@ -127,6 +135,10 @@ jobs: env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} - run: cargo nextest run --workspace --exclude cargo-codspeed + - name: Run the telemetry tests, only built in telemetry builds + run: cargo nextest run -p codspeed --features telemetry + env: + RUSTFLAGS: --cfg codspeed_telemetry test-cargo-codspeed: runs-on: ubuntu-latest diff --git a/Cargo.lock b/Cargo.lock index 8471bc53..16f9e437 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -2,21 +2,6 @@ # It is not intended for manual editing. version = 3 -[[package]] -name = "addr2line" -version = "0.22.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e4503c46a5c0c7844e948c9a4d6acd9f50cccb4de1c48eb9e291ea17470c678" -dependencies = [ - "gimli", -] - -[[package]] -name = "adler" -version = "1.0.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f26201604c87b1e01bd3d98f8d5d9a8fcbb815e8cedb41ffccbeb4bf593a35fe" - [[package]] name = "aho-corasick" version = "1.1.3" @@ -201,7 +186,7 @@ dependencies = [ "polling 2.8.0", "rustix 0.37.27", "slab", - "socket2", + "socket2 0.4.10", "waker-fn", ] @@ -353,6 +338,17 @@ version = "4.7.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "8b75356056920673b02621b35afd0f7dda9306d03c79a30f5c56c44cf256e3de" +[[package]] +name = "async-trait" +version = "0.1.92" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "82f6aeea286b8eb4dd3431a1be1b59d290ace00f5bfd8e2a159bc2a05e2c1667" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + [[package]] name = "atomic-waker" version = "1.1.2" @@ -366,19 +362,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "0c4b4d0bd25bd0b74681c0ad21497610ce1b7c91b1022cd21c80c6fbdd9476b0" [[package]] -name = "backtrace" -version = "0.3.73" +name = "base64" +version = "0.23.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5cc23269a4f8976d0a4d2e7109211a419fe30e8d88d677cd60b6bc79c5732e0a" -dependencies = [ - "addr2line", - "cc", - "cfg-if", - "libc", - "miniz_oxide", - "object", - "rustc-demangle", -] +checksum = "ac07cdecf99051d9a5238b80f35af32cdeba5b336e55d957b318b50137e18da5" [[package]] name = "bencher" @@ -394,9 +381,9 @@ checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" [[package]] name = "bitflags" -version = "2.6.0" +version = "2.13.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b048fb63fd8b5923fc5aa7b340d8e156aec7ec02f0c78fa8a6ddc2613f6f71de" +checksum = "3ded4057c258ba199e2d26386d3af3780957ecaee6c4ef4041c6b4b8b97c0b06" [[package]] name = "blocking" @@ -434,6 +421,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" +[[package]] +name = "bytes" +version = "1.12.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fc652a48c352aef3ea3aed32080501cf3ef6ed5da78602a020c991775b0aff04" + [[package]] name = "camino" version = "1.2.1" @@ -573,7 +566,7 @@ dependencies = [ "heck", "proc-macro2", "quote", - "syn", + "syn 2.0.96", ] [[package]] @@ -591,11 +584,22 @@ dependencies = [ "colored", "getrandom 0.4.3", "glob", + "lazy_static", "nix", + "opentelemetry", + "opentelemetry-appender-tracing", + "opentelemetry-otlp", + "opentelemetry-proto", + "opentelemetry_sdk", + "prost", "serde", "serde_json", "statrs", "tempfile", + "tracing", + "tracing-log", + "tracing-opentelemetry", + "tracing-subscriber", ] [[package]] @@ -642,7 +646,7 @@ dependencies = [ "oorandom", "plotters", "quickcheck", - "rand", + "rand 0.8.5", "rayon", "regex", "serde", @@ -673,8 +677,10 @@ version = "0.0.0" dependencies = [ "codspeed-divan-compat", "fastrand 2.3.0", + "log", "num-bigint", "num-traits", + "tracing", ] [[package]] @@ -686,7 +692,7 @@ dependencies = [ "proc-macro-crate", "proc-macro2", "quote", - "syn", + "syn 2.0.96", ] [[package]] @@ -715,7 +721,7 @@ version = "3.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "faf9468729b8cbcea668e36183cb69d317348c2e08e994829fb56ebfdfbaac34" dependencies = [ - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -801,6 +807,17 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6184e33543162437515c2e2b48714794e37845ec9851711914eec9d308f6ebe8" +[[package]] +name = "displaydoc" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c6232dd377dcc64799954cbd3a9bb882e9cdc1308ccd87b1c098f1fb2eaf82a8" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + [[package]] name = "divan-macros" version = "0.1.17" @@ -809,7 +826,7 @@ checksum = "8dc51d98e636f5e3b0759a39257458b22619cac7e96d932da6eeb052891bb67c" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.96", ] [[package]] @@ -832,12 +849,12 @@ checksum = "5443807d6dff69373d433ab9ef5378ad8df50ca6298caf15de6e52e24aaf54d5" [[package]] name = "errno" -version = "0.3.9" +version = "0.3.14" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "534c5cf6194dfab3db3242765c03bbe257cf92f22b38f6bc0c58d59108a820ba" +checksum = "39cab71617ae0d63f51a36d69f866391735b51691dbda63cf6f96d042b63efeb" dependencies = [ "libc", - "windows-sys 0.52.0", + "windows-sys 0.61.2", ] [[package]] @@ -908,6 +925,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "form_urlencoded" +version = "1.2.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb4cb245038516f5f85277875cdaa4f7d2c9a0fa0468de06ed190163b1581fcf" +dependencies = [ + "percent-encoding", +] + [[package]] name = "fs_extra" version = "1.3.0" @@ -990,6 +1016,17 @@ dependencies = [ "pin-project-lite", ] +[[package]] +name = "futures-macro" +version = "0.3.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "162ee34ebcb7c64a8abebc059ce0fee27c2262618d7b60ed8faf72fef13c3650" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + [[package]] name = "futures-sink" version = "0.3.31" @@ -1017,6 +1054,7 @@ dependencies = [ "futures-channel", "futures-core", "futures-io", + "futures-macro", "futures-sink", "futures-task", "memchr", @@ -1038,20 +1076,26 @@ dependencies = [ [[package]] name = "getrandom" -version = "0.4.3" +version = "0.3.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +checksum = "899def5c37c4fd7b2664648c28120ecec138e4d395b459e5ca34f9cce2dd77fd" dependencies = [ "cfg-if", "libc", - "r-efi", + "r-efi 5.3.0", + "wasip2", ] [[package]] -name = "gimli" -version = "0.29.0" +name = "getrandom" +version = "0.4.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "40ecd4077b5ae9fd2e9e169b102c6c330d0605168eb0e8bf79952b256dbefffd" +checksum = "300e883d756b2e4ec94e02791f39b04b522276138852cfc41d9fb7e904106099" +dependencies = [ + "cfg-if", + "libc", + "r-efi 6.0.0", +] [[package]] name = "glob" @@ -1105,6 +1149,233 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbf6a919d6cf397374f7dfeeea91d974c7c0a7221d0d0f4f20d859d329e53fcc" +[[package]] +name = "http" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "918d3568bebf352712bc2ef3d46a8bcf1a75b373be6539de198e9105cbbf9ce0" +dependencies = [ + "bytes", + "itoa", +] + +[[package]] +name = "http-body" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ca2a8f2913ee65f60facd6a5905613afaa448497a0230cc41ce022d93290bc2c" +dependencies = [ + "bytes", + "http", +] + +[[package]] +name = "http-body-util" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23169fe34a5fbcdd3f3862e78fb9b6fccd5f02a6dc6f732547005d45631ce71c" +dependencies = [ + "bytes", + "futures-core", + "http", + "http-body", + "pin-project-lite", +] + +[[package]] +name = "httparse" +version = "1.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6dbf3de79e51f3d586ab4cb9d5c3e2c14aa28ed23d180cf89b4df0454a69cc87" + +[[package]] +name = "httpdate" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "df3b46402a9d5adb4c86a0cf463f42e19994e3ee891101b1841f30a545cb49a9" + +[[package]] +name = "hyper" +version = "1.12.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2c3e324da4c95177d6291d4c8730197c0d1822f8a9766814a4a44fa5ab797c9c" +dependencies = [ + "atomic-waker", + "bytes", + "futures-core", + "http", + "http-body", + "httparse", + "itoa", + "pin-project-lite", + "smallvec", + "tokio", + "want", +] + +[[package]] +name = "hyper-util" +version = "0.1.21" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ddc03d96684f9226b8a787cdb71488417b53ab5ea8fdb1dac946cb9431cc8bff" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-util", + "http", + "http-body", + "httparse", + "hyper", + "ipnet", + "libc", + "percent-encoding", + "pin-project-lite", + "socket2 0.6.5", + "tokio", + "tower-service", + "tracing", +] + +[[package]] +name = "icu_collections" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db2fa452206ebee18c4b5c2274dbf1de17008e874b4dc4f0aea9d01ca79e4526" +dependencies = [ + "displaydoc", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_locid" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "13acbb8371917fc971be86fc8057c41a64b521c184808a698c02acc242dbf637" +dependencies = [ + "displaydoc", + "litemap", + "tinystr", + "writeable", + "zerovec", +] + +[[package]] +name = "icu_locid_transform" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01d11ac35de8e40fdeda00d9e1e9d92525f3f9d887cdd7aa81d727596788b54e" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_locid_transform_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_locid_transform_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7515e6d781098bf9f7205ab3fc7e9709d34554ae0b21ddbcb5febfa4bc7df11d" + +[[package]] +name = "icu_normalizer" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19ce3e0da2ec68599d193c93d088142efd7f9c5d6fc9b803774855747dc6a84f" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_normalizer_data", + "icu_properties", + "icu_provider", + "smallvec", + "utf16_iter", + "utf8_iter", + "write16", + "zerovec", +] + +[[package]] +name = "icu_normalizer_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c5e8338228bdc8ab83303f16b797e177953730f601a96c25d10cb3ab0daa0cb7" + +[[package]] +name = "icu_properties" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93d6020766cfc6302c15dbbc9c8778c37e62c14427cb7f6e601d849e092aeef5" +dependencies = [ + "displaydoc", + "icu_collections", + "icu_locid_transform", + "icu_properties_data", + "icu_provider", + "tinystr", + "zerovec", +] + +[[package]] +name = "icu_properties_data" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85fb8799753b75aee8d2a21d7c14d9f38921b54b3dbda10f5a3c7a7b82dba5e2" + +[[package]] +name = "icu_provider" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ed421c8a8ef78d3e2dbc98a973be2f3770cb42b606e3ab18d6237c4dfde68d9" +dependencies = [ + "displaydoc", + "icu_locid", + "icu_provider_macros", + "stable_deref_trait", + "tinystr", + "writeable", + "yoke", + "zerofrom", + "zerovec", +] + +[[package]] +name = "icu_provider_macros" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ec89e9337638ecdc08744df490b221a7399bf8d164eb52a665454e60e075ad6" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "idna" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3b0875f23caa03898994f6ddc501886a45c7d3d62d04d2d90788d47be1b1e4de" +dependencies = [ + "idna_adapter", + "smallvec", + "utf8_iter", +] + +[[package]] +name = "idna_adapter" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "daca1df1c957320b2cf139ac61e7bd64fed304c5040df000a745aa1de3b4ef71" +dependencies = [ + "icu_normalizer", + "icu_properties", +] + [[package]] name = "indexmap" version = "2.14.0" @@ -1135,6 +1406,12 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "ipnet" +version = "2.12.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "791930b43c0d5973160d90a8f3894509f2b273430f5c5c73b668636d0287c5c0" + [[package]] name = "is-terminal" version = "0.4.12" @@ -1178,10 +1455,12 @@ checksum = "49f1f14873335454500d59611f1cf4a4b0f786f9ac11f4312a78e4cf2566695b" [[package]] name = "js-sys" -version = "0.3.69" +version = "0.3.106" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "29c15563dc2726973df627357ce0c9ddddbea194836909d655df6a75d2cf296d" +checksum = "7883d941dae510fb2d978fc3fe018c71c9e2892fd38854de3e8b92c2e5ad9cc5" dependencies = [ + "cfg-if", + "futures-util", "wasm-bindgen", ] @@ -1194,6 +1473,12 @@ dependencies = [ "log", ] +[[package]] +name = "lazy_static" +version = "1.5.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "20870f649af7073d53e38067b2a84312175d56ea15217e1b15bc83506ec50afb" + [[package]] name = "libc" version = "0.2.182" @@ -1222,15 +1507,30 @@ version = "0.4.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "78b3ae25bc7c8c38cec158d1f2757ee79e9b3740fbc7ccf0e59e4b08d793fa89" +[[package]] +name = "litemap" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4ee93343901ab17bd981295f2cf0026d4ad018c7c31ba84549a4ddbb47a45104" + [[package]] name = "log" -version = "0.4.22" +version = "0.4.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a7a70ba024b9dc04c27ea2f0c0548feb474ec5c54bba33a7f72f873a39d07b24" +checksum = "f9f8bd3e56ce4dfc153cf470fffbfa98c7620958b312ca5c3a4b8d5181fd13c6" dependencies = [ "value-bag", ] +[[package]] +name = "matchers" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1525a2a28c7f4fa0fc98bb91ae755d1e2d1505079e05539e35bc876b5d65ae9" +dependencies = [ + "regex-automata", +] + [[package]] name = "memchr" version = "2.7.4" @@ -1247,12 +1547,14 @@ dependencies = [ ] [[package]] -name = "miniz_oxide" -version = "0.7.4" +name = "mio" +version = "1.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b8a240ddb74feaf34a79a7add65a741f3167852fba007066dcac1ca548d89c08" +checksum = "a69bcab0ad47271a0234d9422b131806bf3968021e5dc9328caf2d4cd58557fc" dependencies = [ - "adler", + "libc", + "wasi", + "windows-sys 0.61.2", ] [[package]] @@ -1261,7 +1563,7 @@ version = "0.31.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5d6d0705320c1e6ba1d912b5e37cf18071b6c2e9b7fa8215a1e8a7651966f5d3" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.13.2", "cfg-if", "cfg_aliases", "libc", @@ -1301,15 +1603,6 @@ dependencies = [ "autocfg", ] -[[package]] -name = "object" -version = "0.36.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3f203fa8daa7bb185f760ae12bd8e097f63d17041dcdcaf675ac54cdf863170e" -dependencies = [ - "memchr", -] - [[package]] name = "once_cell" version = "1.19.0" @@ -1322,12 +1615,101 @@ version = "11.1.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b410bbe7e14ab526a0e86877eb47c6996a2bd7746f027ba551028c925390e4e9" +[[package]] +name = "opentelemetry" +version = "0.33.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8c1f1a6ac9c875a4941e4c7c085b61e8f4daa326de03b4701768c3491a6ad9ee" +dependencies = [ + "futures-core", + "futures-sink", + "js-sys", + "pin-project-lite", + "thiserror", +] + +[[package]] +name = "opentelemetry-appender-tracing" +version = "0.33.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f01f1c53c46ce80e5fd8cb7a646f13766fd8f315a8a5b75b9bad59a08d2c5c7" +dependencies = [ + "opentelemetry", + "tracing", + "tracing-core", + "tracing-log", + "tracing-subscriber", +] + +[[package]] +name = "opentelemetry-http" +version = "0.33.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "302249bd8ee804789faaa7b5b2cbf79bc61cb864714079c644e3828af5b7e811" +dependencies = [ + "async-trait", + "bytes", + "http", + "opentelemetry", + "reqwest", +] + +[[package]] +name = "opentelemetry-otlp" +version = "0.33.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0756af24841e367128ef31c831999937b8b5c5197d47914590d8f4e2e88c2e1a" +dependencies = [ + "http", + "httpdate", + "opentelemetry", + "opentelemetry-http", + "opentelemetry-proto", + "opentelemetry_sdk", + "prost", + "reqwest", + "thiserror", +] + +[[package]] +name = "opentelemetry-proto" +version = "0.33.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d576f5dddc3299341bcd85a04be17b2564844c0c9563214f02d85f2524d1afae" +dependencies = [ + "opentelemetry", + "opentelemetry_sdk", + "prost", +] + +[[package]] +name = "opentelemetry_sdk" +version = "0.33.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "372a912db2974d1990207006fd0e3aa0ef3fd4ad3be43c65cfaadf2206866061" +dependencies = [ + "futures-channel", + "futures-executor", + "futures-util", + "opentelemetry", + "percent-encoding", + "portable-atomic", + "rand 0.9.5", + "thiserror", +] + [[package]] name = "parking" version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bb813b8af86854136c6922af0598d719255ecb2179515e6e7730d468f05c9cae" +[[package]] +name = "percent-encoding" +version = "2.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9b4f627cb1b25917193a259e49bdad08f671f8d9708acfd5fe0a8c1455d87220" + [[package]] name = "pin-project-lite" version = "0.2.14" @@ -1410,6 +1792,12 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "portable-atomic" +version = "1.15.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05c8b63e8d9609db387f0324918f81d68fe27748f084ef092fb35954d0539a85" + [[package]] name = "ppv-lite86" version = "0.2.20" @@ -1468,23 +1856,52 @@ dependencies = [ ] [[package]] -name = "quickcheck" -version = "1.0.3" +name = "prost" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6" +checksum = "528ac67416ff8646872a3c02cad9cc4ee5dc9f9540c9b10771855c95cb2e5ae1" dependencies = [ - "rand", + "bytes", + "prost-derive", ] [[package]] -name = "quote" -version = "1.0.47" +name = "prost-derive" +version = "0.14.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +checksum = "b570b25f7617e43d59005d0990ccb79e950a423952cea19671b7a876da390adf" dependencies = [ + "anyhow", + "itertools 0.14.0", "proc-macro2", + "quote", + "syn 2.0.96", ] +[[package]] +name = "quickcheck" +version = "1.0.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "588f6378e4dd99458b60ec275b4477add41ce4fa9f64dcba6f15adccb19b50d6" +dependencies = [ + "rand 0.8.5", +] + +[[package]] +name = "quote" +version = "1.0.47" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fbf4db142a473a8d80c26bbf18454ed458bf8d26c8219c331daecfdbd079001" +dependencies = [ + "proc-macro2", +] + +[[package]] +name = "r-efi" +version = "5.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "69cdb34c158ceb288df11e18b4bd39de994f6657d83847bdffdbd7f346754b0f" + [[package]] name = "r-efi" version = "6.0.0" @@ -1498,8 +1915,18 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ "libc", - "rand_chacha", - "rand_core", + "rand_chacha 0.3.1", + "rand_core 0.6.4", +] + +[[package]] +name = "rand" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9ef1d0d795eb7d84685bca4f72f3649f064e6641543d3a8c415898726a57b41" +dependencies = [ + "rand_chacha 0.9.0", + "rand_core 0.9.5", ] [[package]] @@ -1509,7 +1936,17 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e6c10a63a0fa32252be49d21e7709d4d4baf8d231c2dbce1eaa8141b9b127d88" dependencies = [ "ppv-lite86", - "rand_core", + "rand_core 0.6.4", +] + +[[package]] +name = "rand_chacha" +version = "0.9.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3022b5f1df60f26e1ffddd6c66e8aa15de382ae63b3a0c1bfc0e4d3e3f325cb" +dependencies = [ + "ppv-lite86", + "rand_core 0.9.5", ] [[package]] @@ -1521,6 +1958,15 @@ dependencies = [ "getrandom 0.2.15", ] +[[package]] +name = "rand_core" +version = "0.9.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "76afc826de14238e6e8c374ddcc1fa19e374fd8dd986b0d2af0d02377261d83c" +dependencies = [ + "getrandom 0.3.4", +] + [[package]] name = "rayon" version = "1.10.0" @@ -1591,6 +2037,37 @@ version = "1.9.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ba39f3699c378cd8970968dcbff9c43159ea4cfbd88d43c00b22f2ef10a435d2" +[[package]] +name = "reqwest" +version = "0.13.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "16a1cfa75cc186dd73d5818e510e042e40927bccc9c236b061cea97e1eb08029" +dependencies = [ + "base64", + "bytes", + "futures-channel", + "futures-core", + "futures-util", + "http", + "http-body", + "http-body-util", + "hyper", + "hyper-util", + "js-sys", + "log", + "percent-encoding", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower", + "tower-http", + "tower-service", + "url", + "wasm-bindgen", + "wasm-bindgen-futures", + "web-sys", +] + [[package]] name = "rstest" version = "0.27.0" @@ -1616,16 +2093,10 @@ dependencies = [ "regex", "relative-path", "rustc_version", - "syn", + "syn 2.0.96", "unicode-ident", ] -[[package]] -name = "rustc-demangle" -version = "0.1.24" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "719b953e2095829ee67db738b3bfa9fa368c94900df327b3f07fe6e794d2fe1f" - [[package]] name = "rustc_version" version = "0.4.1" @@ -1655,13 +2126,19 @@ version = "0.38.34" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "70dc5ec042f7a43c4a73241207cecc9873a06d45debb38b329f8541d85c2730f" dependencies = [ - "bitflags 2.6.0", + "bitflags 2.13.2", "errno", "libc", "linux-raw-sys 0.4.14", "windows-sys 0.52.0", ] +[[package]] +name = "rustversion" +version = "1.0.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf54715a573b99ac80df0bc206da022bcd442c974952c7b9720069370852e21f" + [[package]] name = "ryu" version = "1.0.18" @@ -1714,7 +2191,7 @@ checksum = "d540f220d3187173da220f885ab66608367b6574e925011a9353e4badda91d79" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.96", ] [[package]] @@ -1739,6 +2216,15 @@ dependencies = [ "serde", ] +[[package]] +name = "sharded-slab" +version = "0.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f40ca3c46823713e0d4209592e8d6e826aa57e928f09752619fc696c499637f6" +dependencies = [ + "lazy_static", +] + [[package]] name = "shlex" version = "2.0.1" @@ -1763,6 +2249,12 @@ dependencies = [ "autocfg", ] +[[package]] +name = "smallvec" +version = "1.16.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5b3dc8af474f516a851ff4bd12db780f948b9250ad37211e4eec0bccea54e01b" + [[package]] name = "smol" version = "1.3.0" @@ -1807,6 +2299,22 @@ dependencies = [ "winapi", ] +[[package]] +name = "socket2" +version = "0.6.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c3d1e2c7f27f8d4cb10542a02c49005dbd6e93095799d6f3be745fae9f8fedd4" +dependencies = [ + "libc", + "windows-sys 0.61.2", +] + +[[package]] +name = "stable_deref_trait" +version = "1.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ce2be8dc25455e1f91df71bfa12ad37d7af1092ae736f3a6cd0e37bc7810596" + [[package]] name = "statrs" version = "0.18.0" @@ -1834,6 +2342,48 @@ dependencies = [ "unicode-ident", ] +[[package]] +name = "syn" +version = "3.0.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d78c8dee4c7bf0e14673097256fed6142ce9d3b85a408189d07482442145823b" +dependencies = [ + "proc-macro2", + "quote", + "unicode-ident", +] + +[[package]] +name = "sync_wrapper" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bf256ce5efdfa370213c1dabab5935a12e49f2c58d15e9eac2870d3b4f27263" +dependencies = [ + "futures-core", +] + +[[package]] +name = "synstructure" +version = "0.13.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "728a70f3dbaf5bab7f0c4b1ac8d7ae5ea60a4b5549c8a5914361c99147a709d2" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + +[[package]] +name = "synstructure" +version = "0.14.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "901704edd0dfe137f1987838ee4f259e4e063c31371bdb423f7ae38ec6f77f02" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", +] + [[package]] name = "tempfile" version = "3.7.1" @@ -1889,7 +2439,26 @@ checksum = "7f7cf42b4507d8ea322120659672cf1b9dbb93f8f2d4ecfd6e51350ff5b17a1d" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.96", +] + +[[package]] +name = "thread_local" +version = "1.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ad99c4c6d32803332c548b1af0540b357b3f5fc0be8f6c6bfe8b2e6ae784070" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "tinystr" +version = "0.7.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9117f5d4db391c1cf6927e7bea3db74b9a1c1add8f7eda9ffd5364f40f57b82f" +dependencies = [ + "displaydoc", + "zerovec", ] [[package]] @@ -1904,12 +2473,15 @@ dependencies = [ [[package]] name = "tokio" -version = "1.44.1" +version = "1.50.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f382da615b842244d4b8738c82ed1275e6c5dd90c459a30941cd07080b06c91a" +checksum = "27ad5e34374e03cfffefc301becb44e9dc3c17584f414349ebe29ed26661822d" dependencies = [ - "backtrace", + "libc", + "mio", "pin-project-lite", + "socket2 0.6.5", + "windows-sys 0.61.2", ] [[package]] @@ -1976,27 +2548,158 @@ dependencies = [ "winnow 1.0.3", ] +[[package]] +name = "tower" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ebe5ef63511595f1344e2d5cfa636d973292adc0eec1f0ad45fae9f0851ab1d4" +dependencies = [ + "futures-core", + "futures-util", + "pin-project-lite", + "sync_wrapper", + "tokio", + "tower-layer", + "tower-service", +] + +[[package]] +name = "tower-http" +version = "0.6.11" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4cfcf7e2740e6fc6d4d688b4ef00650406bb94adf4731e43c096c3a19fe40840" +dependencies = [ + "bitflags 2.13.2", + "bytes", + "futures-util", + "http", + "http-body", + "pin-project-lite", + "tower", + "tower-layer", + "tower-service", + "url", +] + +[[package]] +name = "tower-layer" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "121c2a6cda46980bb0fcd1647ffaf6cd3fc79a013de288782836f6df9c48780e" + +[[package]] +name = "tower-service" +version = "0.3.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8df9b6e13f2d32c91b9bd719c00d1958837bc7dec474d94952798cc8e69eeec3" + [[package]] name = "tracing" -version = "0.1.40" +version = "0.1.44" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c3523ab5a71916ccf420eebdf5521fcef02141234bbc0b8a49f2fdc4544364ef" +checksum = "63e71662fa4b2a2c3a26f570f037eb95bb1f85397f3cd8076caed2f026a6d100" dependencies = [ "pin-project-lite", + "tracing-attributes", "tracing-core", ] +[[package]] +name = "tracing-attributes" +version = "0.1.31" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7490cfa5ec963746568740651ac6781f701c9c5ea257c58e057f3ba8cf69e8da" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", +] + [[package]] name = "tracing-core" -version = "0.1.32" +version = "0.1.36" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "db97caf9d906fbde555dd62fa95ddba9eecfd14cb388e4f491a66d74cd5fb79a" +dependencies = [ + "once_cell", + "valuable", +] + +[[package]] +name = "tracing-log" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee855f1f400bd0e5c02d150ae5de3840039a3f54b025156404e34c23c03f47c3" +dependencies = [ + "log", + "once_cell", + "tracing-core", +] + +[[package]] +name = "tracing-opentelemetry" +version = "0.34.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0a904802a1b902f43638b677ff2a650847e3b4404101b6c586d648e8c1e3e8fe" +dependencies = [ + "js-sys", + "opentelemetry", + "tracing", + "tracing-core", + "tracing-subscriber", + "web-time", +] + +[[package]] +name = "tracing-subscriber" +version = "0.3.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb7f578e5945fb242538965c2d0b04418d38ec25c79d160cd279bf0731c8d319" +dependencies = [ + "matchers", + "once_cell", + "regex-automata", + "sharded-slab", + "thread_local", + "tracing", + "tracing-core", +] + +[[package]] +name = "try-lock" +version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c06d3da6113f116aaee68e4d601191614c9053067f9ab7f6edbcb161237daa54" +checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" [[package]] name = "unicode-ident" -version = "1.0.26" +version = "1.0.27" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2c754d6c33795a1c324727428e5a7dedb5b06195f9890bdbcba760d3e246563" + +[[package]] +name = "url" +version = "2.5.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff67a8a4397373c3ef660812acab3268222035010ab8680ec4215f38ba3d0eed" +dependencies = [ + "form_urlencoded", + "idna", + "percent-encoding", + "serde", +] + +[[package]] +name = "utf16_iter" +version = "1.0.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c8232dd3cdaed5356e0f716d285e4b40b932ac434100fe9b7e0e8e935b9e6246" + +[[package]] +name = "utf8_iter" +version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d245f478577f809a851594d02313b640fb437e0bb33866753cff937863096954" +checksum = "b6c140620e7ffbb22c2dee59cafe6084a59b5ffc27a8859a5f0d494b5d52b6be" [[package]] name = "utf8parse" @@ -2004,11 +2707,17 @@ version = "0.2.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "06abde3611657adf66d383f00b093d7faecc7fa57071cce2578660c9f1010821" +[[package]] +name = "valuable" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" + [[package]] name = "value-bag" -version = "1.9.0" +version = "1.14.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a84c137d37ab0142f0f2ddfe332651fdbf252e7b7dbb4e67b6c1f1b2e925101" +checksum = "2799ffb329a792ecfd902b71306c8a815a6ef1c0470fa9953a6aa4d4cecbe511" [[package]] name = "wait-timeout" @@ -2035,6 +2744,15 @@ dependencies = [ "winapi-util", ] +[[package]] +name = "want" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ec4cdd0dd910afe868b7ef477227d8d538b46b3075031afee8a9f2acb0a2ed0b" +dependencies = [ + "try-lock", +] + [[package]] name = "wasi" version = "0.11.0+wasi-snapshot-preview1" @@ -2042,27 +2760,24 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" [[package]] -name = "wasm-bindgen" -version = "0.2.92" +name = "wasip2" +version = "1.0.4+wasi-0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4be2531df63900aeb2bca0daaaddec08491ee64ceecbee5076636a3b026795a8" +checksum = "b67efb37e106e55ce722a510d6b5f9c17f083e5fc79afc2badeb12cc313d9487" dependencies = [ - "cfg-if", - "wasm-bindgen-macro", + "wit-bindgen", ] [[package]] -name = "wasm-bindgen-backend" -version = "0.2.92" +name = "wasm-bindgen" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "614d787b966d3989fa7bb98a654e369c762374fd3213d212cfc0251257e747da" +checksum = "9bb54f33acc68fd454578d9820b0bde1a1a3d17aa17bb7b6595806d02886d409" dependencies = [ - "bumpalo", - "log", + "cfg-if", "once_cell", - "proc-macro2", - "quote", - "syn", + "rustversion", + "wasm-bindgen-macro", "wasm-bindgen-shared", ] @@ -2080,9 +2795,9 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro" -version = "0.2.92" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a1f8823de937b71b9460c0c34e25f3da88250760bec0ebac694b49997550d726" +checksum = "2e29d0c35b16e224a7eeb5cd2d25e3e1968fbd65604117b44d3b789d00ee8535" dependencies = [ "quote", "wasm-bindgen-macro-support", @@ -2090,22 +2805,25 @@ dependencies = [ [[package]] name = "wasm-bindgen-macro-support" -version = "0.2.92" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e94f17b526d0a461a191c78ea52bbce64071ed5c04c9ffe424dcb38f74171bb7" +checksum = "6f501a8bc3719dba86ef8ae4728879c08001bea749eb1333ac5b91e040e2a6b7" dependencies = [ + "bumpalo", "proc-macro2", "quote", - "syn", - "wasm-bindgen-backend", + "syn 3.0.9", "wasm-bindgen-shared", ] [[package]] name = "wasm-bindgen-shared" -version = "0.2.92" +version = "0.2.129" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "af190c94f2773fdb3729c55b007a722abb5384da03bc0986df4c289bf5567e96" +checksum = "23f0c9c52aa7cd7d77769a4cfe2a9adb1b331f489a41d912ce14513d5ab995c6" +dependencies = [ + "unicode-ident", +] [[package]] name = "web-sys" @@ -2117,6 +2835,16 @@ dependencies = [ "wasm-bindgen", ] +[[package]] +name = "web-time" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5a6580f308b1fad9207618087a65c04e7a10bc77e02c8e84e9b00dd4b12fa0bb" +dependencies = [ + "js-sys", + "wasm-bindgen", +] + [[package]] name = "winapi" version = "0.3.9" @@ -2148,6 +2876,12 @@ version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" +[[package]] +name = "windows-link" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0805222e57f7521d6a62e36fa9163bc891acd422f971defe97d64e70d0a4fe5" + [[package]] name = "windows-sys" version = "0.48.0" @@ -2166,6 +2900,15 @@ dependencies = [ "windows-targets 0.52.6", ] +[[package]] +name = "windows-sys" +version = "0.61.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ae137229bcbd6cdf0f7b80a31df61766145077ddf49416a728b02cb3921ff3fc" +dependencies = [ + "windows-link", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -2305,6 +3048,48 @@ dependencies = [ "memchr", ] +[[package]] +name = "wit-bindgen" +version = "0.57.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ebf944e87a7c253233ad6766e082e3cd714b5d03812acc24c318f549614536e" + +[[package]] +name = "write16" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d1890f4022759daae28ed4fe62859b1236caebfc61ede2f63ed4e695f3f6d936" + +[[package]] +name = "writeable" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e9df38ee2d2c3c5948ea468a8406ff0db0b29ae1ffde1bcf20ef305bcc95c51" + +[[package]] +name = "yoke" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "120e6aef9aa629e3d4f52dc8cc43a015c7724194c97dfaf45180d2daf2b77f40" +dependencies = [ + "serde", + "stable_deref_trait", + "yoke-derive", + "zerofrom", +] + +[[package]] +name = "yoke-derive" +version = "0.7.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2380878cad4ac9aac1e2435f3eb4020e8374b5f13c296cb75b4620ff8e229154" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", + "synstructure 0.13.2", +] + [[package]] name = "zerocopy" version = "0.7.35" @@ -2323,5 +3108,48 @@ checksum = "fa4f8080344d4671fb4e831a13ad1e68092748387dfc4f55e356242fae12ce3e" dependencies = [ "proc-macro2", "quote", - "syn", + "syn 2.0.96", +] + +[[package]] +name = "zerofrom" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0ec05a11813ea801ff6d75110ad09cd0824ddba17dfe17128ea0d5f68e6c5272" +dependencies = [ + "zerofrom-derive", +] + +[[package]] +name = "zerofrom-derive" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f75b4683f6c7f45248d4d64056a24298c6281e0993356d7d1b4a1a962ef10d4a" +dependencies = [ + "proc-macro2", + "quote", + "syn 3.0.9", + "synstructure 0.14.0", +] + +[[package]] +name = "zerovec" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "aa2b893d79df23bfb12d5461018d408ea19dfafe76c2c7ef6d4eba614f8ff079" +dependencies = [ + "yoke", + "zerofrom", + "zerovec-derive", +] + +[[package]] +name = "zerovec-derive" +version = "0.10.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e3c6377872d72510393f688a555d7097b0f741995c7a00f0407f786dd486b2d" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.96", ] diff --git a/crates/cargo-codspeed/src/app.rs b/crates/cargo-codspeed/src/app.rs index 6c309a10..971437d9 100644 --- a/crates/cargo-codspeed/src/app.rs +++ b/crates/cargo-codspeed/src/app.rs @@ -36,7 +36,19 @@ impl Cli { offline, frozen, measurement_mode, + keep_tracing, } => { + let is_telemetry_only = !measurement_mode.is_empty() + && measurement_mode + .iter() + .all(|m| m == &MeasurementMode::Telemetry); + if is_telemetry_only && keep_tracing { + bail!( + "`--keep-tracing` has no effect in telemetry mode, which always keeps \ + `tracing`" + ); + } + let passthrough_flags = { let mut passthrough_flags = Vec::new(); if all_features { @@ -89,6 +101,7 @@ impl Cli { profile: profile.clone(), quiet: self.quiet, build_mode, + keep_tracing, passthrough_flags: passthrough_flags.clone(), }, )?; @@ -197,6 +210,11 @@ enum Commands { env = "CODSPEED_RUNNER_MODE" )] measurement_mode: Vec, + + /// Keep `tracing` spans and events compiled in. By default, the `max_level_off` feature of + /// `tracing` compiles them out of every mode but `telemetry`. + #[arg(long, help_heading = COMPILATION_HELP)] + keep_tracing: bool, }, /// Run the previously built benchmarks Run { diff --git a/crates/cargo-codspeed/src/build.rs b/crates/cargo-codspeed/src/build.rs index c99ad51e..58efc3cf 100644 --- a/crates/cargo-codspeed/src/build.rs +++ b/crates/cargo-codspeed/src/build.rs @@ -9,11 +9,17 @@ use cargo_metadata::{camino::Utf8PathBuf, Message, Metadata, TargetKind}; use std::collections::HashMap; use std::process::{exit, Command, Stdio}; +/// Set by `cargo-codspeed` in the builds `tracing` is compiled out of, see the `codspeed` crate +/// manifest +const TRACING_OFF_CFG: &str = "codspeed_tracing_off"; +const TELEMETRY_CFG: &str = "codspeed_telemetry"; + struct BuildOptions<'a> { bench_target_filters: BenchTargetFilters, package_filters: PackageFilters, features: &'a Option>, profile: &'a str, + keep_tracing: bool, passthrough_flags: &'a Vec, } @@ -31,6 +37,8 @@ pub struct BuildConfig { pub profile: String, pub quiet: bool, pub build_mode: BuildMode, + /// Keep `tracing` compiled in when building for a measurement mode + pub keep_tracing: bool, pub passthrough_flags: Vec, } @@ -226,10 +234,23 @@ See `cargo codspeed build --help` for more information."); ]; // Add the codspeed cfg flag if the benchmark should only run once - if build_mode == BuildMode::Analysis { + if build_mode.runs_once() { flags.push("--cfg=codspeed".to_owned()); } + // Compile `tracing` out of measured benchmarks, so that it doesn't weigh on the + // measurements: the `codspeed` crate enables the `max_level_off` feature of `tracing` + // behind this cfg. Only the telemetry build keeps it, to export what it records. + if build_mode != BuildMode::Telemetry && !self.keep_tracing { + flags.push(format!("--cfg={TRACING_OFF_CFG}")); + } + + // Behind this cfg, the `codspeed` crate compiles the export of the telemetry, only needed + // in the telemetry build. + if build_mode == BuildMode::Telemetry { + flags.push(format!("--cfg={TELEMETRY_CFG}")); + } + match std::env::var("RUSTFLAGS") { Result::Ok(existing_rustflags) => { // Expand already existing RUSTFLAGS env var @@ -307,6 +328,7 @@ pub fn build_benches(metadata: &Metadata, config: BuildConfig) -> Result<()> { package_filters: config.package_filters, features: &config.features, profile: &config.profile, + keep_tracing: config.keep_tracing, passthrough_flags: &config.passthrough_flags, } .build(metadata, config.quiet, build_mode)?; diff --git a/crates/cargo-codspeed/src/measurement_mode.rs b/crates/cargo-codspeed/src/measurement_mode.rs index f2635a0c..66a46006 100644 --- a/crates/cargo-codspeed/src/measurement_mode.rs +++ b/crates/cargo-codspeed/src/measurement_mode.rs @@ -7,6 +7,16 @@ pub enum BuildMode { #[default] Analysis, Walltime, + /// Runs each benchmark like [`BuildMode::Analysis`], to collect the telemetry it emits rather + /// than measure it + Telemetry, +} + +impl BuildMode { + /// Whether the benchmarks run once, behind `cfg(codspeed)`, rather than in a walltime loop + pub fn runs_once(self) -> bool { + matches!(self, BuildMode::Analysis | BuildMode::Telemetry) + } } impl fmt::Display for BuildMode { @@ -17,6 +27,7 @@ impl fmt::Display for BuildMode { match self { BuildMode::Analysis => "analysis", BuildMode::Walltime => "walltime", + BuildMode::Telemetry => "telemetry", } ) } @@ -30,6 +41,8 @@ pub enum MeasurementMode { Simulation, Walltime, Memory, + /// Collects the telemetry the benchmarks emit instead of measuring them + Telemetry, } impl From for BuildMode { @@ -37,6 +50,7 @@ impl From for BuildMode { match measurement_mode { MeasurementMode::Simulation | MeasurementMode::Memory => BuildMode::Analysis, MeasurementMode::Walltime => BuildMode::Walltime, + MeasurementMode::Telemetry => BuildMode::Telemetry, } } } @@ -50,6 +64,7 @@ impl fmt::Display for MeasurementMode { MeasurementMode::Simulation => "simulation", MeasurementMode::Walltime => "walltime", MeasurementMode::Memory => "memory", + MeasurementMode::Telemetry => "telemetry", } ) } diff --git a/crates/cargo-codspeed/tests/tracing.in/Cargo.toml b/crates/cargo-codspeed/tests/tracing.in/Cargo.toml new file mode 100644 index 00000000..bf323847 --- /dev/null +++ b/crates/cargo-codspeed/tests/tracing.in/Cargo.toml @@ -0,0 +1,15 @@ +[package] +name = "tracing-example" +version = "0.0.0" +edition = "2021" +publish = false + +[dependencies] +divan = { package = "codspeed-divan-compat", path = "../../../divan_compat" } +tracing = "0.1" + +[workspace] + +[[bench]] +name = "tracing_example" +harness = false diff --git a/crates/cargo-codspeed/tests/tracing.in/benches/tracing_example.rs b/crates/cargo-codspeed/tests/tracing.in/benches/tracing_example.rs new file mode 100644 index 00000000..5d4e7d10 --- /dev/null +++ b/crates/cargo-codspeed/tests/tracing.in/benches/tracing_example.rs @@ -0,0 +1,13 @@ +#[divan::bench] +fn traced() -> u32 { + let _span = tracing::info_span!("traced").entered(); + divan::black_box(1) + 1 +} + +fn main() { + eprintln!( + "tracing static max level: {}", + tracing::level_filters::STATIC_MAX_LEVEL + ); + divan::main(); +} diff --git a/crates/cargo-codspeed/tests/tracing.rs b/crates/cargo-codspeed/tests/tracing.rs new file mode 100644 index 00000000..3e78428b --- /dev/null +++ b/crates/cargo-codspeed/tests/tracing.rs @@ -0,0 +1,84 @@ +use predicates::str::contains; + +mod helpers; +use helpers::*; + +const DIR: &str = "tests/tracing.in"; + +fn build_and_run(build_args: &[&str], run_args: &[&str], expected_level: &str) { + let dir = setup(DIR, Project::Simple); + cargo_codspeed(&dir) + .arg("build") + .args(build_args) + .assert() + .success(); + cargo_codspeed(&dir) + .arg("run") + .args(run_args) + .assert() + .success() + .stderr(contains(format!( + "tracing static max level: {expected_level}" + ))); + teardown(dir); +} + +#[test] +fn test_tracing_compiled_out_for_simulation() { + build_and_run(&[], &[], "off"); +} + +#[test] +fn test_tracing_compiled_out_for_walltime() { + build_and_run(&["-m", "walltime"], &["-m", "walltime"], "off"); +} + +#[test] +fn test_tracing_kept_with_keep_tracing() { + build_and_run(&["--keep-tracing"], &[], "trace"); +} + +#[test] +fn test_tracing_kept_for_telemetry() { + build_and_run(&["-m", "telemetry"], &["-m", "telemetry"], "trace"); +} + +#[test] +fn test_telemetry_run_without_build() { + let dir = setup(DIR, Project::Simple); + cargo_codspeed(&dir) + .args(["run", "-m", "telemetry"]) + .assert() + .failure() + .stderr(contains( + "Error: No benchmarks found for the telemetry mode. Run `cargo codspeed build -m telemetry` first.", + )); + teardown(dir); +} + +#[test] +fn test_tracing_kept_for_telemetry_built_with_other_modes() { + build_and_run(&["-m", "walltime,telemetry"], &["-m", "telemetry"], "trace"); +} + +#[test] +fn test_tracing_kept_with_keep_tracing_and_telemetry() { + build_and_run( + &["-m", "walltime,telemetry", "--keep-tracing"], + &["-m", "walltime"], + "trace", + ); +} + +#[test] +fn test_telemetry_rejects_keep_tracing() { + let dir = setup(DIR, Project::Simple); + cargo_codspeed(&dir) + .args(["build", "-m", "telemetry", "--keep-tracing"]) + .assert() + .failure() + .stderr(contains( + "Error: `--keep-tracing` has no effect in telemetry mode", + )); + teardown(dir); +} diff --git a/crates/codspeed/Cargo.toml b/crates/codspeed/Cargo.toml index 59a5ff0a..6c6afb32 100644 --- a/crates/codspeed/Cargo.toml +++ b/crates/codspeed/Cargo.toml @@ -27,11 +27,83 @@ serde = { workspace = true } serde_json = { workspace = true } statrs = { version = "0.18.0", default-features = false } +tracing = { version = "0.1", default-features = false, features = [ + "std", +], optional = true } +tracing-subscriber = { version = "0.3", default-features = false, features = [ + "std", +], optional = true } + +# The export of the telemetry: `cargo-codspeed` only sets this cfg in its telemetry builds, so that +# the other builds don't compile it, even with the `telemetry` feature. +[target.'cfg(codspeed_telemetry)'.dependencies] +opentelemetry = { version = "0.33", default-features = false, features = [ + "trace", + "logs", +], optional = true } +opentelemetry_sdk = { version = "0.33", default-features = false, features = [ + "trace", + "logs", +], optional = true } +opentelemetry-appender-tracing = { version = "0.33", default-features = false, features = [ + "experimental_metadata_attributes", +], optional = true } +opentelemetry-otlp = { version = "0.33", default-features = false, features = [ + "trace", + "logs", + "http-proto", + "reqwest-blocking-client", +], optional = true } +tracing-log = { version = "0.2", default-features = false, features = [ + "log-tracer", + "std", +], optional = true } +tracing-opentelemetry = { version = "0.34", default-features = false, optional = true } +tracing-subscriber = { version = "0.3", default-features = false, features = [ + "std", + "registry", + "env-filter", +], optional = true } +# Unused directly: `sharded-slab`, a dependency of `tracing-subscriber`, declares +# `lazy_static = "1"` but fails to build with 1.0, which minimal-versions resolution +# (`-Z minimal-versions`) picks. Requiring 1.1 here raises that floor. +lazy_static = { version = "1.1", optional = true } + +# `cargo-codspeed` sets this cfg when building benchmarks for a measurement mode, +# so that features unification compiles the `tracing` spans and events of the whole build out. +[target.'cfg(codspeed_tracing_off)'.dependencies] +tracing = { version = "0.1", default-features = false, features = [ + "max_level_off", +] } + +[features] +telemetry = [ + "dep:opentelemetry", + "dep:opentelemetry_sdk", + "dep:opentelemetry-appender-tracing", + "dep:opentelemetry-otlp", + "dep:tracing", + "dep:tracing-log", + "dep:tracing-opentelemetry", + "dep:tracing-subscriber", + "dep:lazy_static", +] + [[bench]] name = "native" harness = false +[[test]] +name = "telemetry_log" +required-features = ["telemetry"] + [dev-dependencies] +opentelemetry-proto = { version = "0.33", default-features = false, features = [ + "gen-tonic-messages", + "logs", + "trace", +] } +prost = "0.14" tempfile = { workspace = true } [build-dependencies] diff --git a/crates/codspeed/build.rs b/crates/codspeed/build.rs index 81e7850f..a0f425c8 100644 --- a/crates/codspeed/build.rs +++ b/crates/codspeed/build.rs @@ -1,6 +1,7 @@ fn main() { println!("cargo:rustc-check-cfg=cfg(use_instrument_hooks)"); println!("cargo:rustc-check-cfg=cfg(codspeed)"); + println!("cargo:rustc-check-cfg=cfg(codspeed_telemetry)"); println!("cargo:rerun-if-changed=instrument-hooks/dist/core.c"); println!("cargo:rerun-if-changed=instrument-hooks/includes/core.h"); diff --git a/crates/codspeed/src/lib.rs b/crates/codspeed/src/lib.rs index a4bc1c12..ac656df0 100644 --- a/crates/codspeed/src/lib.rs +++ b/crates/codspeed/src/lib.rs @@ -6,5 +6,10 @@ pub mod isolation; mod macros; mod measurement; mod request; +#[cfg(all(feature = "telemetry", codspeed_telemetry))] +pub mod telemetry; +#[cfg(all(feature = "telemetry", not(codspeed_telemetry)))] +#[path = "telemetry_disabled.rs"] +pub mod telemetry; pub mod utils; pub mod walltime_results; diff --git a/crates/codspeed/src/telemetry.rs b/crates/codspeed/src/telemetry.rs new file mode 100644 index 00000000..79a0a714 --- /dev/null +++ b/crates/codspeed/src/telemetry.rs @@ -0,0 +1,682 @@ +//! Export of the `tracing` spans and events emitted by benchmarks over OTLP/HTTP, as traces and +//! logs respectively. +//! +//! Everything is a no-op unless `CODSPEED_OTEL_ENDPOINT` is set. +//! +//! Ended spans and log records are exported in batches from a background thread. Telemetry is +//! collected in a run of its own, whose performance is not measured, so exporting while a +//! benchmark runs doesn't matter. +//! +//! Spans and log records keep the wall clock times of the SDK. With instrument hooks, they also +//! carry their times on the clock of the benchmark markers: spans as their +//! `codspeed.monotonic_start_ns` and `codspeed.monotonic_end_ns` attributes, log records as their +//! `codspeed.monotonic_time_ns` attribute. + +use std::sync::OnceLock; +use std::time::Duration; + +use opentelemetry::logs::{AnyValue, LogRecord as _}; +use opentelemetry::trace::{TraceContextExt as _, TracerProvider as _}; +use opentelemetry::{InstrumentationScope, KeyValue}; +use opentelemetry_appender_tracing::layer::OpenTelemetryTracingBridge; +use opentelemetry_otlp::{LogExporter, SpanExporter, WithExportConfig}; +use opentelemetry_sdk::error::OTelSdkResult; +use opentelemetry_sdk::logs::{BatchLogProcessor, LogProcessor, SdkLogRecord, SdkLoggerProvider}; +use opentelemetry_sdk::trace::{ + BatchSpanProcessor, SdkTracerProvider, Span, SpanData, SpanProcessor, +}; +use opentelemetry_sdk::Resource; +use tracing::dispatcher::WeakDispatch; +use tracing::level_filters::LevelFilter; +use tracing::{span, Dispatch, Subscriber}; +use tracing_log::{AsLog, LogTracer}; +use tracing_subscriber::filter::{filter_fn, FilterExt}; +use tracing_subscriber::layer::{Context, SubscriberExt}; +use tracing_subscriber::{registry::LookupSpan, EnvFilter, Layer}; + +use crate::instrument_hooks::InstrumentHooks; + +/// Base URL of the OTLP/HTTP receiver, e.g. `http://localhost:4318`. +const ENDPOINT_ENV: &str = "CODSPEED_OTEL_ENDPOINT"; + +/// `EnvFilter` directives selecting the exported spans and events. Defaults to `info`. +const FILTER_ENV: &str = "CODSPEED_TELEMETRY_FILTER"; + +const DEFAULT_FILTER: &str = "info"; + +/// Span attribute holding the time the span started at on the clock of the benchmark markers, in +/// nanoseconds. +const MONOTONIC_START_ATTRIBUTE: &str = "codspeed.monotonic_start_ns"; + +/// Span attribute holding the time the span ended at on the clock of the benchmark markers, in +/// nanoseconds. +const MONOTONIC_END_ATTRIBUTE: &str = "codspeed.monotonic_end_ns"; + +/// Log record attribute holding the time the record was emitted at on the clock of the benchmark +/// markers, in nanoseconds. +const MONOTONIC_TIME_ATTRIBUTE: &str = "codspeed.monotonic_time_ns"; + +/// Ended spans, and log records, queued for export, past which they are dropped. Larger than the +/// SDK default of 2048, which a benchmark emitting on every iteration fills faster than the +/// exporter drains it. +const MAX_QUEUE_SIZE: usize = 1 << 16; + +/// Spans or log records sent per export request. +const EXPORT_BATCH_SIZE: usize = 4_096; + +/// Target of the span and event [`warm_up`] emits. +const WARMUP_TARGET: &str = "codspeed::telemetry::warmup"; + +/// Field marking the span and event [`warm_up`] emits, which are not exported. +const WARMUP_FIELD: &str = "codspeed_warmup"; + +/// Spans emitted by the exporter itself would be exported in turn, in an endless loop. +const EXPORTER_FILTER_DIRECTIVES: &[&str] = &[ + "h2=off", + "hyper=off", + "hyper_util=off", + "opentelemetry=off", + "opentelemetry_otlp=off", + "opentelemetry_sdk=off", + "opentelemetry_http=off", + "reqwest=off", + "tower=off", +]; + +#[derive(Debug, Clone)] +struct Providers { + tracer: SdkTracerProvider, + logger: SdkLoggerProvider, +} + +/// Flushes and shuts down the exporters when dropped. +#[must_use = "dropping the guard shuts the exporters down"] +pub struct TelemetryGuard { + providers: Providers, +} + +impl Drop for TelemetryGuard { + fn drop(&mut self) { + if let Err(error) = self.providers.tracer.shutdown() { + eprintln!("Warning: failed to flush OpenTelemetry spans: {error}"); + } + if let Err(error) = self.providers.logger.shutdown() { + eprintln!("Warning: failed to flush OpenTelemetry logs: {error}"); + } + } +} + +/// Builds a layer exporting spans and events to the receiver at +/// `CODSPEED_OTEL_ENDPOINT`, to add to a subscriber of your own. Returns `None` when +/// the variable is unset. +/// +/// Events are exported as log records carrying the trace and span IDs of the span they are emitted +/// in, rather than as span events. +/// +/// Records of the `log` crate reach the subscriber only through a [`LogTracer`], which +/// `tracing-subscriber`'s `init` and `try_init` install with its default `tracing-log` feature. +/// Otherwise, call [`LogTracer::init`]. +/// +/// Keep the returned guard alive until the benchmarks are done. +pub fn layer() -> Option<(impl Layer, TelemetryGuard)> +where + S: Subscriber + for<'span> LookupSpan<'span>, +{ + let providers = build_providers()?; + Some((build_layer(&providers), TelemetryGuard { providers })) +} + +fn build_layer(providers: &Providers) -> impl Layer +where + S: Subscriber + for<'span> LookupSpan<'span>, +{ + // Without a hint, `filter_fn` would lift the max level of the subscriber to `TRACE`. + let span_filter = + filter_fn(|metadata| metadata.is_span()).with_max_level_hint(LevelFilter::TRACE); + let event_filter = + filter_fn(|metadata| metadata.is_event()).with_max_level_hint(LevelFilter::TRACE); + let span_layer = MonotonicEndLayer::default() + .and_then(tracing_opentelemetry::layer().with_tracer(providers.tracer.tracer("codspeed"))) + .and_then(MonotonicStartLayer::default()) + .with_filter(build_filter().and(span_filter)); + let log_layer = OpenTelemetryTracingBridge::new(&providers.logger) + .with_filter(build_filter().and(event_filter)); + span_layer.and_then(log_layer) +} + +/// Installs a global `tracing` subscriber exporting spans and events to the receiver at +/// `CODSPEED_OTEL_ENDPOINT`, and registers its provider as the global OpenTelemetry tracer +/// provider. +/// +/// Records of the `log` crate are exported as log records too, unless a `log` logger is already +/// set. +/// +/// Returns `None` when the variable is unset or a global subscriber is already set. +pub fn init() -> Option { + if tracing::dispatcher::has_been_set() { + return None; + } + + let (layer, guard) = layer()?; + let subscriber = tracing_subscriber::registry().with(layer); + if tracing::subscriber::set_global_default(subscriber).is_err() { + return None; + } + // Set after the subscriber, so that `log` skips the records above its max level. + let _ = LogTracer::builder() + .with_max_level(LevelFilter::current().as_log()) + .init(); + opentelemetry::global::set_tracer_provider(guard.providers.tracer.clone()); + warm_up(); + Some(guard) +} + +/// The first span and the first event of the process take tens of microseconds longer than the +/// next ones to go through the subscriber. Emits one of each, which are not exported, so that the +/// first benchmark emitting telemetry doesn't take that time. +fn warm_up() { + let span = tracing::error_span!(target: WARMUP_TARGET, "warmup", codspeed_warmup = true); + let _entered = span.enter(); + tracing::error!(target: WARMUP_TARGET, codspeed_warmup = true, "warmup"); +} + +fn build_providers() -> Option { + let endpoint = std::env::var(ENDPOINT_ENV) + .ok() + .filter(|endpoint| !endpoint.is_empty())?; + let endpoint = endpoint.trim_end_matches('/'); + + let exporters = SpanExporter::builder() + .with_http() + .with_endpoint(format!("{endpoint}/v1/traces")) + .build() + .and_then(|span_exporter| { + let log_exporter = LogExporter::builder() + .with_http() + .with_endpoint(format!("{endpoint}/v1/logs")) + .build()?; + Ok((span_exporter, log_exporter)) + }); + let (span_exporter, log_exporter) = match exporters { + Ok(exporters) => exporters, + Err(error) => { + eprintln!("Warning: failed to build the OpenTelemetry exporters: {error}"); + return None; + } + }; + + let resource = Resource::builder() + .with_service_name(service_name()) + .build(); + let span_processor = BatchSpanProcessor::builder(span_exporter) + .with_batch_config( + opentelemetry_sdk::trace::BatchConfigBuilder::default() + .with_max_queue_size(MAX_QUEUE_SIZE) + .with_max_export_batch_size(EXPORT_BATCH_SIZE) + .build(), + ) + .build(); + let log_processor = BatchLogProcessor::builder(log_exporter) + .with_batch_config( + opentelemetry_sdk::logs::BatchConfigBuilder::default() + .with_max_queue_size(MAX_QUEUE_SIZE) + .with_max_export_batch_size(EXPORT_BATCH_SIZE) + .build(), + ) + .build(); + Some(Providers { + tracer: SdkTracerProvider::builder() + .with_span_processor(WarmupFilter(span_processor)) + .with_resource(resource.clone()) + .build(), + logger: SdkLoggerProvider::builder() + .with_log_processor(GeneratedEventNameProcessor) + .with_log_processor(MonotonicTimeProcessor) + .with_log_processor(WarmupFilter(log_processor)) + .with_resource(resource) + .build(), + }) +} + +/// The time on the clock of the benchmark markers, `None` without instrument hooks. +fn monotonic_now() -> Option { + match InstrumentHooks::current_timestamp() { + 0 => None, + timestamp => i64::try_from(timestamp).ok(), + } +} + +/// Sets an attribute of the OpenTelemetry span of the `tracing` span `id`. Does nothing for the +/// spans the OpenTelemetry layer filters out. +fn set_span_attribute(dispatch: &OnceLock, id: &span::Id, attribute: KeyValue) { + let Some(dispatch) = dispatch.get().and_then(WeakDispatch::upgrade) else { + return; + }; + if let Some(cx) = tracing_opentelemetry::get_otel_context(id, &dispatch) { + cx.span().set_attribute(attribute); + } +} + +/// Sets the [`MONOTONIC_START_ATTRIBUTE`] of each span when it is created. +/// +/// The OpenTelemetry layer only starts the OpenTelemetry span of a `tracing` span when it is first +/// entered, or closed, while it takes its start time when the span is created. This layer starts +/// it right after its creation, to set the attribute then. It must come after the OpenTelemetry +/// layer, whose span it starts. +#[derive(Default)] +struct MonotonicStartLayer { + dispatch: OnceLock, +} + +impl Layer for MonotonicStartLayer +where + S: Subscriber + for<'span> LookupSpan<'span>, +{ + fn on_register_dispatch(&self, subscriber: &Dispatch) { + let _ = self.dispatch.set(subscriber.downgrade()); + } + + fn on_new_span(&self, _attrs: &span::Attributes<'_>, id: &span::Id, _ctx: Context<'_, S>) { + if let Some(timestamp) = monotonic_now() { + set_span_attribute( + &self.dispatch, + id, + KeyValue::new(MONOTONIC_START_ATTRIBUTE, timestamp), + ); + } + } +} + +/// The time a span was last exited at, on the clock of the benchmark markers. +struct LastExit(i64); + +/// Sets the [`MONOTONIC_END_ATTRIBUTE`] of each span when it is closed: the time it was last +/// exited at, or the time it is closed at when it was never entered, as the OpenTelemetry layer +/// takes its end time. +/// +/// The OpenTelemetry layer ends the span when it is closed, and the SDK only adds attributes, so +/// the attribute is set once, then. This layer must come before the OpenTelemetry layer. +#[derive(Default)] +struct MonotonicEndLayer { + dispatch: OnceLock, +} + +impl Layer for MonotonicEndLayer +where + S: Subscriber + for<'span> LookupSpan<'span>, +{ + fn on_register_dispatch(&self, subscriber: &Dispatch) { + let _ = self.dispatch.set(subscriber.downgrade()); + } + + fn on_exit(&self, id: &span::Id, ctx: Context<'_, S>) { + if let (Some(timestamp), Some(span)) = (monotonic_now(), ctx.span(id)) { + // A span entered on several threads is exited concurrently, and the exits may take + // the lock in another order than they read the clock in. + let mut extensions = span.extensions_mut(); + match extensions.get_mut::() { + Some(last_exit) => last_exit.0 = last_exit.0.max(timestamp), + None => extensions.insert(LastExit(timestamp)), + } + } + } + + fn on_close(&self, id: span::Id, ctx: Context<'_, S>) { + let last_exit = ctx + .span(&id) + .and_then(|span| span.extensions().get::().map(|exit| exit.0)); + if let Some(timestamp) = last_exit.or_else(monotonic_now) { + set_span_attribute( + &self.dispatch, + &id, + KeyValue::new(MONOTONIC_END_ATTRIBUTE, timestamp), + ); + } + } +} + +/// Sets the [`MONOTONIC_TIME_ATTRIBUTE`] of the emitted log records, which are emitted as their +/// event happens. +#[derive(Debug)] +struct MonotonicTimeProcessor; + +impl LogProcessor for MonotonicTimeProcessor { + fn emit(&self, record: &mut SdkLogRecord, _instrumentation: &InstrumentationScope) { + if let Some(timestamp) = monotonic_now() { + record.add_attribute(MONOTONIC_TIME_ATTRIBUTE, timestamp); + } + } + + fn force_flush(&self) -> OTelSdkResult { + Ok(()) + } + + fn shutdown_with_timeout(&self, _timeout: Duration) -> OTelSdkResult { + Ok(()) + } +} + +/// Event name `tracing-log` gives every record of the `log` crate. +const LOG_EVENT_NAME: &str = "log event"; + +/// Clears the event names `tracing` generates for the events given none, so that only the +/// events named at their callsite are exported with a name. Their location stays in the +/// `code.*` attributes. +#[derive(Debug)] +struct GeneratedEventNameProcessor; + +impl LogProcessor for GeneratedEventNameProcessor { + fn emit(&self, record: &mut SdkLogRecord, _instrumentation: &InstrumentationScope) { + if has_generated_event_name(record) { + record.set_event_name(""); + } + } + + fn force_flush(&self) -> OTelSdkResult { + Ok(()) + } + + fn shutdown_with_timeout(&self, _timeout: Duration) -> OTelSdkResult { + Ok(()) + } +} + +/// Whether the event name is `log event`, or the `event :` that `tracing` generates +/// from the location of the record. +fn has_generated_event_name(record: &SdkLogRecord) -> bool { + let Some(name) = record.event_name() else { + return false; + }; + if name == LOG_EVENT_NAME { + return true; + } + let attribute = |key: &str| { + record + .attributes_iter() + .find(|(attribute_key, _)| attribute_key.as_str() == key) + .map(|(_, value)| value) + }; + let (Some(AnyValue::String(file)), Some(&AnyValue::Int(line))) = + (attribute("code.filepath"), attribute("code.lineno")) + else { + return false; + }; + name.strip_prefix("event ") + .and_then(|location| location.strip_prefix(file.as_str())) + .and_then(|location| location.strip_prefix(':')) + .is_some_and(|location| location.parse() == Ok(line)) +} + +/// Drops the span and event [`warm_up`] emits, before handing the others to the processor it +/// wraps. +#[derive(Debug)] +struct WarmupFilter

(P); + +impl SpanProcessor for WarmupFilter

{ + fn on_start(&self, span: &mut Span, cx: &opentelemetry::Context) { + self.0.on_start(span, cx); + } + + fn on_end(&self, span: SpanData) { + let is_warmup = span + .attributes + .iter() + .any(|attribute| attribute.key.as_str() == WARMUP_FIELD); + if !is_warmup { + self.0.on_end(span); + } + } + + fn force_flush(&self) -> OTelSdkResult { + self.0.force_flush() + } + + fn shutdown_with_timeout(&self, timeout: Duration) -> OTelSdkResult { + self.0.shutdown_with_timeout(timeout) + } + + fn set_resource(&mut self, resource: &Resource) { + self.0.set_resource(resource); + } +} + +impl LogProcessor for WarmupFilter

{ + fn emit(&self, record: &mut SdkLogRecord, instrumentation: &InstrumentationScope) { + let is_warmup = record + .attributes_iter() + .any(|(key, _)| key.as_str() == WARMUP_FIELD); + if !is_warmup { + self.0.emit(record, instrumentation); + } + } + + fn force_flush(&self) -> OTelSdkResult { + self.0.force_flush() + } + + fn shutdown_with_timeout(&self, timeout: Duration) -> OTelSdkResult { + self.0.shutdown_with_timeout(timeout) + } + + fn set_resource(&mut self, resource: &Resource) { + self.0.set_resource(resource); + } +} + +fn build_filter() -> EnvFilter { + let user_directives = std::env::var(FILTER_ENV).unwrap_or_else(|_| DEFAULT_FILTER.to_owned()); + let warmup_directive = format!("{WARMUP_TARGET}=error"); + let directives = [user_directives.as_str(), warmup_directive.as_str()] + .into_iter() + .chain(EXPORTER_FILTER_DIRECTIVES.iter().copied()) + .collect::>() + .join(","); + EnvFilter::builder().parse_lossy(directives) +} + +/// The benchmark executable name, without the hash cargo appends to it. +fn service_name() -> String { + let file_stem = std::env::current_exe().ok().and_then(|exe| { + exe.file_stem() + .map(|stem| stem.to_string_lossy().into_owned()) + }); + match file_stem { + Some(stem) => match stem.rsplit_once('-') { + Some((name, hash)) if is_cargo_hash(hash) => name.to_owned(), + _ => stem, + }, + None => "codspeed-benchmark".to_owned(), + } +} + +fn is_cargo_hash(s: &str) -> bool { + s.len() == 16 && s.chars().all(|c| c.is_ascii_hexdigit()) +} + +#[cfg(test)] +mod tests { + use super::*; + use opentelemetry::Value; + use opentelemetry_sdk::logs::LogBatch; + use opentelemetry_sdk::trace::SpanData; + use std::sync::{Arc, Mutex}; + + #[derive(Debug, Clone, Default)] + struct RecordingExporter(Arc>>); + + impl opentelemetry_sdk::trace::SpanExporter for RecordingExporter { + async fn export(&self, batch: Vec) -> OTelSdkResult { + self.0.lock().unwrap().extend(batch); + Ok(()) + } + } + + #[derive(Debug, Clone, Default)] + struct RecordingLogExporter(Arc>>); + + impl opentelemetry_sdk::logs::LogExporter for RecordingLogExporter { + async fn export(&self, batch: LogBatch<'_>) -> OTelSdkResult { + let records = batch.iter().map(|(record, _)| record.clone()); + self.0.lock().unwrap().extend(records); + Ok(()) + } + } + + fn log_body(record: &SdkLogRecord) -> String { + match record.body() { + Some(AnyValue::String(body)) => body.to_string(), + other => panic!("unexpected log body {other:?}"), + } + } + + #[test] + fn exports_events_as_log_records_of_their_span() { + let span_exporter = RecordingExporter::default(); + let log_exporter = RecordingLogExporter::default(); + let providers = Providers { + tracer: SdkTracerProvider::builder() + .with_simple_exporter(span_exporter.clone()) + .build(), + logger: SdkLoggerProvider::builder() + .with_simple_exporter(log_exporter.clone()) + .build(), + }; + let subscriber = tracing_subscriber::registry().with(build_layer(&providers)); + + tracing::subscriber::with_default(subscriber, || { + let _span = tracing::info_span!("parent").entered(); + tracing::info!("inside"); + }); + + let spans = span_exporter.0.lock().unwrap(); + let [span] = spans.as_slice() else { + panic!("expected one span, got {spans:?}"); + }; + assert!(span.events.is_empty()); + let records = log_exporter.0.lock().unwrap(); + let [record] = records.as_slice() else { + panic!("expected one log record, got {records:?}"); + }; + assert_eq!(log_body(record), "inside"); + let trace_context = record.trace_context().unwrap(); + assert_eq!(trace_context.trace_id, span.span_context.trace_id()); + assert_eq!(trace_context.span_id, span.span_context.span_id()); + } + + #[test] + fn clears_the_event_names_generated_by_tracing() { + let log_exporter = RecordingLogExporter::default(); + let providers = Providers { + tracer: SdkTracerProvider::builder().build(), + logger: SdkLoggerProvider::builder() + .with_log_processor(GeneratedEventNameProcessor) + .with_simple_exporter(log_exporter.clone()) + .build(), + }; + let subscriber = tracing_subscriber::registry().with(build_layer(&providers)); + + tracing::subscriber::with_default(subscriber, || { + tracing::info!("unnamed"); + tracing::info!(name: "cache.miss", "named"); + }); + + let records = log_exporter.0.lock().unwrap(); + let names = records + .iter() + .map(|record| (log_body(record), record.event_name())) + .collect::>(); + assert_eq!( + names, + [ + ("unnamed".to_owned(), Some("")), + ("named".to_owned(), Some("cache.miss")), + ] + ); + let has_file = records[0].attributes_iter().any(|(key, value)| { + key.as_str() == "code.filepath" && value == &AnyValue::from(file!()) + }); + assert!(has_file); + } + + fn providers( + span_exporter: &RecordingExporter, + log_exporter: &RecordingLogExporter, + ) -> Providers { + Providers { + tracer: SdkTracerProvider::builder() + .with_simple_exporter(span_exporter.clone()) + .build(), + logger: SdkLoggerProvider::builder() + .with_log_processor(MonotonicTimeProcessor) + .with_simple_exporter(log_exporter.clone()) + .build(), + } + } + + fn span_attribute(span: &SpanData, key: &str) -> i64 { + let value = span + .attributes + .iter() + .find(|attribute| attribute.key.as_str() == key) + .map(|attribute| attribute.value.clone()); + match value { + Some(Value::I64(value)) => value, + _ => panic!("no {key} in {:?}", span.attributes), + } + } + + #[cfg(all(use_instrument_hooks, any(target_os = "linux", target_os = "macos")))] + #[test] + fn records_the_monotonic_time_of_spans_and_log_records() { + let span_exporter = RecordingExporter::default(); + let log_exporter = RecordingLogExporter::default(); + let subscriber = tracing_subscriber::registry() + .with(build_layer(&providers(&span_exporter, &log_exporter))); + let now = || InstrumentHooks::current_timestamp() as i64; + let pause = || std::thread::sleep(Duration::from_millis(1)); + + let times = tracing::subscriber::with_default(subscriber, || { + let before = now(); + let span = tracing::info_span!("entered after its creation"); + let created = now(); + pause(); + let entered = span.enter(); + tracing::info!("inside"); + let logged = now(); + drop(entered); + let exited = now(); + pause(); + drop(span); + let never_entered = tracing::info_span!("never entered"); + pause(); + let closing = now(); + drop(never_entered); + let closed = now(); + [before, created, logged, exited, closing, closed] + }); + let [before, created, logged, exited, closing, closed] = times; + + let spans = span_exporter.0.lock().unwrap(); + let [span, never_entered] = &spans[..] else { + panic!("expected two spans, got {spans:?}"); + }; + // Started when created, not when entered, and ended when last exited, not when closed. + let start = span_attribute(span, MONOTONIC_START_ATTRIBUTE); + assert!(before <= start && start <= created); + let end = span_attribute(span, MONOTONIC_END_ATTRIBUTE); + assert!(logged <= end && end <= exited); + // Ended when closed. + let end = span_attribute(never_entered, MONOTONIC_END_ATTRIBUTE); + assert!(closing <= end && end <= closed); + + let records = log_exporter.0.lock().unwrap(); + let time = records[0] + .attributes_iter() + .find(|(key, _)| key.as_str() == MONOTONIC_TIME_ATTRIBUTE) + .map(|(_, value)| value.clone()); + let Some(AnyValue::Int(time)) = time else { + panic!("no monotonic time in {:?}", records[0]); + }; + assert!(created <= time && time <= logged); + } +} diff --git a/crates/codspeed/src/telemetry_disabled.rs b/crates/codspeed/src/telemetry_disabled.rs new file mode 100644 index 00000000..f5190df2 --- /dev/null +++ b/crates/codspeed/src/telemetry_disabled.rs @@ -0,0 +1,24 @@ +//! Stands in for the export of the `tracing` spans and events in the builds that don't compile it: +//! only the telemetry builds of `cargo-codspeed` do. Nothing is exported. + +use tracing::Subscriber; +use tracing_subscriber::layer::Identity; +use tracing_subscriber::registry::LookupSpan; +use tracing_subscriber::Layer; + +/// Flushes and shuts down the exporters when dropped. +#[must_use = "dropping the guard shuts the exporters down"] +pub struct TelemetryGuard(()); + +/// Returns `None`: the layer only exports in the telemetry builds. +pub fn layer() -> Option<(impl Layer, TelemetryGuard)> +where + S: Subscriber + for<'span> LookupSpan<'span>, +{ + None::<(Identity, TelemetryGuard)> +} + +/// Returns `None`: the subscriber is only installed in the telemetry builds. +pub fn init() -> Option { + None +} diff --git a/crates/codspeed/tests/telemetry_log.rs b/crates/codspeed/tests/telemetry_log.rs new file mode 100644 index 00000000..492004b0 --- /dev/null +++ b/crates/codspeed/tests/telemetry_log.rs @@ -0,0 +1,124 @@ +//! Exports the records of the `log` crate through [`telemetry::init`], to an OTLP/HTTP receiver. +//! +//! In a test binary of its own, since `init` installs process-wide loggers. +#![cfg(codspeed_telemetry)] + +use std::io::{BufRead, BufReader, Read, Write}; +use std::net::TcpListener; +use std::sync::mpsc; +use std::thread; + +use codspeed::telemetry; +use opentelemetry_proto::tonic::collector::logs::v1::ExportLogsServiceRequest; +use opentelemetry_proto::tonic::collector::trace::v1::ExportTraceServiceRequest; +use opentelemetry_proto::tonic::common::v1::{any_value::Value, KeyValue}; +use opentelemetry_proto::tonic::logs::v1::SeverityNumber; +use prost::Message; +use tracing_log::log; + +/// Accepts OTLP/HTTP requests on a local port, sending the path and body of each. +fn spawn_receiver() -> (String, mpsc::Receiver<(String, Vec)>) { + let listener = TcpListener::bind("127.0.0.1:0").unwrap(); + let endpoint = format!("http://{}", listener.local_addr().unwrap()); + let (sender, receiver) = mpsc::channel(); + thread::spawn(move || { + for stream in listener.incoming() { + let mut stream = BufReader::new(stream.unwrap()); + let mut lines = (&mut stream).lines().map(Result::unwrap); + let path = lines.next().unwrap().split(' ').nth(1).unwrap().to_owned(); + let headers = lines + .take_while(|header| !header.is_empty()) + .collect::>(); + // The exporter sends uncompressed bodies of known length. + let content_length = headers + .iter() + .find_map(|header| { + let (name, value) = header.split_once(':')?; + name.eq_ignore_ascii_case("content-length") + .then(|| value.trim().parse::().unwrap()) + }) + .expect("a Content-Length header"); + let mut body = vec![0; content_length]; + stream.read_exact(&mut body).unwrap(); + sender.send((path, body)).unwrap(); + stream + .get_mut() + .write_all(b"HTTP/1.1 200 OK\r\nContent-Length: 0\r\nConnection: close\r\n\r\n") + .unwrap(); + } + }); + (endpoint, receiver) +} + +fn attribute(attributes: &[KeyValue], key: &str) -> Option { + let attribute = attributes.iter().find(|attribute| attribute.key == key)?; + attribute.value.clone()?.value +} + +fn string(value: &str) -> Option { + Some(Value::StringValue(value.to_owned())) +} + +#[test] +fn exports_the_records_of_the_log_crate() { + let (endpoint, requests) = spawn_receiver(); + std::env::set_var("CODSPEED_OTEL_ENDPOINT", endpoint); + std::env::remove_var("CODSPEED_TELEMETRY_FILTER"); + let guard = telemetry::init().expect("telemetry should be enabled"); + // Taken from the filter of the subscriber: the `log` macros skip the records above it. + assert_eq!(log::max_level(), log::LevelFilter::Info); + + log::debug!("filtered out by level"); + log::info!(target: "reqwest::connect", "filtered out by target"); + let span = tracing::info_span!("span").entered(); + log::info!("from log"); + let line = line!() - 1; + drop(span); + drop(guard); + + let (mut spans, mut records) = (Vec::new(), Vec::new()); + for (path, body) in requests.try_iter() { + match path.as_str() { + "/v1/traces" => spans.extend( + ExportTraceServiceRequest::decode(body.as_slice()) + .unwrap() + .resource_spans + .into_iter() + .flat_map(|resource| resource.scope_spans) + .flat_map(|scope| scope.spans), + ), + "/v1/logs" => records.extend( + ExportLogsServiceRequest::decode(body.as_slice()) + .unwrap() + .resource_logs + .into_iter() + .flat_map(|resource| resource.scope_logs) + .flat_map(|scope| scope.log_records), + ), + other => panic!("unexpected request to {other}"), + } + } + let [span] = spans.as_slice() else { + panic!("expected one span, got {spans:?}"); + }; + let [record] = records.as_slice() else { + panic!("expected one log record, got {records:?}"); + }; + let body = record.body.clone().and_then(|body| body.value); + assert_eq!(body, string("from log")); + assert_eq!(record.severity_number, SeverityNumber::Info as i32); + assert_eq!(record.severity_text, "INFO"); + assert_eq!(record.event_name, ""); + let attributes = &record.attributes; + assert_eq!(attribute(attributes, "code.filepath"), string(file!())); + assert_eq!( + attribute(attributes, "code.lineno"), + Some(Value::IntValue(line.into())) + ); + assert_eq!( + attribute(attributes, "code.namespace"), + string(module_path!()) + ); + assert_eq!(record.trace_id, span.trace_id); + assert_eq!(record.span_id, span.span_id); +} diff --git a/crates/divan_compat/Cargo.toml b/crates/divan_compat/Cargo.toml index ffaeeefe..859a2f21 100644 --- a/crates/divan_compat/Cargo.toml +++ b/crates/divan_compat/Cargo.toml @@ -24,6 +24,10 @@ codspeed-divan-compat-macros = { version = "=6.0.0", path = './macros' } regex = "1.11.3" clap = { version = "4", default-features = false, features = ["std", "env"] } +[features] +# Export the `tracing` spans and events of benchmarks over OTLP to `CODSPEED_OTEL_ENDPOINT` +telemetry = ["codspeed/telemetry"] + [dev-dependencies] tokio = { version = "1.39", features = ["rt-multi-thread"] } diff --git a/crates/divan_compat/README.md b/crates/divan_compat/README.md index dc448a89..3f02ea83 100644 --- a/crates/divan_compat/README.md +++ b/crates/divan_compat/README.md @@ -75,6 +75,47 @@ Done running my_benchmark Finished running 1 benchmark suite(s) ``` +### Exporting `tracing` spans and logs with OpenTelemetry + +With the opt-in `telemetry` feature, `divan::main()` installs a global `tracing` subscriber that +exports the spans emitted by your benchmarks as traces, and their events as logs, over OTLP/HTTP. +Each log record carries the trace and span IDs of the span it was emitted in: + +```toml title="Cargo.toml" +[dev-dependencies] +divan = { package = "codspeed-divan-compat", version = "*", features = ["telemetry"] } +``` + +- `CODSPEED_OTEL_ENDPOINT`: base URL of the OTLP/HTTP receiver, e.g. `http://localhost:4318`. + Nothing is exported when it is unset. +- `CODSPEED_TELEMETRY_FILTER`: [`EnvFilter`](https://docs.rs/tracing-subscriber/latest/tracing_subscriber/filter/struct.EnvFilter.html) + directives selecting what is exported. Defaults to `info`. + +The records of the [`log`](https://docs.rs/log) crate are exported as logs too, unless a `log` +logger is set before calling `divan::main()`. + +The tracer provider is also registered as the global OpenTelemetry tracer provider, and spans and +logs are flushed when `divan::main()` returns. + +If your benchmarks already set a global subscriber before calling `divan::main()`, nothing is +installed. Add the CodSpeed layer to your subscriber instead, and keep its guard alive until the +benchmarks are done. `tracing-subscriber`'s `init()` also forwards the records of the `log` crate +to it, with its default `tracing-log` feature: + +```rust +use tracing_subscriber::{layer::SubscriberExt, util::SubscriberInitExt}; + +fn main() { + let (telemetry_layer, _telemetry_guard) = divan::telemetry::layer().unzip(); + tracing_subscriber::registry() + .with(telemetry_layer) + .with(tracing_subscriber::fmt::layer()) + .init(); + + divan::main(); +} +``` + ### Not supported: - [`divan::bench(crate = xxx)`](https://docs.rs/divan/latest/divan/attr.bench.html#crate): due to how the compatibility layer works internally, we do not plan to support this feature. diff --git a/crates/divan_compat/examples/Cargo.toml b/crates/divan_compat/examples/Cargo.toml index 40c57f3c..bd8ed957 100644 --- a/crates/divan_compat/examples/Cargo.toml +++ b/crates/divan_compat/examples/Cargo.toml @@ -8,10 +8,12 @@ license = "MIT OR Apache-2.0" [dependencies] -divan = { package = "codspeed-divan-compat", path = ".." } +divan = { package = "codspeed-divan-compat", path = "..", features = ["telemetry"] } fastrand = "2.3.0" +log = "0.4" num-bigint = { version = "0.4", optional = true } num-traits = { version = "0.2", optional = true } +tracing = "0.1" [features] default = ["big-math"] @@ -48,3 +50,7 @@ harness = false [[bench]] name = "alloc" harness = false + +[[bench]] +name = "telemetry" +harness = false diff --git a/crates/divan_compat/examples/benches/telemetry.rs b/crates/divan_compat/examples/benches/telemetry.rs new file mode 100644 index 00000000..7b8969e4 --- /dev/null +++ b/crates/divan_compat/examples/benches/telemetry.rs @@ -0,0 +1,59 @@ +//! Benchmarks emitting telemetry in different ways: `tracing` spans and events, records of the +//! `log` crate, or nothing at all. + +use tracing::{debug, debug_span, info, info_span, warn}; + +fn parse(input: &str) -> Vec { + let _span = info_span!("parse", input_len = input.len()).entered(); + let values: Vec = input + .split(',') + .filter_map(|field| field.parse().ok()) + .collect(); + info!(count = values.len(), "parsed values"); + values +} + +fn sum(values: &[u32]) -> u32 { + let _span = info_span!("sum").entered(); + let _filtered_out = debug_span!("sum_inner").entered(); + debug!("filtered out by the default filter"); + values.iter().sum() +} + +/// Nested `tracing` spans, with events in them. +#[divan::bench] +fn parse_and_sum() -> u32 { + let _span = info_span!("parse_and_sum").entered(); + let values = parse("1,2,x,3"); + if values.len() < 4 { + warn!(skipped = 4 - values.len(), "skipped invalid fields"); + } + sum(&values) +} + +/// A `tracing` event outside of any span. +#[divan::bench] +fn event_without_span() -> u32 { + let values = std::hint::black_box([1, 2, 3]); + info!(count = values.len(), "summed without a span"); + values.iter().sum() +} + +/// Records of the `log` crate, without `tracing`. +#[divan::bench] +fn log_records() -> u32 { + let values = std::hint::black_box([1, 2, 3]); + log::info!("summing {} values, logged with the log crate", values.len()); + log::debug!("filtered out by the default filter"); + values.iter().sum() +} + +/// No telemetry at all. +#[divan::bench] +fn no_telemetry() -> u32 { + std::hint::black_box([1, 2, 3]).iter().sum() +} + +fn main() { + divan::main(); +} diff --git a/crates/divan_compat/src/lib.rs b/crates/divan_compat/src/lib.rs index b977abfa..dfca90c3 100644 --- a/crates/divan_compat/src/lib.rs +++ b/crates/divan_compat/src/lib.rs @@ -1,4 +1,6 @@ pub use codspeed::codspeed_uri; +#[cfg(feature = "telemetry")] +pub use codspeed::telemetry; #[cfg(not(codspeed))] mod compat_divan { @@ -19,3 +21,13 @@ mod compat_divan { } pub use compat_divan::*; + +/// Runs all registered benchmarks. +/// +/// With the `telemetry` feature, spans and events are exported for the duration of the run. +pub fn main() { + #[cfg(feature = "telemetry")] + let _telemetry_guard = telemetry::init(); + + compat_divan::main(); +}