Skip to content

Discussion, improve CounterDataPoint.inc() critical path #1471

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

Open
wants to merge 3 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -32,14 +32,17 @@
public class Counter extends StatefulMetric<CounterDataPoint, Counter.DataPoint>
implements CounterDataPoint {

private final boolean exemplarsEnabled;
private final ExemplarSamplerConfig exemplarSamplerConfig;

private Counter(Builder builder, PrometheusProperties prometheusProperties) {
super(builder);
MetricsProperties[] properties = getMetricProperties(builder, prometheusProperties);
exemplarsEnabled = getConfigProperty(properties, MetricsProperties::getExemplarsEnabled);
if (exemplarsEnabled) {
boolean exemplarsEnabled = getConfigProperty(properties, MetricsProperties::getExemplarsEnabled);
// exemplars might be enabled specifically for a metric, however, if the code
// says withoutExemplars they should stay disabled.
boolean notTurnedOffWithinCode =
builder == null || builder.exemplarsEnabled == null || builder.exemplarsEnabled;
if (exemplarsEnabled && notTurnedOffWithinCode) {
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
if (exemplarsEnabled && notTurnedOffWithinCode) {
if (exemplarsEnabled && !Objects.equals(builder.exemplarsEnabled, false)) {

Copy link
Member

Choose a reason for hiding this comment

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

builder can't be null (adding NullAway is on my list...)

exemplarSamplerConfig =
new ExemplarSamplerConfig(prometheusProperties.getExemplarProperties(), 1);
} else {
Expand Down Expand Up @@ -93,15 +96,15 @@ protected CounterSnapshot collect(List<Labels> labels, List<DataPoint> metricDat

@Override
protected boolean isExemplarsEnabled() {
return exemplarsEnabled;
return exemplarSamplerConfig != null;
}

@Override
protected DataPoint newDataPoint() {
if (isExemplarsEnabled()) {
return new DataPoint(new ExemplarSampler(exemplarSamplerConfig));
} else {
return new DataPoint(null);
return new DataPointIgnoringExemplars();
}
}

Expand All @@ -112,13 +115,13 @@ static String stripTotalSuffix(String name) {
return name;
}

class DataPoint implements CounterDataPoint {
public static class DataPoint implements CounterDataPoint {

private final DoubleAdder doubleValue = new DoubleAdder();
// LongAdder is 20% faster than DoubleAdder. So let's use the LongAdder for long observations,
// and DoubleAdder for double observations. If the user doesn't observe any double at all,
// we will be using the LongAdder and get the best performance.
private final LongAdder longValue = new LongAdder();
protected final LongAdder longValue = new LongAdder();
private final long createdTimeMillis = System.currentTimeMillis();
private final ExemplarSampler exemplarSampler; // null if isExemplarsEnabled() is false

Expand Down Expand Up @@ -168,7 +171,11 @@ public void incWithExemplar(double amount, Labels labels) {
}
}

private void validateAndAdd(long amount) {
protected boolean isExemplarsEnabled() {
return exemplarSampler != null;
}

protected void validateAndAdd(long amount) {
if (amount < 0) {
throw new IllegalArgumentException(
"Negative increment " + amount + " is illegal for Counter metrics.");
Expand All @@ -184,7 +191,7 @@ private void validateAndAdd(double amount) {
doubleValue.add(amount);
}

private CounterSnapshot.CounterDataPointSnapshot collect(Labels labels) {
protected CounterSnapshot.CounterDataPointSnapshot collect(Labels labels) {
// Read the exemplar first. Otherwise, there is a race condition where you might
// see an Exemplar for a value that's not counted yet.
// If there are multiple Exemplars (by default it's just one), use the newest.
Expand All @@ -202,6 +209,51 @@ private CounterSnapshot.CounterDataPointSnapshot collect(Labels labels) {
}
}

/**
* Specialized data point, which is used in case no exemplar support is enabled.
* Applications can cast to this data time to speed up counter increment operations.
*/
public static final class DataPointIgnoringExemplars extends DataPoint {

private DataPointIgnoringExemplars() {
super(null);
}

/**
* This is one of the most used metric. Override for speed. Direct shortcut to
* the {@code LongAdder.add} method with just only validating the argument. This
* override is actually not really needed because the override of {@link #isExemplarsEnabled()}
* and inlining has the same effect in the end, however, for everyone inspecting
* the code it makes it obvious that the increment a long counter code path is as short
* as possible.
*/
@Override
public void inc(long amount) {
validateAndAdd(amount);
}

/**
* Counter increment, probably the most used metric update method. Specialise and skip
* validation. Basically, the JVM JIT is doing this for us and inlining the code. However,
* for people that are curious about performance and inspecting the code, it might be good
* to be assured that this goes directly to the `LongAdder` method.
*/
@Override
public void inc() {
longValue.increment();
}

/**
* Override for speed. Since final, the JVM will inline code for this
* method and all Exemplar code will be left out.
*/
@Override
protected boolean isExemplarsEnabled() {
return false;
}

}

public static Builder builder() {
return new Builder(PrometheusProperties.get());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -319,8 +319,6 @@ void incWithExemplar2() {
public void testExemplarSamplerDisabled() {
Counter counter =
Counter.builder()
// .withExemplarSampler((inc, prev) -> {throw new RuntimeException("unexpected call to
// exemplar sampler");})
.name("count_total")
.withoutExemplars()
.build();
Expand All @@ -330,6 +328,21 @@ public void testExemplarSamplerDisabled() {
assertThat(getData(counter).getExemplar()).isNull();
}

@Test
public void testExemplarSamplerDisabledReturnsFastCounter() {
Counter counter =
Counter.builder()
.name("count_total")
.withoutExemplars()
.build();
Counter.DataPointIgnoringExemplars fasterCounter = (Counter.DataPointIgnoringExemplars)
counter.labelValues();
assertThat(getData(counter).getValue()).isEqualTo(0);
fasterCounter.inc(1);
assertThat(getData(counter).getValue()).isEqualTo(1);
assertThat(fasterCounter.isExemplarsEnabled()).isFalse();
}

@Test
public void testConstLabelsFirst() {
assertThatExceptionOfType(IllegalArgumentException.class)
Expand Down