-
Notifications
You must be signed in to change notification settings - Fork 804
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
Support _created
time series suppression
#791
Conversation
@dhoard thank you for suggested code changes - they where added to the PR with a minor twist. Can you please suggest the best place to document this? would it be the It may not be a very good idea exposing any code as public unless its explicitly supposed to be part of the interface. |
@mindw We should document the use of the environment variable in the | It may not be a very good idea exposing any code as public unless its explicitly supposed to be part of the interface. I agree. Typically, the code would use a static initialization block in |
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.
Thanks for the PR. I added a few comments, nothing major. Please have a look.
@@ -150,44 +149,43 @@ public MetricFamilySamples filter(Predicate<String> sampleNameFilter) { | |||
* {@code # HELP}), and as this name <a href="https://github.com/prometheus/common/issues/319">must be unique</a> | |||
* we include the name without suffix here as well. | |||
*/ | |||
public String[] getNames() { | |||
public List<String> getNames() { |
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 about leaving getNames()
as it is?
The _created
names in getNames()
are used to prevent illegal name clashes, like somebody registering a counter named my_counter_total
but also a gauge named my_counter_created
. These are a weird corner cases, and I think it's strange to allow this when the PROMETHEUS_DISABLE_CREATED_SERIES
environment variable is set.
If we leave getNames()
as it was, PROMETHEUS_DISABLE_CREATED_SERIES
would just enable/disable the _created
series. If we change getNames()
, we will allow weird corner cases that are usually prevented. As a result, there will be corner cases where an application breaks unless PROMETHEUS_DISABLE_CREATED_SERIES
is set. I don't think this is the intention.
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.
#774 (comment) suggests these are actually not forbidden just discouraged. Sure, evil minded integrators may abuse this by developing their code under PROMETHEUS_DISABLE_CREATED_SERIES=true
but it will break on the first time it will not be used.
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.
#774 (comment) says that my general statement
it is illegal to use the _created suffix in custom metric names
is incorrect. However, it is still true that OpenMetrics disallows _created
metrics when there are potential name clashes:
The name of a MetricFamily MUST NOT result in a potential clash for sample metric names as per the ABNF with another MetricFamily in the Text Format within a MetricSet. An example would be a gauge called "foo_created" as a counter called "foo" could create a "foo_created" in the text format.
As the getNames()
method is used to prevent these name clashes, I think it would be good to leave it as it is.
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.
Fair enough - I've reverted the this change. Thanks for taking the time and effort explaining it!
protected static final String DISABLE_CREATED_SERIES = "PROMETHEUS_DISABLE_CREATED_SERIES"; | ||
private static final List<String> TRUTHS = Arrays.asList("true", "1", "t"); | ||
|
||
protected static boolean USE_CREATED = getUseCreated(); |
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.
- Please make it
final
. It's not supposed to change. - If you make it
final
but still want to test it, you should not make itstatic
. - Rename to
CREATED_SERIES_DISABLED
because that makes it clearer that this is related to thePROMETHEUS_DISABLE_CREATED_SERIES
environment variable. - If you revert the changes to
getNames()
, you could move this one level down in the class hierarchy toSimpleCollector
.
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.
- Please make it
final
. It's not supposed to change.- If you make it
final
but still want to test it, you should not make itstatic
.
Yes, it was final - but then I could not find a way to test it. It I remove the static
every object will have its own copy of it which seems to be excessive. The above is a compromise.
- Rename to
CREATED_SERIES_DISABLED
because that makes it clearer that this is related to thePROMETHEUS_DISABLE_CREATED_SERIES
environment variable.
That will flip it's meaning and will change all code from
if (USE_CREATED) {
to
if (!CREATED_SERIES_DISABLED) {
Which is a double negative and is considered as a poor programming practice. For example - https://cleankotlin.nl/blog/double-negations#:~:text=Double%20negations%20are%20not%20not,can%20hide%20in%20your%20program.
- If you revert the changes to
getNames()
, you could move this one level down in the class hierarchy toSimpleCollector
.
Not sure of the benefit - let's resolve getNames()
discussion first.
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.
I think if the flag should not change at runtime it should be final
, even if that makes it a bit harder to test. There are a few options how to write a test if it's final
:
- Make it non-static
- Set the field via reflection (you can remove the
final
modifier via reflection) - Write an integration test with Testcontainers test (then you could set the environment variable before starting the test application).
Regarding the name of the flag: It's always hard to maintain code if there are different names for the same concept. If we name the environment variable PROMETHEUS_DISABLE_CREATED_SERIES
but the flag USE_CREATED
somebody reading the code would always need to double-check if these mean the same thing. If we use a similar name, it's more obvious.
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.
I think if the flag should not change at runtime it should be
final
, even if that makes it a bit harder to test. There are a few options how to write a test if it'sfinal
:
- Make it non-static
- Set the field via reflection (you can remove the
final
modifier via reflection)- Write an integration test with Testcontainers test (then you could set the environment variable before starting the test application).
I'm not sure what I can add. The extra variable per object seems like too much a price to pay given the alternative.
Can you please provide some guidance on how to use Testcontainers
here? I'm unfamiliar with that framework.
Otherwise it seems like a good way on making the variable final (which something I wanted to do in the first place). It will also make the new dependency redundant.
Regarding the name of the flag: It's always hard to maintain code if there are different names for the same concept. If we name the environment variable
PROMETHEUS_DISABLE_CREATED_SERIES
but the flagUSE_CREATED
somebody reading the code would always need to double-check if these mean the same thing. If we use a similar name, it's more obvious.
Thanks for pointing this rule out. We're talking about readability multiple places vs readability one place. For me the many wins - the condition should be read as "TRUE" instead of "NOT NOT". Finally, the this choice mirrors the python one. The environment variable is mentioned once in a function and is pretty easy to lookup in any decent IDE.
|
||
List<Collector.MetricFamilySamples> mfsList; | ||
try { | ||
env.setup(); |
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.
If you make the flag final
, you'll need to call env.setup()
before initializing the metrics.
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.
How can I do that?
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.
That was referring to
If you make it final but still want to test it, you should not make it static.
There are a few other options how to write a test though.
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.
As noted above - some guidance/example on how to use the framework would be appreciated.
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.
Have a look at JavaVersionsIT
. I guess for your test you won't need different Java versions, but apart from that it should be similar.
simpleclient/pom.xml
Outdated
<dependency> | ||
<groupId>org.mockito</groupId> | ||
<artifactId>mockito-inline</artifactId> | ||
<version>3.12.4</version> | ||
<scope>test</scope> | ||
</dependency> |
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.
Not needed?
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.
Alas, it seems to be needed - https://github.com/webcompere/system-stubs#dependencies .
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.
I removed the dependency, and the test works just fine. I don't understand why it is needed.
Anyway, if the system-stubs
dependency is hard to understand, it might be good not to use it. A few of the options how to make the code testable do not include modifying environment variables at runtime:
- Use reflection to set the flag
- Write an integration test with Testcontainers.
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.
Yes, that is the expected output for JVM 11.
Quoting from the link above:
⚠ WARNING: JDK Compatibility. From JDK16 onwards, there are deeper restrictons on the ability to use reflection. Previous versions of this library, and others in the space, encounter an Illegal Reflective Access warning, or even a runtime error such as java.lang.reflect.InaccessibleObjectException when trying to manipulate the Map behind the system's environment variables.
Consequently, this library now uses mockito-inline version 3.x to enable the interception of calls for reading environment variables. This requires consumers to both use a compatible version of Mockito AND be prepared for the inline implementation of Mockito mocks.
Note: Groovy users may need Mockito >= 4.5.0 for compatibility.
As noted above - some guidance/example on how to use the Testcontainers will make this discussion moot.
…ed` time series Signed-off-by: Gabi Davar <[email protected]>
Signed-off-by: Gabi Davar <[email protected]>
Signed-off-by: Gabi Davar <[email protected]>
Signed-off-by: Gabi Davar <[email protected]>
Signed-off-by: Gabi Davar <[email protected]>
@mindw I think I'll do a release this week, and it would be good to include this feature. What do you think about adding the feature now (and make the flag |
No problem - tests will wait ill the weekend |
Signed-off-by: Gabi Davar <[email protected]>
Signed-off-by: Gabi Davar <[email protected]>
Thanks a lot @mindw. One last thing: I'm wondering whether However, thinking about it I feel that it might be worthwhile to move the environment variable parsing into its own class, like this: class EnvironmentVariables {
private static final String DISABLE_CREATED_SERIES = "PROMETHEUS_DISABLE_CREATED_SERIES";
private static final List<String> TRUTHS = Arrays.asList("true", "1", "t");
private static final boolean createdSeriesEnabled = !isTrue(DISABLE_CREATED_SERIES);
static boolean isCreatedSeriesEnabled() {
return createdSeriesEnabled;
}
private static boolean isTrue(String envVarName) {
String stringValue = System.getenv(envVarName);
if (stringValue != null) {
return TRUTHS.contains(stringValue.toLowerCase());
}
return false;
}
} Then the if (EnvironmentVariables.isCreatedSeriesEnabled()) {
// add _created sample
} I feel this would help with separation of concerns. What do you think? |
@fstab @mindw I personally like...
Since it's really environment configuration. Then the Histogram, Counter, and Summary could use it like this:
|
Signed-off-by: Gabi Davar <[email protected]>
@fstab It is done :) It seems to me that both suggestions converged. |
👍 thanks! |
If
PROMETHEUS_DISABLE_CREATED_SERIES
environment variable is true suppress creation of_created
OpenMetrics time series.My Java skills are pretty much non-existant, all comments are welcome :)
Fixes #774
When merging, please consider using
squash-merging
, thanks :)CC @fstab @SuperQ
Help needed:
Place to put documentationAlso support setting via property?