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

Commit 035fb02

Browse files
authored
Add derived gauge API (#220)
* Add derived gauge API * Update package-lock.json and add comments. * fix review comments
1 parent 6b27a67 commit 035fb02

File tree

4 files changed

+440
-2
lines changed

4 files changed

+440
-2
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,8 @@ All notable changes to this project will be documented in this file.
88
- Add Resource API.
99
- Add Metrics API.
1010
- Remove support for `min`/`max` in the stats Distribution to make it compatible with Metrics.
11-
- Add Gauges (`DoubleGauge`, `LongGauge`) APIs.
1211
- Remove default prefix from [exporter-prometheus]. This could be a breaking change if you have Prometheus metrics from OpenCensus Prometheus exporter of previous versions, please point to the new metrics with no prefix instead.
12+
- Add Gauges (`DoubleGauge`, `LongGauge`, `DerivedDoubleGauge`, `DerivedLongGauge`) APIs.
1313

1414
## 0.0.7 - 2018-11-12
1515
**Contains API breaking changes for stats/metrics implementations**

packages/opencensus-core/package-lock.json

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 220 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,220 @@
1+
/**
2+
* Copyright 2018, 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 {validateArrayElementsNotNull, validateNotNull} from '../../common/validations';
18+
import {LabelKey, LabelValue, Metric, MetricDescriptor, MetricDescriptorType, TimeSeries} from '../export/types';
19+
import * as types from '../gauges/types';
20+
import {hashLabelValues} from '../utils';
21+
22+
/**
23+
* Interface for objects with "length()" method.
24+
*/
25+
export interface LengthMethodInterface {
26+
length(): number;
27+
}
28+
29+
/**
30+
* Interface for objects with "length" attribute (e.g. Array).
31+
*/
32+
export interface LengthAttributeInterface {
33+
length: number;
34+
}
35+
36+
/**
37+
* Interface for objects with "size" method.
38+
*/
39+
export interface SizeMethodInterface {
40+
size(): number;
41+
}
42+
43+
/**
44+
* Interface for objects with "size" attribute (e.g. Map, Set).
45+
*/
46+
export interface SizeAttributeInterface {
47+
size: number;
48+
}
49+
50+
/**
51+
* Interface for objects with "getValue" method.
52+
*/
53+
export interface ToValueInterface {
54+
getValue(): number;
55+
}
56+
57+
type ValueExtractor = () => number;
58+
59+
interface GaugeEntry {
60+
readonly labelValues: LabelValue[];
61+
readonly extractor: ValueExtractor;
62+
}
63+
64+
export type AccessorInterface = LengthAttributeInterface|LengthMethodInterface|
65+
SizeAttributeInterface|SizeMethodInterface|ToValueInterface;
66+
67+
/**
68+
* DerivedGauge metric
69+
*/
70+
export class DerivedGauge implements types.Meter {
71+
private metricDescriptor: MetricDescriptor;
72+
private labelKeysLength: number;
73+
private registeredPoints: Map<string, GaugeEntry> = new Map();
74+
private extractor: ValueExtractor;
75+
76+
private static readonly LABEL_VALUE = 'labelValue';
77+
private static readonly LABEL_VALUES = 'labelValues';
78+
private static readonly OBJECT = 'obj';
79+
private static readonly NUMBER = 'number';
80+
private static readonly FUNCTION = 'function';
81+
private static readonly ERROR_MESSAGE_INVALID_SIZE =
82+
'Label Keys and Label Values don\'t have same size';
83+
private static readonly ERROR_MESSAGE_DUPLICATE_TIME_SERIES =
84+
'A different time series with the same labels already exists.';
85+
private static readonly ERROR_MESSAGE_UNKNOWN_INTERFACE =
86+
'Unknown interface/object type';
87+
88+
/**
89+
* Constructs a new DerivedGauge instance.
90+
*
91+
* @param {string} name The name of the metric.
92+
* @param {string} description The description of the metric.
93+
* @param {string} unit The unit of the metric.
94+
* @param {MetricDescriptorType} type The type of metric.
95+
* @param {LabelKey[]} labelKeys The list of the label keys.
96+
*/
97+
constructor(
98+
name: string, description: string, unit: string,
99+
type: MetricDescriptorType, labelKeys: LabelKey[]) {
100+
this.metricDescriptor = {name, description, unit, type, labelKeys};
101+
this.labelKeysLength = labelKeys.length;
102+
}
103+
104+
// Checks if the specified collection is a LengthAttributeInterface.
105+
// tslint:disable-next-line:no-any
106+
protected static isLengthAttributeInterface(obj: any):
107+
obj is LengthAttributeInterface {
108+
return obj && typeof obj.length === DerivedGauge.NUMBER;
109+
}
110+
111+
// Checks if the specified collection is a LengthMethodInterface.
112+
// tslint:disable-next-line:no-any
113+
protected static isLengthMethodInterface(obj: any):
114+
obj is LengthMethodInterface {
115+
return obj && typeof obj.length === DerivedGauge.FUNCTION;
116+
}
117+
118+
// Checks if the specified collection is a SizeAttributeInterface.
119+
// tslint:disable-next-line:no-any
120+
protected static isSizeAttributeInterface(obj: any):
121+
obj is SizeAttributeInterface {
122+
return obj && typeof obj.size === DerivedGauge.NUMBER;
123+
}
124+
125+
// Checks if the specified collection is a SizeMethodInterface.
126+
// tslint:disable-next-line:no-any
127+
protected static isSizeMethodInterface(obj: any): obj is SizeMethodInterface {
128+
return obj && typeof obj.size === DerivedGauge.FUNCTION;
129+
}
130+
131+
// Checks if the specified callbackFn is a ToValueInterface.
132+
// tslint:disable-next-line:no-any
133+
protected static isToValueInterface(obj: any): obj is ToValueInterface {
134+
return obj && typeof obj.getValue === DerivedGauge.FUNCTION;
135+
}
136+
137+
/**
138+
* Creates a TimeSeries. The value of a single point in the TimeSeries is
139+
* observed from a obj. The ValueExtractor is invoked whenever
140+
* metrics are collected, meaning the reported value is up-to-date.
141+
*
142+
* @param {LabelValue[]} labelValues The list of the label values.
143+
* @param obj The obj to get the size or length or value from. If multiple
144+
* options are available, the value (ToValueInterface) takes precedence
145+
* first, followed by length and size. e.g value -> length -> size.
146+
*/
147+
createTimeSeries(labelValues: LabelValue[], obj: AccessorInterface): void {
148+
validateArrayElementsNotNull(
149+
validateNotNull(labelValues, DerivedGauge.LABEL_VALUES),
150+
DerivedGauge.LABEL_VALUE);
151+
validateNotNull(obj, DerivedGauge.OBJECT);
152+
153+
const hash = hashLabelValues(labelValues);
154+
if (this.registeredPoints.has(hash)) {
155+
throw new Error(DerivedGauge.ERROR_MESSAGE_DUPLICATE_TIME_SERIES);
156+
}
157+
if (this.labelKeysLength !== labelValues.length) {
158+
throw new Error(DerivedGauge.ERROR_MESSAGE_INVALID_SIZE);
159+
}
160+
161+
if (DerivedGauge.isToValueInterface(obj)) {
162+
this.extractor = () => obj.getValue();
163+
} else if (DerivedGauge.isLengthAttributeInterface(obj)) {
164+
this.extractor = () => obj.length;
165+
} else if (DerivedGauge.isLengthMethodInterface(obj)) {
166+
this.extractor = () => obj.length();
167+
} else if (DerivedGauge.isSizeAttributeInterface(obj)) {
168+
this.extractor = () => obj.size;
169+
} else if (DerivedGauge.isSizeMethodInterface(obj)) {
170+
this.extractor = () => obj.size();
171+
} else {
172+
throw new Error(DerivedGauge.ERROR_MESSAGE_UNKNOWN_INTERFACE);
173+
}
174+
175+
this.registeredPoints.set(hash, {labelValues, extractor: this.extractor});
176+
}
177+
178+
/**
179+
* Removes the TimeSeries from the gauge metric, if it is present. i.e.
180+
* references to previous Point objects are invalid (not part of the
181+
* metric).
182+
*
183+
* @param {LabelValue[]} labelValues The list of label values.
184+
*/
185+
removeTimeSeries(labelValues: LabelValue[]): void {
186+
validateNotNull(labelValues, DerivedGauge.LABEL_VALUES);
187+
this.registeredPoints.delete(hashLabelValues(labelValues));
188+
}
189+
190+
/**
191+
* Removes all TimeSeries from the gauge metric. i.e. references to all
192+
* previous Point objects are invalid (not part of the metric).
193+
*/
194+
clear(): void {
195+
this.registeredPoints.clear();
196+
}
197+
198+
/**
199+
* Provides a Metric with one or more TimeSeries.
200+
*
201+
* @returns {Metric} The Metric.
202+
*/
203+
getMetric(): Metric {
204+
if (this.registeredPoints.size === 0) {
205+
return null;
206+
}
207+
const [seconds, nanos] = process.hrtime();
208+
return {
209+
descriptor: this.metricDescriptor,
210+
timeseries: Array.from(
211+
this.registeredPoints,
212+
([_, gaugeEntry]) => ({
213+
labelValues: gaugeEntry.labelValues,
214+
points: [
215+
{value: gaugeEntry.extractor(), timestamp: {seconds, nanos}}
216+
]
217+
} as TimeSeries))
218+
};
219+
}
220+
}

0 commit comments

Comments
 (0)