Skip to content

Commit ea38204

Browse files
authored
Allow optimizing locking for built-in exemplar reservoirs (#7423)
Fixes #7388 Benchmarks seem like mostly noise. It isn't actually necessary to lock in the exemplar reservoir today because of our SDK design, but this allows us to make optimizations in the future. After #7427, improvements to exemplar reservoir locking will greatly improve the ExemplarEnabled benchmarks. Parallel benchmarks: ``` goos: linux goarch: amd64 pkg: go.opentelemetry.io/otel/sdk/metric cpu: Intel(R) Xeon(R) CPU @ 2.20GHz │ main24.txt │ exemplar24.txt │ │ sec/op │ sec/op vs base │ SyncMeasure/NoView/ExemplarsDisabled/Int64Counter/Attributes/0-24 399.5n ± 16% 367.8n ± 17% ~ (p=0.310 n=6) SyncMeasure/NoView/ExemplarsDisabled/Int64Counter/Attributes/1-24 369.4n ± 27% 410.2n ± 11% ~ (p=0.240 n=6) SyncMeasure/NoView/ExemplarsDisabled/Int64Counter/Attributes/10-24 372.6n ± 23% 398.9n ± 8% ~ (p=0.394 n=6) SyncMeasure/NoView/ExemplarsDisabled/Float64Counter/Attributes/0-24 313.4n ± 12% 357.7n ± 20% ~ (p=0.065 n=6) SyncMeasure/NoView/ExemplarsDisabled/Float64Counter/Attributes/1-24 389.9n ± 12% 379.5n ± 9% ~ (p=0.818 n=6) SyncMeasure/NoView/ExemplarsDisabled/Float64Counter/Attributes/10-24 441.4n ± 13% 359.1n ± 18% -18.64% (p=0.009 n=6) SyncMeasure/NoView/ExemplarsDisabled/Int64UpDownCounter/Attributes/0-24 415.8n ± 22% 400.3n ± 16% ~ (p=0.937 n=6) SyncMeasure/NoView/ExemplarsDisabled/Int64UpDownCounter/Attributes/1-24 346.9n ± 8% 364.6n ± 19% ~ (p=0.240 n=6) SyncMeasure/NoView/ExemplarsDisabled/Int64UpDownCounter/Attributes/10-24 358.9n ± 14% 407.1n ± 17% ~ (p=0.093 n=6) SyncMeasure/NoView/ExemplarsDisabled/Float64UpDownCounter/Attributes/0-24 381.9n ± 27% 375.2n ± 10% ~ (p=0.937 n=6) SyncMeasure/NoView/ExemplarsDisabled/Float64UpDownCounter/Attributes/1-24 361.9n ± 19% 389.6n ± 23% ~ (p=0.818 n=6) SyncMeasure/NoView/ExemplarsDisabled/Float64UpDownCounter/Attributes/10-24 356.0n ± 8% 416.1n ± 14% +16.90% (p=0.015 n=6) SyncMeasure/NoView/ExemplarsDisabled/Int64Gauge/Attributes/0-24 313.9n ± 11% 385.7n ± 19% +22.88% (p=0.041 n=6) SyncMeasure/NoView/ExemplarsDisabled/Int64Gauge/Attributes/1-24 368.8n ± 18% 387.6n ± 17% ~ (p=0.394 n=6) SyncMeasure/NoView/ExemplarsDisabled/Int64Gauge/Attributes/10-24 346.1n ± 40% 460.3n ± 16% ~ (p=0.065 n=6) SyncMeasure/NoView/ExemplarsDisabled/Float64Gauge/Attributes/0-24 325.9n ± 10% 357.8n ± 19% +9.77% (p=0.026 n=6) SyncMeasure/NoView/ExemplarsDisabled/Float64Gauge/Attributes/1-24 372.1n ± 18% 395.2n ± 14% ~ (p=0.180 n=6) SyncMeasure/NoView/ExemplarsDisabled/Float64Gauge/Attributes/10-24 353.5n ± 23% 416.0n ± 15% +17.66% (p=0.024 n=6) SyncMeasure/NoView/ExemplarsDisabled/Int64Histogram/Attributes/0-24 351.8n ± 15% 362.7n ± 7% ~ (p=0.699 n=6) SyncMeasure/NoView/ExemplarsDisabled/Int64Histogram/Attributes/1-24 378.8n ± 17% 413.4n ± 13% ~ (p=0.288 n=6) SyncMeasure/NoView/ExemplarsDisabled/Int64Histogram/Attributes/10-24 361.5n ± 13% 418.2n ± 14% ~ (p=0.132 n=6) SyncMeasure/NoView/ExemplarsDisabled/Float64Histogram/Attributes/0-24 305.0n ± 21% 361.9n ± 13% ~ (p=0.065 n=6) SyncMeasure/NoView/ExemplarsDisabled/Float64Histogram/Attributes/1-24 411.1n ± 12% 403.9n ± 9% ~ (p=0.937 n=6) SyncMeasure/NoView/ExemplarsDisabled/Float64Histogram/Attributes/10-24 353.4n ± 39% 380.9n ± 17% ~ (p=0.394 n=6) SyncMeasure/NoView/ExemplarsDisabled/ExponentialInt64Histogram/Attributes/0-24 457.5n ± 33% 454.2n ± 13% ~ (p=1.000 n=6) SyncMeasure/NoView/ExemplarsDisabled/ExponentialInt64Histogram/Attributes/1-24 436.6n ± 23% 459.0n ± 10% ~ (p=0.310 n=6) SyncMeasure/NoView/ExemplarsDisabled/ExponentialInt64Histogram/Attributes/10-24 383.3n ± 22% 461.9n ± 12% +20.51% (p=0.002 n=6) SyncMeasure/NoView/ExemplarsDisabled/ExponentialFloat64Histogram/Attributes/0-24 371.9n ± 14% 421.4n ± 19% +13.33% (p=0.004 n=6) SyncMeasure/NoView/ExemplarsDisabled/ExponentialFloat64Histogram/Attributes/1-24 433.7n ± 20% 490.8n ± 10% ~ (p=0.310 n=6) SyncMeasure/NoView/ExemplarsDisabled/ExponentialFloat64Histogram/Attributes/10-24 433.2n ± 18% 511.4n ± 9% +18.05% (p=0.041 n=6) SyncMeasure/NoView/ExemplarsEnabled/Int64Counter/Attributes/0-24 477.5n ± 14% 384.6n ± 7% -19.47% (p=0.002 n=6) SyncMeasure/NoView/ExemplarsEnabled/Int64Counter/Attributes/1-24 481.1n ± 17% 430.0n ± 18% ~ (p=0.065 n=6) SyncMeasure/NoView/ExemplarsEnabled/Int64Counter/Attributes/10-24 425.1n ± 27% 436.3n ± 12% ~ (p=0.699 n=6) SyncMeasure/NoView/ExemplarsEnabled/Float64Counter/Attributes/0-24 394.5n ± 8% 415.5n ± 15% ~ (p=0.589 n=6) SyncMeasure/NoView/ExemplarsEnabled/Float64Counter/Attributes/1-24 434.4n ± 13% 440.8n ± 15% ~ (p=0.937 n=6) SyncMeasure/NoView/ExemplarsEnabled/Float64Counter/Attributes/10-24 481.3n ± 19% 404.1n ± 14% -16.05% (p=0.009 n=6) SyncMeasure/NoView/ExemplarsEnabled/Int64UpDownCounter/Attributes/0-24 364.9n ± 29% 424.3n ± 8% ~ (p=0.065 n=6) SyncMeasure/NoView/ExemplarsEnabled/Int64UpDownCounter/Attributes/1-24 401.2n ± 24% 482.2n ± 12% +20.20% (p=0.002 n=6) SyncMeasure/NoView/ExemplarsEnabled/Int64UpDownCounter/Attributes/10-24 438.6n ± 19% 404.8n ± 18% ~ (p=0.485 n=6) SyncMeasure/NoView/ExemplarsEnabled/Float64UpDownCounter/Attributes/0-24 392.7n ± 17% 427.7n ± 25% ~ (p=0.180 n=6) SyncMeasure/NoView/ExemplarsEnabled/Float64UpDownCounter/Attributes/1-24 392.6n ± 5% 388.5n ± 7% ~ (p=0.818 n=6) SyncMeasure/NoView/ExemplarsEnabled/Float64UpDownCounter/Attributes/10-24 401.3n ± 19% 409.7n ± 8% ~ (p=0.818 n=6) SyncMeasure/NoView/ExemplarsEnabled/Int64Gauge/Attributes/0-24 369.8n ± 15% 374.2n ± 17% ~ (p=0.818 n=6) SyncMeasure/NoView/ExemplarsEnabled/Int64Gauge/Attributes/1-24 359.4n ± 13% 387.1n ± 16% ~ (p=0.180 n=6) SyncMeasure/NoView/ExemplarsEnabled/Int64Gauge/Attributes/10-24 393.2n ± 18% 450.0n ± 10% +14.43% (p=0.015 n=6) SyncMeasure/NoView/ExemplarsEnabled/Float64Gauge/Attributes/0-24 399.8n ± 23% 361.2n ± 11% ~ (p=0.065 n=6) SyncMeasure/NoView/ExemplarsEnabled/Float64Gauge/Attributes/1-24 439.4n ± 25% 412.0n ± 10% ~ (p=0.310 n=6) SyncMeasure/NoView/ExemplarsEnabled/Float64Gauge/Attributes/10-24 401.7n ± 17% 380.0n ± 11% ~ (p=0.394 n=6) SyncMeasure/NoView/ExemplarsEnabled/Int64Histogram/Attributes/0-24 508.8n ± 18% 532.6n ± 16% ~ (p=0.937 n=6) SyncMeasure/NoView/ExemplarsEnabled/Int64Histogram/Attributes/1-24 505.9n ± 22% 494.8n ± 14% ~ (p=0.589 n=6) SyncMeasure/NoView/ExemplarsEnabled/Int64Histogram/Attributes/10-24 597.8n ± 10% 490.5n ± 23% ~ (p=0.065 n=6) SyncMeasure/NoView/ExemplarsEnabled/Float64Histogram/Attributes/0-24 566.2n ± 21% 482.9n ± 10% ~ (p=0.132 n=6) SyncMeasure/NoView/ExemplarsEnabled/Float64Histogram/Attributes/1-24 440.2n ± 9% 549.3n ± 8% +24.77% (p=0.002 n=6) SyncMeasure/NoView/ExemplarsEnabled/Float64Histogram/Attributes/10-24 436.3n ± 16% 530.8n ± 19% ~ (p=0.093 n=6) SyncMeasure/NoView/ExemplarsEnabled/ExponentialInt64Histogram/Attributes/0-24 395.8n ± 25% 441.6n ± 9% ~ (p=0.065 n=6) SyncMeasure/NoView/ExemplarsEnabled/ExponentialInt64Histogram/Attributes/1-24 440.2n ± 9% 455.2n ± 7% ~ (p=0.180 n=6) SyncMeasure/NoView/ExemplarsEnabled/ExponentialInt64Histogram/Attributes/10-24 415.7n ± 12% 527.5n ± 9% +26.91% (p=0.002 n=6) SyncMeasure/NoView/ExemplarsEnabled/ExponentialFloat64Histogram/Attributes/0-24 376.1n ± 19% 461.9n ± 13% +22.81% (p=0.009 n=6) SyncMeasure/NoView/ExemplarsEnabled/ExponentialFloat64Histogram/Attributes/1-24 383.6n ± 10% 422.7n ± 26% +10.21% (p=0.015 n=6) SyncMeasure/NoView/ExemplarsEnabled/ExponentialFloat64Histogram/Attributes/10-24 398.5n ± 9% 538.5n ± 7% +35.13% (p=0.002 n=6) geomean 399.4n 422.3n +5.72% ``` Single-threaded benchmarks: ``` goos: linux goarch: amd64 pkg: go.opentelemetry.io/otel/sdk/metric cpu: Intel(R) Xeon(R) CPU @ 2.20GHz │ main1.txt │ exemplar1.txt │ │ sec/op │ sec/op vs base │ SyncMeasure/NoView/ExemplarsDisabled/Int64Counter/Attributes/0 180.1n ± 21% 156.2n ± 10% ~ (p=0.132 n=6) SyncMeasure/NoView/ExemplarsDisabled/Int64Counter/Attributes/1 168.4n ± 9% 179.2n ± 11% ~ (p=0.132 n=6) SyncMeasure/NoView/ExemplarsDisabled/Int64Counter/Attributes/10 164.0n ± 10% 199.0n ± 20% +21.30% (p=0.004 n=6) SyncMeasure/NoView/ExemplarsDisabled/Float64Counter/Attributes/0 153.9n ± 6% 170.1n ± 2% +10.53% (p=0.002 n=6) SyncMeasure/NoView/ExemplarsDisabled/Float64Counter/Attributes/1 178.0n ± 5% 178.5n ± 6% ~ (p=0.818 n=6) SyncMeasure/NoView/ExemplarsDisabled/Float64Counter/Attributes/10 175.8n ± 7% 165.3n ± 18% ~ (p=0.589 n=6) SyncMeasure/NoView/ExemplarsDisabled/Int64UpDownCounter/Attributes/0 152.0n ± 8% 163.1n ± 26% ~ (p=0.132 n=6) SyncMeasure/NoView/ExemplarsDisabled/Int64UpDownCounter/Attributes/1 168.1n ± 8% 168.0n ± 14% ~ (p=0.818 n=6) SyncMeasure/NoView/ExemplarsDisabled/Int64UpDownCounter/Attributes/10 167.4n ± 4% 164.3n ± 12% ~ (p=0.699 n=6) SyncMeasure/NoView/ExemplarsDisabled/Float64UpDownCounter/Attributes/0 151.7n ± 17% 156.0n ± 28% ~ (p=0.310 n=6) SyncMeasure/NoView/ExemplarsDisabled/Float64UpDownCounter/Attributes/1 173.6n ± 5% 169.3n ± 5% -2.45% (p=0.041 n=6) SyncMeasure/NoView/ExemplarsDisabled/Float64UpDownCounter/Attributes/10 169.3n ± 4% 165.7n ± 7% ~ (p=0.394 n=6) SyncMeasure/NoView/ExemplarsDisabled/Int64Gauge/Attributes/0 155.5n ± 15% 153.8n ± 11% ~ (p=0.558 n=6) SyncMeasure/NoView/ExemplarsDisabled/Int64Gauge/Attributes/1 166.7n ± 3% 173.5n ± 7% ~ (p=0.132 n=6) SyncMeasure/NoView/ExemplarsDisabled/Int64Gauge/Attributes/10 174.4n ± 17% 167.6n ± 13% ~ (p=0.699 n=6) SyncMeasure/NoView/ExemplarsDisabled/Float64Gauge/Attributes/0 180.1n ± 32% 154.8n ± 5% -14.02% (p=0.002 n=6) SyncMeasure/NoView/ExemplarsDisabled/Float64Gauge/Attributes/1 204.6n ± 23% 173.8n ± 19% ~ (p=0.069 n=6) SyncMeasure/NoView/ExemplarsDisabled/Float64Gauge/Attributes/10 226.6n ± 28% 174.5n ± 10% ~ (p=0.132 n=6) SyncMeasure/NoView/ExemplarsDisabled/Int64Histogram/Attributes/0 132.7n ± 13% 140.7n ± 14% ~ (p=0.132 n=6) SyncMeasure/NoView/ExemplarsDisabled/Int64Histogram/Attributes/1 143.4n ± 9% 162.6n ± 9% +13.42% (p=0.004 n=6) SyncMeasure/NoView/ExemplarsDisabled/Int64Histogram/Attributes/10 154.7n ± 7% 172.3n ± 6% +11.38% (p=0.002 n=6) SyncMeasure/NoView/ExemplarsDisabled/Float64Histogram/Attributes/0 136.8n ± 10% 145.5n ± 14% ~ (p=0.240 n=6) SyncMeasure/NoView/ExemplarsDisabled/Float64Histogram/Attributes/1 148.4n ± 11% 159.7n ± 8% ~ (p=0.167 n=6) SyncMeasure/NoView/ExemplarsDisabled/Float64Histogram/Attributes/10 193.0n ± 25% 165.5n ± 9% ~ (p=0.310 n=6) SyncMeasure/NoView/ExemplarsDisabled/ExponentialInt64Histogram/Attributes/0 249.8n ± 47% 229.5n ± 7% ~ (p=0.394 n=6) SyncMeasure/NoView/ExemplarsDisabled/ExponentialInt64Histogram/Attributes/1 262.1n ± 20% 245.1n ± 12% ~ (p=0.699 n=6) SyncMeasure/NoView/ExemplarsDisabled/ExponentialInt64Histogram/Attributes/10 285.4n ± 20% 249.9n ± 17% ~ (p=0.132 n=6) SyncMeasure/NoView/ExemplarsDisabled/ExponentialFloat64Histogram/Attributes/0 272.9n ± 20% 216.3n ± 6% -20.75% (p=0.002 n=6) SyncMeasure/NoView/ExemplarsDisabled/ExponentialFloat64Histogram/Attributes/1 311.8n ± 29% 234.4n ± 6% ~ (p=0.132 n=6) SyncMeasure/NoView/ExemplarsDisabled/ExponentialFloat64Histogram/Attributes/10 228.2n ± 6% 234.2n ± 4% ~ (p=0.240 n=6) SyncMeasure/NoView/ExemplarsEnabled/Int64Counter/Attributes/0 289.2n ± 39% 263.3n ± 10% ~ (p=1.000 n=6) SyncMeasure/NoView/ExemplarsEnabled/Int64Counter/Attributes/1 271.9n ± 18% 280.6n ± 8% ~ (p=0.589 n=6) SyncMeasure/NoView/ExemplarsEnabled/Int64Counter/Attributes/10 272.1n ± 6% 303.7n ± 14% ~ (p=0.310 n=6) SyncMeasure/NoView/ExemplarsEnabled/Float64Counter/Attributes/0 280.1n ± 8% 268.8n ± 5% ~ (p=0.240 n=6) SyncMeasure/NoView/ExemplarsEnabled/Float64Counter/Attributes/1 291.6n ± 81% 268.8n ± 7% ~ (p=0.180 n=6) SyncMeasure/NoView/ExemplarsEnabled/Float64Counter/Attributes/10 276.3n ± 13% 278.1n ± 6% ~ (p=0.784 n=6) SyncMeasure/NoView/ExemplarsEnabled/Int64UpDownCounter/Attributes/0 254.7n ± 7% 282.4n ± 5% +10.85% (p=0.026 n=6) SyncMeasure/NoView/ExemplarsEnabled/Int64UpDownCounter/Attributes/1 277.5n ± 11% 285.2n ± 16% ~ (p=0.937 n=6) SyncMeasure/NoView/ExemplarsEnabled/Int64UpDownCounter/Attributes/10 267.0n ± 10% 275.9n ± 5% ~ (p=0.240 n=6) SyncMeasure/NoView/ExemplarsEnabled/Float64UpDownCounter/Attributes/0 256.9n ± 3% 286.4n ± 8% +11.46% (p=0.002 n=6) SyncMeasure/NoView/ExemplarsEnabled/Float64UpDownCounter/Attributes/1 267.9n ± 16% 278.0n ± 12% ~ (p=0.180 n=6) SyncMeasure/NoView/ExemplarsEnabled/Float64UpDownCounter/Attributes/10 272.5n ± 4% 267.4n ± 6% ~ (p=0.310 n=6) SyncMeasure/NoView/ExemplarsEnabled/Int64Gauge/Attributes/0 272.5n ± 22% 266.2n ± 14% ~ (p=0.589 n=6) SyncMeasure/NoView/ExemplarsEnabled/Int64Gauge/Attributes/1 355.2n ± 11% 275.2n ± 7% -22.52% (p=0.002 n=6) SyncMeasure/NoView/ExemplarsEnabled/Int64Gauge/Attributes/10 281.3n ± 7% 268.9n ± 4% ~ (p=0.093 n=6) SyncMeasure/NoView/ExemplarsEnabled/Float64Gauge/Attributes/0 257.0n ± 9% 308.9n ± 7% +20.20% (p=0.002 n=6) SyncMeasure/NoView/ExemplarsEnabled/Float64Gauge/Attributes/1 265.9n ± 9% 319.6n ± 7% +20.18% (p=0.002 n=6) SyncMeasure/NoView/ExemplarsEnabled/Float64Gauge/Attributes/10 279.9n ± 3% 332.9n ± 7% +18.92% (p=0.002 n=6) SyncMeasure/NoView/ExemplarsEnabled/Int64Histogram/Attributes/0 313.9n ± 11% 345.1n ± 10% +9.91% (p=0.002 n=6) SyncMeasure/NoView/ExemplarsEnabled/Int64Histogram/Attributes/1 332.1n ± 7% 359.9n ± 9% +8.37% (p=0.009 n=6) SyncMeasure/NoView/ExemplarsEnabled/Int64Histogram/Attributes/10 366.4n ± 55% 380.1n ± 4% ~ (p=0.240 n=6) SyncMeasure/NoView/ExemplarsEnabled/Float64Histogram/Attributes/0 336.8n ± 12% 346.3n ± 7% ~ (p=0.589 n=6) SyncMeasure/NoView/ExemplarsEnabled/Float64Histogram/Attributes/1 361.9n ± 18% 375.0n ± 8% +3.62% (p=0.004 n=6) SyncMeasure/NoView/ExemplarsEnabled/Float64Histogram/Attributes/10 351.8n ± 6% 392.1n ± 4% +11.46% (p=0.002 n=6) SyncMeasure/NoView/ExemplarsEnabled/ExponentialInt64Histogram/Attributes/0 358.8n ± 9% 344.3n ± 5% ~ (p=0.310 n=6) SyncMeasure/NoView/ExemplarsEnabled/ExponentialInt64Histogram/Attributes/1 352.0n ± 7% 331.5n ± 5% -5.84% (p=0.041 n=6) SyncMeasure/NoView/ExemplarsEnabled/ExponentialInt64Histogram/Attributes/10 373.5n ± 10% 353.7n ± 9% ~ (p=0.394 n=6) SyncMeasure/NoView/ExemplarsEnabled/ExponentialFloat64Histogram/Attributes/0 328.5n ± 9% 345.4n ± 5% +5.14% (p=0.015 n=6) SyncMeasure/NoView/ExemplarsEnabled/ExponentialFloat64Histogram/Attributes/1 349.1n ± 28% 337.8n ± 9% ~ (p=0.240 n=6) SyncMeasure/NoView/ExemplarsEnabled/ExponentialFloat64Histogram/Attributes/10 347.5n ± 10% 344.2n ± 10% ~ (p=1.000 n=6) geomean 235.3n 234.4n -0.38% ```
1 parent 6c54ef6 commit ea38204

File tree

8 files changed

+175
-21
lines changed

8 files changed

+175
-21
lines changed

sdk/metric/exemplar/fixed_size_reservoir.go

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"time"
1111

1212
"go.opentelemetry.io/otel/attribute"
13+
"go.opentelemetry.io/otel/sdk/metric/internal/reservoir"
1314
)
1415

1516
// FixedSizeReservoirProvider returns a provider of [FixedSizeReservoir].
@@ -34,6 +35,7 @@ var _ Reservoir = &FixedSizeReservoir{}
3435
// If there are more than k, the Reservoir will then randomly sample all
3536
// additional measurement with a decreasing probability.
3637
type FixedSizeReservoir struct {
38+
reservoir.ConcurrentSafe
3739
*storage
3840

3941
// count is the number of measurement seen.
@@ -123,12 +125,12 @@ func (r *FixedSizeReservoir) Offer(ctx context.Context, t time.Time, n Value, a
123125
// https://github.com/MrAlias/reservoir-sampling for a performance
124126
// comparison of reservoir sampling algorithms.
125127

126-
if int(r.count) < cap(r.store) {
127-
r.store[r.count] = newMeasurement(ctx, t, n, a)
128+
if int(r.count) < cap(r.measurements) {
129+
r.store(int(r.count), newMeasurement(ctx, t, n, a))
128130
} else if r.count == r.next {
129131
// Overwrite a random existing measurement with the one offered.
130-
idx := int(rand.Int64N(int64(cap(r.store))))
131-
r.store[idx] = newMeasurement(ctx, t, n, a)
132+
idx := int(rand.Int64N(int64(cap(r.measurements))))
133+
r.store(idx, newMeasurement(ctx, t, n, a))
132134
r.advance()
133135
}
134136
r.count++
@@ -139,7 +141,7 @@ func (r *FixedSizeReservoir) reset() {
139141
// This resets the number of exemplars known.
140142
r.count = 0
141143
// Random index inserts should only happen after the storage is full.
142-
r.next = int64(cap(r.store))
144+
r.next = int64(cap(r.measurements))
143145

144146
// Initial random number in the series used to generate r.next.
145147
//
@@ -150,7 +152,7 @@ func (r *FixedSizeReservoir) reset() {
150152
// This maps the uniform random number in (0,1) to a geometric distribution
151153
// over the same interval. The mean of the distribution is inversely
152154
// proportional to the storage capacity.
153-
r.w = math.Exp(math.Log(r.randomFloat64()) / float64(cap(r.store)))
155+
r.w = math.Exp(math.Log(r.randomFloat64()) / float64(cap(r.measurements)))
154156

155157
r.advance()
156158
}
@@ -170,7 +172,7 @@ func (r *FixedSizeReservoir) advance() {
170172
// therefore the next r.w will be based on the same distribution (i.e.
171173
// `max(u_1,u_2,...,u_k)`). Therefore, we can sample the next r.w by
172174
// computing the next random number `u` and take r.w as `w * u^(1/k)`.
173-
r.w *= math.Exp(math.Log(r.randomFloat64()) / float64(cap(r.store)))
175+
r.w *= math.Exp(math.Log(r.randomFloat64()) / float64(cap(r.measurements)))
174176
// Use the new random number in the series to calculate the count of the
175177
// next measurement that will be stored.
176178
//

sdk/metric/exemplar/fixed_size_reservoir_test.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,7 @@ func TestNewFixedSizeReservoirSamplingCorrectness(t *testing.T) {
4545
}
4646

4747
var sum float64
48-
for _, m := range r.store {
48+
for _, m := range r.measurements {
4949
sum += m.Value.Float64()
5050
}
5151
mean := sum / float64(sampleSize)

sdk/metric/exemplar/histogram_reservoir.go

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"time"
1111

1212
"go.opentelemetry.io/otel/attribute"
13+
"go.opentelemetry.io/otel/sdk/metric/internal/reservoir"
1314
)
1415

1516
// HistogramReservoirProvider is a provider of [HistogramReservoir].
@@ -39,6 +40,7 @@ var _ Reservoir = &HistogramReservoir{}
3940
// falls within a histogram bucket. The histogram bucket upper-boundaries are
4041
// define by bounds.
4142
type HistogramReservoir struct {
43+
reservoir.ConcurrentSafe
4244
*storage
4345

4446
// bounds are bucket bounds in ascending order.
@@ -57,14 +59,14 @@ type HistogramReservoir struct {
5759
// parameters are the value and dropped (filtered) attributes of the
5860
// measurement respectively.
5961
func (r *HistogramReservoir) Offer(ctx context.Context, t time.Time, v Value, a []attribute.KeyValue) {
60-
var x float64
62+
var n float64
6163
switch v.Type() {
6264
case Int64ValueType:
63-
x = float64(v.Int64())
65+
n = float64(v.Int64())
6466
case Float64ValueType:
65-
x = v.Float64()
67+
n = v.Float64()
6668
default:
6769
panic("unknown value type")
6870
}
69-
r.store[sort.SearchFloat64s(r.bounds, x)] = newMeasurement(ctx, t, v, a)
71+
r.store(sort.SearchFloat64s(r.bounds, n), newMeasurement(ctx, t, v, a))
7072
}

sdk/metric/exemplar/storage.go

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ package exemplar // import "go.opentelemetry.io/otel/sdk/metric/exemplar"
55

66
import (
77
"context"
8+
"sync"
89
"time"
910

1011
"go.opentelemetry.io/otel/attribute"
@@ -13,24 +14,33 @@ import (
1314

1415
// storage is an exemplar storage for [Reservoir] implementations.
1516
type storage struct {
16-
// store are the measurements sampled.
17+
mu sync.Mutex
18+
// measurements are the measurements sampled.
1719
//
1820
// This does not use []metricdata.Exemplar because it potentially would
1921
// require an allocation for trace and span IDs in the hot path of Offer.
20-
store []measurement
22+
measurements []measurement
2123
}
2224

2325
func newStorage(n int) *storage {
24-
return &storage{store: make([]measurement, n)}
26+
return &storage{measurements: make([]measurement, n)}
27+
}
28+
29+
func (r *storage) store(idx int, m measurement) {
30+
r.mu.Lock()
31+
defer r.mu.Unlock()
32+
r.measurements[idx] = m
2533
}
2634

2735
// Collect returns all the held exemplars.
2836
//
2937
// The Reservoir state is preserved after this call.
3038
func (r *storage) Collect(dest *[]Exemplar) {
31-
*dest = reset(*dest, len(r.store), len(r.store))
39+
r.mu.Lock()
40+
defer r.mu.Unlock()
41+
*dest = reset(*dest, len(r.measurements), len(r.measurements))
3242
var n int
33-
for _, m := range r.store {
43+
for _, m := range r.measurements {
3444
if !m.valid {
3545
continue
3646
}

sdk/metric/internal/aggregate/filtered_reservoir.go

Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,12 @@ package aggregate // import "go.opentelemetry.io/otel/sdk/metric/internal/aggreg
55

66
import (
77
"context"
8+
"sync"
89
"time"
910

1011
"go.opentelemetry.io/otel/attribute"
1112
"go.opentelemetry.io/otel/sdk/metric/exemplar"
13+
"go.opentelemetry.io/otel/sdk/metric/internal/reservoir"
1214
)
1315

1416
// FilteredExemplarReservoir wraps a [exemplar.Reservoir] with a filter.
@@ -29,6 +31,11 @@ type FilteredExemplarReservoir[N int64 | float64] interface {
2931
type filteredExemplarReservoir[N int64 | float64] struct {
3032
filter exemplar.Filter
3133
reservoir exemplar.Reservoir
34+
// The exemplar.Reservoir is not required to be concurrent safe, but
35+
// implementations can indicate that they are concurrent-safe by embedding
36+
// reservoir.ConcurrentSafe in order to improve performance.
37+
reservoirMux sync.Mutex
38+
concurrentSafe bool
3239
}
3340

3441
// NewFilteredExemplarReservoir creates a [FilteredExemplarReservoir] which only offers values
@@ -37,17 +44,30 @@ func NewFilteredExemplarReservoir[N int64 | float64](
3744
f exemplar.Filter,
3845
r exemplar.Reservoir,
3946
) FilteredExemplarReservoir[N] {
47+
_, concurrentSafe := r.(reservoir.ConcurrentSafe)
4048
return &filteredExemplarReservoir[N]{
41-
filter: f,
42-
reservoir: r,
49+
filter: f,
50+
reservoir: r,
51+
concurrentSafe: concurrentSafe,
4352
}
4453
}
4554

4655
func (f *filteredExemplarReservoir[N]) Offer(ctx context.Context, val N, attr []attribute.KeyValue) {
4756
if f.filter(ctx) {
57+
ts := time.Now()
58+
if !f.concurrentSafe {
59+
f.reservoirMux.Lock()
60+
defer f.reservoirMux.Unlock()
61+
}
4862
// only record the current time if we are sampling this measurement.
49-
f.reservoir.Offer(ctx, time.Now(), exemplar.NewValue(val), attr)
63+
f.reservoir.Offer(ctx, ts, exemplar.NewValue(val), attr)
5064
}
5165
}
5266

53-
func (f *filteredExemplarReservoir[N]) Collect(dest *[]exemplar.Exemplar) { f.reservoir.Collect(dest) }
67+
func (f *filteredExemplarReservoir[N]) Collect(dest *[]exemplar.Exemplar) {
68+
if !f.concurrentSafe {
69+
f.reservoirMux.Lock()
70+
defer f.reservoirMux.Unlock()
71+
}
72+
f.reservoir.Collect(dest)
73+
}
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package aggregate // import "go.opentelemetry.io/otel/sdk/metric/internal/aggregate"
5+
6+
import (
7+
"context"
8+
"sync"
9+
"testing"
10+
"time"
11+
12+
"github.com/stretchr/testify/assert"
13+
14+
"go.opentelemetry.io/otel/attribute"
15+
"go.opentelemetry.io/otel/sdk/metric/exemplar"
16+
"go.opentelemetry.io/otel/sdk/metric/internal/reservoir"
17+
)
18+
19+
func TestConcurrentSafeFilteredReservoir(t *testing.T) {
20+
for _, tc := range []struct {
21+
desc string
22+
reservoir exemplar.Reservoir
23+
expectConcurrentSafe bool
24+
}{
25+
{
26+
desc: "concurrent safe",
27+
reservoir: &concurrentSafeReservoir{},
28+
expectConcurrentSafe: true,
29+
},
30+
{
31+
desc: "not concurrent safe",
32+
reservoir: &notConcurrentSafeReservoir{},
33+
expectConcurrentSafe: false,
34+
},
35+
} {
36+
t.Run(tc.desc, func(t *testing.T) {
37+
reservoir := NewFilteredExemplarReservoir[int64](exemplar.AlwaysOnFilter, tc.reservoir)
38+
var wg sync.WaitGroup
39+
for range 5 {
40+
wg.Add(1)
41+
go func() {
42+
reservoir.Offer(t.Context(), 25, []attribute.KeyValue{})
43+
wg.Done()
44+
}()
45+
}
46+
into := []exemplar.Exemplar{}
47+
for range 2 {
48+
reservoir.Collect(&into)
49+
}
50+
wg.Wait()
51+
assert.Len(t, into, 1)
52+
assert.Equal(t, reservoir.(*filteredExemplarReservoir[int64]).concurrentSafe, tc.expectConcurrentSafe)
53+
})
54+
}
55+
}
56+
57+
type notConcurrentSafeReservoir struct {
58+
ex exemplar.Exemplar
59+
}
60+
61+
func (r *notConcurrentSafeReservoir) Offer(
62+
_ context.Context,
63+
t time.Time,
64+
val exemplar.Value,
65+
attr []attribute.KeyValue,
66+
) {
67+
r.ex = exemplar.Exemplar{
68+
FilteredAttributes: attr,
69+
Time: t,
70+
Value: val,
71+
}
72+
}
73+
74+
func (r *notConcurrentSafeReservoir) Collect(dest *[]exemplar.Exemplar) {
75+
*dest = make([]exemplar.Exemplar, 1)
76+
(*dest)[0].FilteredAttributes = r.ex.FilteredAttributes
77+
(*dest)[0].Time = r.ex.Time
78+
(*dest)[0].Value = r.ex.Value
79+
*dest = (*dest)[:1]
80+
}
81+
82+
type concurrentSafeReservoir struct {
83+
base notConcurrentSafeReservoir
84+
sync.Mutex
85+
reservoir.ConcurrentSafe
86+
}
87+
88+
func (r *concurrentSafeReservoir) Offer(
89+
ctx context.Context,
90+
t time.Time,
91+
val exemplar.Value,
92+
attr []attribute.KeyValue,
93+
) {
94+
r.Lock()
95+
defer r.Unlock()
96+
r.base.Offer(ctx, t, val, attr)
97+
}
98+
99+
func (r *concurrentSafeReservoir) Collect(dest *[]exemplar.Exemplar) {
100+
r.Lock()
101+
defer r.Unlock()
102+
r.base.Collect(dest)
103+
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
package reservoir // import "go.opentelemetry.io/otel/sdk/metric/internal/reservoir"
5+
6+
// ConcurrentSafe is an interface that can be embedded in an
7+
// exemplar.Reservoir to indicate to the SDK that it is safe to invoke its
8+
// methods concurrently. If this interface is not embedded, the SDK assumes it
9+
// is not safe to call concurrently and locks around Reservoir methods. This
10+
// is currently only used by the built-in reservoirs.
11+
type ConcurrentSafe interface{ concurrentSafe() }
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
// Copyright The OpenTelemetry Authors
2+
// SPDX-License-Identifier: Apache-2.0
3+
4+
// Package reservoir contains experimental features used by built-in exemplar
5+
// reservoirs which require coordination with the metrics SDK.
6+
package reservoir // import "go.opentelemetry.io/otel/sdk/metric/internal/reservoir"

0 commit comments

Comments
 (0)