Skip to content

Commit

Permalink
feat(console-metric-exporter): add temporality configuration
Browse files Browse the repository at this point in the history
  • Loading branch information
pichlermarc committed Nov 7, 2022
1 parent d154066 commit d914b4b
Show file tree
Hide file tree
Showing 5 changed files with 132 additions and 68 deletions.
1 change: 1 addition & 0 deletions experimental/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ All notable changes to experimental packages in this project will be documented
* feat(detectors): add browser detector module [#3292](https://github.com/open-telemetry/opentelemetry-js/pull/3292) @abinet18
* deps: remove unused proto-loader dependencies and update grpc-js and proto-loader versions [#3337](https://github.com/open-telemetry/opentelemetry-js/pull/3337) @seemk
* feat(metrics-exporters): configure temporality via environment variable [#3305](https://github.com/open-telemetry/opentelemetry-js/pull/3305) @pichlermarc
* feat(console-metric-exporter): add temporality configuration [#3387](https://github.com/open-telemetry/opentelemetry-js/pull/3387) @pichlermarc

### :bug: (Bug Fix)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,17 @@ export type AggregationSelector = (instrumentType: InstrumentType) => Aggregatio
export type AggregationTemporalitySelector = (instrumentType: InstrumentType) => AggregationTemporality;

export const DEFAULT_AGGREGATION_SELECTOR: AggregationSelector = _instrumentType => Aggregation.Default();
export const DEFAULT_AGGREGATION_TEMPORALITY_SELECTOR: AggregationTemporalitySelector = _instrumentType => AggregationTemporality.CUMULATIVE;
export const CUMULATIVE_AGGREGATION_TEMPORALITY_SELECTOR: AggregationTemporalitySelector = _instrumentType => AggregationTemporality.CUMULATIVE;
export const DELTA_AGGREGATION_TEMPORALITY_SELECTOR: AggregationTemporalitySelector = (instrumentType: InstrumentType) => {
switch (instrumentType) {
case InstrumentType.COUNTER:
case InstrumentType.OBSERVABLE_COUNTER:
case InstrumentType.HISTOGRAM:
case InstrumentType.OBSERVABLE_GAUGE:
return AggregationTemporality.DELTA;
case InstrumentType.UP_DOWN_COUNTER:
case InstrumentType.OBSERVABLE_UP_DOWN_COUNTER:
return AggregationTemporality.CUMULATIVE;
}
};
export const DEFAULT_AGGREGATION_TEMPORALITY_SELECTOR: AggregationTemporalitySelector = CUMULATIVE_AGGREGATION_TEMPORALITY_SELECTOR;
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,34 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { ExportResult, ExportResultCode } from '@opentelemetry/core';
import {
ExportResult,
ExportResultCode
} from '@opentelemetry/core';
import { InstrumentType } from '../InstrumentDescriptor';
import { AggregationTemporality } from './AggregationTemporality';
import { ResourceMetrics } from './MetricData';
import { PushMetricExporter } from './MetricExporter';
import {
AggregationTemporalitySelector,
CUMULATIVE_AGGREGATION_TEMPORALITY_SELECTOR,
DELTA_AGGREGATION_TEMPORALITY_SELECTOR
} from './AggregationSelector';

interface ConsoleMetricExporterOptions {
temporalityPreference?: AggregationTemporality
}

/* eslint-disable no-console */
export class ConsoleMetricExporter implements PushMetricExporter {
protected _shutdown = false;
protected _temporalitySelector: AggregationTemporalitySelector;

constructor(options?: ConsoleMetricExporterOptions) {
this._temporalitySelector = options?.temporalityPreference === AggregationTemporality.DELTA ?
DELTA_AGGREGATION_TEMPORALITY_SELECTOR :
CUMULATIVE_AGGREGATION_TEMPORALITY_SELECTOR;
}

export(metrics: ResourceMetrics, resultCallback: (result: ExportResult) => void): void {
if (this._shutdown) {
Expand All @@ -38,7 +57,7 @@ export class ConsoleMetricExporter implements PushMetricExporter {
}

selectAggregationTemporality(_instrumentType: InstrumentType): AggregationTemporality {
return AggregationTemporality.CUMULATIVE;
return this._temporalitySelector(_instrumentType);
}

shutdown(): Promise<void> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,12 @@ import { MeterProvider } from '../../src/MeterProvider';
import { defaultResource } from '../util';
import * as assert from 'assert';
import * as sinon from 'sinon';
import { assertAggregationTemporalitySelector } from './utils';
import {
CUMULATIVE_AGGREGATION_TEMPORALITY_SELECTOR,
DELTA_AGGREGATION_TEMPORALITY_SELECTOR
} from '../../src/export/AggregationSelector';
import { AggregationTemporality } from '../../src';


async function waitForNumberOfExports(exporter: sinon.SinonSpy<[metrics: ResourceMetrics, resultCallback: (result: ExportResult) => void], void>, numberOfExports: number): Promise<void> {
Expand All @@ -38,71 +44,95 @@ async function waitForNumberOfExports(exporter: sinon.SinonSpy<[metrics: Resourc

/* eslint-disable no-console */
describe('ConsoleMetricExporter', () => {
let previousConsoleDir: any;
let exporter: ConsoleMetricExporter;
let meterProvider: MeterProvider;
let meterReader: PeriodicExportingMetricReader;
let meter: metrics.Meter;

beforeEach(() => {
previousConsoleDir = console.dir;
console.dir = () => {};

exporter = new ConsoleMetricExporter();
meterProvider = new MeterProvider({ resource: defaultResource });
meter = meterProvider.getMeter('ConsoleMetricExporter', '1.0.0');
meterReader = new PeriodicExportingMetricReader({
exporter: exporter,
exportIntervalMillis: 100,
exportTimeoutMillis: 100
describe('export', () => {
let previousConsoleDir: any;
let exporter: ConsoleMetricExporter;
let meterProvider: MeterProvider;
let meterReader: PeriodicExportingMetricReader;
let meter: metrics.Meter;

beforeEach(() => {
previousConsoleDir = console.dir;
console.dir = () => {};

exporter = new ConsoleMetricExporter();
meterProvider = new MeterProvider({ resource: defaultResource });
meter = meterProvider.getMeter('ConsoleMetricExporter', '1.0.0');
meterReader = new PeriodicExportingMetricReader({
exporter: exporter,
exportIntervalMillis: 100,
exportTimeoutMillis: 100
});
meterProvider.addMetricReader(meterReader);
});
meterProvider.addMetricReader(meterReader);
});

afterEach(async () => {
console.dir = previousConsoleDir;
afterEach(async () => {
console.dir = previousConsoleDir;

await meterReader.shutdown();
});

it('should export information about metric', async () => {
const counter = meter.createCounter('counter_total', {
description: 'a test description',
});
const counterAttribute = { key1: 'attributeValue1' };
counter.add(10, counterAttribute);
counter.add(10, counterAttribute);

const histogram = meter.createHistogram('histogram', { description: 'a histogram' });
histogram.record(10);
histogram.record(100);
histogram.record(1000);

const spyConsole = sinon.spy(console, 'dir');
const spyExport = sinon.spy(exporter, 'export');

await waitForNumberOfExports(spyExport, 1);
const resourceMetrics = spyExport.args[0];
const firstResourceMetric = resourceMetrics[0];
const consoleArgs = spyConsole.args[0];
const consoleMetric = consoleArgs[0];
const keys = Object.keys(consoleMetric).sort().join(',');

await meterReader.shutdown();
const expectedKeys = [
'dataPointType',
'dataPoints',
'descriptor',
].join(',');

assert.ok(firstResourceMetric.resource.attributes.resourceKey === 'my-resource', 'resourceKey');
assert.ok(keys === expectedKeys, 'expectedKeys');
assert.ok(consoleMetric.descriptor.name === 'counter_total', 'name');
assert.ok(consoleMetric.descriptor.description === 'a test description', 'description');
assert.ok(consoleMetric.descriptor.type === 'COUNTER', 'type');
assert.ok(consoleMetric.descriptor.unit === '', 'unit');
assert.ok(consoleMetric.descriptor.valueType === 1, 'valueType');
assert.ok(consoleMetric.dataPoints[0].attributes.key1 === 'attributeValue1', 'ensure metric attributes exists');

assert.ok(spyExport.calledOnce);
});
});

it('should export information about metric', async () => {
const counter = meter.createCounter('counter_total', {
description: 'a test description',
describe('constructor', () => {
it('with no arguments should select cumulative temporality', () => {
const exporter = new ConsoleMetricExporter();
assertAggregationTemporalitySelector(exporter, CUMULATIVE_AGGREGATION_TEMPORALITY_SELECTOR);
});

it('with empty options should select cumulative temporality', () => {
const exporter = new ConsoleMetricExporter({});
assertAggregationTemporalitySelector(exporter, CUMULATIVE_AGGREGATION_TEMPORALITY_SELECTOR);
});

it('with cumulative preference should select cumulative temporality', () => {
const exporter = new ConsoleMetricExporter({ temporalityPreference: AggregationTemporality.CUMULATIVE });
assertAggregationTemporalitySelector(exporter, CUMULATIVE_AGGREGATION_TEMPORALITY_SELECTOR);
});

it('with delta preference should select delta temporality', () => {
const exporter = new ConsoleMetricExporter({ temporalityPreference: AggregationTemporality.DELTA });
assertAggregationTemporalitySelector(exporter, DELTA_AGGREGATION_TEMPORALITY_SELECTOR);
});
const counterAttribute = { key1: 'attributeValue1' };
counter.add(10, counterAttribute);
counter.add(10, counterAttribute);

const histogram = meter.createHistogram('histogram', { description: 'a histogram' });
histogram.record(10);
histogram.record(100);
histogram.record(1000);

const spyConsole = sinon.spy(console, 'dir');
const spyExport = sinon.spy(exporter, 'export');

await waitForNumberOfExports(spyExport, 1);
const resourceMetrics = spyExport.args[0];
const firstResourceMetric = resourceMetrics[0];
const consoleArgs = spyConsole.args[0];
const consoleMetric = consoleArgs[0];
const keys = Object.keys(consoleMetric).sort().join(',');

const expectedKeys = [
'dataPointType',
'dataPoints',
'descriptor',
].join(',');

assert.ok(firstResourceMetric.resource.attributes.resourceKey === 'my-resource', 'resourceKey');
assert.ok(keys === expectedKeys, 'expectedKeys');
assert.ok(consoleMetric.descriptor.name === 'counter_total', 'name');
assert.ok(consoleMetric.descriptor.description === 'a test description', 'description');
assert.ok(consoleMetric.descriptor.type === 'COUNTER', 'type');
assert.ok(consoleMetric.descriptor.unit === '', 'unit');
assert.ok(consoleMetric.descriptor.valueType === 1, 'valueType');
assert.ok(consoleMetric.dataPoints[0].attributes.key1 === 'attributeValue1', 'ensure metric attributes exists');

assert.ok(spyExport.calledOnce);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,8 @@ import {
AggregationSelector,
AggregationTemporalitySelector,
InstrumentType,
MetricReader
MetricReader,
PushMetricExporter
} from '../../src';
import * as assert from 'assert';

Expand All @@ -36,9 +37,9 @@ const instrumentTypes = [
* @param reader
* @param expectedSelector
*/
export function assertAggregationSelector(reader: MetricReader, expectedSelector: AggregationSelector) {
export function assertAggregationSelector(reader: MetricReader | PushMetricExporter, expectedSelector: AggregationSelector) {
for (const instrumentType of instrumentTypes) {
assert.strictEqual(reader.selectAggregation(instrumentType),
assert.strictEqual(reader.selectAggregation?.(instrumentType),
expectedSelector(instrumentType),
`incorrect aggregation selection for ${InstrumentType[instrumentType]}`);
}
Expand All @@ -49,9 +50,9 @@ export function assertAggregationSelector(reader: MetricReader, expectedSelector
* @param reader
* @param expectedSelector
*/
export function assertAggregationTemporalitySelector(reader: MetricReader, expectedSelector: AggregationTemporalitySelector) {
export function assertAggregationTemporalitySelector(reader: MetricReader | PushMetricExporter, expectedSelector: AggregationTemporalitySelector) {
for (const instrumentType of instrumentTypes) {
assert.strictEqual(reader.selectAggregationTemporality(instrumentType),
assert.strictEqual(reader.selectAggregationTemporality?.(instrumentType),
expectedSelector(instrumentType),
`incorrect aggregation temporality selection for ${InstrumentType[instrumentType]}`);
}
Expand Down

0 comments on commit d914b4b

Please sign in to comment.