From 801d7cac05c8c14b00aa3c73a273e405fd257e45 Mon Sep 17 00:00:00 2001 From: Elias Ram Date: Thu, 23 May 2024 18:52:55 +0200 Subject: [PATCH 1/7] ref(metrics): Add normalization and update set metrics hashing --- Cargo.lock | 994 +++++++++++++++++- sentry-core/Cargo.toml | 4 + sentry-core/src/cadence.rs | 7 +- .../src/{metrics.rs => metrics/mod.rs} | 200 ++-- sentry-core/src/metrics/normalization/mod.rs | 3 + .../metrics/normalization/normalized_name.rs | 36 + .../metrics/normalization/normalized_tags.rs | 171 +++ .../metrics/normalization/normalized_unit.rs | 64 ++ sentry-core/src/units.rs | 6 + sentry-types/src/protocol/envelope.rs | 7 + 10 files changed, 1376 insertions(+), 116 deletions(-) rename sentry-core/src/{metrics.rs => metrics/mod.rs} (89%) create mode 100644 sentry-core/src/metrics/normalization/mod.rs create mode 100644 sentry-core/src/metrics/normalization/normalized_name.rs create mode 100644 sentry-core/src/metrics/normalization/normalized_tags.rs create mode 100644 sentry-core/src/metrics/normalization/normalized_unit.rs diff --git a/Cargo.lock b/Cargo.lock index 7c934098..840d94b7 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -272,6 +272,15 @@ dependencies = [ "memchr", ] +[[package]] +name = "aligned" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "377e4c0ba83e4431b10df45c1d4666f178ea9c552cac93e60c3a88bf32785923" +dependencies = [ + "as-slice", +] + [[package]] name = "alloc-no-stdlib" version = "2.0.4" @@ -287,6 +296,21 @@ dependencies = [ "alloc-no-stdlib", ] +[[package]] +name = "android-tzdata" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e999941b234f3131b00bc13c22d06e8c5ff726d1b6318ac7eb276997bbb4fef0" + +[[package]] +name = "android_system_properties" +version = "0.1.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "819e7219dbd41043ac279b19830f2efc897156490d7fd6ea916720117ee66311" +dependencies = [ + "libc", +] + [[package]] name = "anes" version = "0.1.6" @@ -314,6 +338,15 @@ version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "23b62fc65de8e4e7f52534fb52b0f3ed04746ae267519eef2a83941e8085068b" +[[package]] +name = "as-slice" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "516b6b4f0e40d50dcda9365d53964ec74560ad4284da2e7fc97122cd83174516" +dependencies = [ + "stable_deref_trait", +] + [[package]] name = "async-channel" version = "1.9.0" @@ -673,6 +706,28 @@ version = "0.22.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9475866fec1451be56a3c2400fd081ff546538961565ccb5b7142cbd22bc7a51" +[[package]] +name = "bindgen" +version = "0.63.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "36d860121800b2a9a94f9b5604b332d5cffb234ce17609ea479d723dbc9d3885" +dependencies = [ + "bitflags 1.3.2", + "cexpr", + "clang-sys", + "lazy_static", + "lazycell", + "log", + "peeking_take_while", + "proc-macro2", + "quote", + "regex", + "rustc-hash", + "shlex", + "syn 1.0.109", + "which", +] + [[package]] name = "bitflags" version = "1.3.2" @@ -740,12 +795,41 @@ dependencies = [ "alloc-stdlib", ] +[[package]] +name = "bstr" +version = "1.9.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "05efc5cfd9110c8416e471df0e96702d58690178e206e61b7173706673c93706" +dependencies = [ + "memchr", + "serde", +] + +[[package]] +name = "build-time" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f1219c19fc29b7bfd74b7968b420aff5bc951cf517800176e795d6b2300dd382" +dependencies = [ + "chrono", + "once_cell", + "proc-macro2", + "quote", + "syn 2.0.58", +] + [[package]] name = "bumpalo" version = "3.15.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "7ff69b9dd49fd426c69a0db9fc04dd934cdb6645ff000864d98f7e2af8830eaa" +[[package]] +name = "byteorder" +version = "1.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1fd0f2584146f6f2ef48085050886acf353beff7305ebd1ae69500e27c67f64b" + [[package]] name = "bytes" version = "0.5.6" @@ -776,6 +860,38 @@ dependencies = [ "crossbeam-channel", ] +[[package]] +name = "camino" +version = "1.1.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e0ec6b951b160caa93cc0c7b209e5a3bff7aae9062213451ac99493cd844c239" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo-platform" +version = "0.1.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "24b1f0365a6c6bb4020cd05806fd0d33c44d38046b8bd7f0e40814b9763cabfc" +dependencies = [ + "serde", +] + +[[package]] +name = "cargo_metadata" +version = "0.18.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2d886547e41f740c616ae73108f6eb70afe6d940c7bc697cb30f13daec073037" +dependencies = [ + "camino", + "cargo-platform", + "semver 1.0.20", + "serde", + "serde_json", + "thiserror", +] + [[package]] name = "cast" version = "0.3.0" @@ -792,12 +908,33 @@ dependencies = [ "libc", ] +[[package]] +name = "cexpr" +version = "0.6.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6fac387a98bb7c37292057cffc56d62ecb629900026402633ae9160df93a8766" +dependencies = [ + "nom 7.1.3", +] + [[package]] name = "cfg-if" version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "chrono" +version = "0.4.38" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a21f936df1771bf62b77f047b726c4625ff2e8aa607c01ec06e5a05bd8463401" +dependencies = [ + "android-tzdata", + "iana-time-zone", + "num-traits", + "windows-targets 0.52.4", +] + [[package]] name = "ciborium" version = "0.2.1" @@ -834,6 +971,17 @@ dependencies = [ "generic-array", ] +[[package]] +name = "clang-sys" +version = "1.7.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "67523a3b4be3ce1989d607a828d036249522dd9c1c8de7f4dd2dae43a37369d1" +dependencies = [ + "glob", + "libc", + "libloading", +] + [[package]] name = "clap" version = "4.4.10" @@ -859,6 +1007,15 @@ version = "0.6.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "702fc72eb24e5a1e48ce58027a675bc24edd52096d5397d4aea7c6dd9eca0bd1" +[[package]] +name = "cmake" +version = "0.1.50" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a31c789563b815f77f4250caee12365734369f942439b7defd71e18a48197130" +dependencies = [ + "cc", +] + [[package]] name = "concurrent-queue" version = "2.3.0" @@ -875,7 +1032,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19b076e143e1d9538dde65da30f8481c2a6c44040edb8e02b9bf1351edb92ce3" dependencies = [ "lazy_static", - "nom", + "nom 5.1.3", "serde", ] @@ -885,6 +1042,26 @@ version = "0.4.9" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fbdcdcb6d86f71c5e97409ad45898af11cbc995b4ee8112d59095a28d376c935" +[[package]] +name = "const_format" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a214c7af3d04997541b18d432afaff4c455e79e2029079647e72fc2bd27673" +dependencies = [ + "const_format_proc_macros", +] + +[[package]] +name = "const_format_proc_macros" +version = "0.2.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c7f6ff08fd20f4f299298a28e2dfa8a8ba1036e6cd2460ac1de7b425d76f2500" +dependencies = [ + "proc-macro2", + "quote", + "unicode-xid", +] + [[package]] name = "convert_case" version = "0.4.0" @@ -952,9 +1129,9 @@ checksum = "dcb25d077389e53838a8158c8e99174c5a9d902dee4904320db714f3c653ffba" [[package]] name = "crc32fast" -version = "1.3.2" +version = "1.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b540bd8bc810d3885c6ea91e2018302f68baba2129ab3e88f32389ee9370880d" +checksum = "a97769d94ddab943e4510d138150169a2758b5ef3eb191a9ee688de3e23ef7b3" dependencies = [ "cfg-if", ] @@ -995,6 +1172,12 @@ dependencies = [ "itertools", ] +[[package]] +name = "critical-section" +version = "1.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7059fff8937831a9ae6f0fe4d658ffabf58f2ca96aa9dec1c889f936f705f216" + [[package]] name = "crossbeam-channel" version = "0.5.8" @@ -1108,6 +1291,49 @@ dependencies = [ "windows-sys 0.48.0", ] +[[package]] +name = "cvt" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d2ae9bf77fbf2d39ef573205d554d87e86c12f1994e9ea335b0651b9b278bcf1" +dependencies = [ + "cfg-if", +] + +[[package]] +name = "darling" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "83b2eb4d90d12bdda5ed17de686c2acb4c57914f8f921b8da7e112b5a36f3fe1" +dependencies = [ + "darling_core", + "darling_macro", +] + +[[package]] +name = "darling_core" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "622687fe0bac72a04e5599029151f5796111b90f1baaa9b544d807a5e31cd120" +dependencies = [ + "fnv", + "ident_case", + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "darling_macro" +version = "0.20.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "733cabb43482b1a1b53eee8583c2b9e8684d592215ea83efd305dd31bc2f0178" +dependencies = [ + "darling_core", + "quote", + "syn 2.0.58", +] + [[package]] name = "dashmap" version = "5.5.3" @@ -1145,6 +1371,38 @@ dependencies = [ "uuid", ] +[[package]] +name = "defmt" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a99dd22262668b887121d4672af5a64b238f026099f1a2a1b322066c9ecfe9e0" +dependencies = [ + "bitflags 1.3.2", + "defmt-macros", +] + +[[package]] +name = "defmt-macros" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3a9f309eff1f79b3ebdf252954d90ae440599c26c2c553fe87a2d17195f2dcb" +dependencies = [ + "defmt-parser", + "proc-macro-error", + "proc-macro2", + "quote", + "syn 2.0.58", +] + +[[package]] +name = "defmt-parser" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ff4a5fefe330e8d7f31b16a318f9ce81000d8e35e69b93eae154d16d2278f70f" +dependencies = [ + "thiserror", +] + [[package]] name = "deranged" version = "0.3.9" @@ -1198,6 +1456,127 @@ version = "1.10.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "11157ac094ffbdde99aa67b23417ebdd801842852b500e395a45a9c0aac03e4a" +[[package]] +name = "embassy-futures" +version = "0.1.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1f878075b9794c1e4ac788c95b728f26aa6366d32eeb10c7051389f898f7d067" + +[[package]] +name = "embassy-sync" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dd938f25c0798db4280fcd8026bf4c2f48789aebf8f77b6e5cf8a7693ba114ec" +dependencies = [ + "cfg-if", + "critical-section", + "embedded-io-async", + "futures-util", + "heapless", +] + +[[package]] +name = "embedded-can" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e9d2e857f87ac832df68fa498d18ddc679175cf3d2e4aa893988e5601baf9438" +dependencies = [ + "nb 1.1.0", +] + +[[package]] +name = "embedded-hal" +version = "0.2.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "35949884794ad573cf46071e41c9b60efb0cb311e3ca01f7af807af1debc66ff" +dependencies = [ + "nb 0.1.3", + "void", +] + +[[package]] +name = "embedded-hal" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "361a90feb7004eca4019fb28352a9465666b24f840f5c3cddf0ff13920590b89" + +[[package]] +name = "embedded-hal-async" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c4c685bbef7fe13c3c6dd4da26841ed3980ef33e841cddfa15ce8a8fb3f1884" +dependencies = [ + "embedded-hal 1.0.0", +] + +[[package]] +name = "embedded-hal-nb" +version = "1.0.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fba4268c14288c828995299e59b12babdbe170f6c6d73731af1b4648142e8605" +dependencies = [ + "embedded-hal 1.0.0", + "nb 1.1.0", +] + +[[package]] +name = "embedded-io" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "edd0f118536f44f5ccd48bcb8b111bdc3de888b58c74639dfb034a357d0f206d" + +[[package]] +name = "embedded-io-async" +version = "0.6.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3ff09972d4073aa8c299395be75161d582e7629cd663171d62af73c8d50dba3f" +dependencies = [ + "embedded-io", +] + +[[package]] +name = "embedded-svc" +version = "0.27.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac6f87e7654f28018340aa55f933803017aefabaa5417820a3b2f808033c7bbc" +dependencies = [ + "defmt", + "embedded-io", + "embedded-io-async", + "enumset", + "heapless", + "log", + "no-std-net", + "num_enum", + "serde", + "strum 0.25.0", + "strum_macros 0.25.3", +] + +[[package]] +name = "embuild" +version = "0.31.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4caa4f198bb9152a55c0103efb83fa4edfcbb8625f4c9e94ae8ec8e23827c563" +dependencies = [ + "anyhow", + "bindgen", + "bitflags 1.3.2", + "cmake", + "filetime", + "globwalk", + "home", + "log", + "remove_dir_all", + "serde", + "serde_json", + "shlex", + "strum 0.24.1", + "tempfile", + "thiserror", + "which", +] + [[package]] name = "encoding_rs" version = "0.8.33" @@ -1207,6 +1586,28 @@ dependencies = [ "cfg-if", ] +[[package]] +name = "enumset" +version = "1.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "226c0da7462c13fb57e5cc9e0dc8f0635e7d27f276a3a7fd30054647f669007d" +dependencies = [ + "enumset_derive", + "serde", +] + +[[package]] +name = "enumset_derive" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08b6c6ab82d70f08844964ba10c7babb716de2ecaeab9be5717918a5177d3af" +dependencies = [ + "darling", + "proc-macro2", + "quote", + "syn 2.0.58", +] + [[package]] name = "env_logger" version = "0.10.1" @@ -1220,6 +1621,15 @@ dependencies = [ "termcolor", ] +[[package]] +name = "envy" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f47e0157f2cb54f5ae1bd371b30a2ae4311e1c028f575cd4e81de7353215965" +dependencies = [ + "serde", +] + [[package]] name = "equivalent" version = "1.0.1" @@ -1245,6 +1655,68 @@ dependencies = [ "windows-sys 0.52.0", ] +[[package]] +name = "esp-idf-hal" +version = "0.43.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f7adf3fb19a9ca016cbea1ab8a7b852ac69df8fcde4923c23d3b155efbc42a74" +dependencies = [ + "atomic-waker", + "embassy-sync", + "embedded-can", + "embedded-hal 0.2.7", + "embedded-hal 1.0.0", + "embedded-hal-async", + "embedded-hal-nb", + "embedded-io", + "embedded-io-async", + "embuild", + "enumset", + "esp-idf-sys", + "heapless", + "log", + "nb 1.1.0", + "num_enum", +] + +[[package]] +name = "esp-idf-svc" +version = "0.48.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2180642ca122a7fec1ec417a9b1a77aa66aaa067fdf1daae683dd8caba84f26b" +dependencies = [ + "embassy-futures", + "embedded-hal-async", + "embedded-svc", + "embuild", + "enumset", + "esp-idf-hal", + "heapless", + "log", + "num_enum", + "uncased", +] + +[[package]] +name = "esp-idf-sys" +version = "0.34.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "2e148f97c04ed3e9181a08bcdc9560a515aad939b0ba7f50a0022e294665e0af" +dependencies = [ + "anyhow", + "bindgen", + "build-time", + "cargo_metadata", + "const_format", + "embuild", + "envy", + "libc", + "regex", + "serde", + "strum 0.24.1", + "which", +] + [[package]] name = "event-listener" version = "2.5.3" @@ -1287,6 +1759,18 @@ version = "2.0.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" +[[package]] +name = "filetime" +version = "0.2.23" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1ee447700ac8aa0b2f2bd7bc4462ad686ba06baa6727ac149a2d6277f0d240fd" +dependencies = [ + "cfg-if", + "libc", + "redox_syscall", + "windows-sys 0.52.0", +] + [[package]] name = "findshlibs" version = "0.10.2" @@ -1350,6 +1834,20 @@ dependencies = [ "percent-encoding", ] +[[package]] +name = "fs_at" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "982f82cc75107eef84f417ad6c53ae89bf65b561937ca4a3b3b0fd04d0aa2425" +dependencies = [ + "aligned", + "cfg-if", + "cvt", + "libc", + "nix", + "windows-sys 0.48.0", +] + [[package]] name = "futures" version = "0.3.29" @@ -1528,6 +2026,30 @@ version = "0.3.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d2fabcfbdc87f4758337ca535fb41a6d701b65693ce38287d856d1674551ec9b" +[[package]] +name = "globset" +version = "0.4.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "57da3b9b5b85bd66f31093f8c408b90a74431672542466497dcbdfdc02034be1" +dependencies = [ + "aho-corasick", + "bstr", + "log", + "regex-automata", + "regex-syntax", +] + +[[package]] +name = "globwalk" +version = "0.8.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "93e3af942408868f6934a7b85134a3230832b9977cf66125df2f9edcfce4ddcc" +dependencies = [ + "bitflags 1.3.2", + "ignore", + "walkdir", +] + [[package]] name = "gloo-timers" version = "0.2.6" @@ -1565,6 +2087,15 @@ version = "1.8.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "eabb4a44450da02c90444cf74558da904edde8fb4e9035a9a6a4e15445af0bd7" +[[package]] +name = "hash32" +version = "0.3.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "47d60b12902ba28e2730cd37e95b8c9223af2808df9e902d4df49588d1470606" +dependencies = [ + "byteorder", +] + [[package]] name = "hashbrown" version = "0.12.3" @@ -1577,6 +2108,23 @@ version = "0.14.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604" +[[package]] +name = "heapless" +version = "0.8.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0bfb9eb618601c89945a70e254898da93b13be0388091d42117462b265bb3fad" +dependencies = [ + "hash32", + "serde", + "stable_deref_trait", +] + +[[package]] +name = "heck" +version = "0.4.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8" + [[package]] name = "hermit-abi" version = "0.3.9" @@ -1609,6 +2157,15 @@ dependencies = [ "digest 0.9.0", ] +[[package]] +name = "home" +version = "0.5.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e3d1354bf6b7235cb4a0576c2619fd4ed18183f689b12b006a0ee7329eeff9a5" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "hostname" version = "0.4.0" @@ -1843,14 +2400,59 @@ dependencies = [ "tracing", ] +[[package]] +name = "iana-time-zone" +version = "0.1.60" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e7ffbb5a1b541ea2561f8c41c087286cc091e21e556a4f09a8f6cbf17b69b141" +dependencies = [ + "android_system_properties", + "core-foundation-sys", + "iana-time-zone-haiku", + "js-sys", + "wasm-bindgen", + "windows-core", +] + +[[package]] +name = "iana-time-zone-haiku" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f31827a206f56af32e590ba56d5d2d085f558508192593743f16b2306495269f" +dependencies = [ + "cc", +] + +[[package]] +name = "ident_case" +version = "1.0.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b9e0384b61958566e926dc50660321d12159025e767c18e043daf26b70104c39" + [[package]] name = "idna" version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +checksum = "634d9b1461af396cad843f47fdba5597a4f9e6ddd4bfb6ff5d85028c25cb12f6" +dependencies = [ + "unicode-bidi", + "unicode-normalization", +] + +[[package]] +name = "ignore" +version = "0.4.22" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b46810df39e66e925525d6e38ce1e7f6e1d208f72dc39757880fcb66e2c58af1" dependencies = [ - "unicode-bidi", - "unicode-normalization", + "crossbeam-deque", + "globset", + "log", + "memchr", + "regex-automata", + "same-file", + "walkdir", + "winapi-util", ] [[package]] @@ -1995,6 +2597,12 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" +[[package]] +name = "lazycell" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "830d08ce1d1d941e6b30645f1a0eb5643013d835ce3779a5fc208261dbe10f55" + [[package]] name = "lexical-core" version = "0.7.6" @@ -2014,6 +2622,16 @@ version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" +[[package]] +name = "libloading" +version = "0.8.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0c2a198fb6b0eada2a8df47933734e6d35d350665a33a3593d7164fa52c75c19" +dependencies = [ + "cfg-if", + "windows-targets 0.52.4", +] + [[package]] name = "libnghttp2-sys" version = "0.1.8+1.55.1" @@ -2121,6 +2739,12 @@ dependencies = [ "unicase", ] +[[package]] +name = "minimal-lexical" +version = "0.2.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "68354c5c6bd36d73ff3feceb05efa59b6acb7626617f4962be322a825e61f79a" + [[package]] name = "miniz_oxide" version = "0.7.2" @@ -2160,6 +2784,41 @@ dependencies = [ "tempfile", ] +[[package]] +name = "nb" +version = "0.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "801d31da0513b6ec5214e9bf433a77966320625a37860f910be265be6e18d06f" +dependencies = [ + "nb 1.1.0", +] + +[[package]] +name = "nb" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d5439c4ad607c3c23abf66de8c8bf57ba8adcd1f129e699851a6e43935d339d" + +[[package]] +name = "nix" +version = "0.26.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "598beaf3cc6fdd9a5dfb1630c2800c7acd31df7aaf0f565796fba2b53ca1af1b" +dependencies = [ + "bitflags 1.3.2", + "cfg-if", + "libc", +] + +[[package]] +name = "no-std-net" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1bcece43b12349917e096cddfa66107277f123e6c96a5aea78711dc601a47152" +dependencies = [ + "serde", +] + [[package]] name = "nom" version = "5.1.3" @@ -2171,6 +2830,25 @@ dependencies = [ "version_check", ] +[[package]] +name = "nom" +version = "7.1.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d273983c5a657a70a3e8f2a01329822f3b8c8172b73826411a55751e404a0a4a" +dependencies = [ + "memchr", + "minimal-lexical", +] + +[[package]] +name = "normpath" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5831952a9476f2fed74b77d74182fa5ddc4d21c72ec45a333b250e3ed0272804" +dependencies = [ + "windows-sys 0.52.0", +] + [[package]] name = "nu-ansi-term" version = "0.46.0" @@ -2200,6 +2878,27 @@ dependencies = [ "libc", ] +[[package]] +name = "num_enum" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "02339744ee7253741199f897151b38e72257d13802d4ee837285cc2990a90845" +dependencies = [ + "num_enum_derive", +] + +[[package]] +name = "num_enum_derive" +version = "0.7.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "681030a937600a36906c185595136d26abfebb4aa9c65701cefcaf8578bb982b" +dependencies = [ + "proc-macro-crate", + "proc-macro2", + "quote", + "syn 2.0.58", +] + [[package]] name = "object" version = "0.32.2" @@ -2323,6 +3022,12 @@ version = "1.0.14" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "de3145af08024dea9fa9914f381a17b8fc6034dfb00f3a84013f7ff43f29ed4c" +[[package]] +name = "peeking_take_while" +version = "0.1.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "19b17cddbe7ec3f8bc800887bab5e717348c95ea2ca0b1bf0837fb964dc67099" + [[package]] name = "percent-encoding" version = "2.3.1" @@ -2469,6 +3174,39 @@ dependencies = [ "log", ] +[[package]] +name = "proc-macro-crate" +version = "3.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6d37c51ca738a55da99dc0c4a34860fd675453b8b36209178c2249bb13651284" +dependencies = [ + "toml_edit", +] + +[[package]] +name = "proc-macro-error" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c" +dependencies = [ + "proc-macro-error-attr", + "proc-macro2", + "quote", + "syn 1.0.109", + "version_check", +] + +[[package]] +name = "proc-macro-error-attr" +version = "1.0.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869" +dependencies = [ + "proc-macro2", + "quote", + "version_check", +] + [[package]] name = "proc-macro-hack" version = "0.5.20+deprecated" @@ -2651,6 +3389,22 @@ version = "1.9.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c707298afce11da2efef2f600116fa93ffa7a032b5d7b628aa17711ec81383ca" +[[package]] +name = "remove_dir_all" +version = "0.8.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23895cfadc1917fed9c6ed76a8c2903615fa3704f7493ff82b364c6540acc02b" +dependencies = [ + "aligned", + "cfg-if", + "cvt", + "fs_at", + "lazy_static", + "libc", + "normpath", + "windows-sys 0.45.0", +] + [[package]] name = "reqwest" version = "0.12.3" @@ -2746,6 +3500,12 @@ version = "0.1.23" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d626bb9dae77e28219937af045c257c28bfd3f69333c512553507f5f9798cb76" +[[package]] +name = "rustc-hash" +version = "1.1.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "08d43f7aa6b08d49f382cde6a7982047c3426db949b1424bc4b7ec9ae12c6ce2" + [[package]] name = "rustc_version" version = "0.2.3" @@ -2937,6 +3697,9 @@ name = "semver" version = "1.0.20" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "836fa6a3e1e547f9a2c4040802ec865b5d85f4014efe00555d7090a3dcaa1090" +dependencies = [ + "serde", +] [[package]] name = "semver-parser" @@ -2946,11 +3709,13 @@ checksum = "388a1df253eca08550bef6c72392cfe7c30914bf41df5269b68cbd6ff8f570a3" [[package]] name = "sentry" -version = "0.32.2" +version = "0.32.3" dependencies = [ "actix-web", "anyhow", "curl", + "embedded-svc", + "esp-idf-svc", "http-client", "httpdate", "isahc", @@ -2982,7 +3747,7 @@ dependencies = [ [[package]] name = "sentry-actix" -version = "0.32.2" +version = "0.32.3" dependencies = [ "actix-web", "futures", @@ -2994,7 +3759,7 @@ dependencies = [ [[package]] name = "sentry-anyhow" -version = "0.32.2" +version = "0.32.3" dependencies = [ "anyhow", "sentry", @@ -3004,7 +3769,7 @@ dependencies = [ [[package]] name = "sentry-backtrace" -version = "0.32.2" +version = "0.32.3" dependencies = [ "backtrace", "once_cell", @@ -3014,7 +3779,7 @@ dependencies = [ [[package]] name = "sentry-contexts" -version = "0.32.2" +version = "0.32.3" dependencies = [ "hostname", "libc", @@ -3027,28 +3792,32 @@ dependencies = [ [[package]] name = "sentry-core" -version = "0.32.2" +version = "0.32.3" dependencies = [ "anyhow", "cadence", + "crc32fast", "criterion", "futures", + "itertools", "log", "once_cell", "rand 0.8.5", "rayon", + "regex", "sentry", "sentry-types", "serde", "serde_json", "thiserror", "tokio", + "unicode-segmentation", "uuid", ] [[package]] name = "sentry-debug-images" -version = "0.32.2" +version = "0.32.3" dependencies = [ "findshlibs", "once_cell", @@ -3057,7 +3826,7 @@ dependencies = [ [[package]] name = "sentry-log" -version = "0.32.2" +version = "0.32.3" dependencies = [ "log", "pretty_env_logger", @@ -3067,7 +3836,7 @@ dependencies = [ [[package]] name = "sentry-panic" -version = "0.32.2" +version = "0.32.3" dependencies = [ "sentry", "sentry-backtrace", @@ -3076,7 +3845,7 @@ dependencies = [ [[package]] name = "sentry-slog" -version = "0.32.2" +version = "0.32.3" dependencies = [ "erased-serde", "sentry", @@ -3088,7 +3857,7 @@ dependencies = [ [[package]] name = "sentry-tower" -version = "0.32.2" +version = "0.32.3" dependencies = [ "anyhow", "axum 0.7.1", @@ -3108,7 +3877,7 @@ dependencies = [ [[package]] name = "sentry-tracing" -version = "0.32.2" +version = "0.32.3" dependencies = [ "log", "sentry", @@ -3122,7 +3891,7 @@ dependencies = [ [[package]] name = "sentry-types" -version = "0.32.2" +version = "0.32.3" dependencies = [ "debugid", "hex", @@ -3238,6 +4007,12 @@ dependencies = [ "lazy_static", ] +[[package]] +name = "shlex" +version = "1.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0fda2ff0d084019ba4d7c6f371c95d8fd75ce3524c3cb8fb653a3023f6323e64" + [[package]] name = "signal-hook-registry" version = "1.4.1" @@ -3326,6 +4101,12 @@ dependencies = [ "lock_api", ] +[[package]] +name = "stable_deref_trait" +version = "1.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a8f112729512f8e442d81f95a8a7ddf2b7c6b8a1a6f509a95864142b30cab2d3" + [[package]] name = "standback" version = "0.2.17" @@ -3390,6 +4171,50 @@ version = "0.1.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "213701ba3370744dcd1a12960caa4843b3d68b4d1c0a5d575e0d65b2ee9d16c0" +[[package]] +name = "strum" +version = "0.24.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "063e6045c0e62079840579a7e47a355ae92f60eb74daaf156fb1e84ba164e63f" +dependencies = [ + "strum_macros 0.24.3", +] + +[[package]] +name = "strum" +version = "0.25.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "290d54ea6f91c969195bdbcd7442c8c2a2ba87da8bf60a7ee86a235d4bc1e125" +dependencies = [ + "strum_macros 0.25.3", +] + +[[package]] +name = "strum_macros" +version = "0.24.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1e385be0d24f186b4ce2f9982191e7101bb737312ad61c1f2f984f34bcf85d59" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 1.0.109", +] + +[[package]] +name = "strum_macros" +version = "0.25.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "23dc1fa9ac9c169a78ba62f0b841814b7abae11bdd047b9c58f893439e309ea0" +dependencies = [ + "heck", + "proc-macro2", + "quote", + "rustversion", + "syn 2.0.58", +] + [[package]] name = "subtle" version = "2.5.0" @@ -3675,6 +4500,23 @@ dependencies = [ "tracing", ] +[[package]] +name = "toml_datetime" +version = "0.6.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4badfd56924ae69bcc9039335b2e017639ce3f9b001c393c1b2d1ef846ce2cbf" + +[[package]] +name = "toml_edit" +version = "0.21.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a8534fd7f78b5405e860340ad6575217ce99f38d4d5c8f2442cb5ecb50090e1" +dependencies = [ + "indexmap 2.2.6", + "toml_datetime", + "winnow", +] + [[package]] name = "tonic" version = "0.11.0" @@ -3823,6 +4665,15 @@ dependencies = [ "libc", ] +[[package]] +name = "uncased" +version = "0.9.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e1b88fcfe09e89d3866a5c11019378088af2d24c3fbd4f0543f96b479ec90697" +dependencies = [ + "version_check", +] + [[package]] name = "unicase" version = "2.7.0" @@ -3853,6 +4704,18 @@ dependencies = [ "tinyvec", ] +[[package]] +name = "unicode-segmentation" +version = "1.11.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" + +[[package]] +name = "unicode-xid" +version = "0.2.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f962df74c8c05a667b5ee8bcf162993134c104e96440b663c8daa176dc772d8c" + [[package]] name = "universal-hash" version = "0.4.0" @@ -3931,6 +4794,12 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "void" +version = "1.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6a02e4885ed3bc0f2de90ea6dd45ebcbb66dacffe03547fadbb0eeae2770887d" + [[package]] name = "waker-fn" version = "1.1.1" @@ -4059,6 +4928,18 @@ dependencies = [ "rustls-pki-types", ] +[[package]] +name = "which" +version = "4.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ba24419a2078cd2b0f2ede2691b6c66d8e47836da3b6db8265ebad47afbfc7" +dependencies = [ + "either", + "home", + "once_cell", + "rustix 0.38.32", +] + [[package]] name = "winapi" version = "0.3.9" @@ -4109,6 +4990,15 @@ dependencies = [ "windows-targets 0.52.4", ] +[[package]] +name = "windows-sys" +version = "0.45.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "75283be5efb2831d37ea142365f009c02ec203cd29a3ebecbc093d52315b66d0" +dependencies = [ + "windows-targets 0.42.2", +] + [[package]] name = "windows-sys" version = "0.48.0" @@ -4127,6 +5017,21 @@ dependencies = [ "windows-targets 0.52.4", ] +[[package]] +name = "windows-targets" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8e5180c00cd44c9b1c88adb3693291f1cd93605ded80c250a75d472756b4d071" +dependencies = [ + "windows_aarch64_gnullvm 0.42.2", + "windows_aarch64_msvc 0.42.2", + "windows_i686_gnu 0.42.2", + "windows_i686_msvc 0.42.2", + "windows_x86_64_gnu 0.42.2", + "windows_x86_64_gnullvm 0.42.2", + "windows_x86_64_msvc 0.42.2", +] + [[package]] name = "windows-targets" version = "0.48.5" @@ -4157,6 +5062,12 @@ dependencies = [ "windows_x86_64_msvc 0.52.4", ] +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "597a5118570b68bc08d8d59125332c54f1ba9d9adeedeef5b99b02ba2b0698f8" + [[package]] name = "windows_aarch64_gnullvm" version = "0.48.5" @@ -4169,6 +5080,12 @@ version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" +[[package]] +name = "windows_aarch64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "e08e8864a60f06ef0d0ff4ba04124db8b0fb3be5776a5cd47641e942e58c4d43" + [[package]] name = "windows_aarch64_msvc" version = "0.48.5" @@ -4181,6 +5098,12 @@ version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" +[[package]] +name = "windows_i686_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c61d927d8da41da96a81f029489353e68739737d3beca43145c8afec9a31a84f" + [[package]] name = "windows_i686_gnu" version = "0.48.5" @@ -4193,6 +5116,12 @@ version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" +[[package]] +name = "windows_i686_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "44d840b6ec649f480a41c8d80f9c65108b92d89345dd94027bfe06ac444d1060" + [[package]] name = "windows_i686_msvc" version = "0.48.5" @@ -4205,6 +5134,12 @@ version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" +[[package]] +name = "windows_x86_64_gnu" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8de912b8b8feb55c064867cf047dda097f92d51efad5b491dfb98f6bbb70cb36" + [[package]] name = "windows_x86_64_gnu" version = "0.48.5" @@ -4217,6 +5152,12 @@ version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "26d41b46a36d453748aedef1486d5c7a85db22e56aff34643984ea85514e94a3" + [[package]] name = "windows_x86_64_gnullvm" version = "0.48.5" @@ -4229,6 +5170,12 @@ version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" +[[package]] +name = "windows_x86_64_msvc" +version = "0.42.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9aec5da331524158c6d1a4ac0ab1541149c0b9505fde06423b02f5ef0106b9f0" + [[package]] name = "windows_x86_64_msvc" version = "0.48.5" @@ -4241,6 +5188,15 @@ version = "0.52.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" +[[package]] +name = "winnow" +version = "0.5.40" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f593a95398737aeed53e489c785df13f3618e41dbcd6718c6addbf1395aa6876" +dependencies = [ + "memchr", +] + [[package]] name = "winreg" version = "0.52.0" diff --git a/sentry-core/Cargo.toml b/sentry-core/Cargo.toml index 3c2bfd0e..145ee1cd 100644 --- a/sentry-core/Cargo.toml +++ b/sentry-core/Cargo.toml @@ -31,12 +31,16 @@ UNSTABLE_cadence = ["dep:cadence", "UNSTABLE_metrics"] [dependencies] cadence = { version = "0.29.0", optional = true } +crc32fast = "1.4.0" +itertools = "0.10.5" log = { version = "0.4.8", optional = true, features = ["std"] } once_cell = "1" rand = { version = "0.8.1", optional = true } +regex = "1.7.3" sentry-types = { version = "0.32.3", path = "../sentry-types" } serde = { version = "1.0.104", features = ["derive"] } serde_json = { version = "1.0.46" } +unicode-segmentation = "1.11.0" uuid = { version = "1.0.0", features = ["v4", "serde"], optional = true } [dev-dependencies] diff --git a/sentry-core/src/cadence.rs b/sentry-core/src/cadence.rs index 8cd7c3d0..401f53d9 100644 --- a/sentry-core/src/cadence.rs +++ b/sentry-core/src/cadence.rs @@ -154,9 +154,10 @@ mod tests { println!("{metrics}"); - assert!(metrics.contains("sentry.test.count.with.tags:1|c|#foo:bar|T")); - assert!(metrics.contains("sentry.test.some.count:11|c|T")); - assert!(metrics.contains("sentry.test.some.distr:1:2:3|d|T")); + assert!(metrics + .contains("sentry.test.count.with.tags@none:1|c|#environment:production,foo:bar|T")); + assert!(metrics.contains("sentry.test.some.count@none:11|c|#environment:production|T")); + assert!(metrics.contains("sentry.test.some.distr@none:1:2:3|d|#environment:production|T")); assert_eq!(items.next(), None); } } diff --git a/sentry-core/src/metrics.rs b/sentry-core/src/metrics/mod.rs similarity index 89% rename from sentry-core/src/metrics.rs rename to sentry-core/src/metrics/mod.rs index 0e8b6f3b..df007197 100644 --- a/sentry-core/src/metrics.rs +++ b/sentry-core/src/metrics/mod.rs @@ -44,14 +44,19 @@ //! //! [our docs]: https://develop.sentry.dev/delightful-developer-metrics/ +mod normalization; + use std::borrow::Cow; -use std::collections::hash_map::{DefaultHasher, Entry}; +use std::collections::hash_map::Entry; use std::collections::{BTreeMap, BTreeSet, HashMap}; -use std::fmt::{self, Write}; +use std::fmt::{self, Display}; use std::sync::{Arc, Mutex}; use std::thread::{self, JoinHandle}; use std::time::{Duration, SystemTime, UNIX_EPOCH}; +use normalization::normalized_name::NormalizedName; +use normalization::normalized_tags::NormalizedTags; +use normalization::normalized_unit::NormalizedUnit; use sentry_types::protocol::latest::{Envelope, EnvelopeItem}; use crate::client::TransportArc; @@ -168,15 +173,23 @@ impl MetricValue { } } +impl Display for MetricValue { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + match self { + Self::Counter(v) => write!(f, "{}", v), + Self::Distribution(v) => write!(f, "{}", v), + Self::Gauge(v) => write!(f, "{}", v), + Self::Set(v) => write!(f, "{}", v), + } + } +} + /// Hashes the given set value. /// /// Sets only guarantee 32-bit accuracy, but arbitrary strings are allowed on the protocol. Upon /// parsing, they are hashed and only used as hashes subsequently. fn hash_set_value(string: &str) -> u32 { - use std::hash::Hasher; - let mut hasher = DefaultHasher::default(); - hasher.write(string.as_bytes()); - hasher.finish() as u32 + crc32fast::hash(string.as_bytes()) } #[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, PartialOrd, Ord)] @@ -510,6 +523,24 @@ impl Metric { client.add_metric(self); } } + + /// Convert the metric into an [`Envelope`] containing a single [`EnvelopeItem::Statsd`]. + pub fn to_envelope(self) -> Envelope { + let timestamp = SystemTime::now() + .duration_since(UNIX_EPOCH) + .unwrap_or_default() + .as_secs(); + let data = format!( + "{}@{}:{}|{}|#{}|T{}", + NormalizedName::from(self.name.as_ref()), + NormalizedUnit::from(self.unit), + self.value, + self.value.ty(), + NormalizedTags::from(self.tags), + timestamp + ); + Envelope::from_item(EnvelopeItem::Statsd(data.into_bytes())) + } } /// A builder for metrics. @@ -550,6 +581,26 @@ impl MetricBuilder { self } + /// Adds multiple tags to the metric. + /// + /// Tags allow you to add dimensions to metrics. They are key-value pairs that can be filtered + /// or grouped by in Sentry. + /// + /// When sent to Sentry via [`MetricBuilder::send`] or when added to a + /// [`Client`](crate::Client), the client may add default tags to the metrics, such as the + /// `release` or the `environment` from the Scope. + pub fn with_tags(mut self, tags: T) -> Self + where + T: IntoIterator, + K: Into, + V: Into, + { + tags.into_iter().for_each(|(k, v)| { + self.metric.tags.insert(k.into(), v.into()); + }); + self + } + /// Sets the timestamp for the metric. /// /// By default, the timestamp is set to the current time when the metric is built or sent. @@ -723,9 +774,13 @@ fn get_default_tags(options: &ClientOptions) -> TagMap { if let Some(ref release) = options.release { tags.insert("release".into(), release.clone()); } - if let Some(ref environment) = options.environment { - tags.insert("environment".into(), environment.clone()); - } + tags.insert( + "environment".into(), + options + .environment + .clone() + .unwrap_or(Cow::Borrowed("production")), + ); tags } @@ -778,11 +833,8 @@ impl Worker { for (timestamp, buckets) in buckets { for (key, value) in buckets { - write!(&mut out, "{}", SafeKey(key.name.as_ref()))?; - if key.unit != MetricUnit::None { - write!(&mut out, "@{}", key.unit)?; - } - + write!(&mut out, "{}", NormalizedName::from(key.name.as_ref()))?; + write!(&mut out, "@{}", NormalizedUnit::from(key.unit))?; match value { BucketValue::Counter(c) => { write!(&mut out, ":{}", c)?; @@ -807,16 +859,9 @@ impl Worker { } write!(&mut out, "|{}", key.ty.as_str())?; - - for (i, (k, v)) in key.tags.iter().chain(&self.default_tags).enumerate() { - match i { - 0 => write!(&mut out, "|#")?, - _ => write!(&mut out, ",")?, - } - - write!(&mut out, "{}:{}", SafeKey(k.as_ref()), SafeVal(v.as_ref()))?; - } - + let normalized_tags = + NormalizedTags::from(key.tags).with_default_tags(&self.default_tags); + write!(&mut out, "|#{}", normalized_tags)?; writeln!(&mut out, "|T{}", timestamp)?; } } @@ -922,51 +967,6 @@ impl Drop for MetricAggregator { } } -fn safe_fmt(f: &mut fmt::Formatter<'_>, string: &str, mut check: F) -> fmt::Result -where - F: FnMut(char) -> bool, -{ - let mut valid = true; - - for c in string.chars() { - if check(c) { - valid = true; - f.write_char(c)?; - } else if valid { - valid = false; - f.write_char('_')?; - } - } - - Ok(()) -} - -// Helper that serializes a string into a safe format for metric names or tag keys. -struct SafeKey<'s>(&'s str); - -impl<'s> fmt::Display for SafeKey<'s> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - safe_fmt(f, self.0, |c| { - c.is_ascii_alphanumeric() || matches!(c, '_' | '-' | '.' | '/') - }) - } -} - -// Helper that serializes a string into a safe format for tag values. -struct SafeVal<'s>(&'s str); - -impl<'s> fmt::Display for SafeVal<'s> { - fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { - safe_fmt(f, self.0, |c| { - c.is_alphanumeric() - || matches!( - c, - '_' | ':' | '/' | '@' | '.' | '{' | '}' | '[' | ']' | '$' | '-' - ) - }) - } -} - #[cfg(test)] mod tests { use crate::test::{with_captured_envelopes, with_captured_envelopes_options}; @@ -1007,7 +1007,10 @@ mod tests { }); let metrics = get_single_metrics(&envelopes); - assert_eq!(metrics, format!("my.metric:1|c|#and:more,foo:bar|T{ts}")); + assert_eq!( + metrics, + format!("my.metric@none:1|c|#and:more,environment:production,foo:bar|T{ts}") + ); } #[test] @@ -1022,7 +1025,10 @@ mod tests { }); let metrics = get_single_metrics(&envelopes); - assert_eq!(metrics, format!("my.metric@custom:1|c|T{ts}")); + assert_eq!( + metrics, + format!("my.metric@custom:1|c|#environment:production|T{ts}") + ); } #[test] @@ -1034,7 +1040,10 @@ mod tests { }); let metrics = get_single_metrics(&envelopes); - assert_eq!(metrics, format!("my_metric:1|c|T{ts}")); + assert_eq!( + metrics, + format!("my___metric@none:1|c|#environment:production|T{ts}") + ); } #[test] @@ -1051,29 +1060,17 @@ mod tests { let metrics = get_single_metrics(&envelopes); assert_eq!( metrics, - format!("my.metric:1|c|#foo-bar_blub:_$föö{{}}|T{ts}") + format!("my.metric@none:1|c|#environment:production,foo-barblub:%$föö{{}}|T{ts}") ); } - #[test] - fn test_own_namespace() { - let (time, ts) = current_time(); - - let envelopes = with_captured_envelopes(|| { - Metric::count("ns/my.metric").with_time(time).send(); - }); - - let metrics = get_single_metrics(&envelopes); - assert_eq!(metrics, format!("ns/my.metric:1|c|T{ts}")); - } - #[test] fn test_default_tags() { let (time, ts) = current_time(); let options = ClientOptions { release: Some("myapp@1.0.0".into()), - environment: Some("production".into()), + environment: Some("development".into()), ..Default::default() }; @@ -1090,7 +1087,7 @@ mod tests { let metrics = get_single_metrics(&envelopes); assert_eq!( metrics, - format!("requests:1|c|#foo:bar,environment:production,release:myapp@1.0.0|T{ts}") + format!("requests@none:1|c|#environment:development,foo:bar,release:myapp@1.0.0|T{ts}") ); } @@ -1104,7 +1101,10 @@ mod tests { }); let metrics = get_single_metrics(&envelopes); - assert_eq!(metrics, format!("my.metric:3|c|T{ts}")); + assert_eq!( + metrics, + format!("my.metric@none:3|c|#environment:production|T{ts}") + ); } #[test] @@ -1121,7 +1121,10 @@ mod tests { }); let metrics = get_single_metrics(&envelopes); - assert_eq!(metrics, format!("my.metric@second:0.2:0.1|d|T{ts}")); + assert_eq!( + metrics, + format!("my.metric@second:0.2:0.1|d|#environment:production|T{ts}") + ); } #[test] @@ -1138,7 +1141,10 @@ mod tests { }); let metrics = get_single_metrics(&envelopes); - assert_eq!(metrics, format!("my.metric:2:1|d|T{ts}")); + assert_eq!( + metrics, + format!("my.metric@none:2:1|d|#environment:production|T{ts}") + ); } #[test] @@ -1153,7 +1159,10 @@ mod tests { }); let metrics = get_single_metrics(&envelopes); - assert_eq!(metrics, format!("my.metric:3410894750:3817476724|s|T{ts}")); + assert_eq!( + metrics, + format!("my.metric@none:907060870:980881731|s|#environment:production|T{ts}") + ); } #[test] @@ -1167,7 +1176,10 @@ mod tests { }); let metrics = get_single_metrics(&envelopes); - assert_eq!(metrics, format!("my.metric:1.5:1:2:4.5:3|g|T{ts}")); + assert_eq!( + metrics, + format!("my.metric@none:1.5:1:2:4.5:3|g|#environment:production|T{ts}") + ); } #[test] @@ -1182,8 +1194,8 @@ mod tests { let metrics = get_single_metrics(&envelopes); println!("{metrics}"); - assert!(metrics.contains(&format!("my.metric:1|c|T{ts}"))); - assert!(metrics.contains(&format!("my.dist:2|d|T{ts}"))); + assert!(metrics.contains(&format!("my.metric@none:1|c|#environment:production|T{ts}"))); + assert!(metrics.contains(&format!("my.dist@none:2|d|#environment:production|T{ts}"))); } #[test] diff --git a/sentry-core/src/metrics/normalization/mod.rs b/sentry-core/src/metrics/normalization/mod.rs new file mode 100644 index 00000000..54ece645 --- /dev/null +++ b/sentry-core/src/metrics/normalization/mod.rs @@ -0,0 +1,3 @@ +pub mod normalized_name; +pub mod normalized_tags; +pub mod normalized_unit; diff --git a/sentry-core/src/metrics/normalization/normalized_name.rs b/sentry-core/src/metrics/normalization/normalized_name.rs new file mode 100644 index 00000000..4eabcfe7 --- /dev/null +++ b/sentry-core/src/metrics/normalization/normalized_name.rs @@ -0,0 +1,36 @@ +use regex::Regex; +use std::borrow::Cow; + +pub struct NormalizedName<'a> { + name: Cow<'a, str>, +} + +impl<'a> From<&'a str> for NormalizedName<'a> { + fn from(name: &'a str) -> Self { + Self { + name: Regex::new(r"[^a-zA-Z0-9_\-.]") + .expect("Regex should compile") + .replace_all(name, "_"), + } + } +} + +impl std::fmt::Display for NormalizedName<'_> { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.name) + } +} + +#[cfg(test)] +mod test { + use crate::metrics::NormalizedName; + + #[test] + fn test_from() { + let expected = "aA1_-.____________"; + + let actual = NormalizedName::from("aA1_-./+ö{😀\n\t\r\\| ,").to_string(); + + assert_eq!(expected, actual); + } +} diff --git a/sentry-core/src/metrics/normalization/normalized_tags.rs b/sentry-core/src/metrics/normalization/normalized_tags.rs new file mode 100644 index 00000000..3b09f0ff --- /dev/null +++ b/sentry-core/src/metrics/normalization/normalized_tags.rs @@ -0,0 +1,171 @@ +use itertools::Itertools; +use regex::Regex; +use std::collections::HashMap; +use unicode_segmentation::UnicodeSegmentation; + +use crate::metrics::TagMap; + +pub struct NormalizedTags { + tags: HashMap, +} + +impl From for NormalizedTags { + fn from(tags: TagMap) -> Self { + Self { + tags: tags + .iter() + .map(|(k, v)| (Self::normalize_key(k), Self::normalize_value(v))) + .filter(|(k, v)| !v.is_empty() && !k.is_empty()) + .collect(), + } + } +} + +impl NormalizedTags { + pub fn with_default_tags(mut self, tags: &TagMap) -> Self { + tags.iter().for_each(|(k, v)| { + self.tags + .entry(Self::normalize_key(k)) + .or_insert(Self::normalize_value(v)); + }); + self + } + + fn normalize_key(key: &str) -> String { + Regex::new(r"[^a-zA-Z0-9_\-./]") + .expect("Tag normalization regex should compile") + .replace_all(&key.graphemes(true).take(32).collect::(), "") + .to_string() + } + + fn normalize_value(value: &str) -> String { + value + .graphemes(true) + .take(200) + .collect::() + .replace('\\', "\\\\") + .replace('\n', "\\n") + .replace('\r', "\\r") + .replace('\t', "\\t") + .replace('|', "\\u{7c}") + .replace(',', "\\u{2c}") + } +} + +impl std::fmt::Display for NormalizedTags { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + let res = self + .tags + .iter() + .map(|(k, v)| format!("{}:{}", k, v)) + .sorted() + .join(","); + write!(f, "{res}") + } +} + +#[cfg(test)] +mod test { + use super::NormalizedTags; + use super::TagMap; + + #[test] + fn test_replacement_characters() { + let tags = TagMap::from_iter( + [ + ("a\na", "a\na"), + ("b\rb", "b\rb"), + ("c\tc", "c\tc"), + ("d\\d", "d\\d"), + ("e|e", "e|e"), + ("f,f", "f,f"), + ] + .into_iter() + .map(|(k, v)| (k.into(), v.into())), + ); + let expected = "aa:a\\na,bb:b\\rb,cc:c\\tc,dd:d\\\\d,ee:e\\u{7c}e,ff:f\\u{2c}f"; + + let actual = NormalizedTags::from(tags).to_string(); + + assert_eq!(expected, actual); + } + + #[test] + fn test_empty_tags() { + let tags = TagMap::from_iter( + [("+", "a"), ("a", ""), ("", "a"), ("", "")] + .into_iter() + .map(|(k, v)| (k.into(), v.into())), + ); + let expected = ""; + + let actual = NormalizedTags::from(tags).to_string(); + + assert_eq!(expected, actual); + } + + #[test] + fn test_special_characters() { + let tags = TagMap::from([("aA1_-./+ö{ 😀".into(), "aA1_-./+ö{ 😀".into())]); + let expected = "aA1_-./:aA1_-./+ö{ 😀"; + + let actual = NormalizedTags::from(tags).to_string(); + + assert_eq!(expected, actual); + } + + #[test] + fn test_add_default_tags() { + let default_tags = TagMap::from_iter( + [ + ("release", "default_release"), + ("environment", "production"), + ] + .into_iter() + .map(|(k, v)| (k.into(), v.into())), + ); + let expected = "environment:production,release:default_release"; + + let actual = NormalizedTags::from(TagMap::new()) + .with_default_tags(&default_tags) + .to_string(); + + assert_eq!(expected, actual); + } + + #[test] + fn test_override_default_tags() { + let default_tags = TagMap::from_iter( + [ + ("release", "default_release"), + ("environment", "production"), + ] + .into_iter() + .map(|(k, v)| (k.into(), v.into())), + ); + let expected = "environment:custom_env,release:custom_release"; + + let actual = NormalizedTags::from(TagMap::from_iter( + [("release", "custom_release"), ("environment", "custom_env")] + .into_iter() + .map(|(k, v)| (k.into(), v.into())), + )) + .with_default_tags(&default_tags) + .to_string(); + + assert_eq!(expected, actual); + } + + #[test] + fn test_tag_lengths() { + let expected = "abcdefghijklmnopqrstuvwxyzabcde:abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopq🙂"; + + let actual = NormalizedTags::from(TagMap::from([ + ("abcdefghijklmnopqrstuvwxyzabcde🙂fghijklmnopqrstuvwxyz".into(), + "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopq🙂rstuvwxyz".into()), + ])) + .to_string(); + + assert_eq!(expected, actual); + } +} diff --git a/sentry-core/src/metrics/normalization/normalized_unit.rs b/sentry-core/src/metrics/normalization/normalized_unit.rs new file mode 100644 index 00000000..8744cb74 --- /dev/null +++ b/sentry-core/src/metrics/normalization/normalized_unit.rs @@ -0,0 +1,64 @@ +use regex::Regex; + +use crate::units::MetricUnit; + +pub struct NormalizedUnit { + unit: String, +} + +impl From for NormalizedUnit { + fn from(unit: MetricUnit) -> Self { + let unsafe_unit = unit.to_string(); + let safe_unit = Regex::new(r"[^a-zA-Z0-9_]") + .expect("Regex should compile") + .replace_all(&unsafe_unit, ""); + let non_empty_safe_unit = match safe_unit.is_empty() { + true => MetricUnit::None.to_string(), + false => safe_unit.into(), + }; + Self { + unit: non_empty_safe_unit, + } + } +} + +impl std::fmt::Display for NormalizedUnit { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{}", self.unit) + } +} + +#[cfg(test)] +mod test { + use crate::{metrics::NormalizedUnit, units::MetricUnit}; + + #[test] + fn test_from() { + let unit = MetricUnit::Custom("aA1_-./+ö{😀\n\t\r\\| ,".into()); + let expected = "aA1_"; + + let actual = NormalizedUnit::from(unit).to_string(); + + assert_eq!(expected, actual); + } + + #[test] + fn test_from_empty() { + let unit = MetricUnit::None; + let expected = "none"; + + let actual = NormalizedUnit::from(unit).to_string(); + + assert_eq!(expected, actual); + } + + #[test] + fn test_from_empty_after_normalization() { + let unit = MetricUnit::Custom("+".into()); + let expected = "none"; + + let actual = NormalizedUnit::from(unit).to_string(); + + assert_eq!(expected, actual); + } +} diff --git a/sentry-core/src/units.rs b/sentry-core/src/units.rs index bc47af65..a0f5026c 100644 --- a/sentry-core/src/units.rs +++ b/sentry-core/src/units.rs @@ -120,6 +120,12 @@ impl From> for MetricUnit { } } +impl From> for MetricUnit { + fn from(unit: Option) -> Self { + unit.map_or_else(|| Self::None, |u| Self::Custom(u.into())) + } +} + /// Time duration units used in [`MetricUnit::Duration`]. /// /// Defaults to `millisecond`. diff --git a/sentry-types/src/protocol/envelope.rs b/sentry-types/src/protocol/envelope.rs index 56809a7f..1ea52f04 100644 --- a/sentry-types/src/protocol/envelope.rs +++ b/sentry-types/src/protocol/envelope.rs @@ -429,6 +429,13 @@ impl Envelope { Self::from_bytes_raw(bytes) } + /// Creates a new Envelope containing the provided item. + pub fn from_item(item: EnvelopeItem) -> Envelope { + let mut envelope = Self::new(); + envelope.add_item(item); + envelope + } + fn parse_header(slice: &[u8]) -> Result<(EnvelopeHeader, usize), EnvelopeError> { let mut stream = serde_json::Deserializer::from_slice(slice).into_iter(); From f15f559f35fee0515efc21ab01015b8bb2376ff1 Mon Sep 17 00:00:00 2001 From: Elias Ram Date: Fri, 24 May 2024 13:06:44 +0200 Subject: [PATCH 2/7] ref(core): Use clone_from instead of clone as suggested by clippy --- sentry-core/src/client.rs | 6 +++--- sentry-core/src/metrics/mod.rs | 2 +- sentry-core/src/performance.rs | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/sentry-core/src/client.rs b/sentry-core/src/client.rs index a485d99c..ed52f05b 100644 --- a/sentry-core/src/client.rs +++ b/sentry-core/src/client.rs @@ -207,13 +207,13 @@ impl Client { } if event.release.is_none() { - event.release = self.options.release.clone(); + event.release.clone_from(&self.options.release); } if event.environment.is_none() { - event.environment = self.options.environment.clone(); + event.environment.clone_from(&self.options.environment); } if event.server_name.is_none() { - event.server_name = self.options.server_name.clone(); + event.server_name.clone_from(&self.options.server_name); } if &event.platform == "other" { event.platform = "native".into(); diff --git a/sentry-core/src/metrics/mod.rs b/sentry-core/src/metrics/mod.rs index df007197..01b39cc8 100644 --- a/sentry-core/src/metrics/mod.rs +++ b/sentry-core/src/metrics/mod.rs @@ -534,7 +534,7 @@ impl Metric { "{}@{}:{}|{}|#{}|T{}", NormalizedName::from(self.name.as_ref()), NormalizedUnit::from(self.unit), - self.value, + "1:2", self.value.ty(), NormalizedTags::from(self.tags), timestamp diff --git a/sentry-core/src/performance.rs b/sentry-core/src/performance.rs index d707c370..552cc563 100644 --- a/sentry-core/src/performance.rs +++ b/sentry-core/src/performance.rs @@ -552,8 +552,8 @@ impl Transaction { Hub::current().with_current_scope(|scope| scope.apply_to_transaction(&mut transaction)); let opts = client.options(); - transaction.release = opts.release.clone(); - transaction.environment = opts.environment.clone(); + transaction.release.clone_from(&opts.release); + transaction.environment.clone_from(&opts.environment); transaction.sdk = Some(std::borrow::Cow::Owned(client.sdk_info.clone())); drop(inner); From 92396ea77ae772a174f0733abacc0fb9c8ba169b Mon Sep 17 00:00:00 2001 From: Elias Ram Date: Fri, 24 May 2024 13:18:11 +0200 Subject: [PATCH 3/7] ref(tracing): Use clone_into instead of to_owned as suggested by clippy --- sentry-tracing/src/converters.rs | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/sentry-tracing/src/converters.rs b/sentry-tracing/src/converters.rs index 9f290607..08f32909 100644 --- a/sentry-tracing/src/converters.rs +++ b/sentry-tracing/src/converters.rs @@ -279,10 +279,12 @@ where } if let Some(exception) = exceptions.last_mut() { - exception - .mechanism - .get_or_insert_with(Mechanism::default) - .ty = "tracing".to_owned(); + "tracing".clone_into( + &mut exception + .mechanism + .get_or_insert_with(Mechanism::default) + .ty, + ); } Event { From b383fb6749af86bdb6fcddd79ee69dd4912ac4af Mon Sep 17 00:00:00 2001 From: Elias Ram Date: Fri, 24 May 2024 13:46:26 +0200 Subject: [PATCH 4/7] fixed to_envelope value --- sentry-core/src/metrics/mod.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sentry-core/src/metrics/mod.rs b/sentry-core/src/metrics/mod.rs index 01b39cc8..df007197 100644 --- a/sentry-core/src/metrics/mod.rs +++ b/sentry-core/src/metrics/mod.rs @@ -534,7 +534,7 @@ impl Metric { "{}@{}:{}|{}|#{}|T{}", NormalizedName::from(self.name.as_ref()), NormalizedUnit::from(self.unit), - "1:2", + self.value, self.value.ty(), NormalizedTags::from(self.tags), timestamp From 493d0ff7623e99f625a5dfef1a46e2eef9beb430 Mon Sep 17 00:00:00 2001 From: Elias Ram Date: Fri, 24 May 2024 14:21:43 +0200 Subject: [PATCH 5/7] enable manually added timestamp in to_envelope --- sentry-core/src/metrics/mod.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/sentry-core/src/metrics/mod.rs b/sentry-core/src/metrics/mod.rs index df007197..f7e56caa 100644 --- a/sentry-core/src/metrics/mod.rs +++ b/sentry-core/src/metrics/mod.rs @@ -526,7 +526,9 @@ impl Metric { /// Convert the metric into an [`Envelope`] containing a single [`EnvelopeItem::Statsd`]. pub fn to_envelope(self) -> Envelope { - let timestamp = SystemTime::now() + let timestamp = self + .time + .unwrap_or(SystemTime::now()) .duration_since(UNIX_EPOCH) .unwrap_or_default() .as_secs(); From 835f025bb7ed3c3e9096b3ec0128f9c53809783e Mon Sep 17 00:00:00 2001 From: Elias Ram Date: Mon, 27 May 2024 14:04:22 +0200 Subject: [PATCH 6/7] Optimize regex and string allocation --- Cargo.lock | 24 +-- sentry-core/Cargo.toml | 3 +- sentry-core/src/metrics/mod.rs | 73 +++++++++- sentry-core/src/metrics/normalization/mod.rs | 21 +++ .../metrics/normalization/normalized_name.rs | 21 ++- .../metrics/normalization/normalized_tags.rs | 137 ++++++++++-------- .../metrics/normalization/normalized_unit.rs | 49 ++++--- sentry-types/src/protocol/envelope.rs | 32 +--- 8 files changed, 225 insertions(+), 135 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 840d94b7..03d64734 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1148,7 +1148,7 @@ dependencies = [ "clap", "criterion-plot", "is-terminal", - "itertools", + "itertools 0.10.5", "num-traits", "once_cell", "oorandom", @@ -1169,7 +1169,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "6b50826342786a51a89e2da3a28f1c32b06e387201bc2d19791f622c673706b1" dependencies = [ "cast", - "itertools", + "itertools 0.10.5", ] [[package]] @@ -2552,6 +2552,15 @@ dependencies = [ "either", ] +[[package]] +name = "itertools" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" +dependencies = [ + "either", +] + [[package]] name = "itoa" version = "1.0.11" @@ -3239,7 +3248,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "19de2de2a00075bf566bee3bd4db014b11587e84184d3f7a791bc17f1a8e9e48" dependencies = [ "anyhow", - "itertools", + "itertools 0.10.5", "proc-macro2", "quote", "syn 2.0.58", @@ -3799,7 +3808,7 @@ dependencies = [ "crc32fast", "criterion", "futures", - "itertools", + "itertools 0.13.0", "log", "once_cell", "rand 0.8.5", @@ -3811,7 +3820,6 @@ dependencies = [ "serde_json", "thiserror", "tokio", - "unicode-segmentation", "uuid", ] @@ -4704,12 +4712,6 @@ dependencies = [ "tinyvec", ] -[[package]] -name = "unicode-segmentation" -version = "1.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202" - [[package]] name = "unicode-xid" version = "0.2.4" diff --git a/sentry-core/Cargo.toml b/sentry-core/Cargo.toml index 145ee1cd..619f2d31 100644 --- a/sentry-core/Cargo.toml +++ b/sentry-core/Cargo.toml @@ -32,7 +32,7 @@ UNSTABLE_cadence = ["dep:cadence", "UNSTABLE_metrics"] [dependencies] cadence = { version = "0.29.0", optional = true } crc32fast = "1.4.0" -itertools = "0.10.5" +itertools = "0.13.0" log = { version = "0.4.8", optional = true, features = ["std"] } once_cell = "1" rand = { version = "0.8.1", optional = true } @@ -40,7 +40,6 @@ regex = "1.7.3" sentry-types = { version = "0.32.3", path = "../sentry-types" } serde = { version = "1.0.104", features = ["derive"] } serde_json = { version = "1.0.46" } -unicode-segmentation = "1.11.0" uuid = { version = "1.0.0", features = ["v4", "serde"], optional = true } [dev-dependencies] diff --git a/sentry-core/src/metrics/mod.rs b/sentry-core/src/metrics/mod.rs index f7e56caa..f4a38726 100644 --- a/sentry-core/src/metrics/mod.rs +++ b/sentry-core/src/metrics/mod.rs @@ -535,13 +535,13 @@ impl Metric { let data = format!( "{}@{}:{}|{}|#{}|T{}", NormalizedName::from(self.name.as_ref()), - NormalizedUnit::from(self.unit), + NormalizedUnit::from(self.unit.to_string().as_ref()), self.value, self.value.ty(), - NormalizedTags::from(self.tags), + NormalizedTags::from(&self.tags), timestamp ); - Envelope::from_item(EnvelopeItem::Statsd(data.into_bytes())) + EnvelopeItem::Statsd(data.into_bytes()).into() } } @@ -597,9 +597,9 @@ impl MetricBuilder { K: Into, V: Into, { - tags.into_iter().for_each(|(k, v)| { + for (k, v) in tags { self.metric.tags.insert(k.into(), v.into()); - }); + } self } @@ -781,6 +781,7 @@ fn get_default_tags(options: &ClientOptions) -> TagMap { options .environment .clone() + .filter(|e| !e.is_empty()) .unwrap_or(Cow::Borrowed("production")), ); tags @@ -836,7 +837,12 @@ impl Worker { for (timestamp, buckets) in buckets { for (key, value) in buckets { write!(&mut out, "{}", NormalizedName::from(key.name.as_ref()))?; - write!(&mut out, "@{}", NormalizedUnit::from(key.unit))?; + match key.unit { + MetricUnit::Custom(u) => { + write!(&mut out, "@{}", NormalizedUnit::from(u.as_ref()))? + } + _ => write!(&mut out, "@{}", key.unit)?, + } match value { BucketValue::Counter(c) => { write!(&mut out, ":{}", c)?; @@ -862,7 +868,7 @@ impl Worker { write!(&mut out, "|{}", key.ty.as_str())?; let normalized_tags = - NormalizedTags::from(key.tags).with_default_tags(&self.default_tags); + NormalizedTags::from(&key.tags).with_default_tags(&self.default_tags); write!(&mut out, "|#{}", normalized_tags)?; writeln!(&mut out, "|T{}", timestamp)?; } @@ -1093,6 +1099,59 @@ mod tests { ); } + #[test] + fn test_empty_default_tags() { + let (time, ts) = current_time(); + let options = ClientOptions { + release: Some("".into()), + environment: Some("".into()), + ..Default::default() + }; + + let envelopes = with_captured_envelopes_options( + || { + Metric::count("requests") + .with_tag("foo", "bar") + .with_time(time) + .send(); + }, + options, + ); + + let metrics = get_single_metrics(&envelopes); + assert_eq!( + metrics, + format!("requests@none:1|c|#environment:production,foo:bar|T{ts}") + ); + } + + #[test] + fn test_override_default_tags() { + let (time, ts) = current_time(); + let options = ClientOptions { + release: Some("default_release".into()), + environment: Some("default_env".into()), + ..Default::default() + }; + + let envelopes = with_captured_envelopes_options( + || { + Metric::count("requests") + .with_tag("environment", "custom_env") + .with_tag("release", "custom_release") + .with_time(time) + .send(); + }, + options, + ); + + let metrics = get_single_metrics(&envelopes); + assert_eq!( + metrics, + format!("requests@none:1|c|#environment:custom_env,release:custom_release|T{ts}") + ); + } + #[test] fn test_counter() { let (time, ts) = current_time(); diff --git a/sentry-core/src/metrics/normalization/mod.rs b/sentry-core/src/metrics/normalization/mod.rs index 54ece645..e316dd98 100644 --- a/sentry-core/src/metrics/normalization/mod.rs +++ b/sentry-core/src/metrics/normalization/mod.rs @@ -1,3 +1,24 @@ pub mod normalized_name; pub mod normalized_tags; pub mod normalized_unit; + +pub fn truncate(s: &str, max_chars: usize) -> &str { + match s.char_indices().nth(max_chars) { + None => s, + Some((i, _)) => &s[..i], + } +} + +#[cfg(test)] +mod test { + + #[test] + fn test_truncate_ascii_chars() { + assert_eq!("abc", super::truncate("abcde", 3)); + } + + #[test] + fn test_truncate_unicode_chars() { + assert_eq!("😀😀😀", super::truncate("😀😀😀😀😀", 3)); + } +} diff --git a/sentry-core/src/metrics/normalization/normalized_name.rs b/sentry-core/src/metrics/normalization/normalized_name.rs index 4eabcfe7..00277fdc 100644 --- a/sentry-core/src/metrics/normalization/normalized_name.rs +++ b/sentry-core/src/metrics/normalization/normalized_name.rs @@ -1,5 +1,6 @@ +use std::{borrow::Cow, sync::OnceLock}; + use regex::Regex; -use std::borrow::Cow; pub struct NormalizedName<'a> { name: Cow<'a, str>, @@ -7,16 +8,17 @@ pub struct NormalizedName<'a> { impl<'a> From<&'a str> for NormalizedName<'a> { fn from(name: &'a str) -> Self { + static METRIC_NAME_RE: OnceLock = OnceLock::new(); Self { - name: Regex::new(r"[^a-zA-Z0-9_\-.]") - .expect("Regex should compile") - .replace_all(name, "_"), + name: METRIC_NAME_RE + .get_or_init(|| Regex::new(r"[^a-zA-Z0-9_\-.]").expect("Regex should compile")) + .replace_all(super::truncate(name, 150), "_"), } } } impl std::fmt::Display for NormalizedName<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { write!(f, "{}", self.name) } } @@ -33,4 +35,13 @@ mod test { assert_eq!(expected, actual); } + + #[test] + fn test_length_restriction() { + let expected = "a".repeat(150); + + let actual = NormalizedName::from("a".repeat(155).as_ref()).to_string(); + + assert_eq!(expected, actual); + } } diff --git a/sentry-core/src/metrics/normalization/normalized_tags.rs b/sentry-core/src/metrics/normalization/normalized_tags.rs index 3b09f0ff..27fe7a46 100644 --- a/sentry-core/src/metrics/normalization/normalized_tags.rs +++ b/sentry-core/src/metrics/normalization/normalized_tags.rs @@ -1,58 +1,68 @@ use itertools::Itertools; use regex::Regex; -use std::collections::HashMap; -use unicode_segmentation::UnicodeSegmentation; +use std::{borrow::Cow, collections::HashMap, sync::OnceLock}; use crate::metrics::TagMap; -pub struct NormalizedTags { - tags: HashMap, +pub struct NormalizedTags<'a> { + tags: HashMap, String>, } -impl From for NormalizedTags { - fn from(tags: TagMap) -> Self { +impl<'a> From<&'a TagMap> for NormalizedTags<'a> { + fn from(tags: &'a TagMap) -> Self { Self { tags: tags .iter() - .map(|(k, v)| (Self::normalize_key(k), Self::normalize_value(v))) - .filter(|(k, v)| !v.is_empty() && !k.is_empty()) + .map(|(k, v)| { + ( + Self::normalize_key(super::truncate(k, 32)), + Self::normalize_value(super::truncate(v, 200)), + ) + }) + .filter(|(k, v)| !k.is_empty() && !v.is_empty()) .collect(), } } } -impl NormalizedTags { - pub fn with_default_tags(mut self, tags: &TagMap) -> Self { - tags.iter().for_each(|(k, v)| { - self.tags - .entry(Self::normalize_key(k)) - .or_insert(Self::normalize_value(v)); - }); +impl<'a> NormalizedTags<'a> { + pub fn with_default_tags(mut self, tags: &'a TagMap) -> Self { + for (k, v) in tags { + let k = Self::normalize_key(super::truncate(k, 32)); + let v = Self::normalize_value(super::truncate(v, 200)); + if !k.is_empty() && !v.is_empty() { + self.tags.entry(k).or_insert(v); + } + } self } - fn normalize_key(key: &str) -> String { - Regex::new(r"[^a-zA-Z0-9_\-./]") - .expect("Tag normalization regex should compile") - .replace_all(&key.graphemes(true).take(32).collect::(), "") - .to_string() + fn normalize_key(key: &str) -> Cow { + static METRIC_TAG_KEY_RE: OnceLock = OnceLock::new(); + METRIC_TAG_KEY_RE + .get_or_init(|| Regex::new(r"[^a-zA-Z0-9_\-./]").expect("Regex should compile")) + .replace_all(key, "") } fn normalize_value(value: &str) -> String { - value - .graphemes(true) - .take(200) - .collect::() - .replace('\\', "\\\\") - .replace('\n', "\\n") - .replace('\r', "\\r") - .replace('\t', "\\t") - .replace('|', "\\u{7c}") - .replace(',', "\\u{2c}") + let mut escaped = String::with_capacity(value.len()); + for c in value.chars() { + match c { + '\t' => escaped.push_str("\\t"), + '\n' => escaped.push_str("\\n"), + '\r' => escaped.push_str("\\r"), + '\\' => escaped.push_str("\\\\"), + '|' => escaped.push_str("\\u{7c}"), + ',' => escaped.push_str("\\u{2c}"), + _ if c.is_control() => (), + _ => escaped.push(c), + } + } + escaped } } -impl std::fmt::Display for NormalizedTags { +impl std::fmt::Display for NormalizedTags<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { let res = self .tags @@ -85,7 +95,7 @@ mod test { ); let expected = "aa:a\\na,bb:b\\rb,cc:c\\tc,dd:d\\\\d,ee:e\\u{7c}e,ff:f\\u{2c}f"; - let actual = NormalizedTags::from(tags).to_string(); + let actual = NormalizedTags::from(&tags).to_string(); assert_eq!(expected, actual); } @@ -99,7 +109,7 @@ mod test { ); let expected = ""; - let actual = NormalizedTags::from(tags).to_string(); + let actual = NormalizedTags::from(&tags).to_string(); assert_eq!(expected, actual); } @@ -109,24 +119,20 @@ mod test { let tags = TagMap::from([("aA1_-./+ö{ 😀".into(), "aA1_-./+ö{ 😀".into())]); let expected = "aA1_-./:aA1_-./+ö{ 😀"; - let actual = NormalizedTags::from(tags).to_string(); + let actual = NormalizedTags::from(&tags).to_string(); assert_eq!(expected, actual); } #[test] fn test_add_default_tags() { - let default_tags = TagMap::from_iter( - [ - ("release", "default_release"), - ("environment", "production"), - ] - .into_iter() - .map(|(k, v)| (k.into(), v.into())), - ); + let default_tags = TagMap::from([ + ("release".into(), "default_release".into()), + ("environment".into(), "production".into()), + ]); let expected = "environment:production,release:default_release"; - let actual = NormalizedTags::from(TagMap::new()) + let actual = NormalizedTags::from(&TagMap::new()) .with_default_tags(&default_tags) .to_string(); @@ -135,21 +141,16 @@ mod test { #[test] fn test_override_default_tags() { - let default_tags = TagMap::from_iter( - [ - ("release", "default_release"), - ("environment", "production"), - ] - .into_iter() - .map(|(k, v)| (k.into(), v.into())), - ); + let default_tags = TagMap::from([ + ("release".into(), "default_release".into()), + ("environment".into(), "production".into()), + ]); let expected = "environment:custom_env,release:custom_release"; - let actual = NormalizedTags::from(TagMap::from_iter( - [("release", "custom_release"), ("environment", "custom_env")] - .into_iter() - .map(|(k, v)| (k.into(), v.into())), - )) + let actual = NormalizedTags::from(&TagMap::from([ + ("release".into(), "custom_release".into()), + ("environment".into(), "custom_env".into()), + ])) .with_default_tags(&default_tags) .to_string(); @@ -157,13 +158,23 @@ mod test { } #[test] - fn test_tag_lengths() { - let expected = "abcdefghijklmnopqrstuvwxyzabcde:abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopq🙂"; - - let actual = NormalizedTags::from(TagMap::from([ - ("abcdefghijklmnopqrstuvwxyzabcde🙂fghijklmnopqrstuvwxyz".into(), - "abcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopqrstuvwxyzabcdefghijklmnopq🙂rstuvwxyz".into()), - ])) + fn test_length_restriction() { + let expected = "dk".repeat(16) + + ":" + + "dv".repeat(100).as_str() + + "," + + "k".repeat(32).as_str() + + ":" + + "v".repeat(200).as_str(); + + let actual = NormalizedTags::from(&TagMap::from([( + "k".repeat(35).into(), + "v".repeat(210).into(), + )])) + .with_default_tags(&TagMap::from([( + "dk".repeat(35).into(), + "dv".repeat(210).into(), + )])) .to_string(); assert_eq!(expected, actual); diff --git a/sentry-core/src/metrics/normalization/normalized_unit.rs b/sentry-core/src/metrics/normalization/normalized_unit.rs index 8744cb74..40fbb04e 100644 --- a/sentry-core/src/metrics/normalization/normalized_unit.rs +++ b/sentry-core/src/metrics/normalization/normalized_unit.rs @@ -1,28 +1,29 @@ +use std::{borrow::Cow, sync::OnceLock}; + use regex::Regex; use crate::units::MetricUnit; -pub struct NormalizedUnit { - unit: String, +pub struct NormalizedUnit<'a> { + unit: Cow<'a, str>, } -impl From for NormalizedUnit { - fn from(unit: MetricUnit) -> Self { - let unsafe_unit = unit.to_string(); - let safe_unit = Regex::new(r"[^a-zA-Z0-9_]") - .expect("Regex should compile") - .replace_all(&unsafe_unit, ""); - let non_empty_safe_unit = match safe_unit.is_empty() { - true => MetricUnit::None.to_string(), - false => safe_unit.into(), - }; +impl<'a> From<&'a str> for NormalizedUnit<'a> { + fn from(unit: &'a str) -> Self { + static METRIC_UNIT_RE: OnceLock = OnceLock::new(); + let normalized_unit = METRIC_UNIT_RE + .get_or_init(|| Regex::new(r"[^a-zA-Z0-9_]").expect("Regex should compile")) + .replace_all(super::truncate(unit, 15), ""); Self { - unit: non_empty_safe_unit, + unit: match normalized_unit.is_empty() { + true => MetricUnit::None.to_string().into(), + false => normalized_unit, + }, } } } -impl std::fmt::Display for NormalizedUnit { +impl std::fmt::Display for NormalizedUnit<'_> { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{}", self.unit) } @@ -30,34 +31,40 @@ impl std::fmt::Display for NormalizedUnit { #[cfg(test)] mod test { - use crate::{metrics::NormalizedUnit, units::MetricUnit}; + use crate::metrics::NormalizedUnit; #[test] fn test_from() { - let unit = MetricUnit::Custom("aA1_-./+ö{😀\n\t\r\\| ,".into()); let expected = "aA1_"; - let actual = NormalizedUnit::from(unit).to_string(); + let actual = NormalizedUnit::from("aA1_-./+ö{😀\n\t\r\\| ,").to_string(); assert_eq!(expected, actual); } #[test] fn test_from_empty() { - let unit = MetricUnit::None; let expected = "none"; - let actual = NormalizedUnit::from(unit).to_string(); + let actual = NormalizedUnit::from("").to_string(); assert_eq!(expected, actual); } #[test] fn test_from_empty_after_normalization() { - let unit = MetricUnit::Custom("+".into()); let expected = "none"; - let actual = NormalizedUnit::from(unit).to_string(); + let actual = NormalizedUnit::from("+").to_string(); + + assert_eq!(expected, actual); + } + + #[test] + fn test_length_restriction() { + let expected = "a".repeat(15); + + let actual = NormalizedUnit::from("a".repeat(20).as_ref()).to_string(); assert_eq!(expected, actual); } diff --git a/sentry-types/src/protocol/envelope.rs b/sentry-types/src/protocol/envelope.rs index 1ea52f04..c978b74f 100644 --- a/sentry-types/src/protocol/envelope.rs +++ b/sentry-types/src/protocol/envelope.rs @@ -429,13 +429,6 @@ impl Envelope { Self::from_bytes_raw(bytes) } - /// Creates a new Envelope containing the provided item. - pub fn from_item(item: EnvelopeItem) -> Envelope { - let mut envelope = Self::new(); - envelope.add_item(item); - envelope - } - fn parse_header(slice: &[u8]) -> Result<(EnvelopeHeader, usize), EnvelopeError> { let mut stream = serde_json::Deserializer::from_slice(slice).into_iter(); @@ -540,26 +533,13 @@ impl Envelope { } } -impl From> for Envelope { - fn from(event: Event<'static>) -> Self { - let mut envelope = Self::default(); - envelope.add_item(event); - envelope - } -} - -impl From> for Envelope { - fn from(transaction: Transaction<'static>) -> Self { - let mut envelope = Self::default(); - envelope.add_item(transaction); - envelope - } -} - -impl From for Envelope { - fn from(check_in: MonitorCheckIn) -> Self { +impl From for Envelope +where + T: Into, +{ + fn from(item: T) -> Self { let mut envelope = Self::default(); - envelope.add_item(check_in); + envelope.add_item(item.into()); envelope } } From f3e7a57994e4070b14f67abdf3cc1f8a8c0cc77c Mon Sep 17 00:00:00 2001 From: Elias Ram Date: Mon, 27 May 2024 15:11:54 +0200 Subject: [PATCH 7/7] removed From trait --- sentry-core/src/metrics/mod.rs | 19 ++++---- sentry-core/src/metrics/normalization/mod.rs | 4 ++ .../metrics/normalization/normalized_name.rs | 30 ++++--------- .../metrics/normalization/normalized_tags.rs | 45 +++++++++---------- .../metrics/normalization/normalized_unit.rs | 40 ++++++----------- 5 files changed, 56 insertions(+), 82 deletions(-) diff --git a/sentry-core/src/metrics/mod.rs b/sentry-core/src/metrics/mod.rs index f4a38726..65cd7478 100644 --- a/sentry-core/src/metrics/mod.rs +++ b/sentry-core/src/metrics/mod.rs @@ -54,9 +54,6 @@ use std::sync::{Arc, Mutex}; use std::thread::{self, JoinHandle}; use std::time::{Duration, SystemTime, UNIX_EPOCH}; -use normalization::normalized_name::NormalizedName; -use normalization::normalized_tags::NormalizedTags; -use normalization::normalized_unit::NormalizedUnit; use sentry_types::protocol::latest::{Envelope, EnvelopeItem}; use crate::client::TransportArc; @@ -534,11 +531,11 @@ impl Metric { .as_secs(); let data = format!( "{}@{}:{}|{}|#{}|T{}", - NormalizedName::from(self.name.as_ref()), - NormalizedUnit::from(self.unit.to_string().as_ref()), + normalization::normalize_name(self.name.as_ref()), + normalization::normalize_unit(self.unit.to_string().as_ref()), self.value, self.value.ty(), - NormalizedTags::from(&self.tags), + normalization::normalize_tags(&self.tags), timestamp ); EnvelopeItem::Statsd(data.into_bytes()).into() @@ -836,10 +833,14 @@ impl Worker { for (timestamp, buckets) in buckets { for (key, value) in buckets { - write!(&mut out, "{}", NormalizedName::from(key.name.as_ref()))?; + write!( + &mut out, + "{}", + normalization::normalize_name(key.name.as_ref()) + )?; match key.unit { MetricUnit::Custom(u) => { - write!(&mut out, "@{}", NormalizedUnit::from(u.as_ref()))? + write!(&mut out, "@{}", normalization::normalize_unit(u.as_ref()))? } _ => write!(&mut out, "@{}", key.unit)?, } @@ -868,7 +869,7 @@ impl Worker { write!(&mut out, "|{}", key.ty.as_str())?; let normalized_tags = - NormalizedTags::from(&key.tags).with_default_tags(&self.default_tags); + normalization::normalize_tags(&key.tags).with_default_tags(&self.default_tags); write!(&mut out, "|#{}", normalized_tags)?; writeln!(&mut out, "|T{}", timestamp)?; } diff --git a/sentry-core/src/metrics/normalization/mod.rs b/sentry-core/src/metrics/normalization/mod.rs index e316dd98..2b228b90 100644 --- a/sentry-core/src/metrics/normalization/mod.rs +++ b/sentry-core/src/metrics/normalization/mod.rs @@ -2,6 +2,10 @@ pub mod normalized_name; pub mod normalized_tags; pub mod normalized_unit; +pub use normalized_name::normalize_name; +pub use normalized_tags::normalize_tags; +pub use normalized_unit::normalize_unit; + pub fn truncate(s: &str, max_chars: usize) -> &str { match s.char_indices().nth(max_chars) { None => s, diff --git a/sentry-core/src/metrics/normalization/normalized_name.rs b/sentry-core/src/metrics/normalization/normalized_name.rs index 00277fdc..4abc2fe1 100644 --- a/sentry-core/src/metrics/normalization/normalized_name.rs +++ b/sentry-core/src/metrics/normalization/normalized_name.rs @@ -2,36 +2,21 @@ use std::{borrow::Cow, sync::OnceLock}; use regex::Regex; -pub struct NormalizedName<'a> { - name: Cow<'a, str>, -} - -impl<'a> From<&'a str> for NormalizedName<'a> { - fn from(name: &'a str) -> Self { - static METRIC_NAME_RE: OnceLock = OnceLock::new(); - Self { - name: METRIC_NAME_RE - .get_or_init(|| Regex::new(r"[^a-zA-Z0-9_\-.]").expect("Regex should compile")) - .replace_all(super::truncate(name, 150), "_"), - } - } -} - -impl std::fmt::Display for NormalizedName<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { - write!(f, "{}", self.name) - } +pub fn normalize_name(name: &str) -> Cow { + static METRIC_NAME_RE: OnceLock = OnceLock::new(); + METRIC_NAME_RE + .get_or_init(|| Regex::new(r"[^a-zA-Z0-9_\-.]").expect("Regex should compile")) + .replace_all(super::truncate(name, 150), "_") } #[cfg(test)] mod test { - use crate::metrics::NormalizedName; #[test] fn test_from() { let expected = "aA1_-.____________"; - let actual = NormalizedName::from("aA1_-./+ö{😀\n\t\r\\| ,").to_string(); + let actual = super::normalize_name("aA1_-./+ö{😀\n\t\r\\| ,"); assert_eq!(expected, actual); } @@ -40,7 +25,8 @@ mod test { fn test_length_restriction() { let expected = "a".repeat(150); - let actual = NormalizedName::from("a".repeat(155).as_ref()).to_string(); + let too_long_name = "a".repeat(155); + let actual = super::normalize_name(&too_long_name); assert_eq!(expected, actual); } diff --git a/sentry-core/src/metrics/normalization/normalized_tags.rs b/sentry-core/src/metrics/normalization/normalized_tags.rs index 27fe7a46..f98935fb 100644 --- a/sentry-core/src/metrics/normalization/normalized_tags.rs +++ b/sentry-core/src/metrics/normalization/normalized_tags.rs @@ -4,25 +4,23 @@ use std::{borrow::Cow, collections::HashMap, sync::OnceLock}; use crate::metrics::TagMap; -pub struct NormalizedTags<'a> { - tags: HashMap, String>, +pub fn normalize_tags(tags: &TagMap) -> NormalizedTags { + NormalizedTags { + tags: tags + .iter() + .map(|(k, v)| { + ( + NormalizedTags::normalize_key(super::truncate(k, 32)), + NormalizedTags::normalize_value(super::truncate(v, 200)), + ) + }) + .filter(|(k, v)| !k.is_empty() && !v.is_empty()) + .collect(), + } } -impl<'a> From<&'a TagMap> for NormalizedTags<'a> { - fn from(tags: &'a TagMap) -> Self { - Self { - tags: tags - .iter() - .map(|(k, v)| { - ( - Self::normalize_key(super::truncate(k, 32)), - Self::normalize_value(super::truncate(v, 200)), - ) - }) - .filter(|(k, v)| !k.is_empty() && !v.is_empty()) - .collect(), - } - } +pub struct NormalizedTags<'a> { + tags: HashMap, String>, } impl<'a> NormalizedTags<'a> { @@ -76,7 +74,6 @@ impl std::fmt::Display for NormalizedTags<'_> { #[cfg(test)] mod test { - use super::NormalizedTags; use super::TagMap; #[test] @@ -95,7 +92,7 @@ mod test { ); let expected = "aa:a\\na,bb:b\\rb,cc:c\\tc,dd:d\\\\d,ee:e\\u{7c}e,ff:f\\u{2c}f"; - let actual = NormalizedTags::from(&tags).to_string(); + let actual = super::normalize_tags(&tags).to_string(); assert_eq!(expected, actual); } @@ -109,7 +106,7 @@ mod test { ); let expected = ""; - let actual = NormalizedTags::from(&tags).to_string(); + let actual = super::normalize_tags(&tags).to_string(); assert_eq!(expected, actual); } @@ -119,7 +116,7 @@ mod test { let tags = TagMap::from([("aA1_-./+ö{ 😀".into(), "aA1_-./+ö{ 😀".into())]); let expected = "aA1_-./:aA1_-./+ö{ 😀"; - let actual = NormalizedTags::from(&tags).to_string(); + let actual = super::normalize_tags(&tags).to_string(); assert_eq!(expected, actual); } @@ -132,7 +129,7 @@ mod test { ]); let expected = "environment:production,release:default_release"; - let actual = NormalizedTags::from(&TagMap::new()) + let actual = super::normalize_tags(&TagMap::new()) .with_default_tags(&default_tags) .to_string(); @@ -147,7 +144,7 @@ mod test { ]); let expected = "environment:custom_env,release:custom_release"; - let actual = NormalizedTags::from(&TagMap::from([ + let actual = super::normalize_tags(&TagMap::from([ ("release".into(), "custom_release".into()), ("environment".into(), "custom_env".into()), ])) @@ -167,7 +164,7 @@ mod test { + ":" + "v".repeat(200).as_str(); - let actual = NormalizedTags::from(&TagMap::from([( + let actual = super::normalize_tags(&TagMap::from([( "k".repeat(35).into(), "v".repeat(210).into(), )])) diff --git a/sentry-core/src/metrics/normalization/normalized_unit.rs b/sentry-core/src/metrics/normalization/normalized_unit.rs index 40fbb04e..e6b488b1 100644 --- a/sentry-core/src/metrics/normalization/normalized_unit.rs +++ b/sentry-core/src/metrics/normalization/normalized_unit.rs @@ -4,40 +4,26 @@ use regex::Regex; use crate::units::MetricUnit; -pub struct NormalizedUnit<'a> { - unit: Cow<'a, str>, -} - -impl<'a> From<&'a str> for NormalizedUnit<'a> { - fn from(unit: &'a str) -> Self { - static METRIC_UNIT_RE: OnceLock = OnceLock::new(); - let normalized_unit = METRIC_UNIT_RE - .get_or_init(|| Regex::new(r"[^a-zA-Z0-9_]").expect("Regex should compile")) - .replace_all(super::truncate(unit, 15), ""); - Self { - unit: match normalized_unit.is_empty() { - true => MetricUnit::None.to_string().into(), - false => normalized_unit, - }, - } - } -} - -impl std::fmt::Display for NormalizedUnit<'_> { - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - write!(f, "{}", self.unit) +pub fn normalize_unit(unit: &str) -> Cow { + static METRIC_UNIT_RE: OnceLock = OnceLock::new(); + let normalized_unit = METRIC_UNIT_RE + .get_or_init(|| Regex::new(r"[^a-zA-Z0-9_]").expect("Regex should compile")) + .replace_all(super::truncate(unit, 15), ""); + if normalized_unit.is_empty() { + MetricUnit::None.to_string().into() + } else { + normalized_unit } } #[cfg(test)] mod test { - use crate::metrics::NormalizedUnit; #[test] fn test_from() { let expected = "aA1_"; - let actual = NormalizedUnit::from("aA1_-./+ö{😀\n\t\r\\| ,").to_string(); + let actual = super::normalize_unit("aA1_-./+ö{😀\n\t\r\\| ,").to_string(); assert_eq!(expected, actual); } @@ -46,7 +32,7 @@ mod test { fn test_from_empty() { let expected = "none"; - let actual = NormalizedUnit::from("").to_string(); + let actual = super::normalize_unit("").to_string(); assert_eq!(expected, actual); } @@ -55,7 +41,7 @@ mod test { fn test_from_empty_after_normalization() { let expected = "none"; - let actual = NormalizedUnit::from("+").to_string(); + let actual = super::normalize_unit("+").to_string(); assert_eq!(expected, actual); } @@ -64,7 +50,7 @@ mod test { fn test_length_restriction() { let expected = "a".repeat(15); - let actual = NormalizedUnit::from("a".repeat(20).as_ref()).to_string(); + let actual = super::normalize_unit("a".repeat(20).as_ref()).to_string(); assert_eq!(expected, actual); }