Skip to content
Merged
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
4 changes: 2 additions & 2 deletions opentelemetry-sdk/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,8 @@
- **Breaking** Moved the following SDK sampling types from `opentelemetry::trace` to `opentelemetry_sdk::trace` [#3277][3277]:
- `SamplingDecision`, `SamplingResult`
- These types are SDK implementation details and should be imported from `opentelemetry_sdk::trace` instead.
- Fix panics and exploding memory usage from large cardinality limit [#3290][3290]
- `StreamBuilder::build()` now rejects `usize::MAX` as a cardinality limit
with a validation error.
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Add this PR's link.

- Fix Histogram boundaries being ignored in the presence of views [#3312][3312]
- `TracerProviderBuilder::with_sampler` allows to pass boxed instance of `ShouldSample` [#3313][3313]
- Fix ObservableCounter and ObservableUpDownCounter now correctly report only data points from the current measurement cycle, removing stale attribute combinations that are no longer observed. [#3248][3248]
Expand All @@ -58,7 +59,6 @@

[3227]: https://github.com/open-telemetry/opentelemetry-rust/pull/3227
[3277]: https://github.com/open-telemetry/opentelemetry-rust/pull/3277
[3290]: https://github.com/open-telemetry/opentelemetry-rust/pull/3290
[3312]: https://github.com/open-telemetry/opentelemetry-rust/pull/3312
[3248]: https://github.com/open-telemetry/opentelemetry-rust/pull/3248
[3262]: https://github.com/open-telemetry/opentelemetry-rust/pull/3262
Expand Down
23 changes: 22 additions & 1 deletion opentelemetry-sdk/src/metrics/instrument.rs
Original file line number Diff line number Diff line change
Expand Up @@ -259,6 +259,11 @@ impl StreamBuilder {
if limit == 0 {
return Err("Cardinality limit must be greater than 0".into());
}
// Reject usize::MAX because the SDK's internal HashMap capacity
// is sized as `1 + cardinality_limit`, which would overflow.
if limit == usize::MAX {
return Err("Cardinality limit must be less than usize::MAX".into());
}
}

// Validate bucket boundaries if using ExplicitBucketHistogram
Expand Down Expand Up @@ -544,8 +549,24 @@ mod tests {
"Expected cardinality limit validation error message"
);

// Test usize::MAX (invalid — would overflow internal `1 + limit` capacity)
let builder = StreamBuilder::new()
.with_name("valid_name")
.with_cardinality_limit(usize::MAX);

let result = builder.build();
assert!(
result.is_err(),
"Expected error for usize::MAX cardinality limit"
);
assert_eq!(
result.err().unwrap().to_string(),
"Cardinality limit must be less than usize::MAX",
"Expected cardinality limit usize::MAX error message"
);

// Test valid cardinality limits
let valid_limits = vec![1, 10, 100, 1000];
let valid_limits = vec![1, 10, 100, 1000, usize::MAX - 1];
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Marking usize::MAX - 1 as valid here is misleading. It avoids the 1 + cardinality_limit overflow, but ValueMap::new() then calls HashMap::with_capacity(usize::MAX), which still fails at allocation. The PR fixes the arithmetic-overflow case correctly, but any sufficiently large limit will still fail loudly when the stream is first used.

Should we either tighten the validation to a sane upper bound, or decouple the initial HashMap capacity from the configured cardinality limit so a high limit doesn't force a huge upfront allocation?

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Exactly this. The fix that was reverted was not centered around the integer overflow. The fix was to avoid the huge (and wasteful) up-front allocation. By reverting my PR, a very real bug is being re-introduced.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

The discussion about disabling cardinality limit (modifying upfront allocation logic) is being tracked in this #3507. It would be worked upon after the 0.32 release.

for limit in valid_limits {
let builder = StreamBuilder::new()
.with_name("valid_name")
Expand Down
14 changes: 1 addition & 13 deletions opentelemetry-sdk/src/metrics/internal/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@ mod sum;
use core::fmt;
#[cfg(not(target_has_atomic = "64"))]
use portable_atomic::{AtomicI64, AtomicU64};
use std::cmp::min;
use std::collections::{HashMap, HashSet};
use std::ops::{Add, AddAssign, Sub};
use std::sync::atomic::{AtomicBool, AtomicUsize, Ordering};
Expand All @@ -25,7 +24,6 @@ use opentelemetry::otel_debug;
use opentelemetry::{otel_warn, KeyValue};

use super::data::{AggregatedMetrics, MetricData};
use super::pipeline::DEFAULT_CARDINALITY_LIMIT;

// TODO Replace it with LazyLock once it is stable
pub(crate) static STREAM_OVERFLOW_ATTRIBUTES: OnceLock<Vec<KeyValue>> = OnceLock::new();
Expand Down Expand Up @@ -113,9 +111,7 @@ where

fn new(config: A::InitConfig, cardinality_limit: usize) -> Self {
ValueMap {
trackers: RwLock::new(HashMap::with_capacity(
1 + min(DEFAULT_CARDINALITY_LIMIT, cardinality_limit),
)),
trackers: RwLock::new(HashMap::with_capacity(1 + cardinality_limit)),
no_attribute_tracker: Arc::new(TrackerEntry::new(&config)),
count: AtomicUsize::new(0),
config,
Expand Down Expand Up @@ -833,14 +829,6 @@ mod tests {
assert!(f64::abs(0.0 - value2) < 0.0001, "Incorrect second value");
}

#[test]
fn large_cardinality_limit() {
// This is a regression test for panics that used to occur for large cardinality limits

// Should not panic
let _value_map = ValueMap::<Assign<i64>>::new((), usize::MAX);
}

#[test]
fn stale_entry_evicts_both_unsorted_and_sorted_keys() {
// ValueMap stores two HashMap keys per attribute set: one for the insertion
Expand Down
2 changes: 1 addition & 1 deletion opentelemetry-sdk/src/metrics/pipeline.rs
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ impl fmt::Debug for Pipeline {
/// Single or multi-instrument callbacks
type GenericCallback = Arc<dyn Fn() + Send + Sync>;

pub(crate) const DEFAULT_CARDINALITY_LIMIT: usize = 2000;
const DEFAULT_CARDINALITY_LIMIT: usize = 2000;

#[derive(Default)]
struct PipelineInner {
Expand Down
Loading