-
Notifications
You must be signed in to change notification settings - Fork 514
refactor: PushMetricExporter interface #2921
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
Codecov ReportAttention: Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #2921 +/- ##
=======================================
- Coverage 81.3% 81.2% -0.2%
=======================================
Files 126 126
Lines 24254 24334 +80
=======================================
+ Hits 19736 19774 +38
- Misses 4518 4560 +42 ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
MetricReader. * Renamed ResourceMetricRef->ResourceMetric and made that iteration would borrow an item, which will allow improving underlying metric collection without any allocations in the future * Updated CHANGELOG.md
As discussed in slack with @cijothomas, the main change is that export doesn't allocate anything, but exposes borrowed data directly to There's one thing worth discussing. One option is to lock pipeline inside export function, but it's less user friendly experience, because we need to write extra boilerplate just to start collecting... let mut collector = metrics.start_collect(); // acquire lock, which is borrowed from metrics
let mut scope_iter = collector.iter(); // acquire iterator, which is borrowed from collector (underlying structure is hashmap, so it's not really possible to iterate it efficiently without creating iterator.)
while let Some(scope_metric) = scope_iter.next_scope_metric() {
...
} We could improve end user experience by removing one call, but that would require using self-referential struct (with Pin, and extra complexity to handle Drop properly, even though it would be hidden from end user, it's still unnecessary unsafe code). Current approach is more straight forward while let Some(scope_metric) = scope_iter.next_scope_metric() {
...
} More importantly, since we acquire the lock before calling export, I’m fairly confident that with the improvements from MetricReader #2917, we could switch to export(&mut self, ...). This would be beneficial if the exporter wants to cache data before exporting—for example, OTLP exporters could reuse allocated data instead of rebuilding the request object each time. For these reasons, I prefer sticking with the current solution for now and adding export(&mut self) in the future. |
Changes
Changed
PushMetricExporer::export
signature.fn export(&self, metrics: ResourceMetricsRef<'_>) -> ...
. It is very similar toLogExporter::export
, with one key difference: I do not exposeiter()
method on items (metrics) (to create iterator), but directly provide Iterator over data. This means, that it's only possible to iterate everything once.However, since I implement standard
Iterator
trait we still need to have all the items stored somewhere, so I'm not sure how much we can optimize internal implementation...If I would change
Iterator
to LendingIterator, it would be possible to collect/export all metrics without any memory allocation at all!So I have few questions for this PR:
Metric
instance on the stack (iterator itself) ?)ResourceMetrics
andScopeMetrics
will not be present anymore, so I could use these names here (it sounds better thanResourceMetricRef
...). In general, any suggestion for better naming is welcome :)P.S. I wasn't able to provide
set_resource(&mut self, resource: &Resource)
on thePushMetricExporter
(without makingPeriodicReader
very inefficient, and disabling async version of it) before MetricReader is refactored/improved #2917.Merge requirement checklist
CHANGELOG.md
files updated for non-trivial, user-facing changes