-
Notifications
You must be signed in to change notification settings - Fork 1k
Add TagReplacingFilter #6183
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Add TagReplacingFilter #6183
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
/* | ||
* Copyright 2025 VMware, Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package io.micrometer.core.instrument.config.filter; | ||
|
||
class FilterSupport { | ||
|
||
/** | ||
* At the moment of writing, it was impossible to estimate tags count from the outside | ||
* of class, but quite often a temporary storage (ArrayList) had to be allocated | ||
* during processing. To avoid excessive resizes, this constant is introduced to | ||
* preallocate space for such a list. | ||
*/ | ||
public static final int DEFAULT_TAG_COUNT_EXPECTATION = 32; | ||
|
||
private FilterSupport() { | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
/* | ||
* Copyright 2025 VMware, Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package io.micrometer.core.instrument.config.filter; | ||
|
||
import io.micrometer.core.instrument.config.MeterFilter; | ||
|
||
/** | ||
* A fallback for all factory methods that have received an input functionally equivalent | ||
* to "abstain from processing". | ||
* | ||
* @since 1.15 | ||
*/ | ||
public class NoOpFilter implements MeterFilter { | ||
|
||
private static final MeterFilter INSTANCE = new NoOpFilter(); | ||
|
||
private NoOpFilter() { | ||
} | ||
|
||
public static MeterFilter create() { | ||
return INSTANCE; | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
/* | ||
* Copyright 2025 VMware, Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package io.micrometer.core.instrument.config.filter; | ||
|
||
import io.micrometer.common.lang.NonNull; | ||
import io.micrometer.core.instrument.Meter; | ||
import io.micrometer.core.instrument.Tag; | ||
import io.micrometer.core.instrument.config.MeterFilter; | ||
|
||
import java.util.*; | ||
import java.util.function.BiFunction; | ||
import java.util.function.BiPredicate; | ||
import java.util.function.Function; | ||
|
||
public class TagReplacingFilter implements MeterFilter { | ||
|
||
private final BiPredicate<String, String> filter; | ||
|
||
private final BiFunction<String, String, Tag> replacer; | ||
|
||
private final int expectedTagCount; | ||
|
||
TagReplacingFilter(BiPredicate<String, String> filter, BiFunction<String, String, Tag> replacer, | ||
int expectedTagCount) { | ||
this.replacer = replacer; | ||
this.filter = filter; | ||
this.expectedTagCount = expectedTagCount; | ||
} | ||
|
||
@NonNull | ||
@Override | ||
public Meter.Id map(@NonNull Meter.Id id) { | ||
Iterator<Tag> iterator = id.getTagsAsIterable().iterator(); | ||
|
||
if (!iterator.hasNext()) { | ||
// fast path avoiding list allocation completely | ||
return id; | ||
} | ||
|
||
List<Tag> replacement = new ArrayList<>(expectedTagCount); | ||
|
||
boolean intercepted = false; | ||
while (iterator.hasNext()) { | ||
Tag tag = iterator.next(); | ||
String key = tag.getKey(); | ||
String value = tag.getValue(); | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a bit of hacky move that can be hindering performance instead of helping it. The idea is simple - since predicate and replacing function aren't really interested in the tag itself, only in the key and value, let's extract them once, store in registers, and prevent double memory access. This sounds good, but in reality it might end up with two additional stack writes (which are relatively cheap and wouldn't make that much difference): if for any reason compiler can't fully inline the predicate and function, it will have to store the registers on the stack, and instead of one tag reference it will be forced to store one tag and two string references. However, this still saves us one indirection. TBH this is solved simply by running a pair of benchmarks, but i'm all out of energy, and there are a couple more PRs coming |
||
|
||
if (filter.test(key, value)) { | ||
replacement.add(replacer.apply(key, value)); | ||
intercepted = true; | ||
} | ||
else { | ||
replacement.add(tag); | ||
} | ||
} | ||
|
||
return intercepted ? id.replaceTags(replacement) : id; | ||
} | ||
|
||
public static MeterFilter of(BiPredicate<String, String> filter, BiFunction<String, String, Tag> replacer, | ||
int expectedSize) { | ||
return new TagReplacingFilter(filter, replacer, expectedSize); | ||
} | ||
|
||
public static MeterFilter of(BiPredicate<String, String> filter, BiFunction<String, String, Tag> replacer) { | ||
return new TagReplacingFilter(filter, replacer, FilterSupport.DEFAULT_TAG_COUNT_EXPECTATION); | ||
} | ||
|
||
public static MeterFilter classicValueReplacing(String key, Function<String, String> replacer, | ||
Collection<String> exceptions, int expectedSize) { | ||
return of(new ClassicFilter(key, new HashSet<>(exceptions)), new ValueReplacer(replacer), expectedSize); | ||
} | ||
|
||
public static MeterFilter classicValueReplacing(String key, Function<String, String> replacer, | ||
Collection<String> exceptions) { | ||
return classicValueReplacing(key, replacer, exceptions, FilterSupport.DEFAULT_TAG_COUNT_EXPECTATION); | ||
} | ||
|
||
public static MeterFilter classicValueReplacing(String key, Function<String, String> replacer, | ||
String... exceptions) { | ||
return classicValueReplacing(key, replacer, Arrays.asList(exceptions)); | ||
} | ||
|
||
private static class ClassicFilter implements BiPredicate<String, String> { | ||
|
||
private final String matcher; | ||
|
||
private final Set<String> exceptions; | ||
|
||
public ClassicFilter(String matcher, Set<String> exceptions) { | ||
this.matcher = matcher; | ||
this.exceptions = exceptions; | ||
} | ||
|
||
@Override | ||
public boolean test(String key, String value) { | ||
return key.equals(matcher) && !exceptions.contains(value); | ||
} | ||
|
||
} | ||
|
||
private static class ValueReplacer implements BiFunction<String, String, Tag> { | ||
|
||
private final Function<String, String> delegate; | ||
|
||
public ValueReplacer(Function<String, String> delegate) { | ||
this.delegate = delegate; | ||
} | ||
|
||
@Override | ||
public Tag apply(String key, String value) { | ||
return Tag.of(key, delegate.apply(value)); | ||
} | ||
|
||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
/* | ||
* Copyright 2025 VMware, Inc. | ||
* | ||
* Licensed under the Apache License, Version 2.0 (the "License"); | ||
* you may not use this file except in compliance with the License. | ||
* You may obtain a copy of the License at | ||
* | ||
* https://www.apache.org/licenses/LICENSE-2.0 | ||
* | ||
* Unless required by applicable law or agreed to in writing, software | ||
* distributed under the License is distributed on an "AS IS" BASIS, | ||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
* See the License for the specific language governing permissions and | ||
* limitations under the License. | ||
*/ | ||
package io.micrometer.core.instrument.config.filter; |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What were the problems / what was solved: