diff --git a/Cargo.lock b/Cargo.lock index 9d9ae8426..122a50e1e 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -35,7 +35,7 @@ dependencies = [ "bytestring", "derive_more", "encoding_rs", - "foldhash", + "foldhash 0.1.5", "futures-core", "http 0.2.12", "httparse", @@ -133,7 +133,7 @@ dependencies = [ "cfg-if", "derive_more", "encoding_rs", - "foldhash", + "foldhash 0.1.5", "futures-core", "futures-util", "impl-more", @@ -1089,15 +1089,6 @@ version = "0.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "68ccbd3153aa153b2f5eff557537ffce81e4dd6c50ae0eddc41dc8d0c388436f" -[[package]] -name = "cadence" -version = "1.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3075f133bee430b7644c54fb629b9b4420346ffa275a45c81a6babe8b09b4f51" -dependencies = [ - "crossbeam-channel", -] - [[package]] name = "cc" version = "1.2.36" @@ -1750,6 +1741,12 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "endian-type" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c34f04666d835ff5d62e058c3995147c06f42fe86ff053337632bca83e42702d" + [[package]] name = "enum-as-inner" version = "0.6.1" @@ -1880,6 +1877,12 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d9c4f5dac5e15c24eb999c26181a6ca40b39fe946cbe4c263c7209467bc83af2" +[[package]] +name = "foldhash" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ce24cb58228fbb8aa041425bb1050850ac19177686ea6e0f41a70416f56fdb" + [[package]] name = "foreign-types" version = "0.3.2" @@ -2202,7 +2205,16 @@ checksum = "9229cfe53dfd69f0609a49f65461bd93001ea1ef889cd5529dd176593f5338a1" dependencies = [ "allocator-api2", "equivalent", - "foldhash", + "foldhash 0.1.5", +] + +[[package]] +name = "hashbrown" +version = "0.16.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "841d1cc9bed7f9236f321df977030373f4a4163ae1a7dbfe1a51a2c1a51d9100" +dependencies = [ + "foldhash 0.2.0", ] [[package]] @@ -3064,6 +3076,51 @@ dependencies = [ "libc", ] +[[package]] +name = "metrics" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5d5312e9ba3771cfa961b585728215e3d972c950a3eed9252aa093d6301277e8" +dependencies = [ + "ahash", + "portable-atomic", +] + +[[package]] +name = "metrics-exporter-dogstatsd" +version = "0.9.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "961f3712d8a7cfe14caaf74c3af503fe701cee6439ff49a7a3ebd04bf49c0502" +dependencies = [ + "bytes", + "itoa", + "metrics", + "metrics-util", + "ryu", + "thiserror 2.0.17", + "tracing", +] + +[[package]] +name = "metrics-util" +version = "0.20.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cdfb1365fea27e6dd9dc1dbc19f570198bc86914533ad639dae939635f096be4" +dependencies = [ + "aho-corasick", + "crossbeam-epoch", + "crossbeam-utils", + "hashbrown 0.16.1", + "indexmap", + "metrics", + "ordered-float", + "quanta", + "radix_trie", + "rand 0.9.2", + "rand_xoshiro", + "sketches-ddsketch", +] + [[package]] name = "mime" version = "0.3.17" @@ -3261,6 +3318,15 @@ version = "1.0.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "650eef8c711430f1a879fdd01d4745a7deea475becfb90269c06775983bbf086" +[[package]] +name = "nibble_vec" +version = "0.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77a5d83df9f36fe23f0c3648c6bbb8b0298bb5f1939c8f2704431371f4b84d43" +dependencies = [ + "smallvec", +] + [[package]] name = "nom" version = "7.1.3" @@ -3414,6 +3480,15 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "04744f49eae99ab78e0d5c0b603ab218f515ea8cfe5a456d7629ad883a3b6e7d" +[[package]] +name = "ordered-float" +version = "5.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7f4779c6901a562440c3786d08192c6fbda7c1c2060edd10006b05ee35d10f2d" +dependencies = [ + "num-traits", +] + [[package]] name = "os_info" version = "3.12.0" @@ -3810,6 +3885,21 @@ dependencies = [ "cc", ] +[[package]] +name = "quanta" +version = "0.12.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3ab5a9d756f0d97bdc89019bd2e4ea098cf9cde50ee7564dde6b81ccc8f06c7" +dependencies = [ + "crossbeam-utils", + "libc", + "once_cell", + "raw-cpuid", + "wasi 0.11.1+wasi-snapshot-preview1", + "web-sys", + "winapi", +] + [[package]] name = "quick-xml" version = "0.38.3" @@ -3840,6 +3930,16 @@ version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "dc33ff2d4973d518d823d61aa239014831e521c75da58e3df4840d3f47749d09" +[[package]] +name = "radix_trie" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c069c179fcdc6a2fe24d8d18305cf085fdbd4f922c041943e203685d6a1c58fd" +dependencies = [ + "endian-type", + "nibble_vec", +] + [[package]] name = "rand" version = "0.8.5" @@ -3887,6 +3987,15 @@ dependencies = [ "getrandom 0.3.3", ] +[[package]] +name = "rand_xoshiro" +version = "0.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f703f4665700daf5512dcca5f43afa6af89f09db47fb56be587f80636bda2d41" +dependencies = [ + "rand_core 0.9.3", +] + [[package]] name = "range-collections" version = "0.2.4" @@ -3907,6 +4016,15 @@ dependencies = [ "num-traits", ] +[[package]] +name = "raw-cpuid" +version = "11.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "498cd0dc59d73224351ee52a95fee0f1a617a2eae0e7d9d720cc622c73a54186" +dependencies = [ + "bitflags 2.9.4", +] + [[package]] name = "rayon" version = "1.11.0" @@ -5273,9 +5391,7 @@ dependencies = [ "aws-smithy-xml", "bytes", "cab", - "cadence", "chrono", - "crossbeam-utils", "filetime", "flate2", "futures", @@ -5286,12 +5402,13 @@ dependencies = [ "insta", "ipnetwork", "jsonwebtoken", + "metrics", + "metrics-exporter-dogstatsd", "moka", "proguard", "rand 0.9.2", "rayon", "reqwest", - "rustc-hash", "sentry", "serde", "serde-vars", @@ -5304,7 +5421,6 @@ dependencies = [ "symbolicator-test", "tempfile", "thiserror 2.0.17", - "thread_local", "tokio", "tokio-util", "tracing", diff --git a/Cargo.toml b/Cargo.toml index 346b4bd40..2f40cf3b1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -68,12 +68,10 @@ axum-server = "0.7.2" bindgen = "0.72.1" bytes = "1.11.1" cab = "0.6.0" -cadence = "1.0.0" chrono = { version = "0.4.19", features = ["serde"] } clap = { version = "4.3.2", features = ["derive"] } cmake = "0.1.46" console = "0.16.0" -crossbeam-utils = "0.8.19" data-url = "0.3.0" dirs = "6.0.0" filetime = "0.2.16" @@ -92,6 +90,8 @@ jemallocator = { version = "0.5", features = [ "unprefixed_malloc_on_supported_platforms", ] } jsonwebtoken = "9.1.0" +metrics = "0.24.3" +metrics-exporter-dogstatsd = "0.9.6" minidump = "0.26.1" minidump-processor = "0.26.1" minidump-unwind = "0.26.1" @@ -102,7 +102,6 @@ rand = "0.9.0" rayon = "1.10.0" regex = "1.5.5" reqwest = "0.12.15" -rustc-hash = "2.0.0" rustls = { version = "0.23.31", features = ["ring"] } sentry = { version = "0.42.0", default-features = false, features = [ # default features, except `release-health` is disabled @@ -133,7 +132,6 @@ symbolicator-test = { path = "crates/symbolicator-test" } tempfile = "3.10.0" test-assembler = "0.1.5" thiserror = "2.0.16" -thread_local = "1.1.7" tokio = "1.44.2" tokio-metrics = "0.4.5" tokio-util = "0.7.10" diff --git a/crates/symbolicator-js/src/lookup.rs b/crates/symbolicator-js/src/lookup.rs index acda43652..15855d4a9 100644 --- a/crates/symbolicator-js/src/lookup.rs +++ b/crates/symbolicator-js/src/lookup.rs @@ -158,7 +158,7 @@ impl SourceMapLookup { modules_by_abs_path.insert(module.code_file.to_owned(), cached_module); } - let metrics = JsMetrics::new(scope.as_ref().parse().ok()); + let metrics = JsMetrics::default(); let fetcher = ArtifactFetcher { objects, diff --git a/crates/symbolicator-js/src/metrics.rs b/crates/symbolicator-js/src/metrics.rs index 5a982a3f8..7ed3a013f 100644 --- a/crates/symbolicator-js/src/metrics.rs +++ b/crates/symbolicator-js/src/metrics.rs @@ -45,48 +45,38 @@ pub struct JsMetrics { pub fetched_bundles: u64, // Product managers are interested in these metrics as a "funnel": - found_source_via_debugid: i64, - found_source_via_release_with_debugid: i64, - found_source_via_release_old_with_debugid: i64, - found_source_via_scraping_with_debugid: i64, - source_not_found_with_debugid: i64, + found_source_via_debugid: u64, + found_source_via_release_with_debugid: u64, + found_source_via_release_old_with_debugid: u64, + found_source_via_scraping_with_debugid: u64, + source_not_found_with_debugid: u64, - found_source_via_release_without_debugid: i64, - found_source_via_release_old_without_debugid: i64, - found_source_via_scraping_without_debugid: i64, - source_not_found_without_debugid: i64, + found_source_via_release_without_debugid: u64, + found_source_via_release_old_without_debugid: u64, + found_source_via_scraping_without_debugid: u64, + source_not_found_without_debugid: u64, - found_sourcemap_via_debugid: i64, - found_sourcemap_via_release_with_debugid: i64, - found_sourcemap_via_release_old_with_debugid: i64, - found_sourcemap_via_scraping_with_debugid: i64, - sourcemap_not_found_with_debugid: i64, + found_sourcemap_via_debugid: u64, + found_sourcemap_via_release_with_debugid: u64, + found_sourcemap_via_release_old_with_debugid: u64, + found_sourcemap_via_scraping_with_debugid: u64, + sourcemap_not_found_with_debugid: u64, - found_sourcemap_via_release_without_debugid: i64, - found_sourcemap_via_release_old_without_debugid: i64, - found_sourcemap_via_scraping_without_debugid: i64, - sourcemap_not_found_without_debugid: i64, + found_sourcemap_via_release_without_debugid: u64, + found_sourcemap_via_release_old_without_debugid: u64, + found_sourcemap_via_scraping_without_debugid: u64, + sourcemap_not_found_without_debugid: u64, - sourcemap_not_needed: i64, + sourcemap_not_needed: u64, // Engineers might also be interested in these metrics: - found_bundle_via_debugid: i64, - found_bundle_via_index: i64, - found_bundle_via_release: i64, - found_bundle_via_release_old: i64, - - had_debug_id: bool, - project_id: Option, + found_bundle_via_debugid: u64, + found_bundle_via_index: u64, + found_bundle_via_release: u64, + found_bundle_via_release_old: u64, } impl JsMetrics { - pub fn new(project_id: Option) -> Self { - Self { - project_id, - ..Self::default() - } - } - pub fn record_bundle_fetched(&mut self) { self.fetched_bundles += 1; } @@ -94,14 +84,12 @@ impl JsMetrics { pub fn record_file_scraped(&mut self, file_ty: SourceFileType, had_debug_id: bool) { match (file_ty, had_debug_id) { (SourceFileType::SourceMap, true) => { - self.had_debug_id = true; self.found_sourcemap_via_scraping_with_debugid += 1 } (SourceFileType::SourceMap, false) => { self.found_sourcemap_via_scraping_without_debugid += 1 } (_, true) => { - self.had_debug_id = true; self.found_source_via_scraping_with_debugid += 1; } (_, false) => self.found_source_via_scraping_without_debugid += 1, @@ -111,12 +99,10 @@ impl JsMetrics { pub fn record_not_found(&mut self, file_ty: SourceFileType, had_debug_id: bool) { match (file_ty, had_debug_id) { (SourceFileType::SourceMap, true) => { - self.had_debug_id = true; self.sourcemap_not_found_with_debugid += 1; } (SourceFileType::SourceMap, false) => self.sourcemap_not_found_without_debugid += 1, (_, true) => { - self.had_debug_id = true; self.source_not_found_with_debugid += 1; } (_, false) => self.source_not_found_without_debugid += 1, @@ -138,12 +124,10 @@ impl JsMetrics { use SourceFileType::*; match (file_ty, file_identified_by, bundle_resolved_by) { (SourceMap, DebugId, _) => { - self.had_debug_id = true; self.found_sourcemap_via_debugid += 1; } (SourceMap, Url, ReleaseOld) => { if had_debug_id { - self.had_debug_id = true; self.found_sourcemap_via_release_old_with_debugid += 1; } else { self.found_sourcemap_via_release_old_without_debugid += 1; @@ -151,19 +135,16 @@ impl JsMetrics { } (SourceMap, _, _) => { if had_debug_id { - self.had_debug_id = true; self.found_sourcemap_via_release_with_debugid += 1; } else { self.found_sourcemap_via_release_without_debugid += 1; } } (_, DebugId, _) => { - self.had_debug_id = true; self.found_source_via_debugid += 1; } (_, Url, ReleaseOld) => { if had_debug_id { - self.had_debug_id = true; self.found_source_via_release_old_with_debugid += 1; } else { self.found_source_via_release_old_without_debugid += 1; @@ -171,7 +152,6 @@ impl JsMetrics { } (_, _, _) => { if had_debug_id { - self.had_debug_id = true; self.found_source_via_release_with_debugid += 1; } else { self.found_source_via_release_without_debugid += 1; @@ -189,13 +169,13 @@ impl JsMetrics { pub fn submit(&self) { // per-event distribution, emitted as `distribution` - metric!(distribution("js.needed_files") = self.needed_files); - metric!(distribution("js.api_requests") = self.api_requests); - metric!(distribution("js.queried_bundles") = self.queried_bundles); - metric!(distribution("js.fetched_bundles") = self.fetched_bundles); - metric!(distribution("js.queried_artifacts") = self.queried_artifacts); - metric!(distribution("js.fetched_artifacts") = self.fetched_artifacts); - metric!(distribution("js.scraped_files") = self.scraped_files); + metric!(distribution("js.needed_files") = self.needed_files as f64); + metric!(distribution("js.api_requests") = self.api_requests as f64); + metric!(distribution("js.queried_bundles") = self.queried_bundles as f64); + metric!(distribution("js.fetched_bundles") = self.fetched_bundles as f64); + metric!(distribution("js.queried_artifacts") = self.queried_artifacts as f64); + metric!(distribution("js.fetched_artifacts") = self.fetched_artifacts as f64); + metric!(distribution("js.scraped_files") = self.scraped_files as f64); // Sources: metric!( @@ -317,17 +297,6 @@ impl JsMetrics { counter("js.bundle_lookup") += self.found_bundle_via_release_old, "method" => "release-old" ); - - // Count this project if any of its source/sourcemap files had debug ids. - // Also separately count it if such a file wasn't found. - if let Some(project_id) = self.project_id { - if self.had_debug_id { - metric!(set("js.debugid_projects") = project_id); - } - if self.source_not_found_with_debugid > 0 || self.sourcemap_not_found_with_debugid > 0 { - metric!(set("js.debugid_projects_notfound") = project_id); - } - } } } @@ -336,29 +305,30 @@ pub fn record_stacktrace_metrics(event_platform: Option, stats: Symbol let event_platform = event_platform .as_ref() .map(|p| p.as_ref()) - .unwrap_or("none"); + .unwrap_or("none") + .to_owned(); - metric!(distribution("symbolication.num_stacktraces") = stats.num_stacktraces); + metric!(distribution("symbolication.num_stacktraces") = stats.num_stacktraces as f64); for (p, count) in stats.symbolicated_frames { - let frame_platform = p.as_ref().map(|p| p.as_ref()).unwrap_or("none"); + let frame_platform = p.as_ref().map(|p| p.as_ref()).unwrap_or("none").to_owned(); metric!( distribution("symbolication.num_frames") = - count, - "frame_platform" => frame_platform, "event_platform" => event_platform + count as f64, + "frame_platform" => frame_platform, "event_platform" => event_platform.clone() ); } for (p, count) in stats.unsymbolicated_frames { - let frame_platform = p.as_ref().map(|p| p.as_ref()).unwrap_or("none"); + let frame_platform = p.as_ref().map(|p| p.as_ref()).unwrap_or("none").to_owned(); metric!( distribution("symbolication.unsymbolicated_frames") = - count, - "frame_platform" => frame_platform, "event_platform" => event_platform + count as f64, + "frame_platform" => frame_platform, "event_platform" => event_platform.clone() ); } - metric!(distribution("js.missing_sourcescontent") = stats.missing_sourcescontent); + metric!(distribution("js.missing_sourcescontent") = stats.missing_sourcescontent as f64); } #[derive(Debug, Clone, Default)] diff --git a/crates/symbolicator-native/src/caches/bitcode.rs b/crates/symbolicator-native/src/caches/bitcode.rs index 939ec37d5..d0993fa17 100644 --- a/crates/symbolicator-native/src/caches/bitcode.rs +++ b/crates/symbolicator-native/src/caches/bitcode.rs @@ -3,7 +3,6 @@ //! This service downloads and caches the `PList` and [`BcSymbolMap`] used to un-obfuscate //! debug symbols for obfuscated Apple bitcode builds. -use std::fmt::{self, Display}; use std::sync::Arc; use anyhow::Context; @@ -60,15 +59,6 @@ enum AuxDifKind { UuidMap, } -impl Display for AuxDifKind { - fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match self { - AuxDifKind::BcSymbolMap => write!(f, "BCSymbolMap"), - AuxDifKind::UuidMap => write!(f, "UuidMap"), - } - } -} - /// The interface to the [`Cacher`] service. /// /// The main work is done by the [`CacheItemRequest`] impl. @@ -81,7 +71,7 @@ struct FetchFileRequest { } impl FetchFileRequest { - #[tracing::instrument(skip(self, temp_file), fields(kind = %self.kind))] + #[tracing::instrument(skip(self, temp_file), fields(kind = ?self.kind))] async fn fetch_auxdif(&self, temp_file: &mut NamedTempFile) -> CacheContents { fetch_file( self.download_svc.clone(), @@ -95,16 +85,14 @@ impl FetchFileRequest { match self.kind { AuxDifKind::BcSymbolMap => { if let Err(err) = BcSymbolMap::parse(&view) { - let kind = self.kind.to_string(); - metric!(counter("services.bitcode.loaderrror") += 1, "kind" => &kind); + metric!(counter("services.bitcode.loaderrror") += 1, "kind" => "BCSymbolMap"); tracing::debug!("Failed to parse bcsymbolmap: {}", err); return Err(CacheError::Malformed(err.to_string())); } } AuxDifKind::UuidMap => { if let Err(err) = UuidMapping::parse_plist(self.uuid, &view) { - let kind = self.kind.to_string(); - metric!(counter("services.bitcode.loaderrror") += 1, "kind" => &kind); + metric!(counter("services.bitcode.loaderrror") += 1, "kind" => "UuidMap"); tracing::debug!("Failed to parse plist: {}", err); return Err(CacheError::Malformed(err.to_string())); } @@ -224,7 +212,7 @@ impl BitcodeService { let hub = Hub::new_from_top(Hub::current()); hub.configure_scope(|scope| { scope.set_tag("auxdif.debugid", uuid); - scope.set_extra("auxdif.kind", dif_kind.to_string().into()); + scope.set_extra("auxdif.kind", format!("{dif_kind:?}").into()); scope.set_extra("auxdif.source", file_source.source_metric_key().into()); }); let request = FetchFileRequest { diff --git a/crates/symbolicator-native/src/metrics.rs b/crates/symbolicator-native/src/metrics.rs index 7eccf432c..b6df16db2 100644 --- a/crates/symbolicator-native/src/metrics.rs +++ b/crates/symbolicator-native/src/metrics.rs @@ -7,9 +7,9 @@ use symbolicator_sources::ObjectType; use crate::interface::{CompleteObjectInfo, CompleteStacktrace}; -#[derive(Debug, Copy, Clone)] /// Where the Stack Traces in the [`SymbolicateStacktraces`](crate::interface::SymbolicateStacktraces) /// originated from. +#[derive(Debug, Copy, Clone)] pub enum StacktraceOrigin { /// The stack traces came from a direct request to symbolicate. Symbolicate, @@ -19,13 +19,20 @@ pub enum StacktraceOrigin { AppleCrashReport, } -impl std::fmt::Display for StacktraceOrigin { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.write_str(match self { +impl StacktraceOrigin { + /// A static string representation of the stack trace origin. + pub fn as_str(&self) -> &'static str { + match self { StacktraceOrigin::Symbolicate => "symbolicate", StacktraceOrigin::Minidump => "minidump", StacktraceOrigin::AppleCrashReport => "applecrashreport", - }) + } + } +} + +impl std::fmt::Display for StacktraceOrigin { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.write_str(self.as_str()) } } @@ -91,12 +98,12 @@ pub fn record_symbolication_metrics( modules: &[CompleteObjectInfo], stacktraces: &[CompleteStacktrace], ) { - let origin = origin.to_string(); - + let origin = origin.as_str(); let event_platform = event_platform .as_ref() .map(|p| p.as_ref()) - .unwrap_or("none"); + .unwrap_or("none") + .to_owned(); let object_platform = modules .iter() @@ -108,7 +115,7 @@ pub fn record_symbolication_metrics( } }) .unwrap_or(ObjectType::Unknown) - .to_string(); + .as_str(); // Unusable modules that don’t have any kind of ID to look them up with let mut unusable_modules = 0; @@ -148,33 +155,33 @@ pub fn record_symbolication_metrics( } metric!( - distribution("symbolication.num_modules") = modules.len() as u64, - "platform" => &object_platform, "origin" => &origin + distribution("symbolication.num_modules") = modules.len() as f64, + "platform" => object_platform, "origin" => origin ); metric!( - distribution("symbolication.unusable_modules") = unusable_modules, - "platform" => &object_platform, "origin" => &origin + distribution("symbolication.unusable_modules") = unusable_modules as f64, + "platform" => object_platform, "origin" => origin ); metric!( - distribution("symbolication.unparsable_modules") = unparsable_modules, - "platform" => &object_platform, "origin" => &origin + distribution("symbolication.unparsable_modules") = unparsable_modules as f64, + "platform" => object_platform, "origin" => origin ); metric!( - distribution("symbolication.num_stacktraces") = stacktraces.len() as u64, - "platform" => &object_platform, "origin" => &origin + distribution("symbolication.num_stacktraces") = stacktraces.len() as f64, + "platform" => object_platform, "origin" => origin ); metric!( - distribution("symbolication.short_stacktraces") = metrics.short_traces, - "platform" => &object_platform, "origin" => &origin + distribution("symbolication.short_stacktraces") = metrics.short_traces as f64, + "platform" => object_platform, "origin" => origin ); metric!( - distribution("symbolication.truncated_stacktraces") = metrics.truncated_traces, - "platform" => &object_platform, "origin" => &origin + distribution("symbolication.truncated_stacktraces") = metrics.truncated_traces as f64, + "platform" => object_platform, "origin" => origin ); metric!( - distribution("symbolication.bad_stacktraces") = metrics.bad_traces, - "platform" => &object_platform, "origin" => &origin + distribution("symbolication.bad_stacktraces") = metrics.bad_traces as f64, + "platform" => object_platform, "origin" => origin ); // Count number of frames by platform (including no platform) @@ -189,39 +196,39 @@ pub fn record_symbolication_metrics( ); for (p, count) in &frames_by_platform { - let frame_platform = p.map(|p| p.as_ref()).unwrap_or("none"); + let frame_platform = p.map(|p| p.as_ref()).unwrap_or("none").to_owned(); metric!( distribution("symbolication.num_frames") = - count, - "platform" => &object_platform, "origin" => &origin, - "frame_platform" => frame_platform, "event_platform" => event_platform + *count as f64, + "platform" => object_platform, "origin" => origin, + "frame_platform" => frame_platform, "event_platform" => event_platform.clone() ); } metric!( - distribution("symbolication.scanned_frames") = metrics.scanned_frames, - "platform" => &object_platform, "origin" => &origin + distribution("symbolication.scanned_frames") = metrics.scanned_frames as f64, + "platform" => object_platform, "origin" => origin ); metric!( - distribution("symbolication.unsymbolicated_frames") = metrics.unsymbolicated_frames, - "platform" => &object_platform, "origin" => &origin + distribution("symbolication.unsymbolicated_frames") = metrics.unsymbolicated_frames as f64, + "platform" => object_platform, "origin" => origin ); metric!( distribution("symbolication.unsymbolicated_context_frames") = - metrics.unsymbolicated_context_frames, - "platform" => &object_platform, "origin" => &origin + metrics.unsymbolicated_context_frames as f64, + "platform" => object_platform, "origin" => origin ); metric!( distribution("symbolication.unsymbolicated_cfi_frames") = - metrics.unsymbolicated_cfi_frames, - "platform" => &object_platform, "origin" => &origin + metrics.unsymbolicated_cfi_frames as f64, + "platform" => object_platform, "origin" => origin ); metric!( distribution("symbolication.unsymbolicated_scanned_frames") = - metrics.unsymbolicated_scanned_frames, - "platform" => &object_platform, "origin" => &origin + metrics.unsymbolicated_scanned_frames as f64, + "platform" => object_platform, "origin" => origin ); metric!( - distribution("symbolication.unmapped_frames") = metrics.unmapped_frames, - "platform" => &object_platform, "origin" => &origin + distribution("symbolication.unmapped_frames") = metrics.unmapped_frames as f64, + "platform" => object_platform, "origin" => origin ); } diff --git a/crates/symbolicator-native/src/symbolication/process_minidump.rs b/crates/symbolicator-native/src/symbolication/process_minidump.rs index 9a7e0f031..3060be959 100644 --- a/crates/symbolicator-native/src/symbolication/process_minidump.rs +++ b/crates/symbolicator-native/src/symbolication/process_minidump.rs @@ -528,7 +528,7 @@ impl SymbolicationActor { let minidump_file = download_attachment(&self.download_svc, minidump_file).await?; let len = minidump_file.metadata()?.len(); tracing::debug!("Processing minidump ({} bytes)", len); - metric!(distribution("minidump.upload.size") = len); + metric!(distribution("minidump.upload.size") = len as f64); let bv = ByteView::map_file(minidump_file)?; let minidump = Minidump::read(bv)?; diff --git a/crates/symbolicator-proguard/src/metrics.rs b/crates/symbolicator-proguard/src/metrics.rs index 1602c8786..dec295d9c 100644 --- a/crates/symbolicator-proguard/src/metrics.rs +++ b/crates/symbolicator-proguard/src/metrics.rs @@ -12,31 +12,33 @@ pub(crate) fn record_symbolication_metrics( .map(|p| p.as_ref()) .unwrap_or("none"); - metric!(distribution("symbolication.num_exceptions") = stats.symbolicated_exceptions, "event_platform" => event_platform); - metric!(distribution("symbolication.unsymbolicated_exceptions") = stats.unsymbolicated_exceptions, "event_platform" => event_platform); + let event_platform = event_platform.to_owned(); - metric!(distribution("symbolication.num_stacktraces") = stats.num_stacktraces); + metric!(distribution("symbolication.num_exceptions") = stats.symbolicated_exceptions as f64, "event_platform" => event_platform.clone()); + metric!(distribution("symbolication.unsymbolicated_exceptions") = stats.unsymbolicated_exceptions as f64, "event_platform" => event_platform.clone()); + + metric!(distribution("symbolication.num_stacktraces") = stats.num_stacktraces as f64); for (p, count) in stats.symbolicated_frames { - let frame_platform = p.as_ref().map(|p| p.as_ref()).unwrap_or("none"); + let frame_platform = p.as_ref().map(|p| p.as_ref()).unwrap_or("none").to_owned(); metric!( distribution("symbolication.num_frames") = - count, - "frame_platform" => frame_platform, "event_platform" => event_platform + count as f64, + "frame_platform" => frame_platform, "event_platform" => event_platform.clone() ); } for (p, count) in stats.unsymbolicated_frames { - let frame_platform = p.as_ref().map(|p| p.as_ref()).unwrap_or("none"); + let frame_platform = p.as_ref().map(|p| p.as_ref()).unwrap_or("none").to_owned(); metric!( distribution("symbolication.unsymbolicated_frames") = - count, - "frame_platform" => frame_platform, "event_platform" => event_platform + count as f64, + "frame_platform" => frame_platform, "event_platform" => event_platform.clone() ); } - metric!(distribution("symbolication.num_classes") = stats.symbolicated_classes, "event_platform" => event_platform); - metric!(distribution("symbolication.unsymbolicated_classes") = stats.unsymbolicated_classes, "event_platform" => event_platform); + metric!(distribution("symbolication.num_classes") = stats.symbolicated_classes as f64, "event_platform" => event_platform.clone()); + metric!(distribution("symbolication.unsymbolicated_classes") = stats.unsymbolicated_classes as f64, "event_platform" => event_platform); } #[derive(Debug, Clone, Default)] diff --git a/crates/symbolicator-service/Cargo.toml b/crates/symbolicator-service/Cargo.toml index fe52e8fd5..e5262d4f9 100644 --- a/crates/symbolicator-service/Cargo.toml +++ b/crates/symbolicator-service/Cargo.toml @@ -14,9 +14,7 @@ aws-smithy-types = { workspace = true } aws-smithy-xml = { workspace = true } bytes = { workspace = true } cab = { workspace = true } -cadence = { workspace = true } chrono = { workspace = true } -crossbeam-utils = { workspace = true } filetime = { workspace = true } flate2 = { workspace = true } futures = { workspace = true } @@ -26,6 +24,8 @@ humantime-serde = { workspace = true } idna = { workspace = true } ipnetwork = { workspace = true } jsonwebtoken = { workspace = true } +metrics = { workspace = true } +metrics-exporter-dogstatsd = { workspace = true } moka = { workspace = true } proguard = { workspace = true } rand = { workspace = true } @@ -40,7 +40,6 @@ reqwest = { workspace = true, features = [ "hickory-dns", "socks", ] } -rustc-hash = { workspace = true } sentry = { workspace = true, features = ["logs"] } serde = { workspace = true } serde-vars = { workspace = true } @@ -56,7 +55,6 @@ symbolic = { workspace = true, features = [ symbolicator-sources = { workspace = true } tempfile = { workspace = true } thiserror = { workspace = true } -thread_local = { workspace = true } tokio = { workspace = true, features = ["rt", "macros", "fs"] } tokio-util = { workspace = true, features = ["io"] } tracing = { workspace = true } diff --git a/crates/symbolicator-service/src/caching/cleanup.rs b/crates/symbolicator-service/src/caching/cleanup.rs index 79a53b93b..11830d989 100644 --- a/crates/symbolicator-service/src/caching/cleanup.rs +++ b/crates/symbolicator-service/src/caching/cleanup.rs @@ -174,12 +174,12 @@ impl Cache { self.name ); - metric!(gauge("caches.size.files") = stats.retained_files as u64, "cache" => self.name.as_ref()); - metric!(gauge("caches.size.bytes") = stats.retained_bytes, "cache" => self.name.as_ref()); - metric!(gauge("caches.size.metadata_bytes") = stats.retained_metadata_bytes, "cache" => self.name.as_ref()); - metric!(counter("caches.size.files_removed") += stats.removed_files as i64, "cache" => self.name.as_ref()); - metric!(counter("caches.size.bytes_removed") += stats.removed_bytes as i64, "cache" => self.name.as_ref()); - metric!(counter("caches.size.metadata_bytes_removed") += stats.removed_metadata_bytes as i64, "cache" => self.name.as_ref()); + metric!(gauge("caches.size.files") = stats.retained_files as f64, "cache" => self.name.as_str()); + metric!(gauge("caches.size.bytes") = stats.retained_bytes as f64, "cache" => self.name.as_str()); + metric!(gauge("caches.size.metadata_bytes") = stats.retained_metadata_bytes as f64, "cache" => self.name.as_str()); + metric!(counter("caches.size.files_removed") += stats.removed_files as u64, "cache" => self.name.as_str()); + metric!(counter("caches.size.bytes_removed") += stats.removed_bytes, "cache" => self.name.as_str()); + metric!(counter("caches.size.metadata_bytes_removed") += stats.removed_metadata_bytes, "cache" => self.name.as_str()); Ok(()) } diff --git a/crates/symbolicator-service/src/caching/config.rs b/crates/symbolicator-service/src/caching/config.rs index 6437ae653..de9ca4da3 100644 --- a/crates/symbolicator-service/src/caching/config.rs +++ b/crates/symbolicator-service/src/caching/config.rs @@ -17,8 +17,8 @@ pub enum CacheName { SourceIndex, } -impl AsRef for CacheName { - fn as_ref(&self) -> &str { +impl CacheName { + pub fn as_str(&self) -> &'static str { match self { Self::Objects => "objects", Self::ObjectMeta => "object_meta", @@ -38,6 +38,6 @@ impl AsRef for CacheName { impl fmt::Display for CacheName { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - write!(f, "{}", self.as_ref()) + write!(f, "{}", self.as_str()) } } diff --git a/crates/symbolicator-service/src/caching/fs.rs b/crates/symbolicator-service/src/caching/fs.rs index c9957b79c..45af39f61 100644 --- a/crates/symbolicator-service/src/caching/fs.rs +++ b/crates/symbolicator-service/src/caching/fs.rs @@ -71,7 +71,7 @@ impl Cache { in_memory_capacity: u64, ) -> io::Result { let tmp_dir = config.cache_dir("tmp"); - let cache_dir = config.cache_dir(name.as_ref()); + let cache_dir = config.cache_dir(name.as_str()); if let Some(ref dir) = cache_dir { std::fs::create_dir_all(dir)?; diff --git a/crates/symbolicator-service/src/caching/memory.rs b/crates/symbolicator-service/src/caching/memory.rs index f0da301d5..fa447087d 100644 --- a/crates/symbolicator-service/src/caching/memory.rs +++ b/crates/symbolicator-service/src/caching/memory.rs @@ -132,7 +132,7 @@ impl Cacher { pub fn new(config: Cache, shared_cache: SharedCacheRef) -> Self { let cache = InMemoryCache::builder() .max_capacity(config.in_memory_capacity) - .name(config.name().as_ref()) + .name(config.name().as_str()) .expire_after(CacheExpiration) // NOTE: we count all the bookkeeping structures to the weight as well .weigher(|_k, v| { @@ -270,7 +270,7 @@ impl Cacher { let entry = match entry { Some(entry) => entry, None => { - metric!(counter("caches.computation") += 1, "cache" => name.as_ref()); + metric!(counter("caches.computation") += 1, "cache" => name.as_str()); match request.compute(&mut temp_file).await { Ok(()) => { // Now we have written the data to the tempfile we can mmap it, persisting it later @@ -353,16 +353,16 @@ impl Cacher { Err(CacheError::Malformed(_)) => "malformed", Err(_) => "cache-specific error", }, - "is_refresh" => &is_refresh.to_string(), - "cache" => name.as_ref(), + "is_refresh" => is_refresh.to_string(), + "cache" => name.as_str(), ); if let Ok(byte_view) = contents { metric!( - distribution("caches.file.size") = byte_view.len() as u64, + distribution("caches.file.size") = byte_view.len() as f64, "hit" => "false", - "is_refresh" => &is_refresh.to_string(), - "cache" => name.as_ref(), + "is_refresh" => if is_refresh { "true" } else { "false" }, + "cache" => name.as_str(), ); } @@ -468,8 +468,8 @@ impl Cacher { // in a deduplicated background task, which we will not await metric!( counter("caches.file.fallback") += 1, - "version" => &version.to_string(), - "cache" => name.as_ref(), + "version" => version.to_string(), + "cache" => name.as_str(), ); self.spawn_refresh(request, cache_key.clone()); } @@ -493,7 +493,7 @@ impl Cacher { /// will return an `Err`. This err may be persisted in the cache for a time. pub async fn compute_memoized(&self, request: T, cache_key: CacheKey) -> CacheEntry { let name = self.config.name(); - metric!(counter("caches.access") += 1, "cache" => name.as_ref()); + metric!(counter("caches.access") += 1, "cache" => name.as_str()); let entry = self .cache @@ -502,7 +502,7 @@ impl Cacher { .await; if !entry.is_fresh() { - metric!(counter("caches.memory.hit") += 1, "cache" => name.as_ref()); + metric!(counter("caches.memory.hit") += 1, "cache" => name.as_str()); } entry.into_value().data } @@ -521,7 +521,7 @@ impl Cacher { // A file was not found. If this spikes, it's possible that the filesystem cache // just got pruned. - metric!(counter("caches.file.miss") += 1, "cache" => name.as_ref()); + metric!(counter("caches.file.miss") += 1, "cache" => name.as_str()); let data = self .compute(request, cache_key, false) @@ -554,7 +554,7 @@ impl Cacher { if max_lazy_refreshes.fetch_sub(1, Ordering::Relaxed) <= 0 { max_lazy_refreshes.fetch_add(1, Ordering::Relaxed); - metric!(counter("caches.lazy_limit_hit") += 1, "cache" => name.as_ref()); + metric!(counter("caches.lazy_limit_hit") += 1, "cache" => name.as_str()); return; } @@ -638,12 +638,12 @@ fn lookup_local_cache( // This is also reported for "negative cache hits": When we cached // the 404 response from a server as empty file. - metric!(counter("caches.file.hit") += 1, "cache" => name.as_ref()); + metric!(counter("caches.file.hit") += 1, "cache" => name.as_str()); if let Ok(byteview) = data.contents() { metric!( - distribution("caches.file.size") = byteview.len() as u64, + distribution("caches.file.size") = byteview.len() as f64, "hit" => "true", - "cache" => name.as_ref(), + "cache" => name.as_str(), ); } diff --git a/crates/symbolicator-service/src/caching/shared_cache/mod.rs b/crates/symbolicator-service/src/caching/shared_cache/mod.rs index 27b76d909..5d06e25c7 100644 --- a/crates/symbolicator-service/src/caching/shared_cache/mod.rs +++ b/crates/symbolicator-service/src/caching/shared_cache/mod.rs @@ -247,7 +247,7 @@ impl GcsState { }; metric!( counter("services.shared_cache.exists") += 1, - "cache" => cache.as_ref(), + "cache" => cache.as_str(), "status" => status ); ret @@ -417,18 +417,18 @@ enum SharedCacheStoreResult { Skipped, } -impl AsRef for SharedCacheStoreResult { - fn as_ref(&self) -> &str { +impl SharedCacheStoreResult { + fn as_str(&self) -> &'static str { match self { - SharedCacheStoreResult::Written(_) => "written", - SharedCacheStoreResult::Skipped => "skipped", + Self::Written(_) => "written", + Self::Skipped => "skipped", } } } impl fmt::Display for SharedCacheStoreResult { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - write!(f, "{}", self.as_ref()) + write!(f, "{}", self.as_str()) } } @@ -497,11 +497,11 @@ pub enum CacheStoreReason { Refresh, } -impl AsRef for CacheStoreReason { - fn as_ref(&self) -> &str { +impl CacheStoreReason { + fn as_str(&self) -> &'static str { match self { - CacheStoreReason::New => "new", - CacheStoreReason::Refresh => "refresh", + Self::New => "new", + Self::Refresh => "refresh", } } } @@ -566,8 +566,8 @@ impl SharedCacheService { Self::single_uploader(done_tx.clone(), backend.clone(), message) .bind_hub(Hub::new_from_top(Hub::current())) ); - let uploads_in_flight: u64 = (max_concurrent_uploads - uploads_counter) as u64; - metric!(gauge("services.shared_cache.uploads_in_flight") = uploads_in_flight); + let uploads_in_flight = max_concurrent_uploads - uploads_counter; + metric!(gauge("services.shared_cache.uploads_in_flight") = uploads_in_flight as f64); } Some(_) = done_rx.recv() => { uploads_counter += 1; @@ -597,7 +597,7 @@ impl SharedCacheService { sentry::configure_scope(|scope| { let mut map = BTreeMap::new(); map.insert("backend".to_string(), backend.name().into()); - map.insert("cache".to_string(), cache.as_ref().into()); + map.insert("cache".to_string(), cache.as_str().into()); map.insert("path".to_string(), key.clone().into()); scope.set_context("Shared Cache", Context::Other(map)); }); @@ -610,16 +610,15 @@ impl SharedCacheService { Ok(op) => { metric!( counter("services.shared_cache.store") += 1, - "cache" => cache.as_ref(), - "write" => op.as_ref(), + "cache" => cache.as_str(), + "write" => op.as_str(), "status" => "ok", - "reason" => reason.as_ref(), + "reason" => reason.as_str(), ); if let SharedCacheStoreResult::Written(bytes) = op { - let bytes: i64 = bytes.try_into().unwrap_or(i64::MAX); metric!( counter("services.shared_cache.store.bytes") += bytes, - "cache" => cache.as_ref(), + "cache" => cache.as_str(), ); } } @@ -639,9 +638,9 @@ impl SharedCacheService { } metric!( counter("services.shared_cache.store") += 1, - "cache" => cache.as_ref(), + "cache" => cache.as_str(), "status" => "error", - "reason" => reason.as_ref(), + "reason" => reason.as_str(), "errdetails" => errdetails, ); } @@ -683,11 +682,11 @@ impl SharedCacheService { ) -> bool { let _guard = Hub::current().push_scope(); let backend_name = self.backend_name(); - let key = format!("{}/{key}", cache.as_ref()); + let key = format!("{}/{key}", cache.as_str()); sentry::configure_scope(|scope| { let mut map = BTreeMap::new(); map.insert("backend".to_string(), backend_name.into()); - map.insert("cache".to_string(), cache.as_ref().into()); + map.insert("cache".to_string(), cache.as_str().into()); map.insert("path".to_string(), key.clone().into()); scope.set_context("Shared Cache", Context::Other(map)); }); @@ -709,21 +708,20 @@ impl SharedCacheService { Ok(Some(bytes)) => { metric!( counter("services.shared_cache.fetch") += 1, - "cache" => cache.as_ref(), + "cache" => cache.as_str(), "hit" => "true", "status" => "ok", ); - let bytes: i64 = bytes.try_into().unwrap_or(i64::MAX); metric!( counter("services.shared_cache.fetch.bytes") += bytes, - "cache" => cache.as_ref(), + "cache" => cache.as_str(), ); true } Ok(None) => { metric!( counter("services.shared_cache.fetch") += 1, - "cache" => cache.as_ref(), + "cache" => cache.as_str(), "hit" => "false", "status" => "ok", ); @@ -741,7 +739,7 @@ impl SharedCacheService { } metric!( counter("services.shared_cache.fetch") += 1, - "cache" => cache.as_ref(), + "cache" => cache.as_str(), "status" => "error", "errdetails" => errdetails, ); @@ -787,12 +785,12 @@ impl SharedCacheService { } metric!( gauge("services.shared_cache.uploads_queue_capacity") = - self.upload_queue_tx.capacity() as u64 + self.upload_queue_tx.capacity() as f64 ); self.upload_queue_tx .try_send(UploadMessage { cache, - key: format!("{}/{key}", cache.as_ref()), + key: format!("{}/{key}", cache.as_str()), content, done_tx, reason, diff --git a/crates/symbolicator-service/src/download/compression.rs b/crates/symbolicator-service/src/download/compression.rs index 38cff6b55..2bb5f61bd 100644 --- a/crates/symbolicator-service/src/download/compression.rs +++ b/crates/symbolicator-service/src/download/compression.rs @@ -17,7 +17,7 @@ pub fn maybe_decompress_file(src: &mut NamedTempFile) -> io::Result<()> { let metadata = file.metadata()?; // TODO(swatinem): we should rename this to a more descriptive metric, as we use this for *all* // kinds of downloaded files, not only "objects". - metric!(distribution("objects.size") = metadata.len()); + metric!(distribution("objects.size") = metadata.len() as f64); file.rewind()?; if metadata.len() < 4 { diff --git a/crates/symbolicator-service/src/download/deny_list.rs b/crates/symbolicator-service/src/download/deny_list.rs index b70ec7704..b0ad3def9 100644 --- a/crates/symbolicator-service/src/download/deny_list.rs +++ b/crates/symbolicator-service/src/download/deny_list.rs @@ -132,7 +132,7 @@ impl HostDenyList { if !self.never_block.contains(&host) { self.blocked_hosts.insert(host, error.clone()); - metric!(gauge("service.download.blocked-hosts") = self.blocked_hosts.weighted_size(), "source" => source_name); + metric!(gauge("service.download.blocked-hosts") = self.blocked_hosts.weighted_size() as f64, "source" => source_name.to_owned()); } } } diff --git a/crates/symbolicator-service/src/download/index.rs b/crates/symbolicator-service/src/download/index.rs index 9a84aaab3..9f36881e7 100644 --- a/crates/symbolicator-service/src/download/index.rs +++ b/crates/symbolicator-service/src/download/index.rs @@ -440,7 +440,7 @@ impl SourceIndexService { // as a placeholder in case of errors. if entry.is_fresh() || entry.is_old_value_replaced() { let lastid = entry.value().0.as_ref().copied().unwrap_or_default(); - metric!(gauge("index.symstore.lastid") = lastid as u64, "source" => source.source_metric_key()); + metric!(gauge("index.symstore.lastid") = lastid as f64, "source" => source.source_metric_key().to_owned()); } entry.into_value().0 diff --git a/crates/symbolicator-service/src/download/mod.rs b/crates/symbolicator-service/src/download/mod.rs index 8eed3bec1..1dc53dac9 100644 --- a/crates/symbolicator-service/src/download/mod.rs +++ b/crates/symbolicator-service/src/download/mod.rs @@ -244,7 +244,7 @@ impl DownloadService { && let Some(deny_list) = self.host_deny_list.as_ref() && let Some(reason) = deny_list.is_blocked(&host) { - metric!(counter("service.download.blocked") += 1, "source" => &source_metric_key); + metric!(counter("service.download.blocked") += 1, "source" => source_metric_key.clone()); return Err(deny_list.format_error(&host, &reason)); } @@ -264,7 +264,7 @@ impl DownloadService { }; if let Err(ref e @ (CacheError::DownloadError(_) | CacheError::Timeout(_))) = result { - metric!(counter("service.download.failure") += 1, "source" => &source_metric_key); + metric!(counter("service.download.failure") += 1, "source" => source_metric_key.clone()); if source_metric_key == "sentry:project" { ::sentry::configure_scope(|scope| scope.set_tag("host", host.clone())); @@ -290,7 +290,7 @@ impl DownloadService { Err(CacheError::Unsupported(_)) => "unsupported", Err(CacheError::InternalError) => "internalerror", }; - metric!(counter("service.builtin_source.download") += 1, "source" => &source_metric_key, "status" => status); + metric!(counter("service.builtin_source.download") += 1, "source" => source_metric_key, "status" => status); } result @@ -501,7 +501,7 @@ async fn do_download_reqwest_range( ($status:literal) => {{ metric!( counter("download.range_request.initial") += 1, - "source" => &request.source_name, + "source" => request.source_name.to_owned(), "status" => $status, ); }}; @@ -858,10 +858,10 @@ impl Drop for MeasureSourceDownloadGuard<'_> { let duration = self.creation_time.elapsed(); metric!( timer("download_duration") = duration, - "task_name" => self.task_name, - "status" => status, - "source" => self.source_name, - "streams" => &streams, + "task_name" => self.task_name.to_owned(), + "status" => status.to_owned(), + "source" => self.source_name.to_owned(), + "streams" => streams.clone(), ); let bytes_transferred = *self.bytes_transferred.get_mut(); @@ -876,19 +876,19 @@ impl Drop for MeasureSourceDownloadGuard<'_> { .unwrap_or(bytes_transferred); metric!( - distribution("download_throughput") = throughput, - "task_name" => self.task_name, - "status" => status, - "source" => self.source_name, - "streams" => &streams, + distribution("download_throughput") = throughput as f64, + "task_name" => self.task_name.to_owned(), + "status" => status.to_owned(), + "source" => self.source_name.to_owned(), + "streams" => streams.clone(), ); metric!( - distribution("download_size") = bytes_transferred, - "task_name" => self.task_name, - "status" => status, - "source" => self.source_name, - "streams" => &streams, + distribution("download_size") = bytes_transferred as f64, + "task_name" => self.task_name.to_owned(), + "status" => status.to_owned(), + "source" => self.source_name.to_owned(), + "streams" => streams, ); } } diff --git a/crates/symbolicator-service/src/metrics.rs b/crates/symbolicator-service/src/metrics.rs index 2687d52b7..0c509376c 100644 --- a/crates/symbolicator-service/src/metrics.rs +++ b/crates/symbolicator-service/src/metrics.rs @@ -1,489 +1,53 @@ -//! Provides access to the metrics sytem. -use std::collections::{BTreeMap, BTreeSet}; -use std::fmt::Write; -use std::net::SocketAddr; -use std::ops::Deref; -use std::sync::{Arc, Mutex, OnceLock}; -use std::thread; -use std::time::Duration; +//! Provides access to the metrics system. +use std::collections::BTreeMap; -use cadence::{BufferedUdpMetricSink, MetricSink, QueuingMetricSink, StatsdClient}; -use crossbeam_utils::CachePadded; -use rustc_hash::FxHashMap; -use thread_local::ThreadLocal; +use metrics_exporter_dogstatsd::{AggregationMode, DogStatsDBuilder}; -static METRICS_CLIENT: OnceLock = OnceLock::new(); - -#[derive(Debug, Clone)] -struct Sink(Arc); - -impl MetricSink for Sink { - fn emit(&self, metric: &str) -> std::io::Result { - self.0.emit(metric) - } - fn flush(&self) -> std::io::Result<()> { - self.0.flush() - } -} - -type LocalAggregators = Arc>>>; - -/// The globally configured Metrics, including a `cadence` client, and a local aggregator. -#[derive(Debug)] -pub struct MetricsWrapper { - /// The raw `cadence` client. - statsd_client: StatsdClient, - - /// A thread local aggregator. - local_aggregator: LocalAggregators, -} - -impl MetricsWrapper { - /// Invokes the provided callback with a mutable reference to a thread-local [`LocalAggregator`]. - fn with_local_aggregator(&self, f: impl FnOnce(&mut LocalAggregator)) { - let mut local_aggregator = self - .local_aggregator - .get_or(Default::default) - .lock() - .unwrap(); - f(&mut local_aggregator) - } -} - -impl Deref for MetricsWrapper { - type Target = StatsdClient; - - fn deref(&self) -> &Self::Target { - &self.statsd_client - } -} - -/// We are not (yet) aggregating distributions, but keeping every value. -/// To not overwhelm downstream services, we send them in batches instead of all at once. -const DISTRIBUTION_BATCH_SIZE: usize = 1; - -/// The interval in which to flush out metrics. -/// NOTE: In particular for timer metrics, we have observed that for some reason, *some* of the timer -/// metrics are getting lost (interestingly enough, not all of them) and are not being aggregated into the `.count` -/// sub-metric collected by `veneur`. Lets just flush a lot more often in order to emit less metrics per-flush. -const SEND_INTERVAL: Duration = Duration::from_millis(125); - -/// Creates [`LocalAggregators`] and starts a thread that will periodically -/// send aggregated metrics upstream to the `sink`. -fn make_aggregator(prefix: &str, formatted_global_tags: String, sink: Sink) -> LocalAggregators { - let local_aggregators = LocalAggregators::default(); - - let aggregators = Arc::clone(&local_aggregators); - let prefix = if prefix.is_empty() { - String::new() - } else { - format!("{}.", prefix.trim_end_matches('.')) - }; - - let thread_fn = move || { - // to avoid reallocation, just reserve some space. - // the size is rather arbitrary, but should be large enough for formatted metrics. - let mut formatted_metric = String::with_capacity(256); - let mut suffix = String::with_capacity(128); - - loop { - thread::sleep(SEND_INTERVAL); - - let (total_counters, total_distributions, total_sets) = aggregate_all(&aggregators); - - // send all the aggregated "counter like" metrics - for (AggregationKey { ty, name, tags }, value) in total_counters { - formatted_metric.push_str(&prefix); - formatted_metric.push_str(name); - - let _ = write!(&mut formatted_metric, ":{value}{ty}{formatted_global_tags}"); - - if let Some(tags) = tags { - if formatted_global_tags.is_empty() { - formatted_metric.push_str("|#"); - } else { - formatted_metric.push(','); - } - formatted_metric.push_str(&tags); - } - - let _ = sink.emit(&formatted_metric); - - formatted_metric.clear(); - } - - // send all the aggregated "distribution like" metrics - // we do this in a batched manner, as we do not actually *aggregate* them, - // but still send each value individually. - for (AggregationKey { ty, name, tags }, value) in total_distributions { - suffix.push_str(&formatted_global_tags); - if let Some(tags) = tags { - if formatted_global_tags.is_empty() { - suffix.push_str("|#"); - } else { - suffix.push(','); - } - suffix.push_str(&tags); - } - - for batch in value.chunks(DISTRIBUTION_BATCH_SIZE) { - formatted_metric.push_str(&prefix); - formatted_metric.push_str(name); - - for value in batch { - let _ = write!(&mut formatted_metric, ":{value}"); - } - - formatted_metric.push_str(ty); - formatted_metric.push_str(&suffix); - - let _ = sink.emit(&formatted_metric); - formatted_metric.clear(); - } - - suffix.clear(); - } - - for (AggregationKey { ty, name, tags }, value) in total_sets { - suffix.push_str(&formatted_global_tags); - if let Some(tags) = tags { - if formatted_global_tags.is_empty() { - suffix.push_str("|#"); - } else { - suffix.push(','); - } - suffix.push_str(&tags); - } - - for value in value { - formatted_metric.push_str(&prefix); - formatted_metric.push_str(name); - - let _ = write!(&mut formatted_metric, ":{value}"); - - formatted_metric.push_str(ty); - formatted_metric.push_str(&suffix); - - let _ = sink.emit(&formatted_metric); - formatted_metric.clear(); - } - - suffix.clear(); - } - } - }; - - thread::Builder::new() - .name("metrics-aggregator".into()) - .spawn(thread_fn) - .unwrap(); - - local_aggregators -} - -fn aggregate_all( - aggregators: &LocalAggregators, -) -> (AggregatedCounters, AggregatedDistributions, AggregatedSets) { - let mut total_counters = AggregatedCounters::default(); - let mut total_distributions = AggregatedDistributions::default(); - let mut total_sets = AggregatedSets::default(); - - for local_aggregator in aggregators.iter() { - let (local_counters, local_distributions, local_sets) = { - let mut local_aggregator = local_aggregator.lock().unwrap(); - ( - std::mem::take(&mut local_aggregator.aggregated_counters), - std::mem::take(&mut local_aggregator.aggregated_distributions), - std::mem::take(&mut local_aggregator.aggregated_sets), - ) - }; - - // aggregate all the "counter like" metrics - if total_counters.is_empty() { - total_counters = local_counters; - } else { - for (key, value) in local_counters { - let ty = key.ty; - let aggregated_value = total_counters.entry(key).or_default(); - if ty == "|c" { - *aggregated_value += value; - } else if ty == "|g" { - // FIXME: when aggregating multiple thread-locals, - // we don’t really know which one is the "latest". - // But it also does not really matter that much? - *aggregated_value = value; - } - } - } - - // aggregate all the "distribution like" metrics - if total_distributions.is_empty() { - total_distributions = local_distributions; - } else { - for (key, value) in local_distributions { - let aggregated_value = total_distributions.entry(key).or_default(); - aggregated_value.extend(value); - } - } - - // aggregate all the "set like" metrics - if total_sets.is_empty() { - total_sets = local_sets; - } else { - for (key, value) in local_sets { - let aggregated_value = total_sets.entry(key).or_default(); - aggregated_value.extend(value); - } - } - } - - (total_counters, total_distributions, total_sets) -} - -/// The key by which we group/aggregate metrics. -#[derive(Eq, Ord, PartialEq, PartialOrd, Hash, Debug)] -struct AggregationKey { - /// The metric type, pre-formatted as a statsd suffix such as `|c`. - ty: &'static str, - /// The name of the metric. - name: &'static str, - /// The metric tags, pre-formatted as a statsd suffix, excluding the `|#` prefix. - tags: Option>, -} - -type AggregatedCounters = FxHashMap; -type AggregatedSets = FxHashMap>; -type AggregatedDistributions = FxHashMap>; - -pub trait IntoDistributionValue { - fn into_value(self) -> f64; -} - -impl IntoDistributionValue for Duration { - fn into_value(self) -> f64 { - self.as_secs_f64() * 1_000. - } -} - -impl IntoDistributionValue for usize { - fn into_value(self) -> f64 { - self as f64 - } -} - -impl IntoDistributionValue for u64 { - fn into_value(self) -> f64 { - self as f64 - } -} - -impl IntoDistributionValue for i32 { - fn into_value(self) -> f64 { - self as f64 - } -} - -/// The `thread_local` aggregator which pre-aggregates metrics per-thread. -#[derive(Default, Debug)] -pub struct LocalAggregator { - /// A mutable scratch-buffer that is reused to format tags into it. - buf: String, - /// A map of all the `counter` and `gauge` metrics we have aggregated thus far. - aggregated_counters: AggregatedCounters, - /// A map of all the `set` metrics we have aggregated thus far. - aggregated_sets: AggregatedSets, - /// A map of all the `timer` and `histogram` metrics we have aggregated thus far. - aggregated_distributions: AggregatedDistributions, -} - -impl LocalAggregator { - /// Formats the `tags` into a `statsd` like format with the help of our scratch buffer. - fn format_tags(&mut self, tags: &[(&str, &str)]) -> Option> { - if tags.is_empty() { - return None; - } - - // to avoid reallocation, just reserve some space. - // the size is rather arbitrary, but should be large enough for reasonable tags. - self.buf.reserve(128); - for (key, value) in tags { - if !self.buf.is_empty() { - self.buf.push(','); - } - let _ = write!(&mut self.buf, "{key}:{value}"); - } - let formatted_tags = self.buf.as_str().into(); - self.buf.clear(); - - Some(formatted_tags) - } - - /// Emit a `count` metric, which is aggregated by summing up all values. - pub fn emit_count(&mut self, name: &'static str, value: i64, tags: &[(&'static str, &str)]) { - let tags = self.format_tags(tags); - - let key = AggregationKey { - ty: "|c", - name, - tags, - }; - - let aggregation = self.aggregated_counters.entry(key).or_default(); - *aggregation += value; - } - - /// Emit a `set` metric, which is aggregated in a set. - pub fn emit_set(&mut self, name: &'static str, value: u64, tags: &[(&'static str, &str)]) { - let tags = self.format_tags(tags); - - let key = AggregationKey { - ty: "|s", - name, - tags, - }; - - let aggregation = self.aggregated_sets.entry(key).or_default(); - aggregation.insert(value); - } - - /// Emit a `gauge` metric, for which only the latest value is retained. - pub fn emit_gauge(&mut self, name: &'static str, value: u64, tags: &[(&'static str, &str)]) { - let tags = self.format_tags(tags); - - let key = AggregationKey { - // TODO: maybe we want to give gauges their own aggregations? - ty: "|g", - name, - tags, - }; - - let aggregation = self.aggregated_counters.entry(key).or_default(); - *aggregation = value as i64; - } - - pub fn emit_distribution( - &mut self, - name: &'static str, - value: f64, - tags: &[(&'static str, &str)], - ) { - let tags = self.format_tags(tags); - - let this = &mut *self; - let key = AggregationKey { - ty: "|d", - name, - tags, - }; - - let aggregation = this.aggregated_distributions.entry(key).or_default(); - aggregation.push(value); - } +#[doc(hidden)] +pub mod _metrics { + pub use metrics::*; } /// Tell the metrics system to report to statsd. -pub fn configure_statsd(prefix: &str, addrs: Vec, tags: BTreeMap) { - if !addrs.is_empty() { - tracing::info!("Reporting metrics to statsd at {}", addrs[0]); - } - let socket = std::net::UdpSocket::bind("0.0.0.0:0").unwrap(); - socket.set_nonblocking(true).unwrap(); - let udp_sink = BufferedUdpMetricSink::from(&addrs[..], socket).unwrap(); - let queuing_sink = QueuingMetricSink::from(udp_sink); - let sink = Sink(Arc::new(queuing_sink)); - - let mut builder = StatsdClient::builder(prefix, sink.clone()); - - // pre-format the global tags in `statsd` format, including a leading `|#`. - let mut formatted_global_tags = String::new(); - for (key, value) in tags { - if formatted_global_tags.is_empty() { - formatted_global_tags.push_str("|#"); - } else { - formatted_global_tags.push(','); - } - let _ = write!(&mut formatted_global_tags, "{key}:{value}"); - - builder = builder.with_tag(key, value) - } - let statsd_client = builder.build(); - - let local_aggregator = make_aggregator(prefix, formatted_global_tags, sink); - - let wrapper = MetricsWrapper { - statsd_client, - local_aggregator, - }; - - METRICS_CLIENT.set(wrapper).unwrap(); -} - -/// Invoke a callback with the current [`MetricsWrapper`] and [`LocalAggregator`]. -/// -/// If metrics have not been configured, the callback is not invoked. -/// For the most part the [`metric!`](crate::metric) macro should be used instead. -#[inline(always)] -pub fn with_client(f: F) -where - F: FnOnce(&mut LocalAggregator), -{ - if let Some(client) = METRICS_CLIENT.get() { - client.with_local_aggregator(f) - } +pub fn configure_statsd( + prefix: &str, + addr: &str, + tags: BTreeMap, +) -> anyhow::Result<()> { + let default_labels = tags + .into_iter() + .map(|(key, value)| metrics::Label::new(key, value)) + .collect(); + + DogStatsDBuilder::default() + .with_remote_address(addr)? + .with_telemetry(true) + .with_aggregation_mode(AggregationMode::Aggressive) + .send_histograms_as_distributions(true) + .with_histogram_sampling(false) + .set_global_prefix(prefix) + .with_global_labels(default_labels) + .install()?; + + Ok(()) } /// Emits a metric. #[macro_export] macro_rules! metric { // counters - (counter($id:expr) += $value:expr $(, $k:expr => $v:expr)* $(,)?) => {{ - $crate::metrics::with_client(|local| { - let tags: &[(&'static str, &str)] = &[ - $(($k, $v)),* - ]; - local.emit_count($id, $value, tags); - }); + (counter($($id:tt)+) += $value:expr $(, $($tt:tt)*)?) => {{ + $crate::metrics::_metrics::counter!($($id)* $(, $($tt)*)?).increment($value) }}; - // sets - (set($id:expr) = $value:expr $(, $k:ident = $v:expr)* $(,)?) => { - $crate::metrics::with_client(|local| { - let tags: &[(&'static str, &str)] = &[ - $(($k, $v)),* - ]; - local.emit_set(&$id, $value, tags); - }); - }; - // gauges - (gauge($id:expr) = $value:expr $(, $k:expr => $v:expr)* $(,)?) => {{ - $crate::metrics::with_client(|local| { - let tags: &[(&'static str, &str)] = &[ - $(($k, $v)),* - ]; - local.emit_gauge($id, $value, tags); - }); + (gauge($($id:tt)+) = $value:expr $(, $($tt:tt)*)?) => {{ + $crate::metrics::_metrics::gauge!($($id)* $(, $($tt)*)?).set($value) }}; // timers - (timer($id:expr) = $value:expr $(, $k:expr => $v:expr)* $(,)?) => {{ - $crate::metrics::with_client(|local| { - let tags: &[(&'static str, &str)] = &[ - $(($k, $v)),* - ]; - use $crate::metrics::IntoDistributionValue; - local.emit_distribution($id, ($value).into_value(), tags); - }); - }}; + (timer($($id:tt)+) = $value:expr $(, $($tt:tt)*)?) => {{ $crate::metrics::_metrics::histogram!($($id)* $(, $($tt)*)?).record($value.as_nanos() as f64 / 1e6) }}; // distributions - (distribution($id:expr) = $value:expr $(, $k:expr => $v:expr)* $(,)?) => {{ - $crate::metrics::with_client(|local| { - let tags: &[(&'static str, &str)] = &[ - $(($k, $v)),* - ]; - use $crate::metrics::IntoDistributionValue; - local.emit_distribution($id, ($value).into_value(), tags); - }); - }}; + (distribution($($id:tt)+) = $value:expr $(, $($tt:tt)*)?) => {{ $crate::metrics::_metrics::histogram!($($id)* $(, $($tt)*)?).record($value) }}; } diff --git a/crates/symbolicator-service/src/utils/futures.rs b/crates/symbolicator-service/src/utils/futures.rs index 6ce4b4591..07f8420f1 100644 --- a/crates/symbolicator-service/src/utils/futures.rs +++ b/crates/symbolicator-service/src/utils/futures.rs @@ -100,7 +100,7 @@ impl<'a> MeasureGuard<'a> { pub fn start(&mut self) { metric!( timer("futures.wait_time") = self.creation_time.elapsed(), - "task_name" => self.task_name, + "task_name" => self.task_name.to_owned(), ); } @@ -118,8 +118,8 @@ impl Drop for MeasureGuard<'_> { }; metric!( timer("futures.done") = self.creation_time.elapsed(), - "task_name" => self.task_name, - "status" => status, + "task_name" => self.task_name.to_owned(), + "status" => status.to_owned(), ); } } diff --git a/crates/symbolicator-sources/src/types.rs b/crates/symbolicator-sources/src/types.rs index 74ce4acad..cbb952f33 100644 --- a/crates/symbolicator-sources/src/types.rs +++ b/crates/symbolicator-sources/src/types.rs @@ -58,6 +58,20 @@ pub enum ObjectType { Unknown, } +impl ObjectType { + /// Returns a string representation of the object type. + pub fn as_str(&self) -> &'static str { + match self { + ObjectType::Elf => "elf", + ObjectType::Macho => "macho", + ObjectType::Pe => "pe", + ObjectType::PeDotnet => "pe_dotnet", + ObjectType::Wasm => "wasm", + ObjectType::Unknown => "unknown", + } + } +} + impl FromStr for ObjectType { type Err = Infallible; @@ -85,14 +99,7 @@ impl<'de> Deserialize<'de> for ObjectType { impl fmt::Display for ObjectType { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { - match *self { - ObjectType::Elf => write!(f, "elf"), - ObjectType::Macho => write!(f, "macho"), - ObjectType::Pe => write!(f, "pe"), - ObjectType::PeDotnet => write!(f, "pe_dotnet"), - ObjectType::Wasm => write!(f, "wasm"), - ObjectType::Unknown => write!(f, "unknown"), - } + self.as_str().fmt(f) } } diff --git a/crates/symbolicator-stress/src/logging.rs b/crates/symbolicator-stress/src/logging.rs index a41244dcb..09f6192a0 100644 --- a/crates/symbolicator-stress/src/logging.rs +++ b/crates/symbolicator-stress/src/logging.rs @@ -87,14 +87,14 @@ pub unsafe fn init(config: Config) -> Guard { } })); - let addrs = vec![([127, 0, 0, 1], socket.port()).into()]; + let addr = format!("127.0.0.1:{}", socket.port()); // have some default tags, just to be closer to the real world config let mut tags = BTreeMap::new(); tags.insert("host".into(), "stresstest".into()); tags.insert("env".into(), "stresstest".into()); - metrics::configure_statsd("symbolicator", addrs, tags); + metrics::configure_statsd("symbolicator", &addr, tags).unwrap(); } guard diff --git a/crates/symbolicator/src/cli.rs b/crates/symbolicator/src/cli.rs index c9d174f1b..a9558137d 100644 --- a/crates/symbolicator/src/cli.rs +++ b/crates/symbolicator/src/cli.rs @@ -1,5 +1,5 @@ //! Exposes the command line application. -use std::net::{SocketAddr, ToSocketAddrs}; +use std::net::SocketAddr; use std::path::{Path, PathBuf}; use std::sync::Arc; @@ -148,10 +148,6 @@ pub fn execute() -> Result<()> { } if let Some(ref statsd) = config.metrics.statsd { - let addrs = statsd - .to_socket_addrs() - .with_context(|| format!("invalid statsd host: {statsd}"))? - .collect(); let mut tags = config.metrics.custom_tags.clone(); if let Some(hostname_tag) = config.metrics.hostname_tag.clone() { @@ -199,7 +195,10 @@ pub fn execute() -> Result<()> { } }; - metrics::configure_statsd(&config.metrics.prefix, addrs, tags); + if let Err(e) = metrics::configure_statsd(&config.metrics.prefix, statsd, tags) { + tracing::error!(error = %e, "failed to initialize statsd backend"); + return Err(e); + } } match cli.command { diff --git a/crates/symbolicator/src/endpoints/metrics.rs b/crates/symbolicator/src/endpoints/metrics.rs index a3381c11f..643a829e9 100644 --- a/crates/symbolicator/src/endpoints/metrics.rs +++ b/crates/symbolicator/src/endpoints/metrics.rs @@ -41,7 +41,7 @@ where .unwrap_or(StatusCode::INTERNAL_SERVER_ERROR); metric!( counter("responses.status_code") += 1, - "status" => status.as_str(), + "status" => status.as_str().to_owned(), ); } poll diff --git a/crates/symbolicator/src/service.rs b/crates/symbolicator/src/service.rs index 793260d1a..486038622 100644 --- a/crates/symbolicator/src/service.rs +++ b/crates/symbolicator/src/service.rs @@ -412,7 +412,7 @@ impl RequestService { let current_requests = Arc::clone(&self.inner.current_requests); let num_requests = current_requests.load(Ordering::Relaxed); - metric!(gauge("requests.in_flight") = num_requests as u64); + metric!(gauge("requests.in_flight") = num_requests as f64); // Reject the request if `requests` already contains `max_concurrent_requests` elements. if let Some(max_concurrent_requests) = self.inner.max_concurrent_requests @@ -563,29 +563,29 @@ async fn wrap_response_channel( } } -trait ToMaxingI64: TryInto + Copy { - fn to_maxing_i64(self) -> i64 { - self.try_into().unwrap_or(i64::MAX) +trait ToMaxingU64: TryInto + Copy { + fn to_maxing_u64(self) -> u64 { + self.try_into().unwrap_or(u64::MAX) } } -impl + Copy> ToMaxingI64 for T {} - -pub fn record_task_metrics(name: &str, metrics: &tokio_metrics::TaskMetrics) { - metric!(counter("tasks.instrumented_count") += metrics.instrumented_count.to_maxing_i64(), "taskname" => name); - metric!(counter("tasks.dropped_count") += metrics.dropped_count.to_maxing_i64(), "taskname" => name); - metric!(counter("tasks.first_poll_count") += metrics.first_poll_count.to_maxing_i64(), "taskname" => name); - metric!(counter("tasks.total_first_poll_delay") += metrics.total_first_poll_delay.as_millis().to_maxing_i64(), "taskname" => name); - metric!(counter("tasks.total_idled_count") += metrics.total_idled_count.to_maxing_i64(), "taskname" => name); - metric!(counter("tasks.total_idle_duration") += metrics.total_idle_duration.as_millis().to_maxing_i64(), "taskname" => name); - metric!(counter("tasks.total_scheduled_count") += metrics.total_scheduled_count.to_maxing_i64(), "taskname" => name); - metric!(counter("tasks.total_scheduled_duration") += metrics.total_scheduled_duration.as_millis().to_maxing_i64(), "taskname" => name); - metric!(counter("tasks.total_poll_count") += metrics.total_poll_count.to_maxing_i64(), "taskname" => name); - metric!(counter("tasks.total_poll_duration") += metrics.total_poll_duration.as_millis().to_maxing_i64(), "taskname" => name); - metric!(counter("tasks.total_fast_poll_count") += metrics.total_fast_poll_count.to_maxing_i64(), "taskname" => name); - metric!(counter("tasks.total_fast_poll_durations") += metrics.total_fast_poll_duration.as_millis().to_maxing_i64(), "taskname" => name); - metric!(counter("tasks.total_slow_poll_count") += metrics.total_slow_poll_count.to_maxing_i64(), "taskname" => name); - metric!(counter("tasks.total_slow_poll_duration") += metrics.total_slow_poll_duration.as_millis().to_maxing_i64(), "taskname" => name); +impl + Copy> ToMaxingU64 for T {} + +pub fn record_task_metrics(name: &'static str, metrics: &tokio_metrics::TaskMetrics) { + metric!(counter("tasks.instrumented_count") += metrics.instrumented_count, "taskname" => name); + metric!(counter("tasks.dropped_count") += metrics.dropped_count, "taskname" => name); + metric!(counter("tasks.first_poll_count") += metrics.first_poll_count, "taskname" => name); + metric!(counter("tasks.total_first_poll_delay") += metrics.total_first_poll_delay.as_millis().to_maxing_u64(), "taskname" => name); + metric!(counter("tasks.total_idled_count") += metrics.total_idled_count, "taskname" => name); + metric!(counter("tasks.total_idle_duration") += metrics.total_idle_duration.as_millis().to_maxing_u64(), "taskname" => name); + metric!(counter("tasks.total_scheduled_count") += metrics.total_scheduled_count, "taskname" => name); + metric!(counter("tasks.total_scheduled_duration") += metrics.total_scheduled_duration.as_millis().to_maxing_u64(), "taskname" => name); + metric!(counter("tasks.total_poll_count") += metrics.total_poll_count, "taskname" => name); + metric!(counter("tasks.total_poll_duration") += metrics.total_poll_duration.as_millis().to_maxing_u64(), "taskname" => name); + metric!(counter("tasks.total_fast_poll_count") += metrics.total_fast_poll_count, "taskname" => name); + metric!(counter("tasks.total_fast_poll_durations") += metrics.total_fast_poll_duration.as_millis().to_maxing_u64(), "taskname" => name); + metric!(counter("tasks.total_slow_poll_count") += metrics.total_slow_poll_count, "taskname" => name); + metric!(counter("tasks.total_slow_poll_duration") += metrics.total_slow_poll_duration.as_millis().to_maxing_u64(), "taskname" => name); } #[cfg(test)]