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(metrics): add registerMetric and getMetrics #437

Merged
merged 15 commits into from
Nov 1, 2019
Merged
90 changes: 53 additions & 37 deletions packages/opentelemetry-metrics/src/Handle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,87 +17,103 @@
import * as types from '@opentelemetry/types';
import { TimeSeries } from './export/types';

/**
* This class represent the base to handle, which is responsible for generating
* the TimeSeries.
*/
export class BaseHandle {
protected _data = 0;

constructor(private readonly _labels: string[]) {}

/**
* Returns the TimeSeries with one or more Point.
*
* @param timestamp The time at which the handle is recorded.
* @returns The TimeSeries.
*/
getTimeSeries(timestamp: types.HrTime): TimeSeries {
return {
labelValues: this._labels.map(value => ({ value })),
points: [{ value: this._data, timestamp }],
};
}
}

/**
* CounterHandle allows the SDK to observe/record a single metric event. The
* value of single handle in the `Counter` associated with specified label
* values.
*/
export class CounterHandle implements types.CounterHandle {
private _data = 0;

export class CounterHandle extends BaseHandle implements types.CounterHandle {
constructor(
private readonly _disabled: boolean,
private readonly _monotonic: boolean,
private readonly _valueType: types.ValueType,
private readonly _labelValues: string[],
private readonly _logger: types.Logger
) {}
) {
super(_labelValues);
}

add(value: number): void {
if (this._disabled) return;

if (this._monotonic && value < 0) {
this._logger.error('Monotonic counter cannot descend.');
this._logger.error(
`Monotonic counter cannot descend for ${this._labelValues}`
);
return;
}
if (this._valueType === types.ValueType.INT && !Number.isInteger(value)) {
this._logger.warn(
`INT counter cannot accept a floating-point value for ${this._labelValues}, ignoring the fractional digits.`
);
value = Math.trunc(value);
}
this._data = this._data + value;
}

/**
* Returns the TimeSeries with one or more Point.
*
* @param timestamp The time at which the counter is recorded.
* @returns The TimeSeries.
*/
getTimeSeries(timestamp: types.HrTime): TimeSeries {
return {
labelValues: this._labelValues.map(value => ({ value })),
points: [{ value: this._data, timestamp }],
};
}
}

/**
* GaugeHandle allows the SDK to observe/record a single metric event. The
* value of single handle in the `Gauge` associated with specified label values.
*/
export class GaugeHandle implements types.GaugeHandle {
private _data = 0;

export class GaugeHandle extends BaseHandle implements types.GaugeHandle {
constructor(
private readonly _disabled: boolean,
private readonly _monotonic: boolean,
private readonly _valueType: types.ValueType,
private readonly _labelValues: string[],
private readonly _logger: types.Logger
) {}
) {
super(_labelValues);
}

set(value: number): void {
if (this._disabled) return;

if (this._monotonic && value < this._data) {
this._logger.error('Monotonic gauge cannot descend.');
this._logger.error(
`Monotonic gauge cannot descend for ${this._labelValues}`
);
return;
}
this._data = value;
}

/**
* Returns the TimeSeries with one or more Point.
*
* @param timestamp The time at which the gauge is recorded.
* @returns The TimeSeries.
*/
getTimeSeries(timestamp: types.HrTime): TimeSeries {
return {
labelValues: this._labelValues.map(value => ({ value })),
points: [{ value: this._data, timestamp }],
};
if (this._valueType === types.ValueType.INT && !Number.isInteger(value)) {
this._logger.warn(
`INT gauge cannot accept a floating-point value for ${this._labelValues}, ignoring the fractional digits.`
);
value = Math.trunc(value);
}
this._data = value;
}
}

/**
* MeasureHandle is an implementation of the {@link MeasureHandle} interface.
*/
export class MeasureHandle implements types.MeasureHandle {
export class MeasureHandle extends BaseHandle implements types.MeasureHandle {
record(
value: number,
distContext?: types.DistributedContext,
Expand Down
42 changes: 39 additions & 3 deletions packages/opentelemetry-metrics/src/Meter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,19 +21,22 @@ import {
NOOP_GAUGE_METRIC,
NOOP_MEASURE_METRIC,
} from '@opentelemetry/core';
import { CounterMetric, GaugeMetric } from './Metric';
import { BaseHandle } from './Handle';
import { Metric, CounterMetric, GaugeMetric } from './Metric';
import {
MetricOptions,
DEFAULT_METRIC_OPTIONS,
DEFAULT_CONFIG,
MeterConfig,
} from './types';
import { ReadableMetric } from './export/types';

/**
* 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();

/**
* Constructs a new Meter instance.
Expand Down Expand Up @@ -85,7 +88,9 @@ export class Meter implements types.Meter {
...DEFAULT_METRIC_OPTIONS,
...options,
};
return new CounterMetric(name, opt);
const counter = new CounterMetric(name, opt);
this._registerMetric(name, counter);
return counter;
}

/**
Expand Down Expand Up @@ -113,7 +118,38 @@ export class Meter implements types.Meter {
...DEFAULT_METRIC_OPTIONS,
...options,
};
return new GaugeMetric(name, opt);
const gauge = new GaugeMetric(name, opt);
this._registerMetric(name, gauge);
return gauge;
}

/**
* Gets a collection of Metric`s to be exported.
* @returns The list of metrics.
*/
getMetrics(): ReadableMetric[] {
return Array.from(this._metrics.values())
.map(metric => metric.get())
.filter(metric => !!metric);
}

/**
* Registers metric to register.
* @param name The name of the metric.
* @param metric The metric to register.
*/
private _registerMetric<T extends BaseHandle>(
name: string,
metric: Metric<T>
): void {
if (this._metrics.has(name)) {
mayurkale22 marked this conversation as resolved.
Show resolved Hide resolved
// @todo (issue/474): decide how to handle already registered metric
this._logger.error(
`A metric with the name ${name} has already been registered.`
);
return;
}
this._metrics.set(name, metric);
}

/**
Expand Down
73 changes: 64 additions & 9 deletions packages/opentelemetry-metrics/src/Metric.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,21 +15,35 @@
*/

import * as types from '@opentelemetry/types';
import { hrTime } from '@opentelemetry/core';
import { hashLabelValues } from './Utils';
import { CounterHandle, GaugeHandle } from './Handle';
import { CounterHandle, GaugeHandle, BaseHandle } from './Handle';
import { MetricOptions } from './types';
import {
ReadableMetric,
MetricDescriptor,
MetricDescriptorType,
} from './export/types';

/** This is a SDK implementation of {@link Metric} interface. */
export abstract class Metric<T> implements types.Metric<T> {
export abstract class Metric<T extends BaseHandle> implements types.Metric<T> {
protected readonly _monotonic: boolean;
protected readonly _disabled: boolean;
protected readonly _valueType: types.ValueType;
protected readonly _logger: types.Logger;
private readonly _handles: Map<String, T> = new Map();
private readonly _metricDescriptor: MetricDescriptor;
private readonly _handles: Map<string, T> = new Map();

constructor(name: string, options: MetricOptions) {
this._monotonic = options.monotonic;
this._disabled = options.disabled;
this._logger = options.logger;
constructor(
private readonly _name: string,
private readonly _options: MetricOptions,
private readonly _type: MetricDescriptorType
) {
this._monotonic = _options.monotonic;
this._disabled = _options.disabled;
this._valueType = _options.valueType;
this._logger = _options.logger;
this._metricDescriptor = this._getMetricDescriptor();
}

/**
Expand Down Expand Up @@ -77,18 +91,52 @@ export abstract class Metric<T> implements types.Metric<T> {
return;
}

/**
* Provides a ReadableMetric with one or more TimeSeries.
* @returns The ReadableMetric, or null if TimeSeries is not present in
* Metric.
*/
get(): ReadableMetric | null {
if (this._handles.size === 0) return null;

const timestamp = hrTime();
return {
descriptor: this._metricDescriptor,
timeseries: Array.from(this._handles, ([_, handle]) =>
handle.getTimeSeries(timestamp)
),
};
}

private _getMetricDescriptor(): MetricDescriptor {
return {
name: this._name,
description: this._options.description,
unit: this._options.unit,
labelKeys: this._options.labelKeys,
type: this._type,
};
}

protected abstract _makeHandle(labelValues: string[]): T;
}

/** This is a SDK implementation of Counter Metric. */
export class CounterMetric extends Metric<CounterHandle> {
constructor(name: string, options: MetricOptions) {
super(name, options);
super(
name,
options,
options.valueType === types.ValueType.DOUBLE
? MetricDescriptorType.COUNTER_DOUBLE
: MetricDescriptorType.COUNTER_INT64
);
}
protected _makeHandle(labelValues: string[]): CounterHandle {
return new CounterHandle(
this._disabled,
this._monotonic,
this._valueType,
labelValues,
this._logger
);
Expand All @@ -98,12 +146,19 @@ export class CounterMetric extends Metric<CounterHandle> {
/** This is a SDK implementation of Gauge Metric. */
export class GaugeMetric extends Metric<GaugeHandle> {
constructor(name: string, options: MetricOptions) {
super(name, options);
super(
name,
options,
options.valueType === types.ValueType.DOUBLE
? MetricDescriptorType.GAUGE_DOUBLE
: MetricDescriptorType.GAUGE_INT64
);
}
protected _makeHandle(labelValues: string[]): GaugeHandle {
return new GaugeHandle(
this._disabled,
this._monotonic,
this._valueType,
labelValues,
this._logger
);
Expand Down
2 changes: 1 addition & 1 deletion packages/opentelemetry-metrics/src/export/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ export interface ReadableMetric {

// The resource for the metric. If unset, it may be set to a default value
// provided for a sequence of messages in an RPC stream.
resource: Resource;
resource?: Resource;
}

/** Properties of a Metric type and its schema */
Expand Down
1 change: 1 addition & 0 deletions packages/opentelemetry-metrics/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@
export * from './Handle';
export * from './Meter';
export * from './Metric';
export * from './export/types';
6 changes: 5 additions & 1 deletion packages/opentelemetry-metrics/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@
*/

import { LogLevel } from '@opentelemetry/core';
import { Logger } from '@opentelemetry/types';
import { Logger, ValueType } from '@opentelemetry/types';

/** Options needed for SDK metric creation. */
export interface MetricOptions {
Expand All @@ -42,6 +42,9 @@ export interface MetricOptions {

/** User provided logger. */
logger: Logger;

/** Indicates the type of the recorded value. */
valueType: ValueType;
}

/** MeterConfig provides an interface for configuring a Meter. */
Expand All @@ -64,4 +67,5 @@ export const DEFAULT_METRIC_OPTIONS = {
description: '',
unit: '1',
labelKeys: [],
valueType: ValueType.DOUBLE,
};
Loading