Skip to content

Commit

Permalink
refactor(metrics): metrics provider implements controller
Browse files Browse the repository at this point in the history
Metrics exporting requires a clear policy about when to export. While
MetricsProvider holds all meters created, it can act as a Controller
role to integrate better export strategies like pulling.
  • Loading branch information
legendecas committed May 6, 2020
1 parent a5444dc commit af75f12
Show file tree
Hide file tree
Showing 15 changed files with 157 additions and 109 deletions.
4 changes: 2 additions & 2 deletions getting-started/ts-example/monitoring.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { MeterProvider } from '@opentelemetry/metrics';
import { PushController } from '@opentelemetry/metrics';
import { Metric, BoundCounter } from '@opentelemetry/api';
import { PrometheusExporter } from '@opentelemetry/exporter-prometheus';

Expand All @@ -11,7 +11,7 @@ const exporter = new PrometheusExporter(
},
);

const meter = new MeterProvider({
const meter = new PushController({
exporter,
interval: 1000,
}).getMeter('example-ts');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ import {
CounterMetric,
CounterSumAggregator,
Meter,
MeterProvider,
PushController,
ObserverMetric,
Point,
} from '@opentelemetry/metrics';
Expand Down Expand Up @@ -189,7 +189,7 @@ describe('PrometheusExporter', () => {

beforeEach(done => {
exporter = new PrometheusExporter();
meter = new MeterProvider().getMeter('test-prometheus');
meter = new PushController().getMeter('test-prometheus');
exporter.startServer(done);
});

Expand Down Expand Up @@ -418,7 +418,7 @@ describe('PrometheusExporter', () => {
let exporter: PrometheusExporter | undefined;

beforeEach(() => {
meter = new MeterProvider().getMeter('test-prometheus');
meter = new PushController().getMeter('test-prometheus');
counter = meter.createCounter('counter') as CounterMetric;
counter.bind({ key1: 'labelValue1' }).add(10);
});
Expand Down
8 changes: 4 additions & 4 deletions packages/opentelemetry-metrics/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ npm install --save @opentelemetry/metrics
Choose this kind of metric when the value is a quantity, the sum is of primary interest, and the event count and value distribution are not of primary interest. Counters are defined as `Monotonic = true` by default, meaning that positive values are expected.

```js
const { MeterProvider } = require('@opentelemetry/metrics');
const { PushController } = require('@opentelemetry/metrics');

// Initialize the Meter to capture measurements in various ways.
const meter = new MeterProvider().getMeter('your-meter-name');
const meter = new PushController().getMeter('your-meter-name');

const counter = meter.createCounter('metric_name', {
labelKeys: ['pid'],
Expand All @@ -41,10 +41,10 @@ boundCounter.add(10);
Choose this kind of metric when only last value is important without worry about aggregation

```js
const { MeterProvider, MetricObservable } = require('@opentelemetry/metrics');
const { PushController, MetricObservable } = require('@opentelemetry/metrics');

// Initialize the Meter to capture measurements in various ways.
const meter = new MeterProvider().getMeter('your-meter-name');
const meter = new PushController().getMeter('your-meter-name');

const observer = meter.createObserver('metric_name', {
labelKeys: ['pid', 'core'],
Expand Down
6 changes: 0 additions & 6 deletions packages/opentelemetry-metrics/src/Meter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,6 @@ import {
MeterConfig,
} from './types';
import { Batcher, UngroupedBatcher } from './export/Batcher';
import { PushController } from './export/Controller';
import { NoopExporter } from './export/NoopExporter';

/**
* Meter is an implementation of the {@link Meter} interface.
Expand All @@ -45,10 +43,6 @@ export class Meter implements api.Meter {
this._logger = config.logger || new ConsoleLogger(config.logLevel);
this._batcher = config.batcher ?? new UngroupedBatcher();
this._resource = config.resource || Resource.createTelemetrySDKResource();
// start the push controller
const exporter = config.exporter || new NoopExporter();
const interval = config.interval;
new PushController(this, exporter, interval);
}

/**
Expand Down
48 changes: 0 additions & 48 deletions packages/opentelemetry-metrics/src/MeterProvider.ts

This file was deleted.

39 changes: 15 additions & 24 deletions packages/opentelemetry-metrics/src/export/Controller.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,36 +14,27 @@
* limitations under the License.
*/

import { ExportResult, unrefTimer } from '@opentelemetry/core';
import { ExportResult } from '@opentelemetry/core';
import { Meter } from '../Meter';
import { MetricExporter } from './types';
import { Batcher } from './Batcher';

const DEFAULT_EXPORT_INTERVAL = 60_000;

export class Controller {}

/** Controller organizes a periodic push of metric data. */
export class PushController extends Controller {
private _timer: NodeJS.Timeout;
export abstract class Controller {
protected readonly _meters: Map<string, Meter> = new Map();

constructor(
private readonly _meter: Meter,
private readonly _exporter: MetricExporter,
interval: number = DEFAULT_EXPORT_INTERVAL
) {
super();
this._timer = setInterval(() => {
this._collect();
}, interval);
unrefTimer(this._timer);
}
protected readonly _batcher: Batcher,
protected readonly _exporter: MetricExporter
) {}

private _collect() {
this._meter.collect();
this._exporter.export(this._meter.getBatcher().checkPointSet(), result => {
if (result !== ExportResult.SUCCESS) {
// @todo: log error
}
protected collect() {
for (const meter of this._meters.values()) {
meter.collect();
}
this._exporter.export(this._batcher.checkPointSet(), result => {
this.onExportResult(result);
});
}

protected abstract onExportResult(result: ExportResult): void;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
/*!
* Copyright 2020, 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 { ConsoleLogger, ExportResult, unrefTimer } from '@opentelemetry/core';
import * as api from '@opentelemetry/api';
import { Resource } from '@opentelemetry/resources';
import { Meter } from '../../';
import { DEFAULT_CONFIG, MeterConfig } from '../../types';
import { PushControllerConfig } from './types';
import { NoopExporter } from '../NoopExporter';
import { UngroupedBatcher } from '../Batcher';
import { Controller } from '../Controller';

const DEFAULT_EXPORT_INTERVAL = 60_000;

/**
* This class represents a meter provider collecting metric instrument values periodically.
*/
export class PushController extends Controller implements api.MeterProvider {
private _timer: NodeJS.Timeout;

readonly resource: Resource = Resource.createTelemetrySDKResource();
readonly logger: api.Logger;

constructor(private _config: PushControllerConfig = DEFAULT_CONFIG) {
super(
_config.batcher ?? new UngroupedBatcher(),
_config.exporter ?? new NoopExporter()
);
this.logger = _config.logger ?? new ConsoleLogger(_config.logLevel);

this._timer = setInterval(() => {
this.collect();
}, _config.interval ?? DEFAULT_EXPORT_INTERVAL);
unrefTimer(this._timer);
}

/**
* Returns a Meter, creating one if one with the given name and version is not already created
*
* @returns Meter A Meter with the given name and version
*/
getMeter(name: string, version = '*', config?: MeterConfig): Meter {
const key = `${name}@${version}`;
if (!this._meters.has(key)) {
this._meters.set(key, new Meter(config || this._config));
}

return this._meters.get(key)!;
}

/**
* @implements Controller.onExportResult
*/
protected onExportResult(result: ExportResult): void {
if (result !== ExportResult.SUCCESS) {
// @todo: log error
}
}
}
18 changes: 18 additions & 0 deletions packages/opentelemetry-metrics/src/export/controllers/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
/*!
* Copyright 2020, 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.
*/

export * from './PushController';
export * from './types';
22 changes: 22 additions & 0 deletions packages/opentelemetry-metrics/src/export/controllers/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*!
* Copyright 2020, 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 { MeterConfig } from '../../types';

export interface PushControllerConfig extends MeterConfig {
/** Metric collect interval */
interval?: number;
}
3 changes: 2 additions & 1 deletion packages/opentelemetry-metrics/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,10 @@

export * from './BoundInstrument';
export * from './Meter';
export * from './MeterProvider';
export * from './Metric';
export * from './MetricObservable';
export * from './export/aggregators';
export * from './export/controllers';
export * from './export/Controller';
export * from './export/ConsoleMetricExporter';
export * from './export/types';
3 changes: 0 additions & 3 deletions packages/opentelemetry-metrics/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,9 +64,6 @@ export interface MeterConfig {
/** Metric exporter. */
exporter?: MetricExporter;

/** Metric collect interval */
interval?: number;

/** Resource associated with metric telemetry */
resource?: Resource;

Expand Down
4 changes: 2 additions & 2 deletions packages/opentelemetry-metrics/test/Batcher.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
import * as assert from 'assert';
import * as api from '@opentelemetry/api';
import { NoopLogger } from '@opentelemetry/core';
import { Meter, MeterProvider } from '../src';
import { Meter, PushController } from '../src';

describe('Batcher', () => {
describe('Ungrouped', () => {
Expand All @@ -26,7 +26,7 @@ describe('Batcher', () => {
let barCounter: api.BoundCounter;
let counter: api.Metric<api.BoundCounter>;
beforeEach(() => {
meter = new MeterProvider({
meter = new PushController({
logger: new NoopLogger(),
interval: 10000,
}).getMeter('test-meter');
Expand Down
6 changes: 3 additions & 3 deletions packages/opentelemetry-metrics/test/Meter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import {
CounterMetric,
MetricKind,
Sum,
MeterProvider,
PushController,
MeasureMetric,
Distribution,
ObserverMetric,
Expand All @@ -48,7 +48,7 @@ describe('Meter', () => {
const labels: api.Labels = { [keyb]: 'value2', [keya]: 'value1' };

beforeEach(() => {
meter = new MeterProvider({
meter = new PushController({
logger: new NoopLogger(),
}).getMeter('test-meter');
});
Expand Down Expand Up @@ -553,7 +553,7 @@ describe('Meter', () => {
});

it('should allow custom batcher', () => {
const customMeter = new MeterProvider().getMeter('custom-batcher', '*', {
const customMeter = new PushController().getMeter('custom-batcher', '*', {
batcher: new CustomBatcher(),
});
assert.throws(() => {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@

import * as assert from 'assert';
import * as sinon from 'sinon';
import { ConsoleMetricExporter, MeterProvider, MetricKind } from '../../src';
import { ConsoleMetricExporter, PushController, MetricKind } from '../../src';
import { ValueType } from '@opentelemetry/api';

describe('ConsoleMetricExporter', () => {
Expand All @@ -37,7 +37,7 @@ describe('ConsoleMetricExporter', () => {
it('should export information about metrics', () => {
const spyConsole = sinon.spy(console, 'log');

const meter = new MeterProvider().getMeter(
const meter = new PushController().getMeter(
'test-console-metric-exporter'
);
const counter = meter.createCounter('counter', {
Expand Down
Loading

0 comments on commit af75f12

Please sign in to comment.