Repository navigation
Conversation
Pin open-telemetry/opentelemetry-rust at opentelemetry_sdk-0.33.1 and add restore/backup scripts that copy opentelemetry-rust-patch/ onto the submodule and back, written like the opentelemetry-go ones. setup.sh now initialises the new submodule.
Two new Aggregation variants, selected through a View's Stream like any
other aggregation:
- RowSampled { rows, sample_p }: each of the rows keeps each measurement
with probability sample_p (NitroSketch-style), for Count-Min and
Count Sketch built in the collector.
- Sampled { sample_p, tau }: keeps each measurement with probability
sample_p and always keeps values above tau, for DDSketch.
Each kept measurement is exported as one Gauge data point with its raw
value, its own timestamp and the reserved attributes the Go SDK patch
uses (__asap_row_sampled_admitted_rows, __asap_row_sampled_rows,
__asap_row_sampled_sample_p). One seeded geometric skip counter per
stream decides admission. Observable instruments reject both variants,
and invalid parameters fail when the Stream is built.
The OTLP transform now uses a Gauge point's own time when it has one, so each sampled measurement keeps its timestamp on the wire. A new opentelemetry-proto test builds a MeterProvider with Views selecting both sampling aggregations, exports through the in-memory exporter, encodes to OTLP and checks the reserved attributes.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
feat(rust-sdk): sampling aggregations in a patched opentelemetry-rust SDK
Why
The data plane is moving to Rust: Rust OTel SDK, then the Rust collector
asap-precompute-rs(builds sketches per tumbling window withasap_sketchlib), then a Rust sink. The Go SDK patch already samples raw
measurements in the application before they are serialized, so the collector
builds sketches from a fraction of the traffic. Upstream opentelemetry-rust
only has six fixed aggregations and no per-measurement hook, so a Rust
application has no way to do the same thing.
What
opentelemetry-rustadded as a submodule atopentelemetry_sdk-0.33.1,with an overlay
opentelemetry-rust-patch/(only the changed and addedfiles) and
restore_opentelemetry_rust_patches.sh/backup_opentelemetry_rust_patches.sh, written like the Go ones and run byrestore_otel_patches.sh/backup_otel_patches.sh.setup.shinitialisesthe new submodule.
Aggregationvariants inopentelemetry_sdk:RowSampled { rows, sample_p }for Count-Min and Count Sketch.Sampled { sample_p, tau }for DDSketch.reserved attributes as the Go SDK patch, so the collector reads one format.
opentelemetry-proto's OTLP transform uses a Gauge point's own time whenit has one.
The application uses the SDK as usual (Meter, instruments,
add/record),turns sampling on with a View, and exports with the standard
opentelemetry-otlpexporter.How
opentelemetry-sdk/src/metrics/internal/sampled.rs(new) is one aggregatorfor both variants:
RowSampledmaps torowsrows,Sampledto one rowplus
tau. It plugs into the existing plumbing (AggregateBuilder::sampled,pipeline::aggregate_fn,is_aggregator_compatible), next toLastValue.tauis set and value >tau, keepit with
sample_p = 1.0; otherwise ask the skip counter for the row mask.A non-zero mask stores one
GaugeDataPointwith the filtered attributes,the three reserved attributes, the raw value and
now().semantics whatever the reader's temporality, as in the Go patch, since a
kept measurement is never re-exported).
GaugeDataPointgets a crate-privatetime: Option<SystemTime>with apublic
time()getter.LastValuesetsNone. The OTLP transform usesdp.time().unwrap_or(gauge.time()).StreamBuilder::buildrunsAggregation::validatefor the newvariants (
rowsin1..=64,sample_pin(0, 1],taufinite), soinvalid parameters fail when the View's Stream is built.
Observable instruments report one value per callback, so there is nothing
to sample. They reject the variants, and as for any incompatible View the
SDK logs a warning and falls back to the default aggregation.
rand(already an optional SDK dependency) is now part of themetricsfeature, for
SmallRng.Key code interfaces added / modified
Reserved OTLP attributes on each exported point (same keys and types as the Go
patch and
asapedgeprocessor'sextractRowSampledMeta):__asap_row_sampled_admitted_rowsrset when rowrkept the measurement (uint64bits asint64)__asap_row_sampled_rowsrows(1 forSampled)__asap_row_sampled_sample_psample_p(1.0 for atautail value)Usage
Run
./restore_otel_patches.sh(or./restore_opentelemetry_rust_patches.sh)first so the submodule contains the overlay.
Major algorithm changes
Geometric skip sampling (NitroSketch). Instead of a Bernoulli(p) coin per
(measurement, row) pair, the sampler draws the number of pairs to skip before
the next kept one,
gap ~ Geometric(p)withP(gap = k) = (1 - p)^k p, byinverting its CDF:
gap = floor(ln(U) / ln(1 - p)),Uuniform in(0, 1].The kept positions have the same distribution as independent Bernoulli(p)
coins, so the collector's ×1/p rescale stays unbiased.
Per-row sampling uses one counter over the flattened stream of
(measurement, row) pairs, as the Go
common.GeometricSampler.AdmitRowsdoes.For a measurement with
drows:skip >= d: no row keeps it;skip -= dand the measurement is dropped(one comparison and one subtraction, no RNG draw, no allocation).
skip, draw the next gap, jump ahead, and repeat untilthe position passes
d; the remainder becomes the newskip.Each (measurement, row) pair is then kept independently with probability
p, so rowrkeeps each measurement with probabilitypand a measurementis exported with probability
1 - (1 - p)^d.sample_p = 1skips the RNG andkeeps every row.
Sampledis thed = 1case. Values abovetauare keptoutside the sampled stream and do not move the counter.
There is one counter per stream (instrument + View), shared by all attribute
sets, seeded with a fixed constant so runs and tests are reproducible.
Before this PR
A Rust application could only export the six upstream aggregations. To feed a
collector-side Count-Min sketch it had to export every raw measurement, or
pre-aggregate and lose the per-measurement keys.
After this PR
With
RowSampled { rows: 4, sample_p: 0.25 },requests.add(3, [method=GET])is exported (when at least one row keeps it) as:
A
Sampled { sample_p: 0.01, tau: Some(500.0) }value of 812.0 is alwaysexported with
admitted_rows = 1,rows = 1,sample_p = 1.0. A kept valuebelow
tauhassample_p = 0.01. Measurements that no row keeps are neverexported.
Verification
Run from the restored submodule (
./restore_opentelemetry_rust_patches.sh,then
cd opentelemetry-rust):cargo test -p opentelemetry_sdk --lib --all-features: 420 passed, 25 ignored(upstream suite plus the new tests).
metrics::internal::sampled::tests(100k measurements each; counts within5 standard deviations of the binomial mean): admission rate for
sample_p = 0.1; every value abovetaukept withsample_p = 1.0and therest sampled at
0.01; per-row frequencypand export rate1 - (1 - p)^dforrows = 4, p = 0.25;sample_p = 1with 64 rows givesmask
-1; collect clears and NaN/inf are skipped; raw value, filteredattributes and per-point time are kept.
aggregation::tests::validate_aggregation,instrument::tests::stream_sampling_aggregation_validation: invalidrows/sample_p/taurejected.cargo test -p opentelemetry-proto --all-features --test asap_sampling:MeterProvider with Views selecting both variants, in-memory exporter, OTLP
encoding; checks values, attributes, per-point times, the three reserved
attributes, and that an observable gauge falls back to its default
aggregation.
cargo fmt --all -- --check;cargo clippy -p opentelemetry_sdk -p opentelemetry-proto --all-targets --all-features -- -Dwarnings: clean.cargo check -p opentelemetry_sdk --no-default-features --features metricsand
cargo check -p opentelemetry-otlp -p opentelemetry-stdout --all-features: build.restore_otel_patches.shreproducesthe same diff.
[patch.crates-io]resolution checked withcargo treein a scratch cratethat uses crates.io
opentelemetry-otlp0.33: every opentelemetry crateresolves to the submodule, with no duplicates.