|
7 | 7 | //! Remote Config. |
8 | 8 |
|
9 | 9 | use async_trait::async_trait; |
| 10 | +use foldhash::fast::RandomState as FoldHashState; |
10 | 11 | use hashbrown::{HashMap, HashSet}; |
11 | 12 | use memory_accounting::{MemoryBounds, MemoryBoundsBuilder}; |
12 | 13 | use saluki_config::GenericConfiguration; |
@@ -46,19 +47,20 @@ pub struct MetricTagFilterEntry { |
46 | 47 | } |
47 | 48 |
|
48 | 49 | /// Compiled filter table: metric name → (is_exclude, set of tag key names). |
49 | | -pub type CompiledFilters = HashMap<String, (bool, HashSet<String>)>; |
| 50 | +pub type CompiledFilters = HashMap<String, (bool, HashSet<String, FoldHashState>), FoldHashState>; |
50 | 51 |
|
51 | 52 | /// Compile a slice of filter entries into an O(1)-lookup table. |
52 | 53 | /// |
53 | 54 | /// Merge rules: |
54 | 55 | /// - Same metric name + same action → union of tag key sets. |
55 | 56 | /// - Same metric name + conflicting actions → `exclude` wins. |
56 | 57 | pub fn compile_filters(entries: &[MetricTagFilterEntry]) -> CompiledFilters { |
57 | | - let mut filters: CompiledFilters = HashMap::new(); |
| 58 | + let mut filters: CompiledFilters = HashMap::with_hasher(FoldHashState::default()); |
58 | 59 |
|
59 | 60 | for entry in entries { |
60 | 61 | let is_exclude = entry.action == FilterAction::Exclude; |
61 | | - let tag_set: HashSet<String> = entry.tags.iter().cloned().collect(); |
| 62 | + let mut tag_set = HashSet::with_capacity_and_hasher(entry.tags.len(), FoldHashState::default()); |
| 63 | + tag_set.extend(entry.tags.iter().cloned()); |
62 | 64 |
|
63 | 65 | match filters.entry(entry.metric_name.clone()) { |
64 | 66 | hashbrown::hash_map::Entry::Vacant(e) => { |
@@ -178,44 +180,68 @@ impl Transform for TagFilterlist { |
178 | 180 | } |
179 | 181 | } |
180 | 182 |
|
181 | | -/// Applies a tag filter to a shared tag set, returning a new `TagSet` with the filter applied. |
| 183 | +/// Applies a tag filter to a shared tag set, returning `Some(TagSet)` if any tags were |
| 184 | +/// filtered out, or `None` if the result would be identical to the source. |
182 | 185 | /// |
183 | 186 | /// Tags whose key is in `names` are excluded when `is_exclude` is true, or kept when false. |
184 | | -/// Always constructs a fresh `TagSet` without mutating the source, preserving isolation for |
| 187 | +/// Constructs a fresh `TagSet` without mutating the source, preserving isolation for |
185 | 188 | /// metrics that share the same underlying `Arc<TagSet>`. |
186 | | -fn apply_tag_filter(tags: &SharedTagSet, is_exclude: bool, names: &HashSet<String>) -> TagSet { |
187 | | - let mut out = TagSet::with_capacity(tags.len()); |
| 189 | +#[inline] |
| 190 | +fn apply_tag_filter(tags: &SharedTagSet, is_exclude: bool, names: &HashSet<String, FoldHashState>) -> Option<TagSet> { |
| 191 | + let capacity = if is_exclude { |
| 192 | + tags.len().saturating_sub(names.len()) |
| 193 | + } else { |
| 194 | + names.len().min(tags.len()) |
| 195 | + }; |
| 196 | + let mut out = TagSet::with_capacity(capacity); |
| 197 | + let mut any_change = false; |
188 | 198 | for tag in tags { |
189 | | - let in_list = names.contains(tag.name()); |
190 | | - // XOR: keep if (exclude ∧ not-in-list) ∨ (include ∧ in-list) |
191 | | - if is_exclude != in_list { |
192 | | - out.insert_tag(tag.clone()); |
| 199 | + if is_exclude != names.contains(tag.name()) { |
| 200 | + out.extend([tag.clone()]); |
| 201 | + } else { |
| 202 | + any_change = true; |
193 | 203 | } |
194 | 204 | } |
195 | | - out |
| 205 | + if any_change { |
| 206 | + Some(out) |
| 207 | + } else { |
| 208 | + None |
| 209 | + } |
196 | 210 | } |
197 | 211 |
|
198 | 212 | /// Filter the tags of a distribution metric according to the compiled filter table. |
199 | 213 | /// |
200 | 214 | /// Both instrumented tags and origin tags are filtered using the same tag key list. |
201 | 215 | /// If the metric name is not present in `filters`, the metric is left unchanged. |
| 216 | +/// If filtering would not change any tags, the metric context is left untouched (zero allocations). |
| 217 | +#[inline] |
202 | 218 | pub fn filter_metric_tags(metric: &mut saluki_core::data_model::event::metric::Metric, filters: &CompiledFilters) { |
203 | | - let name = metric.context().name().as_ref().to_owned(); |
204 | | - let Some((is_exclude, tag_names)) = filters.get(&name) else { |
| 219 | + let Some((is_exclude, tag_names)) = filters.get(metric.context().name().as_ref()) else { |
205 | 220 | return; |
206 | 221 | }; |
207 | 222 |
|
208 | 223 | let new_tags = apply_tag_filter(metric.context().tags(), *is_exclude, tag_names); |
209 | 224 |
|
210 | 225 | if metric.context().origin_tags().is_empty() { |
211 | | - // Fast path: no origin_tags to filter; single allocation. |
212 | | - *metric.context_mut() = metric.context().with_tags(new_tags.into_shared()); |
| 226 | + if let Some(filtered) = new_tags { |
| 227 | + *metric.context_mut() = metric.context().with_tags(filtered.into_shared()); |
| 228 | + } |
213 | 229 | } else { |
214 | | - // Filter origin_tags with the same list; single Arc allocation for both. |
215 | 230 | let new_origin = apply_tag_filter(metric.context().origin_tags(), *is_exclude, tag_names); |
216 | | - *metric.context_mut() = metric |
217 | | - .context() |
218 | | - .with_tags_and_origin_tags(new_tags.into_shared(), new_origin.into_shared()); |
| 231 | + match (new_tags, new_origin) { |
| 232 | + (None, None) => {} |
| 233 | + (Some(tags), None) => { |
| 234 | + *metric.context_mut() = metric.context().with_tags(tags.into_shared()); |
| 235 | + } |
| 236 | + (None, Some(origin)) => { |
| 237 | + *metric.context_mut() = metric.context().with_origin_tags(origin.into_shared()); |
| 238 | + } |
| 239 | + (Some(tags), Some(origin)) => { |
| 240 | + *metric.context_mut() = metric |
| 241 | + .context() |
| 242 | + .with_tags_and_origin_tags(tags.into_shared(), origin.into_shared()); |
| 243 | + } |
| 244 | + } |
219 | 245 | } |
220 | 246 | } |
221 | 247 |
|
|
0 commit comments