Skip to content

Commit 70dc60b

Browse files
authored
feat(views): Update addView() to disallow named views that select more than one instrument. (#2820)
1 parent e71a5ee commit 70dc60b

File tree

6 files changed

+499
-13
lines changed

6 files changed

+499
-13
lines changed

experimental/packages/opentelemetry-sdk-metrics-base/src/MeterProvider.ts

Lines changed: 91 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@ import { InstrumentSelector } from './view/InstrumentSelector';
2424
import { MeterSelector } from './view/MeterSelector';
2525
import { View } from './view/View';
2626
import { MetricCollector } from './state/MetricCollector';
27+
import { Aggregation } from './view/Aggregation';
28+
import { FilteringAttributesProcessor } from './view/AttributesProcessor';
29+
import { InstrumentType } from './InstrumentDescriptor';
30+
import { PatternPredicate } from './view/Predicate';
2731

2832
/**
2933
* MeterProviderOptions provides an interface for configuring a MeterProvider.
@@ -33,6 +37,62 @@ export interface MeterProviderOptions {
3337
resource?: Resource;
3438
}
3539

40+
export type ViewOptions = {
41+
/**
42+
* If not provided, the Instrument name will be used by default. This will be used as the name of the metrics stream.
43+
*/
44+
name?: string,
45+
/**
46+
* If not provided, the Instrument description will be used by default.
47+
*/
48+
description?: string,
49+
/**
50+
* If provided, the attributes that are not in the list will be ignored.
51+
* If not provided, all the attribute keys will be used by default.
52+
*/
53+
attributeKeys?: string[],
54+
/**
55+
* The {@link Aggregation} aggregation to be used.
56+
*/
57+
aggregation?: Aggregation,
58+
59+
// TODO: Add ExemplarReservoir
60+
};
61+
62+
export type SelectorOptions = {
63+
instrument?: {
64+
/**
65+
* The type of the Instrument(s).
66+
*/
67+
type?: InstrumentType,
68+
/**
69+
* Name of the Instrument(s) with wildcard support.
70+
*/
71+
name?: string,
72+
}
73+
meter?: {
74+
/**
75+
* The name of the Meter.
76+
*/
77+
name?: string;
78+
/**
79+
* The version of the Meter.
80+
*/
81+
version?: string;
82+
/**
83+
* The schema URL of the Meter.
84+
*/
85+
schemaUrl?: string;
86+
}
87+
};
88+
89+
function isViewOptionsEmpty(options: ViewOptions): boolean {
90+
return (options.name == null &&
91+
options.aggregation == null &&
92+
options.attributeKeys == null &&
93+
options.description == null);
94+
}
95+
3696
/**
3797
* This class implements the {@link metrics.MeterProvider} interface.
3898
*/
@@ -50,8 +110,8 @@ export class MeterProvider implements metrics.MeterProvider {
50110
getMeter(name: string, version = '', options: metrics.MeterOptions = {}): metrics.Meter {
51111
// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#meter-creation
52112
if (this._shutdown) {
53-
api.diag.warn('A shutdown MeterProvider cannot provide a Meter');
54-
return metrics.NOOP_METER;
113+
api.diag.warn('A shutdown MeterProvider cannot provide a Meter');
114+
return metrics.NOOP_METER;
55115
}
56116

57117
return new Meter(this._sharedState, { name, version, schemaUrl: options.schemaUrl });
@@ -69,9 +129,35 @@ export class MeterProvider implements metrics.MeterProvider {
69129
this._sharedState.metricCollectors.push(collector);
70130
}
71131

72-
addView(view: View, instrumentSelector: InstrumentSelector, meterSelector: MeterSelector) {
73-
// https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#view
74-
this._sharedState.viewRegistry.addView(view, instrumentSelector, meterSelector);
132+
addView(options: ViewOptions, selectorOptions?: SelectorOptions) {
133+
if (isViewOptionsEmpty(options)) {
134+
throw new Error('Cannot create view with no view arguments supplied');
135+
}
136+
137+
// the SDK SHOULD NOT allow Views with a specified name to be declared with instrument selectors that
138+
// may select more than one instrument (e.g. wild card instrument name) in the same Meter.
139+
if (options.name != null &&
140+
(selectorOptions?.instrument?.name == null ||
141+
PatternPredicate.hasWildcard(selectorOptions.instrument.name))) {
142+
throw new Error('Views with a specified name must be declared with an instrument selector that selects at most one instrument per meter.');
143+
}
144+
145+
// Create AttributesProcessor if attributeKeys are defined set.
146+
let attributesProcessor = undefined;
147+
if (options.attributeKeys != null) {
148+
attributesProcessor = new FilteringAttributesProcessor(options.attributeKeys);
149+
}
150+
151+
const view = new View({
152+
name: options.name,
153+
description: options.description,
154+
aggregation: options.aggregation,
155+
attributesProcessor: attributesProcessor
156+
});
157+
const instrument = new InstrumentSelector(selectorOptions?.instrument);
158+
const meter = new MeterSelector(selectorOptions?.meter);
159+
160+
this._sharedState.viewRegistry.addView(view, instrument, meter);
75161
}
76162

77163
/**

experimental/packages/opentelemetry-sdk-metrics-base/src/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
export * from './export/AggregationTemporality';
1818
export * from './export/MetricData';
19+
export { MeterProvider, MeterProviderOptions } from './MeterProvider';
1920
export * from './export/MetricExporter';
2021
export * from './export/MetricProducer';
2122
export * from './export/MetricReader';
@@ -26,3 +27,4 @@ export * from './Meter';
2627
export * from './MeterProvider';
2728
export * from './ObservableResult';
2829
export { TimeoutError } from './utils';
30+
export { FilteringAttributesProcessor } from './view/AttributesProcessor';

experimental/packages/opentelemetry-sdk-metrics-base/src/state/MetricStorageRegistry.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ import { getConflictResolutionRecipe, getIncompatibilityDetails } from '../view/
2626
export class MetricStorageRegistry {
2727
private readonly _metricStorageRegistry = new Map<string, MetricStorage[]>();
2828

29+
static create(){
30+
return new MetricStorageRegistry();
31+
}
32+
2933
getStorages(): MetricStorage[] {
3034
let storages: MetricStorage[] = [];
3135
for (const metricStorages of this._metricStorageRegistry.values()) {

experimental/packages/opentelemetry-sdk-metrics-base/src/view/Predicate.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,10 @@ export class PatternPredicate implements Predicate {
5151
static escapePattern(pattern: string): string {
5252
return `^${pattern.replace(ESCAPE, '\\$&').replace('*', '.*')}$`;
5353
}
54+
55+
static hasWildcard(pattern: string): boolean{
56+
return pattern.includes('*');
57+
}
5458
}
5559

5660
export class ExactPredicate implements Predicate {

0 commit comments

Comments
 (0)