Skip to content

Commit

Permalink
feat: implement labelset (#463)
Browse files Browse the repository at this point in the history
* feat: implement labelset

* fix: linting

* fix: doc strings

* fix: expose labels function in Meter

* fix: linting

* fix: address comments

* fix: udpate LabelSet logic

* fix: linting

* fix: address comments

* fix: update "encoded" to "identifier"

* chore: update naming

* fix: conflicts

* chore: update let to const
  • Loading branch information
xiao-lix authored and mayurkale22 committed Nov 7, 2019
1 parent b00a2f3 commit f47c2de
Show file tree
Hide file tree
Showing 11 changed files with 223 additions and 107 deletions.
19 changes: 14 additions & 5 deletions packages/opentelemetry-core/src/metrics/NoopMeter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import {
MetricOptions,
MeasureHandle,
SpanContext,
LabelSet,
Labels,
} from '@opentelemetry/types';

/**
Expand Down Expand Up @@ -58,6 +60,10 @@ export class NoopMeter implements Meter {
createGauge(name: string, options?: MetricOptions): Metric<GaugeHandle> {
return NOOP_GAUGE_METRIC;
}

labels(labels: Labels): LabelSet {
return NOOP_LABEL_SET;
}
}

export class NoopMetric<T> implements Metric<T> {
Expand All @@ -67,12 +73,12 @@ export class NoopMetric<T> implements Metric<T> {
this._handle = handle;
}
/**
* Returns a Handle associated with specified label values.
* Returns a Handle associated with specified LabelSet.
* It is recommended to keep a reference to the Handle instead of always
* calling this method for every operations.
* @param labelValues the list of label values.
* @param labels the canonicalized LabelSet used to associate with this metric handle.
*/
getHandle(labelValues: string[]): T {
getHandle(labels: LabelSet): T {
return this._handle;
}

Expand All @@ -85,9 +91,10 @@ export class NoopMetric<T> implements Metric<T> {

/**
* Removes the Handle from the metric, if it is present.
* @param labelValues the list of label values.
* @param labels the canonicalized LabelSet used to associate with this metric handle.
*/
removeHandle(labelValues: string[]): void {
removeHandle(labels: LabelSet): void {
// @todo: implement this method
return;
}

Expand Down Expand Up @@ -137,3 +144,5 @@ export const NOOP_MEASURE_HANDLE = new NoopMeasureHandle();
export const NOOP_MEASURE_METRIC = new NoopMetric<MeasureHandle>(
NOOP_MEASURE_HANDLE
);

export const NOOP_LABEL_SET = {} as LabelSet;
21 changes: 9 additions & 12 deletions packages/opentelemetry-core/test/metrics/NoopMeter.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,25 +24,26 @@ import {
NOOP_MEASURE_HANDLE,
NOOP_MEASURE_METRIC,
} from '../../src/metrics/NoopMeter';
import { Labels } from '@opentelemetry/types';

describe('NoopMeter', () => {
it('should not crash', () => {
const meter = new NoopMeter();
const counter = meter.createCounter('some-name');
const labels = {} as Labels;
const labelSet = meter.labels(labels);

// ensure NoopMetric does not crash.
counter.setCallback(() => {
assert.fail('callback occurred');
});
counter.getHandle(['val1', 'val2']).add(1);
counter.getHandle(labelSet).add(1);
counter.getDefaultHandle().add(1);
counter.removeHandle(['val1', 'val2']);
counter.removeHandle(labelSet);

// ensure the correct noop const is returned
assert.strictEqual(counter, NOOP_COUNTER_METRIC);
assert.strictEqual(
counter.getHandle(['val1', 'val2']),
NOOP_COUNTER_HANDLE
);
assert.strictEqual(counter.getHandle(labelSet), NOOP_COUNTER_HANDLE);
assert.strictEqual(counter.getDefaultHandle(), NOOP_COUNTER_HANDLE);
counter.clear();

Expand All @@ -61,23 +62,19 @@ describe('NoopMeter', () => {
// ensure the correct noop const is returned
assert.strictEqual(measure, NOOP_MEASURE_METRIC);
assert.strictEqual(measure.getDefaultHandle(), NOOP_MEASURE_HANDLE);
assert.strictEqual(
measure.getHandle(['val1', 'val2']),
NOOP_MEASURE_HANDLE
);
assert.strictEqual(measure.getHandle(labelSet), NOOP_MEASURE_HANDLE);

const gauge = meter.createGauge('some-name');
gauge.getDefaultHandle().set(1);

// ensure the correct noop const is returned
assert.strictEqual(gauge, NOOP_GAUGE_METRIC);
assert.strictEqual(gauge.getDefaultHandle(), NOOP_GAUGE_HANDLE);
assert.strictEqual(gauge.getHandle(['val1', 'val2']), NOOP_GAUGE_HANDLE);
assert.strictEqual(gauge.getHandle(labelSet), NOOP_GAUGE_HANDLE);

const options = {
component: 'tests',
description: 'the testing package',
labelKeys: ['key1', 'key2'],
};

const measureWithOptions = meter.createMeasure('some-name', options);
Expand Down
38 changes: 25 additions & 13 deletions packages/opentelemetry-metrics/src/Handle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,11 @@ import { TimeSeries } from './export/types';
*/
export class BaseHandle {
protected _data = 0;
protected _labelSet: types.LabelSet;

constructor(private readonly _labels: string[]) {}
constructor(labelSet: types.LabelSet) {
this._labelSet = labelSet;
}

/**
* Returns the TimeSeries with one or more Point.
Expand All @@ -34,41 +37,46 @@ export class BaseHandle {
*/
getTimeSeries(timestamp: types.HrTime): TimeSeries {
return {
labelValues: this._labels.map(value => ({ value })),
labelValues: Object.values(this._labelSet.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.
* value of single handle in the `Counter` associated with specified LabelSet.
*/
export class CounterHandle extends BaseHandle implements types.CounterHandle {
constructor(
labelSet: types.LabelSet,
private readonly _disabled: boolean,
private readonly _monotonic: boolean,
private readonly _valueType: types.ValueType,
private readonly _labelValues: string[],
private readonly _logger: types.Logger,
private readonly _onUpdate: Function
) {
super(_labelValues);
super(labelSet);
}

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

if (this._monotonic && value < 0) {
this._logger.error(
`Monotonic counter cannot descend for ${this._labelValues}`
`Monotonic counter cannot descend for ${Object.values(
this._labelSet.labels
)}`
);
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.`
`INT counter cannot accept a floating-point value for ${Object.values(
this._labelSet.labels
)}, ignoring the fractional digits.`
);
value = Math.trunc(value);
}
Expand All @@ -79,33 +87,37 @@ export class CounterHandle extends BaseHandle implements types.CounterHandle {

/**
* GaugeHandle allows the SDK to observe/record a single metric event. The
* value of single handle in the `Gauge` associated with specified label values.
* value of single handle in the `Gauge` associated with specified LabelSet.
*/
export class GaugeHandle extends BaseHandle implements types.GaugeHandle {
constructor(
labelSet: types.LabelSet,
private readonly _disabled: boolean,
private readonly _monotonic: boolean,
private readonly _valueType: types.ValueType,
private readonly _labelValues: string[],
private readonly _logger: types.Logger,
private readonly _onUpdate: Function
) {
super(_labelValues);
super(labelSet);
}

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

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

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.`
`INT gauge cannot accept a floating-point value for ${Object.values(
this._labelSet.labels
)}, ignoring the fractional digits.`
);
value = Math.trunc(value);
}
Expand Down
39 changes: 39 additions & 0 deletions packages/opentelemetry-metrics/src/LabelSet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
/*!
* 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 * as types from '@opentelemetry/types';

/**
* Canonicalized labels with an unique string identifier.
*/
export class LabelSet implements types.LabelSet {
identifier: string;
labels: types.Labels;

constructor(identifier: string, labels: types.Labels) {
this.identifier = identifier;
this.labels = labels;
}
}

/**
* 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;
}
22 changes: 22 additions & 0 deletions packages/opentelemetry-metrics/src/Meter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ import {
DEFAULT_CONFIG,
MeterConfig,
} from './types';
import { LabelSet } from './LabelSet';
import { ReadableMetric, MetricExporter } from './export/types';
import { notNull } from './Utils';
import { ExportResult } from '@opentelemetry/base';
Expand Down Expand Up @@ -187,6 +188,27 @@ export class Meter implements types.Meter {
this._metrics.set(name, metric);
}

/**
* Provide a pre-computed re-useable LabelSet by
* converting the unordered labels into a canonicalized
* set of lables with an unique identifier, useful for pre-aggregation.
* @param labels user provided unordered Labels.
*/
labels(labels: types.Labels): types.LabelSet {
const keys = Object.keys(labels).sort();
const identifier = keys.reduce((result, key) => {
if (result.length > 2) {
result += ',';
}
return (result += key + ':' + labels[key]);
}, '|#');
const sortedLabels: types.Labels = {};
keys.forEach(key => {
sortedLabels[key] = labels[key];
});
return new LabelSet(identifier, sortedLabels);
}

/**
* Ensure a metric name conforms to the following rules:
*
Expand Down
32 changes: 15 additions & 17 deletions packages/opentelemetry-metrics/src/Metric.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@

import * as types from '@opentelemetry/types';
import { hrTime } from '@opentelemetry/core';
import { hashLabelValues } from './Utils';
import { CounterHandle, GaugeHandle, BaseHandle } from './Handle';
import { MetricOptions } from './types';
import {
Expand Down Expand Up @@ -47,18 +46,17 @@ export abstract class Metric<T extends BaseHandle> implements types.Metric<T> {
}

/**
* Returns a Handle associated with specified label values.
* Returns a Handle associated with specified LabelSet.
* It is recommended to keep a reference to the Handle instead of always
* calling this method for each operation.
* @param labelValues the list of label values.
* @param labelSet the canonicalized LabelSet used to associate with this metric handle.
*/
getHandle(labelValues: string[]): T {
const hash = hashLabelValues(labelValues);
if (this._handles.has(hash)) return this._handles.get(hash)!;
getHandle(labelSet: types.LabelSet): T {
if (this._handles.has(labelSet.identifier))
return this._handles.get(labelSet.identifier)!;

const handle = this._makeHandle(labelValues);

this._handles.set(hash, handle);
const handle = this._makeHandle(labelSet);
this._handles.set(labelSet.identifier, handle);
return handle;
}

Expand All @@ -73,10 +71,10 @@ export abstract class Metric<T extends BaseHandle> implements types.Metric<T> {

/**
* Removes the Handle from the metric, if it is present.
* @param labelValues the list of label values.
* @param labelSet the canonicalized LabelSet used to associate with this metric handle.
*/
removeHandle(labelValues: string[]): void {
this._handles.delete(hashLabelValues(labelValues));
removeHandle(labelSet: types.LabelSet): void {
this._handles.delete(labelSet.identifier);
}

/**
Expand Down Expand Up @@ -119,7 +117,7 @@ export abstract class Metric<T extends BaseHandle> implements types.Metric<T> {
};
}

protected abstract _makeHandle(labelValues: string[]): T;
protected abstract _makeHandle(labelSet: types.LabelSet): T;
}

/** This is a SDK implementation of Counter Metric. */
Expand All @@ -137,12 +135,12 @@ export class CounterMetric extends Metric<CounterHandle> {
: MetricDescriptorType.COUNTER_INT64
);
}
protected _makeHandle(labelValues: string[]): CounterHandle {
protected _makeHandle(labelSet: types.LabelSet): CounterHandle {
return new CounterHandle(
labelSet,
this._disabled,
this._monotonic,
this._valueType,
labelValues,
this._logger,
this._onUpdate
);
Expand All @@ -164,12 +162,12 @@ export class GaugeMetric extends Metric<GaugeHandle> {
: MetricDescriptorType.GAUGE_INT64
);
}
protected _makeHandle(labelValues: string[]): GaugeHandle {
protected _makeHandle(labelSet: types.LabelSet): GaugeHandle {
return new GaugeHandle(
labelSet,
this._disabled,
this._monotonic,
this._valueType,
labelValues,
this._logger,
this._onUpdate
);
Expand Down
Loading

0 comments on commit f47c2de

Please sign in to comment.