Skip to content

Conversation

@emadpres
Copy link

@emadpres emadpres commented Nov 22, 2025

Redesigned InMemoryMetricExporter

Fixes #2725.

Changes

Updates InMemoryMetricExporter (similar to what was suggested in #2649) to accept and use a user-specified data collection to store the metrics data, rather than creating one internally and returning it eventually. This makes the exporter more consistent with the 'production' exporters, which are given their sink by the user (e.g., by being passed a gRPC channel to write to) - rather than owning where the data goes. This also means we no longer need to be able to clone the exporter simply to access the data back out in unit tests.

If we are happy with this approach, we can extend it to the other in memory exporters - InMemoryLogExporter and InMemorySpanExporter.

API Changes

  • The InMemoryMetricExporter now must be instanciated through InMemoryMetricExporterBuilder and it no longer has Default and Clone implementations.
  • InMemoryMetricExporterBuilder has the mandatory metrics: Arc<Mutex<Vec<_>>> field to be set.

Merge requirement checklist

  • CONTRIBUTING guidelines followed
  • Unit tests added/ updated
  • Appropriate CHANGELOG.md files updated for non-trivial, user-facing changes
  • Changes in public API reviewed (if applicable)

@emadpres emadpres requested a review from a team as a code owner November 22, 2025 14:32
@linux-foundation-easycla
Copy link

linux-foundation-easycla bot commented Nov 22, 2025

CLA Signed
The committers listed above are authorized under a signed CLA.

@codecov
Copy link

codecov bot commented Nov 22, 2025

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 80.9%. Comparing base (df412fe) to head (c6832c2).

Additional details and impacted files
@@           Coverage Diff           @@
##            main   #3257     +/-   ##
=======================================
+ Coverage   80.8%   80.9%   +0.1%     
=======================================
  Files        129     129             
  Lines      23203   23342    +139     
=======================================
+ Hits       18750   18892    +142     
+ Misses      4453    4450      -3     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.

.exporter
.get_finished_metrics()
.expect("metrics expected to be exported");
f: impl FnOnce(&MetricData<T>),
Copy link
Member

Choose a reason for hiding this comment

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

Also generally I think the callback here is nice way of dealing with borrowing access and avoiding cloning to the stored data, but perhaps there is an option i'm missing. We eat a little bit of complexity in the impl here, but the usage is clean.

Copy link
Member

@scottgerring scottgerring left a comment

Choose a reason for hiding this comment

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

Hey @emadpres thanks for the PR! 🤝
Some comments inline; lmk what you think

pub fn build(self) -> InMemoryMetricExporter {
InMemoryMetricExporter {
metrics: Arc::new(Mutex::new(VecDeque::new())),
metrics: self.metrics.expect("Metrics must be provided"),
Copy link
Member

Choose a reason for hiding this comment

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

Should we return a Result here rather than paninicng?

Comment on lines 18 to +20
/// This exporter is useful for testing and debugging purposes. It stores
/// metric data in a `VecDeque<ResourceMetrics>`. Metrics can be retrieved
/// using the `get_finished_metrics` method.
/// metric data in a user-provided `Vec<ResourceMetrics>`, from which the
/// exported data can be retrieved as well.
Copy link
Member

Choose a reason for hiding this comment

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

I think we should be more explicit here. Something like this perhaps?

Suggested change
/// This exporter is useful for testing and debugging purposes. It stores
/// metric data in a `VecDeque<ResourceMetrics>`. Metrics can be retrieved
/// using the `get_finished_metrics` method.
/// metric data in a user-provided `Vec<ResourceMetrics>`, from which the
/// exported data can be retrieved as well.
/// This exporter is useful for testing and debugging purposes. It stores
/// metric data in a user-provided `Arc<Mutex<Vec<ResourceMetrics>>>`. Users
/// retain a clone of this `Arc` and can access the exported metrics directly
/// by locking the mutex.

.get_finished_metrics()
.expect("metrics expected to be exported");

let resource_metrics = test_context.resource_metrics.lock().unwrap();
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
let resource_metrics = test_context.resource_metrics.lock().unwrap();
let resource_metrics = test_context.resource_metrics.lock().expect("lock poisoned");

.expect("metrics are expected to be exported.");
assert!(
resource_metrics[0].scope_metrics[0].metrics.len() == 1,
let resource_metrics = metrics.lock().unwrap();
Copy link
Member

Choose a reason for hiding this comment

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

I suggest using expect("lock poisoned") for the rest of these too - it captures intent and gives a clearer error message if it ever fails


assert!(
self.resource_metrics.len() == 1,
assert_eq!(
Copy link
Member

Choose a reason for hiding this comment

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

Nice catch!

assert!(resource_metrics.is_empty(), "no metrics should be exported");
}

fn get_aggregation<T: Number>(
fn with_aggregation<T: Number>(
&mut self,
Copy link
Member

Choose a reason for hiding this comment

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

I don't think this needs a mutable ref anymore

Comment on lines +8 to +10
- **Breaking** Removed `Default` and `Clone` implementation from `InMemoryMetricExporter`.
- **Breaking** `InMemoryMetricExporterBuilder` requires mandatory `metrics` field to be set via
`.with_metrics` method.
Copy link
Member

Choose a reason for hiding this comment

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

I think we should phrase this so the user knows what the impact on them is. Something like this perhaps?:

Suggested change
- **Breaking** Removed `Default` and `Clone` implementation from `InMemoryMetricExporter`.
- **Breaking** `InMemoryMetricExporterBuilder` requires mandatory `metrics` field to be set via
`.with_metrics` method.
- **Breaking** Redesigned `InMemoryMetricExporter` to accept user-provided storage instead of managing it internally.
Users now pass an `Arc<Mutex<Vec<ResourceMetrics>>>` via `InMemoryMetricExporterBuilder::with_metrics()` and access
exported data directly through that reference. This removes the need to clone the exporter and aligns with how
production exporters receive their sink. `Default` and `Clone` implementations have been removed.
[#3257](https://github.com/open-telemetry/opentelemetry-rust/pull/3257)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Redesign InMemoryExporter

2 participants