Skip to content

Commit aff50f7

Browse files
nipunn1313Convex, Inc.
authored andcommitted
Support regexes in minitrace sampling (#26764)
So we can sample route groups. GitOrigin-RevId: 59c43d77034fa2c8f15683755dc7a5e4010010a6
1 parent c1ded99 commit aff50f7

File tree

2 files changed

+37
-11
lines changed

2 files changed

+37
-11
lines changed

crates/common/src/knobs.rs

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1006,7 +1006,16 @@ pub static HTTP_SERVER_TIMEOUT_DURATION: LazyLock<Duration> =
10061006
pub static MAX_PUSH_BYTES: LazyLock<usize> =
10071007
LazyLock::new(|| env_config("MAX_PUSH_BYTES", 100_000_000));
10081008

1009-
/// Percentage of request traces that should sampled
1009+
/// Percentage of request traces that should sampled.
1010+
/// Can set a global value, as well as per-route values
1011+
///
1012+
/// Note that the regexes can't contain commas.
1013+
///
1014+
/// Examples:
1015+
/// REQUEST_TRACE_SAMPLE_CONFIG=0.01
1016+
/// REQUEST_TRACE_SAMPLE_CONFIG=/route1=0.50,0.01
1017+
/// REQUEST_TRACE_SAMPLE_CONFIG=/route1=0.50,route2=0.50,0.01
1018+
/// REQUEST_TRACE_SAMPLE_CONFIG=/http/.*=0.50
10101019
pub static REQUEST_TRACE_SAMPLE_CONFIG: LazyLock<SamplingConfig> =
10111020
LazyLock::new(|| env_config("REQUEST_TRACE_SAMPLE_CONFIG", SamplingConfig::default()));
10121021

crates/common/src/minitrace_helpers.rs

Lines changed: 27 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use minitrace::{
99
Span,
1010
};
1111
use rand::Rng;
12+
use regex::Regex;
1213

1314
use crate::knobs::REQUEST_TRACE_SAMPLE_CONFIG;
1415

@@ -44,22 +45,31 @@ pub fn get_sampled_span<R: Rng>(
4445
#[derive(Debug)]
4546
pub struct SamplingConfig {
4647
global: f64,
47-
by_name: BTreeMap<String, f64>,
48+
by_name_regex: BTreeMap<String, (Regex, f64)>,
4849
}
4950

5051
impl Default for SamplingConfig {
5152
fn default() -> Self {
5253
// No sampling by default
5354
Self {
5455
global: 0.0,
55-
by_name: BTreeMap::new(),
56+
by_name_regex: BTreeMap::new(),
5657
}
5758
}
5859
}
5960

6061
impl SamplingConfig {
6162
fn sample_ratio(&self, name: &str) -> f64 {
62-
*self.by_name.get(name).unwrap_or(&self.global)
63+
self.by_name_regex
64+
.values()
65+
.find_map(|(name_regex, sample_ratio)| {
66+
if name_regex.is_match(name) {
67+
Some(*sample_ratio)
68+
} else {
69+
None
70+
}
71+
})
72+
.unwrap_or(self.global)
6373
}
6474
}
6575

@@ -68,14 +78,15 @@ impl FromStr for SamplingConfig {
6878

6979
fn from_str(s: &str) -> anyhow::Result<Self> {
7080
let mut global = None;
71-
let mut by_name = BTreeMap::new();
81+
let mut by_name_regex = BTreeMap::new();
7282
for token in s.split(',') {
7383
let parts: Vec<_> = token.split('=').map(|s| s.trim()).collect();
7484
anyhow::ensure!(parts.len() <= 2, "Too many parts {}", token);
7585
if parts.len() == 2 {
7686
let name = parts[0];
87+
let name_regex = Regex::new(name).context("Failed to parse name regex")?;
7788
let rate: f64 = parts[1].parse().context("Failed to parse sampling rate")?;
78-
let old_value = by_name.insert(name.to_owned(), rate);
89+
let old_value = by_name_regex.insert(name.to_owned(), (name_regex, rate));
7990
anyhow::ensure!(old_value.is_none(), "{} set more than once", name);
8091
} else {
8192
let rate: f64 = parts[0].parse().context("Failed to parse sampling rate")?;
@@ -85,7 +96,7 @@ impl FromStr for SamplingConfig {
8596
}
8697
Ok(SamplingConfig {
8798
global: global.unwrap_or(0.0),
88-
by_name,
99+
by_name_regex,
89100
})
90101
}
91102
}
@@ -108,24 +119,30 @@ mod tests {
108119
fn test_parse_sampling_config() -> anyhow::Result<()> {
109120
let config: SamplingConfig = "1".parse()?;
110121
assert_eq!(config.global, 1.0);
111-
assert_eq!(config.by_name.len(), 0);
122+
assert_eq!(config.by_name_regex.len(), 0);
112123
assert_eq!(config.sample_ratio("a"), 1.0);
113124

114125
let config: SamplingConfig = "a=0.5,b=0.15".parse()?;
115126
assert_eq!(config.global, 0.0);
116-
assert_eq!(config.by_name.len(), 2);
127+
assert_eq!(config.by_name_regex.len(), 2);
117128
assert_eq!(config.sample_ratio("a"), 0.5);
118129
assert_eq!(config.sample_ratio("b"), 0.15);
119130
assert_eq!(config.sample_ratio("c"), 0.0);
120131

121132
let config: SamplingConfig = "a=0.5,b=0.15,0.01".parse()?;
122133
assert_eq!(config.global, 0.01);
123-
assert_eq!(config.by_name.len(), 2);
124-
assert_eq!(config.by_name.len(), 2);
134+
assert_eq!(config.by_name_regex.len(), 2);
135+
assert_eq!(config.by_name_regex.len(), 2);
125136
assert_eq!(config.sample_ratio("a"), 0.5);
126137
assert_eq!(config.sample_ratio("b"), 0.15);
127138
assert_eq!(config.sample_ratio("c"), 0.01);
128139

140+
let config: SamplingConfig = "/f/.*=0.5".parse()?;
141+
assert_eq!(config.by_name_regex.len(), 1);
142+
assert_eq!(config.sample_ratio("/f/a"), 0.5);
143+
assert_eq!(config.sample_ratio("/f/b"), 0.5);
144+
assert_eq!(config.sample_ratio("c"), 0.0);
145+
129146
// Invalid configs.
130147
let err = "100,200".parse::<SamplingConfig>().unwrap_err();
131148
assert!(format!("{}", err).contains("Global sampling rate set more than once"));

0 commit comments

Comments
 (0)