feat(metrics-sdk): bootstrap views api (#2625)
Co-authored-by: Valentin Marchaud <contact@vmarchaud.fr>
This commit is contained in:
parent
13acbd3675
commit
9b5feb2f21
|
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -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);
|
||||
}
|
||||
|
||||
public aggregate(metric: unknown, measurement: Measurement) {
|
||||
this._provider.aggregate(this, metric, measurement);
|
||||
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.');
|
||||
}
|
||||
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -17,132 +17,109 @@
|
|||
import * as api from '@opentelemetry/api';
|
||||
import * as metrics from '@opentelemetry/api-metrics';
|
||||
import { Resource } from '@opentelemetry/resources';
|
||||
import { Measurement } from './Measurement';
|
||||
import { Meter } from './Meter';
|
||||
import { MetricExporter } from './MetricExporter';
|
||||
import { MetricReader } from './MetricReader';
|
||||
import { View } from './View';
|
||||
import { MeterProviderSharedState } from './state/MeterProviderSharedState';
|
||||
import { InstrumentSelector } from './view/InstrumentSelector';
|
||||
import { MeterSelector } from './view/MeterSelector';
|
||||
import { View } from './view/View';
|
||||
|
||||
|
||||
// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#meterprovider
|
||||
|
||||
export type MeterProviderOptions = {
|
||||
resource?: Resource;
|
||||
resource?: Resource;
|
||||
}
|
||||
|
||||
export class MeterProvider {
|
||||
private _resource: Resource;
|
||||
private _shutdown = false;
|
||||
private _metricReaders: MetricReader[] = [];
|
||||
private _metricExporters: MetricExporter[] = [];
|
||||
private _views: View[] = [];
|
||||
private _sharedState: MeterProviderSharedState;
|
||||
private _shutdown = false;
|
||||
private _metricReaders: MetricReader[] = [];
|
||||
private _metricExporters: MetricExporter[] = [];
|
||||
|
||||
constructor(options: MeterProviderOptions) {
|
||||
this._resource = options.resource ?? Resource.empty();
|
||||
constructor(options: MeterProviderOptions) {
|
||||
this._sharedState = new MeterProviderSharedState(options.resource ?? Resource.empty());
|
||||
}
|
||||
|
||||
getMeter(name: string, version = '', options: metrics.MeterOptions = {}): metrics.Meter {
|
||||
// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#meter-creation
|
||||
if (this._shutdown) {
|
||||
api.diag.warn('A shutdown MeterProvider cannot provide a Meter')
|
||||
return metrics.NOOP_METER;
|
||||
}
|
||||
|
||||
/**
|
||||
* **Unstable**
|
||||
*
|
||||
* This method is only here to prevent typescript from complaining and may be removed.
|
||||
*/
|
||||
getResource() {
|
||||
return this._resource;
|
||||
// Spec leaves it unspecified if creating a meter with duplicate
|
||||
// name/version returns the same meter. We create a new one here
|
||||
// for simplicity. This may change in the future.
|
||||
// TODO: consider returning the same meter if the same name/version is used
|
||||
return new Meter(this._sharedState, { name, version, schemaUrl: options.schemaUrl });
|
||||
}
|
||||
|
||||
addMetricReader(metricReader: MetricReader) {
|
||||
this._metricReaders.push(metricReader);
|
||||
}
|
||||
|
||||
addView(view: View, instrumentSelector: InstrumentSelector, meterSelector: MeterSelector) {
|
||||
// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#view
|
||||
this._sharedState.viewRegistry.addView(view, instrumentSelector, meterSelector);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush all buffered data and shut down the MeterProvider and all exporters and metric readers.
|
||||
* Returns a promise which is resolved when all flushes are complete.
|
||||
*
|
||||
* TODO: return errors to caller somehow?
|
||||
*/
|
||||
async shutdown(): Promise<void> {
|
||||
// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#shutdown
|
||||
|
||||
if (this._shutdown) {
|
||||
api.diag.warn('shutdown may only be called once per MeterProvider');
|
||||
return;
|
||||
}
|
||||
|
||||
getMeter(name: string, version = '', options: metrics.MeterOptions = {}): metrics.Meter {
|
||||
// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#meter-creation
|
||||
if (this._shutdown) {
|
||||
api.diag.warn('A shutdown MeterProvider cannot provide a Meter')
|
||||
return metrics.NOOP_METER;
|
||||
// TODO add a timeout - spec leaves it up the the SDK if this is configurable
|
||||
this._shutdown = true;
|
||||
|
||||
// Shut down all exporters and readers.
|
||||
// Log all Errors.
|
||||
for (const exporter of this._metricExporters) {
|
||||
try {
|
||||
await exporter.shutdown();
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
api.diag.error(`Error shutting down: ${e.message}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Spec leaves it unspecified if creating a meter with duplicate
|
||||
// name/version returns the same meter. We create a new one here
|
||||
// for simplicity. This may change in the future.
|
||||
// TODO: consider returning the same meter if the same name/version is used
|
||||
return new Meter(this, { name, version }, options.schemaUrl);
|
||||
/**
|
||||
* Notifies all exporters and metric readers to flush any buffered data.
|
||||
* Returns a promise which is resolved when all flushes are complete.
|
||||
*
|
||||
* TODO: return errors to caller somehow?
|
||||
*/
|
||||
async forceFlush(): Promise<void> {
|
||||
// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#forceflush
|
||||
|
||||
// TODO add a timeout - spec leaves it up the the SDK if this is configurable
|
||||
|
||||
// do not flush after shutdown
|
||||
if (this._shutdown) {
|
||||
api.diag.warn('invalid attempt to force flush after shutdown')
|
||||
return;
|
||||
}
|
||||
|
||||
addMetricReader(metricReader: MetricReader) {
|
||||
this._metricReaders.push(metricReader);
|
||||
}
|
||||
|
||||
addView(view: View) {
|
||||
// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#view
|
||||
this._views.push(view);
|
||||
}
|
||||
|
||||
/**
|
||||
* Flush all buffered data and shut down the MeterProvider and all exporters and metric readers.
|
||||
* Returns a promise which is resolved when all flushes are complete.
|
||||
*
|
||||
* TODO: return errors to caller somehow?
|
||||
*/
|
||||
async shutdown(): Promise<void> {
|
||||
// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#shutdown
|
||||
|
||||
if (this._shutdown) {
|
||||
api.diag.warn('shutdown may only be called once per MeterProvider');
|
||||
return;
|
||||
}
|
||||
|
||||
// TODO add a timeout - spec leaves it up the the SDK if this is configurable
|
||||
this._shutdown = true;
|
||||
|
||||
// Shut down all exporters and readers.
|
||||
// Log all Errors.
|
||||
for (const exporter of this._metricExporters) {
|
||||
try {
|
||||
await exporter.shutdown();
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
api.diag.error(`Error shutting down: ${e.message}`)
|
||||
}
|
||||
}
|
||||
for (const exporter of [...this._metricExporters, ...this._metricReaders]) {
|
||||
try {
|
||||
await exporter.forceFlush();
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
api.diag.error(`Error flushing: ${e.message}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Notifies all exporters and metric readers to flush any buffered data.
|
||||
* Returns a promise which is resolved when all flushes are complete.
|
||||
*
|
||||
* TODO: return errors to caller somehow?
|
||||
*/
|
||||
async forceFlush(): Promise<void> {
|
||||
// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#forceflush
|
||||
|
||||
// TODO add a timeout - spec leaves it up the the SDK if this is configurable
|
||||
|
||||
// do not flush after shutdown
|
||||
if (this._shutdown) {
|
||||
api.diag.warn('invalid attempt to force flush after shutdown')
|
||||
return;
|
||||
}
|
||||
|
||||
for (const exporter of [...this._metricExporters, ...this._metricReaders]) {
|
||||
try {
|
||||
await exporter.forceFlush();
|
||||
} catch (e) {
|
||||
if (e instanceof Error) {
|
||||
api.diag.error(`Error flushing: ${e.message}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public aggregate(_meter: Meter, _metric: unknown, _measurement: Measurement) {
|
||||
// TODO actually aggregate
|
||||
|
||||
/**
|
||||
* if there are no views:
|
||||
* apply the default configuration
|
||||
* else:
|
||||
* for each view:
|
||||
* if view matches:
|
||||
* apply view configuration
|
||||
* if no view matched:
|
||||
* if user has not disabled default fallback:
|
||||
* apply default configuration
|
||||
*/
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,113 +0,0 @@
|
|||
/*
|
||||
* 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 { InstrumentType } from './Instruments';
|
||||
|
||||
// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#view
|
||||
|
||||
/**
|
||||
* A metric view selects a stream of metrics from a MeterProvider and applies
|
||||
* a configuration to that stream. If no configuration is provided, the default
|
||||
* configuration is used.
|
||||
*/
|
||||
export class View {
|
||||
private _selector: Partial<ViewMetricSelector>;
|
||||
|
||||
/**
|
||||
* Construct a metric view
|
||||
*
|
||||
* @param options a required object which describes the view selector and configuration
|
||||
*/
|
||||
constructor(options: ViewOptions) {
|
||||
if (typeof options.selector == null) {
|
||||
throw new Error('Missing required view selector')
|
||||
}
|
||||
|
||||
if (
|
||||
options.selector.instrumentType == null &&
|
||||
options.selector.instrumentName == null &&
|
||||
options.selector.meterName == null &&
|
||||
options.selector.meterVersion == null &&
|
||||
options.selector.meterSchemaUrl == null
|
||||
) {
|
||||
// It is recommended by the SDK specification to fail fast when invalid options are provided
|
||||
throw new Error('Cannot create a view which selects no options');
|
||||
}
|
||||
|
||||
this._selector = options.selector;
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a metric selector, determine if all of this view's metric selectors match.
|
||||
*
|
||||
* @param selector selector to match
|
||||
* @returns boolean
|
||||
*/
|
||||
public match(selector: ViewMetricSelector) {
|
||||
return this._matchSelectorProperty('instrumentType', selector.instrumentType) &&
|
||||
this._matchInstrumentName(selector.instrumentName) &&
|
||||
this._matchSelectorProperty('meterName', selector.meterName) &&
|
||||
this._matchSelectorProperty('meterVersion', selector.meterVersion) &&
|
||||
this._matchSelectorProperty('meterSchemaUrl', selector.meterSchemaUrl);
|
||||
}
|
||||
|
||||
/**
|
||||
* Match instrument name against the configured selector metric name, which may include wildcards
|
||||
*/
|
||||
private _matchInstrumentName(name: string) {
|
||||
if (this._selector.instrumentName == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO wildcard support
|
||||
return this._selector.instrumentName === name;
|
||||
}
|
||||
|
||||
private _matchSelectorProperty<Prop extends keyof ViewMetricSelector>(property: Prop, metricProperty: ViewMetricSelector[Prop]): boolean {
|
||||
if (this._selector[property] == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (this._selector[property] === metricProperty) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
export type ViewMetricSelector = {
|
||||
instrumentType: InstrumentType;
|
||||
instrumentName: string;
|
||||
meterName: string;
|
||||
meterVersion?: string;
|
||||
meterSchemaUrl?: string;
|
||||
}
|
||||
|
||||
export type ViewOptions = {
|
||||
name?: string;
|
||||
selector: Partial<ViewMetricSelector>;
|
||||
streamConfig?: ViewStreamConfig;
|
||||
}
|
||||
|
||||
export type ViewStreamConfig = {
|
||||
description: string;
|
||||
attributeKeys?: string[];
|
||||
|
||||
// TODO use these types when they are defined
|
||||
aggregation?: unknown;
|
||||
exemplarReservoir?: unknown;
|
||||
}
|
||||
|
|
@ -14,12 +14,14 @@
|
|||
* limitations under the License.
|
||||
*/
|
||||
|
||||
import { Measurement } from './Measurement';
|
||||
import { Resource } from '@opentelemetry/resources';
|
||||
import { ViewRegistry } from '../view/ViewRegistry';
|
||||
|
||||
// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#aggregation
|
||||
/**
|
||||
* An internal record for shared meter provider states.
|
||||
*/
|
||||
export class MeterProviderSharedState {
|
||||
viewRegistry = new ViewRegistry();
|
||||
|
||||
export interface Aggregator {
|
||||
aggregate(measurement: Measurement): void;
|
||||
constructor(public resource: Resource) {}
|
||||
}
|
||||
|
||||
// TODO define actual aggregator classes
|
||||
|
|
@ -0,0 +1,29 @@
|
|||
/*
|
||||
* 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 { Context } from '@opentelemetry/api';
|
||||
import { Attributes } from '@opentelemetry/api-metrics';
|
||||
import { WritableMetricStorage } from './WritableMetricStorage';
|
||||
|
||||
export class MultiMetricStorage implements WritableMetricStorage {
|
||||
constructor(private readonly _backingStorages: WritableMetricStorage[]) {}
|
||||
|
||||
record(value: number, attributes: Attributes, context: Context) {
|
||||
this._backingStorages.forEach(it => {
|
||||
it.record(value, attributes, context);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
/*
|
||||
* 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 { Context } from '@opentelemetry/api';
|
||||
import { Attributes } from '@opentelemetry/api-metrics';
|
||||
|
||||
export interface WritableMetricStorage {
|
||||
record(value: number, attributes: Attributes, context: Context): void;
|
||||
}
|
||||
|
||||
export class NoopWritableMetricStorage implements WritableMetricStorage {
|
||||
record(_value: number, _attributes: Attributes, _context: Context): void {}
|
||||
}
|
||||
|
|
@ -0,0 +1,42 @@
|
|||
/*
|
||||
* 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 { InstrumentDescriptor } from '../InstrumentDescriptor';
|
||||
|
||||
// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#aggregation
|
||||
|
||||
/**
|
||||
* Configures how measurements are combined into metrics for {@link View}s.
|
||||
*
|
||||
* Aggregation provides a set of built-in aggregations via static methods.
|
||||
*/
|
||||
export abstract class Aggregation {
|
||||
// TODO: define the actual aggregator classes
|
||||
abstract createAggregator(instrument: InstrumentDescriptor): unknown;
|
||||
|
||||
static None(): Aggregation {
|
||||
return NONE_AGGREGATION;
|
||||
}
|
||||
}
|
||||
|
||||
export class NoneAggregation extends Aggregation {
|
||||
createAggregator(_instrument: InstrumentDescriptor) {
|
||||
// TODO: define aggregator type
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
const NONE_AGGREGATION = new NoneAggregation();
|
||||
|
|
@ -0,0 +1,39 @@
|
|||
/*
|
||||
* 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 { Context } from '@opentelemetry/api';
|
||||
import { Attributes } from '@opentelemetry/api-metrics';
|
||||
|
||||
/**
|
||||
* The {@link AttributesProcessor} is responsible for customizing which
|
||||
* attribute(s) are to be reported as metrics dimension(s) and adding
|
||||
* additional dimension(s) from the {@link Context}.
|
||||
*/
|
||||
export abstract class AttributesProcessor {
|
||||
abstract process(incoming: Attributes, context: Context): Attributes;
|
||||
|
||||
static Noop() {
|
||||
return NOOP;
|
||||
}
|
||||
}
|
||||
|
||||
export class NoopAttributesProcessor extends AttributesProcessor {
|
||||
process(incoming: Attributes, _context: Context) {
|
||||
return incoming;
|
||||
}
|
||||
}
|
||||
|
||||
const NOOP = new NoopAttributesProcessor;
|
||||
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
* 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 { InstrumentType } from '../Instruments';
|
||||
import { PatternPredicate, Predicate } from './Predicate';
|
||||
|
||||
export interface InstrumentSelectorCriteria {
|
||||
name?: string;
|
||||
type?: InstrumentType;
|
||||
}
|
||||
|
||||
export class InstrumentSelector {
|
||||
private _nameFilter: Predicate;
|
||||
private _type?: InstrumentType;
|
||||
|
||||
constructor(criteria?: InstrumentSelectorCriteria) {
|
||||
this._nameFilter = new PatternPredicate(criteria?.name ?? '*');
|
||||
this._type = criteria?.type;
|
||||
}
|
||||
|
||||
getType() {
|
||||
return this._type
|
||||
}
|
||||
|
||||
getNameFilter() {
|
||||
return this._nameFilter;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,50 @@
|
|||
/*
|
||||
* 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 { ExactPredicate, Predicate } from './Predicate';
|
||||
|
||||
export interface MeterSelectorCriteria {
|
||||
name?: string;
|
||||
version?: string;
|
||||
schemaUrl?: string;
|
||||
}
|
||||
|
||||
export class MeterSelector {
|
||||
private _nameFilter: Predicate;
|
||||
private _versionFilter: Predicate;
|
||||
private _schemaUrlFilter: Predicate;
|
||||
|
||||
constructor(criteria?: MeterSelectorCriteria) {
|
||||
this._nameFilter = new ExactPredicate(criteria?.name);
|
||||
this._versionFilter = new ExactPredicate(criteria?.version);
|
||||
this._schemaUrlFilter = new ExactPredicate(criteria?.schemaUrl);
|
||||
}
|
||||
|
||||
getNameFilter() {
|
||||
return this._nameFilter;
|
||||
}
|
||||
|
||||
/**
|
||||
* TODO: semver filter? no spec yet.
|
||||
*/
|
||||
getVersionFilter() {
|
||||
return this._versionFilter;
|
||||
}
|
||||
|
||||
getSchemaUrlFilter() {
|
||||
return this._schemaUrlFilter;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,73 @@
|
|||
/*
|
||||
* 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.
|
||||
*/
|
||||
|
||||
// https://tc39.es/proposal-regex-escaping
|
||||
// escape ^ $ \ . + ? ( ) [ ] { } |
|
||||
// do not need to escape * as we are interpret it as wildcard
|
||||
const ESCAPE = /[\^$\\.+?()[\]{}|]/g;
|
||||
|
||||
export interface Predicate {
|
||||
match(str: string): boolean;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wildcard pattern predicate, support patterns like `*`, `foo*`, `*bar`.
|
||||
*/
|
||||
export class PatternPredicate implements Predicate {
|
||||
private _matchAll: boolean;
|
||||
private _regexp?: RegExp;
|
||||
|
||||
constructor(pattern: string) {
|
||||
if (pattern === '*') {
|
||||
this._matchAll = true;
|
||||
} else {
|
||||
this._matchAll = false;
|
||||
this._regexp = new RegExp(PatternPredicate.escapePattern(pattern));
|
||||
}
|
||||
}
|
||||
|
||||
match(str: string): boolean {
|
||||
if (this._matchAll) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return this._regexp!.test(str);
|
||||
}
|
||||
|
||||
static escapePattern(pattern: string): string {
|
||||
return `^${pattern.replace(ESCAPE, '\\$&').replace('*', '.*')}$`;
|
||||
}
|
||||
}
|
||||
|
||||
export class ExactPredicate implements Predicate {
|
||||
private _matchAll: boolean;
|
||||
private _pattern?: string;
|
||||
|
||||
constructor(pattern?: string) {
|
||||
this._matchAll = pattern === undefined;
|
||||
this._pattern = pattern;
|
||||
}
|
||||
|
||||
match(str: string): boolean {
|
||||
if (this._matchAll) {
|
||||
return true;
|
||||
}
|
||||
if (str === this._pattern) {
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,68 @@
|
|||
/*
|
||||
* 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 { Aggregation } from './Aggregation';
|
||||
import { AttributesProcessor } from './AttributesProcessor';
|
||||
|
||||
// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#view
|
||||
|
||||
export interface ViewStreamConfig {
|
||||
/**
|
||||
* the name of the resulting metric to generate, or null if the same as the instrument.
|
||||
*/
|
||||
name?: string;
|
||||
/**
|
||||
* the name of the resulting metric to generate, or null if the same as the instrument.
|
||||
*/
|
||||
description?: string;
|
||||
/**
|
||||
* the aggregation used for this view.
|
||||
*/
|
||||
aggregation?: Aggregation;
|
||||
/**
|
||||
* processor of attributes before performing aggregation.
|
||||
*/
|
||||
attributesProcessor?: AttributesProcessor;
|
||||
}
|
||||
|
||||
/**
|
||||
* A View provides the flexibility to customize the metrics that are output by
|
||||
* the SDK. For example, the view can
|
||||
* - customize which Instruments are to be processed/ignored.
|
||||
* - customize the aggregation.
|
||||
* - customize which attribute(s) are to be reported as metrics dimension(s).
|
||||
* - add additional dimension(s) from the {@link Context}.
|
||||
*/
|
||||
export class View {
|
||||
readonly name?: string;
|
||||
readonly description?: string;
|
||||
readonly aggregation: Aggregation;
|
||||
readonly attributesProcessor: AttributesProcessor;
|
||||
|
||||
/**
|
||||
* Construct a metric view
|
||||
*
|
||||
* @param config how the result metric streams were configured
|
||||
*/
|
||||
constructor(config?: ViewStreamConfig) {
|
||||
this.name = config?.name;
|
||||
this.description = config?.description;
|
||||
// TODO: the default aggregation should be Aggregation.Default().
|
||||
// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#default-aggregation
|
||||
this.aggregation = config?.aggregation ?? Aggregation.None();
|
||||
this.attributesProcessor = config?.attributesProcessor ?? AttributesProcessor.Noop();
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
* 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 { InstrumentationLibrary } from '@opentelemetry/core';
|
||||
import { InstrumentDescriptor } from '../InstrumentDescriptor';
|
||||
import { InstrumentSelector } from './InstrumentSelector';
|
||||
import { MeterSelector } from './MeterSelector';
|
||||
import { View } from './View';
|
||||
|
||||
interface RegisteredView {
|
||||
instrumentSelector: InstrumentSelector;
|
||||
meterSelector: MeterSelector;
|
||||
view: View;
|
||||
}
|
||||
|
||||
export class ViewRegistry {
|
||||
private static DEFAULT_VIEW = new View();
|
||||
private _registeredViews: RegisteredView[] = [];
|
||||
|
||||
addView(view: View, instrumentSelector: InstrumentSelector = new InstrumentSelector(), meterSelector: MeterSelector = new MeterSelector()) {
|
||||
this._registeredViews.push({
|
||||
instrumentSelector,
|
||||
meterSelector,
|
||||
view,
|
||||
});
|
||||
}
|
||||
|
||||
findViews(instrument: InstrumentDescriptor, meter: InstrumentationLibrary): View[] {
|
||||
const views = this._registeredViews
|
||||
.filter(registeredView => {
|
||||
return this._matchInstrument(registeredView.instrumentSelector, instrument) &&
|
||||
this._matchMeter(registeredView.meterSelector, meter);
|
||||
})
|
||||
.map(it => it.view);
|
||||
|
||||
if (views.length === 0) {
|
||||
return [ViewRegistry.DEFAULT_VIEW];
|
||||
}
|
||||
return views;
|
||||
}
|
||||
|
||||
private _matchInstrument(selector: InstrumentSelector, instrument: InstrumentDescriptor): boolean {
|
||||
return (selector.getType() === undefined || instrument.type === selector.getType()) &&
|
||||
selector.getNameFilter().match(instrument.name);
|
||||
}
|
||||
|
||||
private _matchMeter(selector: MeterSelector, meter: InstrumentationLibrary): boolean {
|
||||
return selector.getNameFilter().match(meter.name) &&
|
||||
(meter.version === undefined || selector.getVersionFilter().match(meter.version)) &&
|
||||
(meter.schemaUrl === undefined || selector.getSchemaUrlFilter().match(meter.schemaUrl));
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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 * as assert from 'assert';
|
||||
import { context } from '@opentelemetry/api';
|
||||
import { NoopAttributesProcessor } from '../../src/view/AttributesProcessor';
|
||||
|
||||
describe('NoopAttributesProcessor', () => {
|
||||
const processor = new NoopAttributesProcessor();
|
||||
|
||||
it('should return identical attributes on process', () => {
|
||||
assert.deepStrictEqual(
|
||||
processor.process({ foo: 'bar' }, context.active()),
|
||||
{
|
||||
foo: 'bar',
|
||||
}
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,86 @@
|
|||
/*
|
||||
* 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 * as assert from 'assert';
|
||||
import { ExactPredicate, PatternPredicate } from '../../src/view/Predicate';
|
||||
|
||||
describe('PatternPredicate', () => {
|
||||
describe('asterisk match', () => {
|
||||
it('should match anything', () => {
|
||||
const predicate = new PatternPredicate('*');
|
||||
assert.ok(predicate.match('foo'));
|
||||
assert.ok(predicate.match(''));
|
||||
});
|
||||
|
||||
it('should match trailing part', () => {
|
||||
const predicate = new PatternPredicate('foo*');
|
||||
assert.ok(predicate.match('foo'));
|
||||
assert.ok(predicate.match('foobar'));
|
||||
|
||||
assert.ok(!predicate.match('_foo'));
|
||||
assert.ok(!predicate.match('bar'));
|
||||
assert.ok(!predicate.match(''));
|
||||
});
|
||||
|
||||
it('should match leading part', () => {
|
||||
const predicate = new PatternPredicate('*bar');
|
||||
assert.ok(predicate.match('foobar'));
|
||||
assert.ok(predicate.match('bar'));
|
||||
|
||||
assert.ok(!predicate.match('foo'));
|
||||
assert.ok(!predicate.match('bar_'));
|
||||
assert.ok(!predicate.match(''));
|
||||
});
|
||||
});
|
||||
|
||||
describe('exact match', () => {
|
||||
it('should match exactly', () => {
|
||||
const predicate = new PatternPredicate('foobar');
|
||||
assert.ok(predicate.match('foobar'));
|
||||
|
||||
assert.ok(!predicate.match('foo'));
|
||||
assert.ok(!predicate.match('_foobar_'));
|
||||
assert.ok(!predicate.match(''));
|
||||
});
|
||||
});
|
||||
|
||||
describe('escapePattern', () => {
|
||||
it('should escape regexp elements', () => {
|
||||
assert.strictEqual(PatternPredicate.escapePattern('^$\\.+?()[]{}|'), '^\\^\\$\\\\\\.\\+\\?\\(\\)\\[\\]\\{\\}\\|$');
|
||||
assert.strictEqual(PatternPredicate.escapePattern('*'), '^.*$');
|
||||
assert.strictEqual(PatternPredicate.escapePattern('foobar'), '^foobar$');
|
||||
assert.strictEqual(PatternPredicate.escapePattern('foo*'), '^foo.*$');
|
||||
assert.strictEqual(PatternPredicate.escapePattern('*bar'), '^.*bar$');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('ExactPredicate', () => {
|
||||
it('should match all', () => {
|
||||
const predicate = new ExactPredicate();
|
||||
assert.ok(predicate.match('foo'));
|
||||
assert.ok(predicate.match(''));
|
||||
});
|
||||
|
||||
it('should exact match', () => {
|
||||
const predicate = new ExactPredicate('foobar');
|
||||
assert.ok(!predicate.match('foo'));
|
||||
assert.ok(!predicate.match('bar'));
|
||||
assert.ok(!predicate.match(''));
|
||||
|
||||
assert.ok(predicate.match('foobar'));
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,32 @@
|
|||
/*
|
||||
* 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 * as assert from 'assert';
|
||||
import { Aggregation } from '../../src/view/Aggregation';
|
||||
import { AttributesProcessor } from '../../src/view/AttributesProcessor';
|
||||
import { View } from '../../src/view/View';
|
||||
|
||||
describe('View', () => {
|
||||
describe('constructor', () => {
|
||||
it('should construct view without arguments', () => {
|
||||
const view = new View();
|
||||
assert.strictEqual(view.name, undefined);
|
||||
assert.strictEqual(view.description, undefined);
|
||||
assert.strictEqual(view.aggregation, Aggregation.None());
|
||||
assert.strictEqual(view.attributesProcessor, AttributesProcessor.Noop());
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -0,0 +1,153 @@
|
|||
/*
|
||||
* 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 * as assert from 'assert';
|
||||
import { ValueType } from '@opentelemetry/api-metrics';
|
||||
import { InstrumentationLibrary } from '@opentelemetry/core';
|
||||
import { InstrumentType } from '../../src/Instruments';
|
||||
import { ViewRegistry } from '../../src/view/ViewRegistry';
|
||||
import { View } from '../../src/view/View';
|
||||
import { InstrumentSelector } from '../../src/view/InstrumentSelector';
|
||||
import { MeterSelector } from '../../src/view/MeterSelector';
|
||||
import { InstrumentDescriptor } from '../../src/InstrumentDescriptor';
|
||||
|
||||
const defaultInstrumentDescriptor: InstrumentDescriptor = {
|
||||
name: '',
|
||||
description: '',
|
||||
type: InstrumentType.COUNTER,
|
||||
unit: '',
|
||||
valueType: ValueType.DOUBLE,
|
||||
};
|
||||
|
||||
const defaultInstrumentationLibrary: InstrumentationLibrary = {
|
||||
name: 'default',
|
||||
version: '1.0.0',
|
||||
schemaUrl: 'https://opentelemetry.io/schemas/1.7.0'
|
||||
};
|
||||
|
||||
describe('ViewRegistry', () => {
|
||||
describe('findViews', () => {
|
||||
it('should return default view if no view registered', () => {
|
||||
const registry = new ViewRegistry();
|
||||
const views = registry.findViews(defaultInstrumentDescriptor, defaultInstrumentationLibrary);
|
||||
assert.strictEqual(views.length, 1);
|
||||
assert.strictEqual(views[0], ViewRegistry['DEFAULT_VIEW']);
|
||||
});
|
||||
|
||||
describe('InstrumentSelector', () => {
|
||||
it('should match view with instrument name', () => {
|
||||
const registry = new ViewRegistry();
|
||||
registry.addView(new View({ name: 'no-filter' }));
|
||||
registry.addView(new View({ name: 'foo' }), new InstrumentSelector({
|
||||
name: 'foo',
|
||||
}));
|
||||
registry.addView(new View({ name: 'bar' }), new InstrumentSelector({
|
||||
name: 'bar'
|
||||
}));
|
||||
|
||||
{
|
||||
const views = registry.findViews({
|
||||
...defaultInstrumentDescriptor,
|
||||
name: 'foo'
|
||||
}, defaultInstrumentationLibrary);
|
||||
|
||||
assert.strictEqual(views.length, 2);
|
||||
assert.strictEqual(views[0].name, 'no-filter')
|
||||
assert.strictEqual(views[1].name, 'foo');
|
||||
}
|
||||
|
||||
{
|
||||
const views = registry.findViews({
|
||||
...defaultInstrumentDescriptor,
|
||||
name: 'bar'
|
||||
}, defaultInstrumentationLibrary);
|
||||
|
||||
assert.strictEqual(views.length, 2);
|
||||
assert.strictEqual(views[0].name, 'no-filter');
|
||||
assert.strictEqual(views[1].name, 'bar');
|
||||
}
|
||||
});
|
||||
|
||||
it('should match view with instrument type', () => {
|
||||
const registry = new ViewRegistry();
|
||||
registry.addView(new View({ name: 'no-filter' }));
|
||||
registry.addView(new View({ name: 'counter' }), new InstrumentSelector({
|
||||
type: InstrumentType.COUNTER,
|
||||
}));
|
||||
registry.addView(new View({ name: 'histogram' }), new InstrumentSelector({
|
||||
type: InstrumentType.HISTOGRAM,
|
||||
}));
|
||||
|
||||
{
|
||||
const views = registry.findViews({
|
||||
...defaultInstrumentDescriptor,
|
||||
type: InstrumentType.COUNTER
|
||||
}, defaultInstrumentationLibrary);
|
||||
|
||||
assert.strictEqual(views.length, 2);
|
||||
assert.strictEqual(views[0].name, 'no-filter')
|
||||
assert.strictEqual(views[1].name, 'counter');
|
||||
}
|
||||
|
||||
{
|
||||
const views = registry.findViews({
|
||||
...defaultInstrumentDescriptor,
|
||||
type: InstrumentType.HISTOGRAM
|
||||
}, defaultInstrumentationLibrary);
|
||||
|
||||
assert.strictEqual(views.length, 2);
|
||||
assert.strictEqual(views[0].name, 'no-filter');
|
||||
assert.strictEqual(views[1].name, 'histogram');
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
describe('MeterSelector', () => {
|
||||
it('should match view with meter name', () => {
|
||||
const registry = new ViewRegistry();
|
||||
registry.addView(new View({ name: 'no-filter' }));
|
||||
registry.addView(new View({ name: 'foo' }), undefined, new MeterSelector({
|
||||
name: 'foo'
|
||||
}));
|
||||
registry.addView(new View({ name: 'bar' }), undefined, new MeterSelector({
|
||||
name: 'bar'
|
||||
}));
|
||||
|
||||
{
|
||||
const views = registry.findViews(defaultInstrumentDescriptor, {
|
||||
...defaultInstrumentationLibrary,
|
||||
name: 'foo',
|
||||
});
|
||||
|
||||
assert.strictEqual(views.length, 2);
|
||||
assert.strictEqual(views[0].name, 'no-filter')
|
||||
assert.strictEqual(views[1].name, 'foo');
|
||||
}
|
||||
|
||||
{
|
||||
const views = registry.findViews(defaultInstrumentDescriptor, {
|
||||
...defaultInstrumentationLibrary,
|
||||
name: 'bar'
|
||||
});
|
||||
|
||||
assert.strictEqual(views.length, 2);
|
||||
assert.strictEqual(views[0].name, 'no-filter');
|
||||
assert.strictEqual(views[1].name, 'bar');
|
||||
}
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
Loading…
Reference in New Issue