Skip to content

Commit 90d0182

Browse files
author
Elias Ram
committed
optimize regex and string allocation
1 parent 493d0ff commit 90d0182

File tree

8 files changed

+214
-134
lines changed

8 files changed

+214
-134
lines changed

Cargo.lock

Lines changed: 13 additions & 11 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

sentry-core/Cargo.toml

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -32,15 +32,14 @@ UNSTABLE_cadence = ["dep:cadence", "UNSTABLE_metrics"]
3232
[dependencies]
3333
cadence = { version = "0.29.0", optional = true }
3434
crc32fast = "1.4.0"
35-
itertools = "0.10.5"
35+
itertools = "0.13.0"
3636
log = { version = "0.4.8", optional = true, features = ["std"] }
3737
once_cell = "1"
3838
rand = { version = "0.8.1", optional = true }
3939
regex = "1.7.3"
4040
sentry-types = { version = "0.32.3", path = "../sentry-types" }
4141
serde = { version = "1.0.104", features = ["derive"] }
4242
serde_json = { version = "1.0.46" }
43-
unicode-segmentation = "1.11.0"
4443
uuid = { version = "1.0.0", features = ["v4", "serde"], optional = true }
4544

4645
[dev-dependencies]

sentry-core/src/metrics/mod.rs

Lines changed: 66 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -535,13 +535,13 @@ impl Metric {
535535
let data = format!(
536536
"{}@{}:{}|{}|#{}|T{}",
537537
NormalizedName::from(self.name.as_ref()),
538-
NormalizedUnit::from(self.unit),
538+
NormalizedUnit::from(self.unit.to_string().as_ref()),
539539
self.value,
540540
self.value.ty(),
541-
NormalizedTags::from(self.tags),
541+
NormalizedTags::from(&self.tags),
542542
timestamp
543543
);
544-
Envelope::from_item(EnvelopeItem::Statsd(data.into_bytes()))
544+
EnvelopeItem::Statsd(data.into_bytes()).into()
545545
}
546546
}
547547

@@ -597,9 +597,9 @@ impl MetricBuilder {
597597
K: Into<MetricStr>,
598598
V: Into<MetricStr>,
599599
{
600-
tags.into_iter().for_each(|(k, v)| {
600+
for (k, v) in tags {
601601
self.metric.tags.insert(k.into(), v.into());
602-
});
602+
}
603603
self
604604
}
605605

@@ -781,6 +781,7 @@ fn get_default_tags(options: &ClientOptions) -> TagMap {
781781
options
782782
.environment
783783
.clone()
784+
.filter(|e| !e.is_empty())
784785
.unwrap_or(Cow::Borrowed("production")),
785786
);
786787
tags
@@ -836,7 +837,12 @@ impl Worker {
836837
for (timestamp, buckets) in buckets {
837838
for (key, value) in buckets {
838839
write!(&mut out, "{}", NormalizedName::from(key.name.as_ref()))?;
839-
write!(&mut out, "@{}", NormalizedUnit::from(key.unit))?;
840+
match key.unit {
841+
MetricUnit::Custom(u) => {
842+
write!(&mut out, "@{}", NormalizedUnit::from(u.as_ref()))?
843+
}
844+
_ => write!(&mut out, "@{}", key.unit)?,
845+
}
840846
match value {
841847
BucketValue::Counter(c) => {
842848
write!(&mut out, ":{}", c)?;
@@ -862,7 +868,7 @@ impl Worker {
862868

863869
write!(&mut out, "|{}", key.ty.as_str())?;
864870
let normalized_tags =
865-
NormalizedTags::from(key.tags).with_default_tags(&self.default_tags);
871+
NormalizedTags::from(&key.tags).with_default_tags(&self.default_tags);
866872
write!(&mut out, "|#{}", normalized_tags)?;
867873
writeln!(&mut out, "|T{}", timestamp)?;
868874
}
@@ -1093,6 +1099,59 @@ mod tests {
10931099
);
10941100
}
10951101

1102+
#[test]
1103+
fn test_empty_default_tags() {
1104+
let (time, ts) = current_time();
1105+
let options = ClientOptions {
1106+
release: Some("".into()),
1107+
environment: Some("".into()),
1108+
..Default::default()
1109+
};
1110+
1111+
let envelopes = with_captured_envelopes_options(
1112+
|| {
1113+
Metric::count("requests")
1114+
.with_tag("foo", "bar")
1115+
.with_time(time)
1116+
.send();
1117+
},
1118+
options,
1119+
);
1120+
1121+
let metrics = get_single_metrics(&envelopes);
1122+
assert_eq!(
1123+
metrics,
1124+
format!("requests@none:1|c|#environment:production,foo:bar|T{ts}")
1125+
);
1126+
}
1127+
1128+
#[test]
1129+
fn test_override_default_tags() {
1130+
let (time, ts) = current_time();
1131+
let options = ClientOptions {
1132+
release: Some("default_release".into()),
1133+
environment: Some("default_env".into()),
1134+
..Default::default()
1135+
};
1136+
1137+
let envelopes = with_captured_envelopes_options(
1138+
|| {
1139+
Metric::count("requests")
1140+
.with_tag("environment", "custom_env")
1141+
.with_tag("release", "custom_release")
1142+
.with_time(time)
1143+
.send();
1144+
},
1145+
options,
1146+
);
1147+
1148+
let metrics = get_single_metrics(&envelopes);
1149+
assert_eq!(
1150+
metrics,
1151+
format!("requests@none:1|c|#environment:custom_env,release:custom_release|T{ts}")
1152+
);
1153+
}
1154+
10961155
#[test]
10971156
fn test_counter() {
10981157
let (time, ts) = current_time();
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,24 @@
11
pub mod normalized_name;
22
pub mod normalized_tags;
33
pub mod normalized_unit;
4+
5+
pub fn truncate(s: &str, max_chars: usize) -> &str {
6+
match s.char_indices().nth(max_chars) {
7+
None => s,
8+
Some((i, _)) => &s[..i],
9+
}
10+
}
11+
12+
#[cfg(test)]
13+
mod test {
14+
15+
#[test]
16+
fn test_truncate_ascii_chars() {
17+
assert_eq!("abc", super::truncate("abcde", 3));
18+
}
19+
20+
#[test]
21+
fn test_truncate_unicode_chars() {
22+
assert_eq!("😀😀😀", super::truncate("😀😀😀😀😀", 3));
23+
}
24+
}

sentry-core/src/metrics/normalization/normalized_name.rs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,24 @@
1+
use std::{borrow::Cow, sync::OnceLock};
2+
13
use regex::Regex;
2-
use std::borrow::Cow;
34

45
pub struct NormalizedName<'a> {
56
name: Cow<'a, str>,
67
}
78

89
impl<'a> From<&'a str> for NormalizedName<'a> {
910
fn from(name: &'a str) -> Self {
11+
static METRIC_NAME_RE: OnceLock<Regex> = OnceLock::new();
1012
Self {
11-
name: Regex::new(r"[^a-zA-Z0-9_\-.]")
12-
.expect("Regex should compile")
13-
.replace_all(name, "_"),
13+
name: METRIC_NAME_RE
14+
.get_or_init(|| Regex::new(r"[^a-zA-Z0-9_\-.]").expect("Regex should compile"))
15+
.replace_all(super::truncate(name, 150), "_"),
1416
}
1517
}
1618
}
1719

1820
impl std::fmt::Display for NormalizedName<'_> {
19-
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
21+
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
2022
write!(f, "{}", self.name)
2123
}
2224
}
@@ -33,4 +35,13 @@ mod test {
3335

3436
assert_eq!(expected, actual);
3537
}
38+
39+
#[test]
40+
fn test_length_restriction() {
41+
let expected = "a".repeat(150);
42+
43+
let actual = NormalizedName::from("a".repeat(155).as_ref()).to_string();
44+
45+
assert_eq!(expected, actual);
46+
}
3647
}

0 commit comments

Comments
 (0)