Skip to content
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

4.1 dev slf4j metric attributes #1275

Merged

Conversation

fabienrenaud
Copy link
Contributor

Copy link
Member

@arteam arteam left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks good, I've left some comments.

}
}

private final LoggerProxy loggerProxy;
private final Marker marker;
private final String prefix;
private final ThreadLocal<StringBuilder> threadLocalStringBuilder = ThreadLocal.withInitial(StringBuilder::new);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I feel that using a ThreadLocal is a premature optimization. It's not clear for me that it will provide a big performance boost over allocating a new StringBuilder. Also, using ThreadLocal can cause strange classloader leaks if one fail to clear a reference to it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed ThreadLocal. StringBuilder has to be a local variable to the report method since there is no guarantee the method won't be executed in parallel.

}

for (Entry<String, Counter> entry : counters.entrySet()) {
logCounter(entry.getKey(), entry.getValue());
if (!getDisabledMetricAttributes().contains(COUNT)) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

count is not used to filtering counters. It's used only to filter the count attribute of histograms, meters, and timers. Users can filter counters by MetricFilter.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed if.

}
}

private void append(StringBuilder b, String key, long value) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It seems to me that perfomance benefits of not conveting primtives to wrappers are slim to have 4 apennd methods. I think one method accepting an Object should be enough, especially considering that in most cases we use Supplier which forces auto-boxing.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think they are worth keeping:

  • they are very simple methods, easy to read, understand, change. no hidden code complexity here.
  • convertDuration and convertRate return double so only having an append(...,Object) could cause extra auto-boxing
  • the JVM can likely optimize primitive -> box(primitive) -> primitive call chains
  • and yes, StringBuilder.append(long) is slightly more efficient than StringBuilder.append(Object) called with a Long (unless the JVM un-erases types for some core classes? -- Hotspot JVM does some surprising optimizations some times :) ).

I can remove them if you feel strongly about it but now that the code is there, I think it's worth keeping.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fair enough

import java.util.SortedMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

import static com.codahale.metrics.MetricAttribute.*;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please don't use star imports, in the codebase we tend to use direct imports.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed.

* Don't report the passed metric attributes for all metrics (e.g. "p999", "stddev" or "m15").
* See {@link MetricAttribute}.
*
* @param disabledMetricAttributes a {@link MetricFilter}
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

s/MetricFilter/a set of MetricAttribute

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed. fixed GraphiteReporter as well.

import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when;
import static com.codahale.metrics.MetricAttribute.*;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please replace the star imports with direct imports.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed.

.filter(MetricFilter.ALL)
.build();

private Set<MetricAttribute> disabledMetricAttributes = null;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of declaring disabledMetricAttributes as a field, I think we should it make a parameter to the infoReporter and errorReporter methods. Othwerise it's not clear how what's a relationship between it and the actual reporter.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like to use this pattern to override default values passed to the constructor of the main unit tested component. I think in the context of unit tests it makes sense since there is only 1 class that is supposed to be tested. In the context of Slf4jReporter, it should be clear that the only way of testing Slf4jReporter is to use its builder and that its builder takes an optional Set<MetricAttribute> disabledMetricAttributes.
This pattern becomes particularly handy when there might be more than 1 default value to overwrite for some tests. The alternatives would be to either invoke the full builder with all the proper default values set but this leads to code duplication; or to add more arguments to the infoReporter and errorReporter factory methods to overwrite the default value but this doesn't scale well when more than a couple of variables needs their default values overwritten and the factory method may end up having as many parameters as the builder...
I'll add a comment clarifying this is used to build the reporter in each test and can be overridden. I will also move at the top of the infoReporter and errorReporter methods to hep identify the relationship.

}

private void verifyError(String expectedLog) {
if (expectedLog == null) {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we really need conditional checks if we use it only in one place?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is no longer necessary since attribute filtering doesn't apply to counters


private void verifyInfo(String expectedLog) {
if (expectedLog == null) {
verify(logger, never()).info(marker, expectedLog);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It looks like this branch of the execution isn't used at all.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the idea was to be consistent with how tests were written.
removed.

import java.util.SortedMap;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
import java.util.function.Supplier;

import static com.codahale.metrics.MetricAttribute.*;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed.

* Don't report the passed metric attributes for all metrics (e.g. "p999", "stddev" or "m15").
* See {@link MetricAttribute}.
*
* @param disabledMetricAttributes a {@link MetricFilter}
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fixed. fixed GraphiteReporter as well.

}
}

private final LoggerProxy loggerProxy;
private final Marker marker;
private final String prefix;
private final ThreadLocal<StringBuilder> threadLocalStringBuilder = ThreadLocal.withInitial(StringBuilder::new);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed ThreadLocal. StringBuilder has to be a local variable to the report method since there is no guarantee the method won't be executed in parallel.

}

for (Entry<String, Counter> entry : counters.entrySet()) {
logCounter(entry.getKey(), entry.getValue());
if (!getDisabledMetricAttributes().contains(COUNT)) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

removed if.

}
}

private void append(StringBuilder b, String key, long value) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think they are worth keeping:

  • they are very simple methods, easy to read, understand, change. no hidden code complexity here.
  • convertDuration and convertRate return double so only having an append(...,Object) could cause extra auto-boxing
  • the JVM can likely optimize primitive -> box(primitive) -> primitive call chains
  • and yes, StringBuilder.append(long) is slightly more efficient than StringBuilder.append(Object) called with a Long (unless the JVM un-erases types for some core classes? -- Hotspot JVM does some surprising optimizations some times :) ).

I can remove them if you feel strongly about it but now that the code is there, I think it's worth keeping.


private void verifyInfo(String expectedLog) {
if (expectedLog == null) {
verify(logger, never()).info(marker, expectedLog);
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the idea was to be consistent with how tests were written.
removed.

}

private void verifyError(String expectedLog) {
if (expectedLog == null) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this is no longer necessary since attribute filtering doesn't apply to counters

}
}

private void appendLongIfEnabled(StringBuilder b, MetricAttribute metricAttribute, Supplier<Long> durationSupplier) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

renamed durationSupplier here since this isn't a duration.

}
}

private void appendDoubleIfEnabled(StringBuilder b, MetricAttribute metricAttribute, Supplier<Double> durationSupplier) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ditto

.filter(MetricFilter.ALL)
.build();

private Set<MetricAttribute> disabledMetricAttributes = null;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I like to use this pattern to override default values passed to the constructor of the main unit tested component. I think in the context of unit tests it makes sense since there is only 1 class that is supposed to be tested. In the context of Slf4jReporter, it should be clear that the only way of testing Slf4jReporter is to use its builder and that its builder takes an optional Set<MetricAttribute> disabledMetricAttributes.
This pattern becomes particularly handy when there might be more than 1 default value to overwrite for some tests. The alternatives would be to either invoke the full builder with all the proper default values set but this leads to code duplication; or to add more arguments to the infoReporter and errorReporter factory methods to overwrite the default value but this doesn't scale well when more than a couple of variables needs their default values overwritten and the factory method may end up having as many parameters as the builder...
I'll add a comment clarifying this is used to build the reporter in each test and can be overridden. I will also move at the top of the infoReporter and errorReporter methods to hep identify the relationship.

@arteam arteam merged commit de99b11 into dropwizard:4.1-development Feb 25, 2018
@arteam
Copy link
Member

arteam commented Feb 25, 2018

Thank you for the contribution!

@arteam arteam modified the milestones: 4.1.0, 4.0.1 Feb 25, 2018
@fabienrenaud fabienrenaud deleted the 4.1-dev-slf4j-metricAttributes branch February 26, 2018 17:13
chids pushed a commit to chids/metrics that referenced this pull request Mar 16, 2018
* Support for disabledMetricAttributes in Slf4jReporter

* Tests for metric attribute filter in Slf4jReporter

* Slf4jReporter: addressed PR feedback
@fabienrenaud
Copy link
Contributor Author

@arteam Could you please release this change? It's been 8 months...

@arteam
Copy link
Member

arteam commented Oct 15, 2018

Sure, will do a 4.1.0 release this week.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants