-
Notifications
You must be signed in to change notification settings - Fork 807
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
feat(sdk-metrics): implement MetricProducer specification #4007
Changes from all commits
2a5a31c
16381c1
8af0460
4ee2de1
15c1d39
f095627
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -18,7 +18,7 @@ import * as api from '@opentelemetry/api'; | |
import { AggregationTemporality } from './AggregationTemporality'; | ||
import { MetricProducer } from './MetricProducer'; | ||
import { CollectionResult } from './MetricData'; | ||
import { callWithTimeout } from '../utils'; | ||
import { FlatMap, callWithTimeout } from '../utils'; | ||
import { InstrumentType } from '../InstrumentDescriptor'; | ||
import { | ||
CollectionOptions, | ||
|
@@ -45,6 +45,13 @@ export interface MetricReaderOptions { | |
* not configured, cumulative is used for all instruments. | ||
*/ | ||
aggregationTemporalitySelector?: AggregationTemporalitySelector; | ||
/** | ||
* **Note, this option is experimental**. Additional MetricProducers to use as a source of | ||
* aggregated metric data in addition to the SDK's metric data. The resource returned by | ||
* these MetricProducers is ignored; the SDK's resource will be used instead. | ||
* @experimental | ||
*/ | ||
metricProducers?: MetricProducer[]; | ||
} | ||
|
||
/** | ||
|
@@ -55,8 +62,10 @@ export abstract class MetricReader { | |
// Tracks the shutdown state. | ||
// TODO: use BindOncePromise here once a new version of @opentelemetry/core is available. | ||
private _shutdown = false; | ||
// MetricProducer used by this instance. | ||
private _metricProducer?: MetricProducer; | ||
// Additional MetricProducers which will be combined with the SDK's output | ||
private _metricProducers: MetricProducer[]; | ||
// MetricProducer used by this instance which produces metrics from the SDK | ||
private _sdkMetricProducer?: MetricProducer; | ||
private readonly _aggregationTemporalitySelector: AggregationTemporalitySelector; | ||
private readonly _aggregationSelector: AggregationSelector; | ||
|
||
|
@@ -66,20 +75,26 @@ export abstract class MetricReader { | |
this._aggregationTemporalitySelector = | ||
options?.aggregationTemporalitySelector ?? | ||
DEFAULT_AGGREGATION_TEMPORALITY_SELECTOR; | ||
this._metricProducers = options?.metricProducers ?? []; | ||
} | ||
|
||
/** | ||
* Set the {@link MetricProducer} used by this instance. | ||
* Set the {@link MetricProducer} used by this instance. **This should only be called by the | ||
* SDK and should be considered internal.** | ||
* | ||
* To add additional {@link MetricProducer}s to a {@link MetricReader}, pass them to the | ||
* constructor as {@link MetricReaderOptions.metricProducers}. | ||
* | ||
* @internal | ||
* @param metricProducer | ||
*/ | ||
setMetricProducer(metricProducer: MetricProducer) { | ||
aabmass marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (this._metricProducer) { | ||
if (this._sdkMetricProducer) { | ||
throw new Error( | ||
'MetricReader can not be bound to a MeterProvider again.' | ||
); | ||
} | ||
this._metricProducer = metricProducer; | ||
this._sdkMetricProducer = metricProducer; | ||
this.onInitialized(); | ||
} | ||
|
||
|
@@ -130,7 +145,7 @@ export abstract class MetricReader { | |
* Collect all metrics from the associated {@link MetricProducer} | ||
*/ | ||
async collect(options?: CollectionOptions): Promise<CollectionResult> { | ||
if (this._metricProducer === undefined) { | ||
if (this._sdkMetricProducer === undefined) { | ||
throw new Error('MetricReader is not bound to a MetricProducer'); | ||
} | ||
|
||
|
@@ -139,9 +154,37 @@ export abstract class MetricReader { | |
throw new Error('MetricReader is shutdown'); | ||
} | ||
|
||
return this._metricProducer.collect({ | ||
timeoutMillis: options?.timeoutMillis, | ||
}); | ||
const [sdkCollectionResults, ...additionalCollectionResults] = | ||
await Promise.all([ | ||
this._sdkMetricProducer.collect({ | ||
timeoutMillis: options?.timeoutMillis, | ||
}), | ||
...this._metricProducers.map(producer => | ||
producer.collect({ | ||
timeoutMillis: options?.timeoutMillis, | ||
}) | ||
), | ||
]); | ||
|
||
// Merge the results, keeping the SDK's Resource | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should add multiple resources support to the metric reader and metric exporter instead of dropping resources silently -- the registered metric producer can be different meter providers that have different resources. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure how we could do that without making a breaking change. Also see this part of the spec:
That's not exactly the case in the JS implementation, but I think the spirit of it is to avoid this happening. I was actually thinking of making the MetricProducer only able to return ScopeMetrics to avoid the issue altogether. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
I like this idea. However, I wonder if we could do this without breaking the It's a tricky situation, but I think we can leave this as-is and then iterate later depending on the outcome of open-telemetry/opentelemetry-specification#3636 - the code proposed here never alters anything unless the user opts into using an experimental feature by setting the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is this OK with you for now @legendecas? |
||
const errors = sdkCollectionResults.errors.concat( | ||
FlatMap(additionalCollectionResults, result => result.errors) | ||
); | ||
const resource = sdkCollectionResults.resourceMetrics.resource; | ||
const scopeMetrics = | ||
sdkCollectionResults.resourceMetrics.scopeMetrics.concat( | ||
FlatMap( | ||
additionalCollectionResults, | ||
result => result.resourceMetrics.scopeMetrics | ||
) | ||
); | ||
return { | ||
resourceMetrics: { | ||
resource, | ||
scopeMetrics, | ||
}, | ||
errors, | ||
}; | ||
} | ||
|
||
/** | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Note the specification asks for a
RegisterMetricProducer
function https://github.com/open-telemetry/opentelemetry-specification/blob/v1.23.0/specification/metrics/sdk.md#registerproducermetricproducerHowever, I since we already have a public
setMetricProducer
which assumes it is receiving the SDK'sMetricCollector
, this seems like the cleanest solution without breaking anything.I will also check with the spec on this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Opened open-telemetry/opentelemetry-specification#3611
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I also think having it as a config makes the most sense in our case. If the spec wording gets changed then I'd much prefer having it be config-only. 🙂
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
open-telemetry/opentelemetry-specification#3613 has a few approvals now