Skip to content

Commit

Permalink
feat(metrics-sdk): bootstrap views api (#2625)
Browse files Browse the repository at this point in the history
Co-authored-by: Valentin Marchaud <contact@vmarchaud.fr>
  • Loading branch information
legendecas and vmarchaud authored Nov 21, 2021
1 parent 13acbd3 commit 9b5feb2
Show file tree
Hide file tree
Showing 19 changed files with 946 additions and 294 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
/*
* Copyright The 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 { MetricOptions, ValueType } from '@opentelemetry/api-metrics';
import { InstrumentType } from './Instruments';

export interface InstrumentDescriptor {
readonly name: string;
readonly description: string;
readonly unit: string;
readonly type: InstrumentType;
readonly valueType: ValueType;
}

export function createInstrumentDescriptor(name: string, type: InstrumentType, options?: MetricOptions): InstrumentDescriptor {
return {
name,
type,
description: options?.description ?? '',
unit: options?.unit ?? '1',
valueType: options?.valueType ?? ValueType.DOUBLE,
};
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,55 +16,51 @@

import * as api from '@opentelemetry/api';
import * as metrics from '@opentelemetry/api-metrics';
import { Meter } from './Meter';
import { InstrumentDescriptor } from './InstrumentDescriptor';
import { WritableMetricStorage } from './state/WritableMetricStorage';

// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/api.md#instrument

export enum InstrumentType {
COUNTER = 'COUNTER',
HISTOGRAM = 'HISTOGRAM',
UP_DOWN_COUNTER = 'UP_DOWN_COUNTER',
OBSERVABLE_COUNTER = 'OBSERVABLE_COUNTER',
OBSERVABLE_GAUGE = 'OBSERVABLE_GAUGE',
OBSERVABLE_UP_DOWN_COUNTER = 'OBSERVABLE_UP_DOWN_COUNTER',
COUNTER = 'COUNTER',
HISTOGRAM = 'HISTOGRAM',
UP_DOWN_COUNTER = 'UP_DOWN_COUNTER',
OBSERVABLE_COUNTER = 'OBSERVABLE_COUNTER',
OBSERVABLE_GAUGE = 'OBSERVABLE_GAUGE',
OBSERVABLE_UP_DOWN_COUNTER = 'OBSERVABLE_UP_DOWN_COUNTER',
}

export class SyncInstrument {
constructor(private _meter: Meter, private _name: string) { }
constructor(private _writableMetricStorage: WritableMetricStorage, private _descriptor: InstrumentDescriptor) { }

getName(): string {
return this._name;
}
getName(): string {
return this._descriptor.name;
}


aggregate(value: number, attributes: metrics.Attributes = {}, ctx: api.Context = api.context.active()) {
this._meter.aggregate(this, {
value,
attributes,
context: ctx,
});
}
aggregate(value: number, attributes: metrics.Attributes = {}, context: api.Context = api.context.active()) {
this._writableMetricStorage.record(value, attributes, context);
}
}

export class UpDownCounter extends SyncInstrument implements metrics.Counter {
add(value: number, attributes?: metrics.Attributes, ctx?: api.Context): void {
this.aggregate(value, attributes, ctx);
}
add(value: number, attributes?: metrics.Attributes, ctx?: api.Context): void {
this.aggregate(value, attributes, ctx);
}
}

export class Counter extends SyncInstrument implements metrics.Counter {
add(value: number, attributes?: metrics.Attributes, ctx?: api.Context): void {
if (value < 0) {
api.diag.warn(`negative value provided to counter ${this.getName()}: ${value}`);
return;
}

this.aggregate(value, attributes, ctx);
add(value: number, attributes?: metrics.Attributes, ctx?: api.Context): void {
if (value < 0) {
api.diag.warn(`negative value provided to counter ${this.getName()}: ${value}`);
return;
}

this.aggregate(value, attributes, ctx);
}
}

export class Histogram extends SyncInstrument implements metrics.Histogram {
record(value: number, attributes?: metrics.Attributes, ctx?: api.Context): void {
this.aggregate(value, attributes, ctx);
}
record(value: number, attributes?: metrics.Attributes, ctx?: api.Context): void {
this.aggregate(value, attributes, ctx);
}
}
88 changes: 53 additions & 35 deletions experimental/packages/opentelemetry-sdk-metrics-base/src/Meter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,50 +16,68 @@

import * as metrics from '@opentelemetry/api-metrics';
import { InstrumentationLibrary } from '@opentelemetry/core';
import { Counter, Histogram, UpDownCounter } from './Instruments';
import { Measurement } from './Measurement';
import { MeterProvider } from './MeterProvider';
import { createInstrumentDescriptor, InstrumentDescriptor } from './InstrumentDescriptor';
import { Counter, Histogram, InstrumentType, UpDownCounter } from './Instruments';
import { MeterProviderSharedState } from './state/MeterProviderSharedState';
import { MultiMetricStorage } from './state/MultiWritableMetricStorage';
import { NoopWritableMetricStorage, WritableMetricStorage } from './state/WritableMetricStorage';

// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/api.md#meter

export class Meter implements metrics.Meter {
// instrumentation library required by spec to be on meter
// spec requires provider config changes to apply to previously created meters, achieved by holding a reference to the provider
constructor(private _provider: MeterProvider, private _instrumentationLibrary: InstrumentationLibrary, private _schemaUrl?: string) { }
private _metricStorageRegistry = new Map<string, WritableMetricStorage>();

/** this exists just to prevent ts errors from unused variables and may be removed */
getSchemaUrl(): string | undefined {
return this._schemaUrl;
}
// instrumentation library required by spec to be on meter
// spec requires provider config changes to apply to previously created meters, achieved by holding a reference to the provider
constructor(private _meterProviderSharedState: MeterProviderSharedState, private _instrumentationLibrary: InstrumentationLibrary) { }

/** this exists just to prevent ts errors from unused variables and may be removed */
getInstrumentationLibrary(): InstrumentationLibrary {
return this._instrumentationLibrary;
}
/** this exists just to prevent ts errors from unused variables and may be removed */
getInstrumentationLibrary(): InstrumentationLibrary {
return this._instrumentationLibrary;
}

createHistogram(_name: string, _options?: metrics.MetricOptions): Histogram {
return new Histogram(this, _name);
}

createCounter(_name: string, _options?: metrics.MetricOptions): metrics.Counter {
return new Counter(this, _name);
}
createHistogram(name: string, options?: metrics.MetricOptions): Histogram {
const descriptor = createInstrumentDescriptor(name, InstrumentType.HISTOGRAM, options);
const storage = this._registerMetricStorage(descriptor);
return new Histogram(storage, descriptor);
}

createUpDownCounter(_name: string, _options?: metrics.MetricOptions): metrics.UpDownCounter {
return new UpDownCounter(this, _name);
}
createCounter(name: string, options?: metrics.MetricOptions): metrics.Counter {
const descriptor = createInstrumentDescriptor(name, InstrumentType.COUNTER, options);
const storage = this._registerMetricStorage(descriptor);
return new Counter(storage, descriptor);
}

createObservableGauge(_name: string, _options?: metrics.MetricOptions, _callback?: (observableResult: metrics.ObservableResult) => void): metrics.ObservableBase {
throw new Error('Method not implemented.');
}
createObservableCounter(_name: string, _options?: metrics.MetricOptions, _callback?: (observableResult: metrics.ObservableResult) => void): metrics.ObservableBase {
throw new Error('Method not implemented.');
}
createObservableUpDownCounter(_name: string, _options?: metrics.MetricOptions, _callback?: (observableResult: metrics.ObservableResult) => void): metrics.ObservableBase {
throw new Error('Method not implemented.');
}
createUpDownCounter(name: string, options?: metrics.MetricOptions): metrics.UpDownCounter {
const descriptor = createInstrumentDescriptor(name, InstrumentType.UP_DOWN_COUNTER, options);
const storage = this._registerMetricStorage(descriptor);
return new UpDownCounter(storage, descriptor);
}

createObservableGauge(_name: string, _options?: metrics.MetricOptions, _callback?: (observableResult: metrics.ObservableResult) => void): metrics.ObservableBase {
throw new Error('Method not implemented.');
}

createObservableCounter(_name: string, _options?: metrics.MetricOptions, _callback?: (observableResult: metrics.ObservableResult) => void): metrics.ObservableBase {
throw new Error('Method not implemented.');
}

createObservableUpDownCounter(_name: string, _options?: metrics.MetricOptions, _callback?: (observableResult: metrics.ObservableResult) => void): metrics.ObservableBase {
throw new Error('Method not implemented.');
}

public aggregate(metric: unknown, measurement: Measurement) {
this._provider.aggregate(this, metric, measurement);
private _registerMetricStorage(descriptor: InstrumentDescriptor) {
const views = this._meterProviderSharedState.viewRegistry.findViews(descriptor, this._instrumentationLibrary);
const storages = views.map(_view => {
// TODO: create actual metric storages.
const storage = new NoopWritableMetricStorage();
// TODO: handle conflicts
this._metricStorageRegistry.set(descriptor.name, storage);
return storage;
});
if (storages.length === 1) {
return storages[0];
}
return new MultiMetricStorage(storages);
}
}
Loading

0 comments on commit 9b5feb2

Please sign in to comment.