"use strict";
/*
 * Copyright The OpenTelemetry Authors
 * SPDX-License-Identifier: Apache-2.0
 */
Object.defineProperty(exports, "__esModule", { value: true });
exports.MetricReader = void 0;
const api = require("@opentelemetry/api");
const utils_1 = require("../utils");
const AggregationSelector_1 = require("./AggregationSelector");
const MetricReaderMetrics_1 = require("./MetricReaderMetrics");
const version_1 = require("../version");
const core_1 = require("@opentelemetry/core");
/**
 * A registered reader of metrics that, when linked to a {@link MetricProducer}, offers global
 * control over metrics.
 */
class MetricReader {
    // Tracks the shutdown state.
    // TODO: use BindOncePromise here once a new version of @opentelemetry/core is available.
    _shutdown = false;
    // Additional MetricProducers which will be combined with the SDK's output
    _metricProducers;
    // MetricProducer used by this instance which produces metrics from the SDK
    _sdkMetricProducer;
    // Metrics about the MetricReader itself
    _selfObsMetrics;
    _aggregationTemporalitySelector;
    _aggregationSelector;
    _cardinalitySelector;
    _otelComponentType;
    constructor(options) {
        this._aggregationSelector =
            options?.aggregationSelector ?? AggregationSelector_1.DEFAULT_AGGREGATION_SELECTOR;
        this._aggregationTemporalitySelector =
            options?.aggregationTemporalitySelector ??
                AggregationSelector_1.DEFAULT_AGGREGATION_TEMPORALITY_SELECTOR;
        this._metricProducers = options?.metricProducers ?? [];
        this._cardinalitySelector = options?.cardinalitySelector;
        this._otelComponentType =
            options?.otelComponentType ?? this.constructor.name;
        this._selfObsMetrics = new MetricReaderMetrics_1.MetricReaderMetrics(this._otelComponentType, api.createNoopMeter());
    }
    setMetricProducer(metricProducer) {
        if (this._sdkMetricProducer) {
            // This check ensures the following requirement from the spec
            // (https://opentelemetry.io/docs/specs/otel/metrics/sdk/#metricreader):
            // > The SDK MUST NOT allow a `MetricReader` instance to be registered
            // > on more than one `MeterProvider` instance.
            //
            // So while the argument is a `MetricProducer`, the relevant user-level
            // error message is about the **MeterProvider**.
            throw new Error('MetricReader can not be bound to a MeterProvider again.');
        }
        this._sdkMetricProducer = metricProducer;
        this.onInitialized();
    }
    _setSelfObsMeterProvider(meterProvider) {
        const meter = meterProvider.getMeter('@opentelemetry/sdk-metrics', version_1.VERSION);
        this._selfObsMetrics = new MetricReaderMetrics_1.MetricReaderMetrics(this._otelComponentType, meter);
    }
    selectAggregation(instrumentType) {
        return this._aggregationSelector(instrumentType);
    }
    selectAggregationTemporality(instrumentType) {
        return this._aggregationTemporalitySelector(instrumentType);
    }
    selectCardinalityLimit(instrumentType) {
        return this._cardinalitySelector
            ? this._cardinalitySelector(instrumentType)
            : 2000; // default value if no selector is provided
    }
    /**
     * Handle once the SDK has initialized this {@link MetricReader}
     * Overriding this method is optional.
     */
    onInitialized() {
        // Default implementation is empty.
    }
    async collect(options) {
        if (this._sdkMetricProducer === undefined) {
            throw new Error('MetricReader is not bound to a MetricProducer');
        }
        // Subsequent invocations to collect are not allowed. SDKs SHOULD return some failure for these calls.
        if (this._shutdown) {
            throw new Error('MetricReader is shutdown');
        }
        const startTime = (0, core_1.hrTime)();
        const [sdkCollectionResults, ...additionalCollectionResults] = await Promise.all([
            this._sdkMetricProducer.collect({
                timeoutMillis: options?.timeoutMillis,
            }),
            ...this._metricProducers.map(producer => producer.collect({
                timeoutMillis: options?.timeoutMillis,
            })),
        ]);
        const endTime = (0, core_1.hrTime)();
        // Merge the results, keeping the SDK's Resource
        const errors = sdkCollectionResults.errors.concat(additionalCollectionResults.flatMap(result => result.errors));
        const collectDuration = (0, core_1.hrTimeToSeconds)((0, core_1.hrTimeDuration)(startTime, endTime));
        this._selfObsMetrics.recordCollection(collectDuration, errors.length > 0
            ? (errors[0].name ?? 'collect_error')
            : undefined);
        const resource = sdkCollectionResults.resourceMetrics.resource;
        const scopeMetrics = sdkCollectionResults.resourceMetrics.scopeMetrics.concat(additionalCollectionResults.flatMap(result => result.resourceMetrics.scopeMetrics));
        return {
            resourceMetrics: {
                resource,
                scopeMetrics,
            },
            errors,
        };
    }
    async shutdown(options) {
        // Do not call shutdown again if it has already been called.
        if (this._shutdown) {
            api.diag.error('Cannot call shutdown twice.');
            return;
        }
        // No timeout if timeoutMillis is undefined or null.
        if (options?.timeoutMillis == null) {
            await this.onShutdown();
        }
        else {
            await (0, utils_1.callWithTimeout)(this.onShutdown(), options.timeoutMillis);
        }
        this._shutdown = true;
    }
    async forceFlush(options) {
        if (this._shutdown) {
            api.diag.warn('Cannot forceFlush on already shutdown MetricReader.');
            return;
        }
        // No timeout if timeoutMillis is undefined or null.
        if (options?.timeoutMillis == null) {
            await this.onForceFlush();
            return;
        }
        await (0, utils_1.callWithTimeout)(this.onForceFlush(), options.timeoutMillis);
    }
}
exports.MetricReader = MetricReader;
//# sourceMappingURL=MetricReader.js.map