Skip to content
Open
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: 3 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,9 @@ For notes on migrating to 2.x / 0.200.x see [the upgrade guide](doc/upgrade-to-2

### :rocket: Features

* feat(sdk-logs): implement log creation metrics [#6433](https://github.com/open-telemetry/opentelemetry-js/pull/6433) @anuraaga
* feat(sdk-metrics): implement metric reader metrics [#6449](https://github.com/open-telemetry/opentelemetry-js/pull/6449) @anuraaga
* feat(core): add `hrTimeToSeconds` [#6449](https://github.com/open-telemetry/opentelemetry-js/pull/6449) @anuraaga
* feat(sdk-logs): implement log creation metrics [#6433](https://github.com/open-telemetry/opentelemetry-js/pull/6433) @anuraaga

### :bug: Bug Fixes

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import type { IncomingMessage, Server, ServerResponse } from 'http';
import { createServer } from 'http';
import type { ExporterConfig } from './export/types';
import { PrometheusSerializer } from './PrometheusSerializer';
import { OTEL_COMPONENT_TYPE_VALUE_PROMETHEUS_HTTP_TEXT_METRIC_EXPORTER } from './semconv';
/** Node.js v8.x compat */
import { URL } from 'url';

Expand Down Expand Up @@ -60,6 +61,8 @@ export class PrometheusExporter extends MetricReader {
},
aggregationTemporalitySelector: _instrumentType =>
AggregationTemporality.CUMULATIVE,
otelComponentType:
OTEL_COMPONENT_TYPE_VALUE_PROMETHEUS_HTTP_TEXT_METRIC_EXPORTER,
metricProducers: config.metricProducers,
});
this._host =
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

/*
* This file contains a copy of unstable semantic convention definitions
* used by this package.
* @see https://github.com/open-telemetry/opentelemetry-js/tree/main/semantic-conventions#unstable-semconv
*/

/**
* Enum value "prometheus_http_text_metric_exporter" for attribute {@link ATTR_OTEL_COMPONENT_TYPE}.
*
* Prometheus metric exporter over HTTP with the default text-based format
*
* @experimental This enum value is experimental and is subject to breaking changes in minor releases of `@opentelemetry/semantic-conventions`.
*/
export const OTEL_COMPONENT_TYPE_VALUE_PROMETHEUS_HTTP_TEXT_METRIC_EXPORTER =
'prometheus_http_text_metric_exporter' as const;
1 change: 1 addition & 0 deletions experimental/packages/opentelemetry-sdk-node/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -243,6 +243,7 @@ OTEL_NODE_EXPERIMENTAL_SDK_METRICS=true
Currently a subset of the specified metrics are implemented. See the following
linkes for details:

- Metric reader metrics: [MetricReaderMetrics](../../../packages//sdk-metrics/src/export/MetricReaderMetrics.ts)
- Logger metrics: [LoggerMetrics.ts](../sdk-logs/src/LoggerMetrics.ts)
- Span metrics: [TracerMetrics.ts](../../../packages/opentelemetry-sdk-trace-base/src/TracerMetrics.ts)

Expand Down
1 change: 1 addition & 0 deletions experimental/packages/opentelemetry-sdk-node/src/sdk.ts
Original file line number Diff line number Diff line change
Expand Up @@ -327,6 +327,7 @@ export class NodeSDK {
resource: this._resource,
views: this._meterProviderConfig?.views ?? [],
readers: this._meterProviderConfig.readers,
sdkMetricsEnabled,
});

this._meterProvider = meterProvider;
Expand Down
13 changes: 11 additions & 2 deletions experimental/packages/opentelemetry-sdk-node/test/sdk.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,7 @@ import { ZipkinExporter } from '@opentelemetry/exporter-zipkin';

import { NOOP_COUNTER_METRIC } from '../../../../api/src/metrics/NoopMeter';
import { ATTR_HOST_NAME, ATTR_PROCESS_PID } from '../src/semconv';
import { NOOP_HISTOGRAM_METRIC } from '../../../../api/src/metrics/NoopMeter';

function assertDefaultContextManagerRegistered() {
assert.ok(
Expand Down Expand Up @@ -419,7 +420,7 @@ describe('Node SDK', () => {
});

const sdk = new NodeSDK({
metricReader: metricReader,
metricReaders: [metricReader],
traceExporter: new ConsoleSpanExporter(),
logRecordProcessors: [
new SimpleLogRecordProcessor(new InMemoryLogRecordExporter()),
Expand All @@ -446,6 +447,10 @@ describe('Node SDK', () => {
);

assert.ok(metrics.getMeterProvider() instanceof MeterProvider);
assert.notDeepEqual(
(metricReader as any)._metrics.collectionDuration,
NOOP_HISTOGRAM_METRIC
);

await sdk.shutdown();
});
Expand All @@ -459,7 +464,7 @@ describe('Node SDK', () => {
});

const sdk = new NodeSDK({
metricReader: metricReader,
metricReaders: [metricReader],
traceExporter: new ConsoleSpanExporter(),
logRecordProcessors: [
new SimpleLogRecordProcessor(new InMemoryLogRecordExporter()),
Expand All @@ -484,6 +489,10 @@ describe('Node SDK', () => {
);

assert.ok(metrics.getMeterProvider() instanceof MeterProvider);
assert.deepEqual(
(metricReader as any)._metrics.collectionDuration,
NOOP_HISTOGRAM_METRIC
);

await sdk.shutdown();
});
Expand Down
15 changes: 11 additions & 4 deletions packages/opentelemetry-core/src/common/time.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,6 +110,14 @@ export function hrTimeToNanoseconds(time: api.HrTime): number {
return time[0] * SECOND_TO_NANOSECONDS + time[1];
}

/**
* Convert hrTime to microseconds.
* @param time
*/
export function hrTimeToMicroseconds(time: api.HrTime): number {
return time[0] * 1e6 + time[1] / 1e3;
}

/**
* Convert hrTime to milliseconds.
* @param time
Expand All @@ -119,13 +127,12 @@ export function hrTimeToMilliseconds(time: api.HrTime): number {
}

/**
* Convert hrTime to microseconds.
* Convert hrTime to seconds.
* @param time
*/
export function hrTimeToMicroseconds(time: api.HrTime): number {
return time[0] * 1e6 + time[1] / 1e3;
export function hrTimeToSeconds(time: api.HrTime): number {
return time[0] + time[1] / SECOND_TO_NANOSECONDS;
}

/**
* check if time is HrTime
* @param value
Expand Down
1 change: 1 addition & 0 deletions packages/opentelemetry-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ export {
hrTimeToMicroseconds,
hrTimeToMilliseconds,
hrTimeToNanoseconds,
hrTimeToSeconds,
hrTimeToTimeStamp,
isTimeInput,
isTimeInputHrTime,
Expand Down
19 changes: 14 additions & 5 deletions packages/opentelemetry-core/test/common/time.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,9 @@ import {
timeInputToHrTime,
hrTimeDuration,
hrTimeToNanoseconds,
hrTimeToMilliseconds,
hrTimeToMicroseconds,
hrTimeToMilliseconds,
hrTimeToSeconds,
hrTimeToTimeStamp,
isTimeInput,
addHrTimes,
Expand Down Expand Up @@ -164,19 +165,27 @@ describe('time', () => {
});
});

describe('#hrTimeToMicroseconds', () => {
it('should return microseconds', () => {
const output = hrTimeToMicroseconds([1, 200000000]);
assert.deepStrictEqual(output, 1200000);
});
});

describe('#hrTimeToMilliseconds', () => {
it('should return milliseconds', () => {
const output = hrTimeToMilliseconds([1, 200000000]);
assert.deepStrictEqual(output, 1200);
});
});

describe('#hrTimeToMicroseconds', () => {
it('should return microseconds', () => {
const output = hrTimeToMicroseconds([1, 200000000]);
assert.deepStrictEqual(output, 1200000);
describe('#hrTimeToSeconds', () => {
it('should return seconds', () => {
const output = hrTimeToSeconds([1, 200000000]);
assert.deepStrictEqual(output, 1.2);
});
});

describe('#isTimeInput', () => {
it('should return true for a number', () => {
assert.strictEqual(isTimeInput(12), true);
Expand Down
11 changes: 10 additions & 1 deletion packages/sdk-metrics/src/MeterProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ import type {
import { diag, createNoopMeter } from '@opentelemetry/api';
import type { Resource } from '@opentelemetry/resources';
import { defaultResource } from '@opentelemetry/resources';
import type { IMetricReader } from './export/MetricReader';
import { MetricReader, type IMetricReader } from './export/MetricReader';
import { MeterProviderSharedState } from './state/MeterProviderSharedState';
import { MetricCollector } from './state/MetricCollector';
import type { ForceFlushOptions, ShutdownOptions } from './types';
Expand All @@ -26,6 +26,12 @@ export interface MeterProviderOptions {
resource?: Resource;
views?: ViewOptions[];
readers?: IMetricReader[];

/**
* Whether to enable SDK metrics for this meter provider.
* @experimental This option is experimental and is subject to breaking changes in minor releases.
*/
sdkMetricsEnabled?: boolean;
}

/**
Expand All @@ -50,6 +56,9 @@ export class MeterProvider implements IMeterProvider {
const collector = new MetricCollector(this._sharedState, metricReader);
metricReader.setMetricProducer(collector);
this._sharedState.metricCollectors.push(collector);
if (options.sdkMetricsEnabled && metricReader instanceof MetricReader) {
metricReader._setMeterProvider(this);
}
}
}
}
Expand Down
33 changes: 33 additions & 0 deletions packages/sdk-metrics/src/export/MetricReader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,9 @@ import {
} from './AggregationSelector';
import type { AggregationOption } from '../view/AggregationOption';
import type { CardinalitySelector } from './CardinalitySelector';
import { MetricReaderMetrics } from './MetricReaderMetrics';
import { VERSION } from '../version';
import { hrTime, hrTimeDuration, hrTimeToSeconds } from '@opentelemetry/core';

export interface MetricReaderOptions {
/**
Expand Down Expand Up @@ -54,6 +57,11 @@ export interface MetricReaderOptions {
* @experimental
*/
metricProducers?: MetricProducer[];
/**
* The component type used for reporting SDK metrics.
* @experimental This option is experimental and is subject to breaking changes in minor releases.
*/
otelComponentType?: string;
}

/**
Expand Down Expand Up @@ -132,9 +140,12 @@ export abstract class MetricReader implements IMetricReader {
private _metricProducers: MetricProducer[];
// MetricProducer used by this instance which produces metrics from the SDK
private _sdkMetricProducer?: MetricProducer;
// Metrics about the MetricReader itself
private _metrics: MetricReaderMetrics;
private readonly _aggregationTemporalitySelector: AggregationTemporalitySelector;
private readonly _aggregationSelector: AggregationSelector;
private readonly _cardinalitySelector?: CardinalitySelector;
private readonly _otelComponentType: string;

constructor(options?: MetricReaderOptions) {
this._aggregationSelector =
Expand All @@ -144,6 +155,12 @@ export abstract class MetricReader implements IMetricReader {
DEFAULT_AGGREGATION_TEMPORALITY_SELECTOR;
this._metricProducers = options?.metricProducers ?? [];
this._cardinalitySelector = options?.cardinalitySelector;
this._otelComponentType =
options?.otelComponentType ?? this.constructor.name;
this._metrics = new MetricReaderMetrics(
this._otelComponentType,
api.createNoopMeter()
);
}

setMetricProducer(metricProducer: MetricProducer) {
Expand All @@ -163,6 +180,11 @@ export abstract class MetricReader implements IMetricReader {
this.onInitialized();
}

_setMeterProvider(meterProvider: api.MeterProvider): void {
const meter = meterProvider.getMeter('@opentelemetry/sdk-metrics', VERSION);
this._metrics = new MetricReaderMetrics(this._otelComponentType, meter);
}

selectAggregation(instrumentType: InstrumentType): AggregationOption {
return this._aggregationSelector(instrumentType);
}
Expand Down Expand Up @@ -214,6 +236,7 @@ export abstract class MetricReader implements IMetricReader {
throw new Error('MetricReader is shutdown');
}

const startTime = hrTime();
const [sdkCollectionResults, ...additionalCollectionResults] =
await Promise.all([
this._sdkMetricProducer.collect({
Expand All @@ -225,11 +248,21 @@ export abstract class MetricReader implements IMetricReader {
})
),
]);
const endTime = hrTime();

// Merge the results, keeping the SDK's Resource
const errors = sdkCollectionResults.errors.concat(
additionalCollectionResults.flatMap(result => result.errors)
);

const collectDuration = hrTimeToSeconds(hrTimeDuration(startTime, endTime));
this._metrics.recordCollection(
collectDuration,
errors.length > 0
? ((errors[0] as Error).name ?? 'collect_error')
: undefined
);

const resource = sdkCollectionResults.resourceMetrics.resource;
const scopeMetrics =
sdkCollectionResults.resourceMetrics.scopeMetrics.concat(
Expand Down
52 changes: 52 additions & 0 deletions packages/sdk-metrics/src/export/MetricReaderMetrics.ts
Comment thread
anuraaga marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
/*
* Copyright The OpenTelemetry Authors
* SPDX-License-Identifier: Apache-2.0
*/

import type { Attributes, Histogram, Meter } from '@opentelemetry/api';
import {
ATTR_ERROR_TYPE,
ATTR_OTEL_COMPONENT_NAME,
ATTR_OTEL_COMPONENT_TYPE,
METRIC_OTEL_SDK_METRIC_READER_COLLECTION_DURATION,
} from '../semconv';

const componentCounter = new Map<string, number>();

/**
* Generates `otel.sdk.metric_reader.*` metrics.
* https://opentelemetry.io/docs/specs/semconv/otel/sdk-metrics/#metric-otelsdkmetric_readercollectionduration
*/
export class MetricReaderMetrics {
private readonly collectionDuration: Histogram;
private readonly standardAttrs: Attributes;

constructor(componentType: string, meter: Meter) {
const counter = componentCounter.get(componentType) ?? 0;
componentCounter.set(componentType, counter + 1);

this.standardAttrs = {
[ATTR_OTEL_COMPONENT_TYPE]: componentType,
[ATTR_OTEL_COMPONENT_NAME]: `${componentType}/${counter}`,
};

this.collectionDuration = meter.createHistogram(
METRIC_OTEL_SDK_METRIC_READER_COLLECTION_DURATION,
{
unit: 's',
description:
'The duration of the collect operation of the metric reader.',
advice: {
explicitBucketBoundaries: [],
},
}
);
}

recordCollection(durationSecs: number, error: string | undefined) {
const attrs = error
? { ...this.standardAttrs, [ATTR_ERROR_TYPE]: error }
: this.standardAttrs;
this.collectionDuration.record(durationSecs, attrs);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,9 @@ import {
import { MetricReader } from './MetricReader';
import type { PushMetricExporter } from './MetricExporter';
import { callWithTimeout, TimeoutError } from '../utils';
import type { MetricProducer } from './MetricProducer';
import { InstrumentType } from './MetricData';
import type { MetricProducer } from './MetricProducer';
import { OTEL_COMPONENT_TYPE_VALUE_PERIODIC_METRIC_READER } from '../semconv';

export type PeriodicExportingMetricReaderOptions = {
/**
Expand Down Expand Up @@ -75,6 +76,7 @@ export class PeriodicExportingMetricReader extends MetricReader {
aggregationSelector: exporter.selectAggregation?.bind(exporter),
aggregationTemporalitySelector:
exporter.selectAggregationTemporality?.bind(exporter),
otelComponentType: OTEL_COMPONENT_TYPE_VALUE_PERIODIC_METRIC_READER,
metricProducers,
cardinalitySelector: (instrumentType: InstrumentType) => {
const limits = {
Expand Down
Loading
Loading