Skip to content
Draft
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
13 changes: 10 additions & 3 deletions sentry-micrometer/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,17 @@ Passive meters are polled every 60 seconds by default:
| `LongTaskTimer` active tasks | `${name}.active` gauge |
| `LongTaskTimer` active duration | `${name}.duration` gauge in milliseconds |
| `FunctionCounter` | Positive counter delta |
| `FunctionTimer` count | `${name}.count` positive counter delta |
| `FunctionTimer` total time | `${name}.total_time` positive counter delta in milliseconds |

The first successful finite `FunctionCounter` poll establishes its baseline and emits nothing.
Later positive deltas are sent. A decreasing value is treated as a reset and establishes a new
baseline.
The first successful finite function-meter poll establishes its baseline and emits nothing. Later
positive deltas are sent. A decreasing value is treated as a reset and establishes a new baseline.
`FunctionTimer` tracks its count and total-time baselines independently.

A `FunctionTimer` does not expose individual durations, so the integration emits neither a
mean gauge nor a distribution. To derive a correctly weighted mean across instances, divide the
sum of `${name}.total_time` by the sum of `${name}.count`. Percentiles cannot be derived from these
cumulative values.

Unsupported custom meters remain readable through Micrometer but are not exported to Sentry.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
package io.sentry.micrometer;

import io.micrometer.core.instrument.Meter;
import io.micrometer.core.instrument.cumulative.CumulativeFunctionTimer;
import java.util.concurrent.TimeUnit;
import java.util.function.ToDoubleFunction;
import java.util.function.ToLongFunction;
import org.jetbrains.annotations.NotNull;

final class SentryFunctionTimer<T> extends CumulativeFunctionTimer<T> {
private final @NotNull SentryMeterRegistry registry;
private final @NotNull SentryMetricInfo countMetricInfo;
private final @NotNull SentryMetricInfo totalTimeMetricInfo;
private volatile boolean removed;
private boolean countInitialized;
private double previousCount;
private boolean totalTimeInitialized;
private double previousTotalTime;

SentryFunctionTimer(
final @NotNull Meter.Id id,
final @NotNull T obj,
final @NotNull ToLongFunction<T> countFunction,
final @NotNull ToDoubleFunction<T> totalTimeFunction,
final @NotNull TimeUnit totalTimeFunctionUnit,
final @NotNull TimeUnit baseTimeUnit,
final @NotNull SentryMeterRegistry registry,
final @NotNull SentryMetricInfo countMetricInfo,
final @NotNull SentryMetricInfo totalTimeMetricInfo) {
super(id, obj, countFunction, totalTimeFunction, totalTimeFunctionUnit, baseTimeUnit);
this.registry = registry;
this.countMetricInfo = countMetricInfo;
this.totalTimeMetricInfo = totalTimeMetricInfo;
}

void poll() {
pollCount();
pollTotalTime();
}

private void pollCount() {
final double currentCount = count();
if (!Double.isFinite(currentCount) || removed || registry.isClosed()) {
return;
}

if (!countInitialized || currentCount < previousCount) {
countInitialized = true;
previousCount = currentCount;
return;
}

final double delta = currentCount - previousCount;
previousCount = currentCount;
if (delta > 0.0 && !removed) {
registry.captureCounter(countMetricInfo, delta);
}
}

private void pollTotalTime() {
final double currentTotalTime = totalTime(TimeUnit.MILLISECONDS);
if (!Double.isFinite(currentTotalTime) || removed || registry.isClosed()) {
return;
}

if (!totalTimeInitialized || currentTotalTime < previousTotalTime) {
totalTimeInitialized = true;
previousTotalTime = currentTotalTime;
return;
}

final double delta = currentTotalTime - previousTotalTime;
previousTotalTime = currentTotalTime;
if (delta > 0.0 && !removed) {
registry.captureCounter(totalTimeMetricInfo, delta);
}
}

void markRemoved() {
removed = true;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
import io.micrometer.core.instrument.Tag;
import io.micrometer.core.instrument.TimeGauge;
import io.micrometer.core.instrument.Timer;
import io.micrometer.core.instrument.cumulative.CumulativeFunctionTimer;
import io.micrometer.core.instrument.distribution.DistributionStatisticConfig;
import io.micrometer.core.instrument.distribution.pause.PauseDetector;
import io.micrometer.core.instrument.internal.DefaultGauge;
Expand Down Expand Up @@ -140,8 +139,16 @@ public SentryMeterRegistry(final long pollIntervalMillis) {
final @NotNull ToLongFunction<T> countFunction,
final @NotNull ToDoubleFunction<T> totalTimeFunction,
final @NotNull TimeUnit totalTimeFunctionUnit) {
return new CumulativeFunctionTimer<>(
id, obj, countFunction, totalTimeFunction, totalTimeFunctionUnit, getBaseTimeUnit());
return new SentryFunctionTimer<>(
id,
obj,
countFunction,
totalTimeFunction,
totalTimeFunctionUnit,
getBaseTimeUnit(),
this,
createMetricInfo(id, ".count", null),
createMetricInfo(id, ".total_time", MetricsUnit.Duration.MILLISECOND));
}

@Override
Expand Down Expand Up @@ -238,6 +245,8 @@ private void publishPassiveMeter(final @NotNull Meter meter) {
publishLongTaskTimer((LongTaskTimer) meter);
} else if (meter instanceof SentryFunctionCounter) {
((SentryFunctionCounter<?>) meter).poll();
} else if (meter instanceof SentryFunctionTimer) {
((SentryFunctionTimer<?>) meter).poll();
}
}

Expand Down Expand Up @@ -269,6 +278,8 @@ private void publishLongTaskTimer(final @NotNull LongTaskTimer timer) {
private void onMeterRemoved(final @NotNull Meter meter) {
if (meter instanceof SentryFunctionCounter) {
((SentryFunctionCounter<?>) meter).markRemoved();
} else if (meter instanceof SentryFunctionTimer) {
((SentryFunctionTimer<?>) meter).markRemoved();
}
}

Expand Down
Loading
Loading