Skip to content

Commit

Permalink
feat(metrics): add registerMetric and getMetrics (#437)
Browse files Browse the repository at this point in the history
* feat(metrics): add registerMetric and getMetric functionality

* fix: getTimeSeries and add more tests

* fix: minor

* fix: add JSDoc

* fix: minor

* fix: remove

* fix: replace String -> string

* fix: avoid casting

* fix: use GAUGE_DOUBLE and COUNTER_DOUBLE type

* fix: add ValueType to indicate type of the metric

* fix: move ValueType.DOUBLE to DEFAULT_METRIC_OPTIONS

* fix: use Number.isInteger

* fix: log an error instead of throw error

* fix: add more test and @todo comment

* fix: link #474 isssue
  • Loading branch information
mayurkale22 authored Nov 1, 2019
1 parent 283458e commit 173cab2
Show file tree
Hide file tree
Showing 8 changed files with 350 additions and 53 deletions.
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)) {
// @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

0 comments on commit 173cab2

Please sign in to comment.