Skip to content
This repository was archived by the owner on Oct 3, 2023. It is now read-only.

Commit 89f20bb

Browse files
authored
Add support for Derived Cumulative API (#503)
* Add support for derived cumulative metrics * fix review comments * Move the type to the arrow function signature * Add JSDoc for CumulativeEntry interface and its members * Add TODO to use unknown type instead of any * fix review comment
1 parent 111223e commit 89f20bb

File tree

6 files changed

+542
-86
lines changed

6 files changed

+542
-86
lines changed

packages/opencensus-core/src/index.ts

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@ export * from './trace/instrumentation/types';
2323
export * from './trace/propagation/types';
2424
export * from './exporters/types';
2525
export * from './common/types';
26+
export * from './metrics/types';
27+
export * from './metrics/cumulative/types';
2628
export * from './metrics/gauges/types';
2729
export {Metric, MetricDescriptor, TimeSeries, MetricDescriptorType, LabelKey, LabelValue, Point as TimeSeriesPoint, DistributionValue, BucketOptions, Bucket as DistributionBucket, SummaryValue, Explicit, Exemplar, Timestamp, Snapshot, ValueAtPercentile, MetricProducerManager, MetricProducer} from './metrics/export/types';
2830

@@ -74,6 +76,7 @@ export * from './metrics/metric-registry';
7476

7577
// Cumulative CLASSES
7678
export * from './metrics/cumulative/cumulative';
79+
export * from './metrics/cumulative/derived-cumulative';
7780

7881
// GAUGES CLASSES
7982
export * from './metrics/gauges/derived-gauge';
Lines changed: 177 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,177 @@
1+
/**
2+
* Copyright 2019, OpenCensus Authors
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
import {getTimestampWithProcessHRTime} from '../../common/time-util';
18+
import {validateArrayElementsNotNull, validateNotNull} from '../../common/validations';
19+
import {LabelKey, LabelValue, Metric, MetricDescriptor, MetricDescriptorType, TimeSeries, Timestamp} from '../export/types';
20+
import {Meter} from '../types';
21+
import {AccessorInterface} from '../types';
22+
import * as util from '../utils';
23+
24+
type ValueExtractor = () => number;
25+
26+
/**
27+
* An interface that describes the entry for every TimeSeries (Point) added to
28+
* the Cumulative metric.
29+
*/
30+
interface CumulativeEntry {
31+
/** The list of the label values. */
32+
readonly labelValues: LabelValue[];
33+
/** The function to get the actual value of point. */
34+
readonly extractor: ValueExtractor;
35+
/** The previous value of the point. */
36+
prevValue: number;
37+
}
38+
39+
/**
40+
* DerivedCumulative metric is used to record aggregated metrics that
41+
* represents a single numerical value accumulated over a time interval.
42+
*/
43+
export class DerivedCumulative implements Meter {
44+
private metricDescriptor: MetricDescriptor;
45+
private labelKeysLength: number;
46+
private registeredPoints: Map<string, CumulativeEntry> = new Map();
47+
private extractor?: ValueExtractor;
48+
private readonly constantLabelValues: LabelValue[];
49+
private startTime: Timestamp;
50+
51+
/**
52+
* Constructs a new DerivedCumulative instance.
53+
*
54+
* @param name The name of the metric.
55+
* @param description The description of the metric.
56+
* @param unit The unit of the metric.
57+
* @param type The type of metric.
58+
* @param labelKeys The list of the label keys.
59+
* @param constantLabels The map of constant labels for the Metric.
60+
* @param startTime The time when the cumulative metric start measuring the
61+
* value.
62+
*/
63+
constructor(
64+
name: string, description: string, unit: string,
65+
type: MetricDescriptorType, labelKeys: LabelKey[],
66+
readonly constantLabels: Map<LabelKey, LabelValue>,
67+
startTime: Timestamp) {
68+
this.labelKeysLength = labelKeys.length;
69+
const keysAndConstantKeys = [...labelKeys, ...constantLabels.keys()];
70+
this.constantLabelValues = [...constantLabels.values()];
71+
72+
this.metricDescriptor =
73+
{name, description, unit, type, labelKeys: keysAndConstantKeys};
74+
this.startTime = startTime;
75+
}
76+
77+
/**
78+
* Creates a TimeSeries. The value of a single point in the TimeSeries is
79+
* observed from an object or function. The ValueExtractor is invoked whenever
80+
* metrics are collected, meaning the reported value is up-to-date.
81+
*
82+
* @param labelValues The list of the label values.
83+
* @param objOrFn obj The obj to get the size or length or value from. If
84+
* multiple options are available, the value (ToValueInterface) takes
85+
* precedence first, followed by length and size. e.g value -> length ->
86+
* size.
87+
* fn is the function that will be called to get the current value
88+
* of the cumulative.
89+
*/
90+
createTimeSeries(labelValues: LabelValue[], objOrFn: AccessorInterface):
91+
void {
92+
validateArrayElementsNotNull(
93+
validateNotNull(labelValues, 'labelValues'), 'labelValue');
94+
validateNotNull(objOrFn, 'obj');
95+
96+
const hash = util.hashLabelValues(labelValues);
97+
if (this.registeredPoints.has(hash)) {
98+
throw new Error(
99+
'A different time series with the same labels already exists.');
100+
}
101+
if (this.labelKeysLength !== labelValues.length) {
102+
throw new Error('Label Keys and Label Values don\'t have same size');
103+
}
104+
105+
if (objOrFn instanceof Function) {
106+
this.extractor = objOrFn;
107+
} else if (util.isToValueInterface(objOrFn)) {
108+
this.extractor = () => objOrFn.getValue();
109+
} else if (util.isLengthAttributeInterface(objOrFn)) {
110+
this.extractor = () => objOrFn.length;
111+
} else if (util.isLengthMethodInterface(objOrFn)) {
112+
this.extractor = () => objOrFn.length();
113+
} else if (util.isSizeAttributeInterface(objOrFn)) {
114+
this.extractor = () => objOrFn.size;
115+
} else if (util.isSizeMethodInterface(objOrFn)) {
116+
this.extractor = () => objOrFn.size();
117+
} else {
118+
throw new Error('Unknown interface/object type');
119+
}
120+
121+
this.registeredPoints.set(
122+
hash, {labelValues, extractor: this.extractor, prevValue: 0});
123+
}
124+
125+
/**
126+
* Removes the TimeSeries from the cumulative metric, if it is present. i.e.
127+
* references to previous Point objects are invalid (not part of the
128+
* metric).
129+
*
130+
* @param labelValues The list of label values.
131+
*/
132+
removeTimeSeries(labelValues: LabelValue[]): void {
133+
validateNotNull(labelValues, 'labelValues');
134+
this.registeredPoints.delete(util.hashLabelValues(labelValues));
135+
}
136+
137+
/**
138+
* Removes all TimeSeries from the cumulative metric. i.e. references to all
139+
* previous Point objects are invalid (not part of the metric).
140+
*/
141+
clear(): void {
142+
this.registeredPoints.clear();
143+
}
144+
145+
/**
146+
* Provides a Metric with one or more TimeSeries.
147+
*
148+
* @returns The Metric, or null if TimeSeries is not present in Metric.
149+
*/
150+
getMetric(): Metric|null {
151+
if (this.registeredPoints.size === 0) {
152+
return null;
153+
}
154+
const timestamp: Timestamp = getTimestampWithProcessHRTime();
155+
return {
156+
descriptor: this.metricDescriptor,
157+
timeseries: Array.from(
158+
this.registeredPoints,
159+
([_, cumulativeEntry]):
160+
TimeSeries => {
161+
const newValue = cumulativeEntry.extractor();
162+
const value = newValue > cumulativeEntry.prevValue ?
163+
newValue :
164+
cumulativeEntry.prevValue;
165+
cumulativeEntry.prevValue = value;
166+
167+
return {
168+
labelValues: [
169+
...cumulativeEntry.labelValues, ...this.constantLabelValues
170+
],
171+
points: [{value, timestamp}],
172+
startTimestamp: this.startTime
173+
};
174+
})
175+
};
176+
}
177+
}

packages/opencensus-core/src/metrics/gauges/derived-gauge.ts

Lines changed: 9 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -18,42 +18,8 @@ import {getTimestampWithProcessHRTime} from '../../common/time-util';
1818
import {validateArrayElementsNotNull, validateNotNull} from '../../common/validations';
1919
import {LabelKey, LabelValue, Metric, MetricDescriptor, MetricDescriptorType, TimeSeries, Timestamp} from '../export/types';
2020
import * as types from '../types';
21-
import {hashLabelValues} from '../utils';
22-
23-
/**
24-
* Interface for objects with "length()" method.
25-
*/
26-
export interface LengthMethodInterface {
27-
length(): number;
28-
}
29-
30-
/**
31-
* Interface for objects with "length" attribute (e.g. Array).
32-
*/
33-
export interface LengthAttributeInterface {
34-
length: number;
35-
}
36-
37-
/**
38-
* Interface for objects with "size" method.
39-
*/
40-
export interface SizeMethodInterface {
41-
size(): number;
42-
}
43-
44-
/**
45-
* Interface for objects with "size" attribute (e.g. Map, Set).
46-
*/
47-
export interface SizeAttributeInterface {
48-
size: number;
49-
}
50-
51-
/**
52-
* Interface for objects with "getValue" method.
53-
*/
54-
export interface ToValueInterface {
55-
getValue(): number;
56-
}
21+
import {AccessorInterface} from '../types';
22+
import * as util from '../utils';
5723

5824
type ValueExtractor = () => number;
5925

@@ -62,14 +28,6 @@ interface GaugeEntry {
6228
readonly extractor: ValueExtractor;
6329
}
6430

65-
interface AccessorFunction {
66-
(): number;
67-
}
68-
69-
export type AccessorInterface =
70-
LengthAttributeInterface|LengthMethodInterface|SizeAttributeInterface|
71-
SizeMethodInterface|ToValueInterface|AccessorFunction;
72-
7331
/**
7432
* DerivedGauge metric
7533
*/
@@ -83,8 +41,6 @@ export class DerivedGauge implements types.Meter {
8341
private static readonly LABEL_VALUE = 'labelValue';
8442
private static readonly LABEL_VALUES = 'labelValues';
8543
private static readonly OBJECT = 'obj';
86-
private static readonly NUMBER = 'number';
87-
private static readonly FUNCTION = 'function';
8844
private static readonly ERROR_MESSAGE_INVALID_SIZE =
8945
'Label Keys and Label Values don\'t have same size';
9046
private static readonly ERROR_MESSAGE_DUPLICATE_TIME_SERIES =
@@ -114,39 +70,6 @@ export class DerivedGauge implements types.Meter {
11470
{name, description, unit, type, labelKeys: keysAndConstantKeys};
11571
}
11672

117-
// Checks if the specified collection is a LengthAttributeInterface.
118-
// tslint:disable-next-line:no-any
119-
protected static isLengthAttributeInterface(obj: any):
120-
obj is LengthAttributeInterface {
121-
return obj && typeof obj.length === DerivedGauge.NUMBER;
122-
}
123-
124-
// Checks if the specified collection is a LengthMethodInterface.
125-
// tslint:disable-next-line:no-any
126-
protected static isLengthMethodInterface(obj: any):
127-
obj is LengthMethodInterface {
128-
return obj && typeof obj.length === DerivedGauge.FUNCTION;
129-
}
130-
131-
// Checks if the specified collection is a SizeAttributeInterface.
132-
// tslint:disable-next-line:no-any
133-
protected static isSizeAttributeInterface(obj: any):
134-
obj is SizeAttributeInterface {
135-
return obj && typeof obj.size === DerivedGauge.NUMBER;
136-
}
137-
138-
// Checks if the specified collection is a SizeMethodInterface.
139-
// tslint:disable-next-line:no-any
140-
protected static isSizeMethodInterface(obj: any): obj is SizeMethodInterface {
141-
return obj && typeof obj.size === DerivedGauge.FUNCTION;
142-
}
143-
144-
// Checks if the specified callbackFn is a ToValueInterface.
145-
// tslint:disable-next-line:no-any
146-
protected static isToValueInterface(obj: any): obj is ToValueInterface {
147-
return obj && typeof obj.getValue === DerivedGauge.FUNCTION;
148-
}
149-
15073
/**
15174
* Creates a TimeSeries. The value of a single point in the TimeSeries is
15275
* observed from a obj or a function. The ValueExtractor is invoked whenever
@@ -167,7 +90,7 @@ export class DerivedGauge implements types.Meter {
16790
DerivedGauge.LABEL_VALUE);
16891
validateNotNull(objOrFn, DerivedGauge.OBJECT);
16992

170-
const hash = hashLabelValues(labelValues);
93+
const hash = util.hashLabelValues(labelValues);
17194
if (this.registeredPoints.has(hash)) {
17295
throw new Error(DerivedGauge.ERROR_MESSAGE_DUPLICATE_TIME_SERIES);
17396
}
@@ -177,15 +100,15 @@ export class DerivedGauge implements types.Meter {
177100

178101
if (objOrFn instanceof Function) {
179102
this.extractor = objOrFn;
180-
} else if (DerivedGauge.isToValueInterface(objOrFn)) {
103+
} else if (util.isToValueInterface(objOrFn)) {
181104
this.extractor = () => objOrFn.getValue();
182-
} else if (DerivedGauge.isLengthAttributeInterface(objOrFn)) {
105+
} else if (util.isLengthAttributeInterface(objOrFn)) {
183106
this.extractor = () => objOrFn.length;
184-
} else if (DerivedGauge.isLengthMethodInterface(objOrFn)) {
107+
} else if (util.isLengthMethodInterface(objOrFn)) {
185108
this.extractor = () => objOrFn.length();
186-
} else if (DerivedGauge.isSizeAttributeInterface(objOrFn)) {
109+
} else if (util.isSizeAttributeInterface(objOrFn)) {
187110
this.extractor = () => objOrFn.size;
188-
} else if (DerivedGauge.isSizeMethodInterface(objOrFn)) {
111+
} else if (util.isSizeMethodInterface(objOrFn)) {
189112
this.extractor = () => objOrFn.size();
190113
} else {
191114
throw new Error(DerivedGauge.ERROR_MESSAGE_UNKNOWN_INTERFACE);
@@ -203,7 +126,7 @@ export class DerivedGauge implements types.Meter {
203126
*/
204127
removeTimeSeries(labelValues: LabelValue[]): void {
205128
validateNotNull(labelValues, DerivedGauge.LABEL_VALUES);
206-
this.registeredPoints.delete(hashLabelValues(labelValues));
129+
this.registeredPoints.delete(util.hashLabelValues(labelValues));
207130
}
208131

209132
/**

packages/opencensus-core/src/metrics/types.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -41,3 +41,36 @@ export interface MetricOptions {
4141
// TODO(mayurkale): Add resource information.
4242
// https://github.com/census-instrumentation/opencensus-specs/pull/248
4343
}
44+
45+
/** Interface for objects with "length()" method. */
46+
export interface LengthMethodInterface {
47+
length(): number;
48+
}
49+
50+
/** Interface for objects with "length" attribute (e.g. Array). */
51+
export interface LengthAttributeInterface {
52+
length: number;
53+
}
54+
55+
/** Interface for objects with "size" method. */
56+
export interface SizeMethodInterface {
57+
size(): number;
58+
}
59+
60+
/** Interface for objects with "size" attribute (e.g. Map, Set). */
61+
export interface SizeAttributeInterface {
62+
size: number;
63+
}
64+
65+
/** Interface for objects with "getValue" method. */
66+
export interface ToValueInterface {
67+
getValue(): number;
68+
}
69+
70+
export interface AccessorFunction {
71+
(): number;
72+
}
73+
74+
export type AccessorInterface =
75+
LengthAttributeInterface|LengthMethodInterface|SizeAttributeInterface|
76+
SizeMethodInterface|ToValueInterface|AccessorFunction;

0 commit comments

Comments
 (0)