Skip to content
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: add addExporter to Meter #482

Merged
Merged
Show file tree
Hide file tree
Changes from 2 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
8 changes: 6 additions & 2 deletions packages/opentelemetry-metrics/src/Handle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,8 @@ export class CounterHandle extends BaseHandle implements types.CounterHandle {
private readonly _monotonic: boolean,
private readonly _valueType: types.ValueType,
private readonly _labelValues: string[],
private readonly _logger: types.Logger
private readonly _logger: types.Logger,
private readonly _onUpdate: Function
) {
super(_labelValues);
}
Expand All @@ -72,6 +73,7 @@ export class CounterHandle extends BaseHandle implements types.CounterHandle {
value = Math.trunc(value);
}
this._data = this._data + value;
this._onUpdate();
}
}

Expand All @@ -85,7 +87,8 @@ export class GaugeHandle extends BaseHandle implements types.GaugeHandle {
private readonly _monotonic: boolean,
private readonly _valueType: types.ValueType,
private readonly _labelValues: string[],
private readonly _logger: types.Logger
private readonly _logger: types.Logger,
private readonly _onUpdate: Function
) {
super(_labelValues);
}
Expand All @@ -107,6 +110,7 @@ export class GaugeHandle extends BaseHandle implements types.GaugeHandle {
value = Math.trunc(value);
}
this._data = value;
this._onUpdate();
}
}

Expand Down
45 changes: 40 additions & 5 deletions packages/opentelemetry-metrics/src/Meter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,17 @@ import {
DEFAULT_CONFIG,
MeterConfig,
} from './types';
import { ReadableMetric } from './export/types';
import { ReadableMetric, MetricExporter } from './export/types';
import { notNull } from './Utils';
import { ExportResult } from '@opentelemetry/base';

/**
* Meter is an implementation of the {@link Meter} interface.
*/
export class Meter implements types.Meter {
private readonly _logger: types.Logger;
private readonly _metrics = new Map();
private readonly _metrics = new Map<string, Metric<BaseHandle>>();
private readonly _exporters: MetricExporter[] = [];

/**
* Constructs a new Meter instance.
Expand Down Expand Up @@ -88,7 +91,9 @@ export class Meter implements types.Meter {
...DEFAULT_METRIC_OPTIONS,
...options,
};
const counter = new CounterMetric(name, opt);
const counter = new CounterMetric(name, opt, () => {
this._exportOneMetric(name);
});
this._registerMetric(name, counter);
return counter;
}
Expand Down Expand Up @@ -118,7 +123,9 @@ export class Meter implements types.Meter {
...DEFAULT_METRIC_OPTIONS,
...options,
};
const gauge = new GaugeMetric(name, opt);
const gauge = new GaugeMetric(name, opt, () => {
this._exportOneMetric(name);
});
this._registerMetric(name, gauge);
return gauge;
}
Expand All @@ -130,7 +137,35 @@ export class Meter implements types.Meter {
getMetrics(): ReadableMetric[] {
return Array.from(this._metrics.values())
.map(metric => metric.get())
.filter(metric => !!metric);
.filter(notNull);
}

/**
* Add an exporter to the list of registered exporters
*
* @param exporter exporter to add to the list of registered exporters
*/
addExporter(exporter: MetricExporter) {
this._exporters.push(exporter);
}

/**
* Send a single metric by name to all registered exporters
*/
private _exportOneMetric(name: string) {
const metric = this._metrics.get(name);
dyladan marked this conversation as resolved.
Show resolved Hide resolved
if (metric) {
const readableMetric = metric.get();
if (readableMetric) {
for (const exporter of this._exporters) {
exporter.export([readableMetric], result => {
if (result !== ExportResult.SUCCESS) {
this._logger.error(`Failed to export ${name}`);
}
});
}
}
}
}

/**
Expand Down
19 changes: 15 additions & 4 deletions packages/opentelemetry-metrics/src/Metric.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,7 @@ export abstract class Metric<T extends BaseHandle> implements types.Metric<T> {
if (this._handles.has(hash)) return this._handles.get(hash)!;

const handle = this._makeHandle(labelValues);

this._handles.set(hash, handle);
return handle;
}
Expand Down Expand Up @@ -123,7 +124,11 @@ export abstract class Metric<T extends BaseHandle> implements types.Metric<T> {

/** This is a SDK implementation of Counter Metric. */
export class CounterMetric extends Metric<CounterHandle> {
constructor(name: string, options: MetricOptions) {
constructor(
name: string,
options: MetricOptions,
private readonly _onUpdate: Function
) {
super(
name,
options,
Expand All @@ -138,14 +143,19 @@ export class CounterMetric extends Metric<CounterHandle> {
this._monotonic,
this._valueType,
labelValues,
this._logger
this._logger,
this._onUpdate
);
}
}

/** This is a SDK implementation of Gauge Metric. */
export class GaugeMetric extends Metric<GaugeHandle> {
constructor(name: string, options: MetricOptions) {
constructor(
name: string,
options: MetricOptions,
private readonly _onUpdate: Function
) {
super(
name,
options,
Expand All @@ -160,7 +170,8 @@ export class GaugeMetric extends Metric<GaugeHandle> {
this._monotonic,
this._valueType,
labelValues,
this._logger
this._logger,
this._onUpdate
);
}
}
9 changes: 9 additions & 0 deletions packages/opentelemetry-metrics/src/Utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,3 +25,12 @@ const COMMA_SEPARATOR = ',';
export function hashLabelValues(labelValues: string[]): string {
return labelValues.sort().join(COMMA_SEPARATOR);
}

/**
* Type guard to remove nulls from arrays
*
* @param value value to be checked for null equality
*/
export function notNull<T>(value: T | null): value is T {
return value !== null;
}
53 changes: 53 additions & 0 deletions packages/opentelemetry-metrics/test/Meter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
hrTime,
hrTimeToMilliseconds,
} from '@opentelemetry/core';
import { NoopExporter } from './mocks/Exporter';

const performanceTimeOrigin = hrTime();

Expand Down Expand Up @@ -481,4 +482,56 @@ describe('Meter', () => {
);
});
});

describe('Exporters', () => {
it('should register an exporter', () => {
const exporter = new NoopExporter();
meter.addExporter(exporter);
assert.equal(meter['_exporters'].length, 1);
});

it('should export a gauge when it is updated', done => {
const exporter = new NoopExporter();
exporter.on('export', metrics => {
assert.equal(metrics[0].descriptor.name, 'name');
assert.equal(metrics[0].timeseries[0].points[0].value, 20);
assert.deepEqual(metrics[0].timeseries[0].labelValues, [
{
value: 'value1',
},
{
value: 'value2',
},
]);
done();
});

meter.addExporter(exporter);
const gauge = meter.createGauge('name') as GaugeMetric;
const handle = gauge.getHandle(['value1', 'value2']);
handle.set(20);
});

it('should export a counter when it is updated', done => {
const counter = meter.createCounter('name') as CounterMetric;
const exporter = new NoopExporter();
exporter.on('export', metrics => {
assert.equal(metrics[0].descriptor.name, 'name');
assert.equal(metrics[0].timeseries[0].points[0].value, 20);
assert.deepEqual(metrics[0].timeseries[0].labelValues, [
{
value: 'value1',
},
{
value: 'value2',
},
]);
done();
});

meter.addExporter(exporter);
const handle = counter.getHandle(['value1', 'value2']);
handle.add(20);
});
});
});
32 changes: 32 additions & 0 deletions packages/opentelemetry-metrics/test/mocks/Exporter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*!
* Copyright 2019, OpenTelemetry Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import { MetricExporter, ReadableMetric } from '../../src/export/types';
import { ExportResult } from '@opentelemetry/base';
import { EventEmitter } from 'events';

export class NoopExporter extends EventEmitter implements MetricExporter {
export(
metrics: ReadableMetric[],
resultCallback: (result: ExportResult) => void
): void {
this.emit('export', metrics, resultCallback);
}

shutdown(): void {
this.emit('shutdown');
}
}