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

Commit 8b80a65

Browse files
authored
Stats: Record against implicit (current) tag context (#418)
* Stats: Record against implicit (current) context * update index.ts * Add example, record withTagContext * Add test on clear
1 parent 6e666d3 commit 8b80a65

File tree

8 files changed

+300
-8
lines changed

8 files changed

+300
-8
lines changed

examples/tags/tag-context-example.js

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
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+
const core = require('@opencensus/core');
18+
19+
const K1 = { name: 'k1' };
20+
const K2 = { name: 'k2' };
21+
const V1 = { value: 'v1' };
22+
const V2 = { value: 'v2' };
23+
24+
const mLatencyMs = core.globalStats.createMeasureDouble(
25+
'm1', core.MeasureUnit.MS, '1st test metric'
26+
);
27+
28+
/** Main method. */
29+
function main () {
30+
const tags1 = new core.TagMap();
31+
tags1.set(K1, V1);
32+
33+
core.withTagContext(tags1, () => {
34+
console.log('Enter Scope 1');
35+
printMap('Add Tags', tags1.tags);
36+
printMap('Current Tags == Default + tags1:', core.getCurrentTagContext().tags);
37+
38+
const tags2 = new core.TagMap();
39+
tags2.set(K2, V2);
40+
core.withTagContext(tags2, () => {
41+
console.log('Enter Scope 2');
42+
printMap('Add Tags', tags2.tags);
43+
printMap('Current Tags == Default + tags1 + tags2:', core.getCurrentTagContext().tags);
44+
45+
const measurement = { measure: mLatencyMs, value: 10 };
46+
core.globalStats.record([measurement]);
47+
console.log('Close Scope 2');
48+
});
49+
printMap('Current Tags == Default + tags1:', core.getCurrentTagContext().tags);
50+
console.log('Close Scope 1');
51+
});
52+
}
53+
54+
function printMap (message, myMap) {
55+
var tags = ` ${message}`;
56+
for (var [key, value] of myMap) {
57+
tags += ` ${key.name} = ${value.value}`;
58+
}
59+
console.log(tags);
60+
}
61+
62+
main();

packages/opencensus-core/src/index.ts

+1
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,7 @@ export * from './stats/recorder';
7171
export * from './stats/bucket-boundaries';
7272
export * from './stats/metric-utils';
7373
export * from './tags/tag-map';
74+
export * from './tags/tagger';
7475
export * from './resource/resource';
7576

7677
// interfaces

packages/opencensus-core/src/internal/cls-ah.ts

+12-3
Original file line numberDiff line numberDiff line change
@@ -22,12 +22,15 @@ import {Context, Func, Namespace as CLSNamespace} from 'continuation-local-stora
2222
import {EventEmitter} from 'events';
2323
import * as shimmer from 'shimmer';
2424

25-
const wrappedSymbol = Symbol('context_wrapped');
25+
const WRAPPED = Symbol('context_wrapped');
26+
/** A map of AsyncResource IDs to Context objects. */
2627
let contexts: {[asyncId: number]: Context;} = {};
2728
let current: Context = {};
2829

30+
// Create the hook.
2931
asyncHook.createHook({init, before, destroy}).enable();
3032

33+
// A list of well-known EventEmitter methods that add event listeners.
3134
const EVENT_EMITTER_METHODS: Array<keyof EventEmitter> =
3235
['addListener', 'on', 'once', 'prependListener', 'prependOnceListener'];
3336

@@ -70,7 +73,7 @@ class AsyncHooksNamespace implements CLSNamespace {
7073
// TODO(kjin): Monitor https://github.com/Microsoft/TypeScript/pull/15473.
7174
// When it's landed and released, we can remove these `any` casts.
7275
// tslint:disable-next-line:no-any
73-
if ((cb as any)[wrappedSymbol] as boolean || !current) {
76+
if ((cb as any)[WRAPPED] as boolean || !current) {
7477
return cb;
7578
}
7679
const boundContext = current;
@@ -82,7 +85,7 @@ class AsyncHooksNamespace implements CLSNamespace {
8285
return res;
8386
};
8487
// tslint:disable-next-line:no-any
85-
(contextWrapper as any)[wrappedSymbol] = true;
88+
(contextWrapper as any)[WRAPPED] = true;
8689
Object.defineProperty(contextWrapper, 'length', {
8790
enumerable: false,
8891
configurable: true,
@@ -115,17 +118,23 @@ const namespace = new AsyncHooksNamespace();
115118

116119
// AsyncWrap Hooks
117120

121+
/** init is called during object construction. */
118122
function init(
119123
uid: number, provider: string, parentUid: number, parentHandle: {}) {
120124
contexts[uid] = current;
121125
}
122126

127+
/** before is called just before the resource's callback is called. */
123128
function before(uid: number) {
124129
if (contexts[uid]) {
125130
current = contexts[uid];
126131
}
127132
}
128133

134+
/**
135+
* destroy is called when the object is no longer used, so also delete
136+
* its entry in the map.
137+
*/
129138
function destroy(uid: number) {
130139
delete contexts[uid];
131140
}

packages/opencensus-core/src/stats/stats.ts

+5-5
Original file line numberDiff line numberDiff line change
@@ -20,8 +20,8 @@ import {StatsEventListener} from '../exporters/types';
2020
import {Metric} from '../metrics/export/types';
2121
import {Metrics} from '../metrics/metrics';
2222
import {TagMap} from '../tags/tag-map';
23+
import {getCurrentTagContext} from '../tags/tagger';
2324
import {TagKey} from '../tags/types';
24-
2525
import {MetricProducerForStats} from './metric-producer';
2626
import {AggregationType, Measure, Measurement, MeasureType, MeasureUnit, Stats, View} from './types';
2727
import {BaseView} from './view';
@@ -80,8 +80,8 @@ export class BaseStats implements Stats {
8080
* @param aggregation The view aggregation type
8181
* @param tagKeys The view columns (tag keys)
8282
* @param description The view description
83-
* @param bucketBoundaries The view bucket boundaries for a distribution
84-
* aggregation type
83+
* @param bucketBoundaries optional The view bucket boundaries for a
84+
* distribution aggregation type
8585
*/
8686
createView(
8787
name: string, measure: Measure, aggregation: AggregationType,
@@ -188,8 +188,8 @@ export class BaseStats implements Stats {
188188
}
189189

190190
if (!tags) {
191-
// TODO(mayurkale): read tags current execution context
192-
tags = new TagMap();
191+
// Record against implicit (current) context
192+
tags = getCurrentTagContext();
193193
}
194194

195195
for (const measurement of measurements) {

packages/opencensus-core/src/tags/tag-map.ts

+8
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,14 @@ export class TagMap {
3131
if (!isValidTagValue(tagValue)) {
3232
throw new Error(`Invalid TagValue: ${tagValue.value}`);
3333
}
34+
let existingKey;
35+
for (const key of this.registeredTags.keys()) {
36+
if (key.name === tagKey.name) {
37+
existingKey = key;
38+
break;
39+
}
40+
}
41+
if (existingKey) this.registeredTags.delete(existingKey);
3442
this.registeredTags.set(tagKey, tagValue);
3543
}
3644

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,66 @@
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 * as cls from '../internal/cls';
18+
import {TagMap} from './tag-map';
19+
20+
const contextManager = cls.createNamespace();
21+
export const EMPTY_TAG_MAP = new TagMap();
22+
const CURRENT_TAG_MAP_KEY = 'current_tag_map';
23+
24+
/** Gets the current tag context. */
25+
export function getCurrentTagContext(): TagMap {
26+
const tagsFromContext = contextManager.get(CURRENT_TAG_MAP_KEY);
27+
if (tagsFromContext) {
28+
return tagsFromContext as TagMap;
29+
}
30+
return EMPTY_TAG_MAP;
31+
}
32+
33+
/**
34+
* Sets the current tag context.
35+
* @param tags The TagMap.
36+
*/
37+
export function setCurrentTagContext(tags: TagMap) {
38+
contextManager.set(CURRENT_TAG_MAP_KEY, tags);
39+
}
40+
41+
/**
42+
* Enters the scope of code where the given `TagMap` is in the current context
43+
* (replacing the previous `TagMap`).
44+
* @param tags The TagMap to be set to the current context.
45+
* @param fn Callback function.
46+
* @returns The callback return.
47+
*/
48+
export function withTagContext<T>(tags: TagMap, fn: cls.Func<T>): T {
49+
const oldContext = getCurrentTagContext();
50+
return contextManager.runAndReturn(() => {
51+
const newContext = new TagMap();
52+
for (const [tagKey, tagValue] of oldContext.tags) {
53+
newContext.set(tagKey, tagValue);
54+
}
55+
for (const [tagKey, tagValue] of tags.tags) {
56+
newContext.set(tagKey, tagValue);
57+
}
58+
setCurrentTagContext(newContext);
59+
return fn();
60+
});
61+
}
62+
63+
/** Clear the current tag context. */
64+
export function clear() {
65+
contextManager.set(CURRENT_TAG_MAP_KEY, EMPTY_TAG_MAP);
66+
}

packages/opencensus-core/test/test-stats.ts

+36
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import * as assert from 'assert';
1818
import {BaseView, globalStats, StatsEventListener, TagKey, TagMap, TagValue} from '../src';
1919
import {AggregationType, LastValueData, Measure, Measurement, MeasureType, MeasureUnit, View} from '../src/stats/types';
20+
import * as tagger from '../src/tags/tagger';
2021

2122
class TestExporter implements StatsEventListener {
2223
registeredViews: View[] = [];
@@ -225,5 +226,40 @@ describe('Stats', () => {
225226
globalStats.record(measurments, tagMap);
226227
assert.equal(testExporter.recordedMeasurements.length, 0);
227228
});
229+
230+
it('should record against implicit context when set', () => {
231+
const tags = new TagMap();
232+
tags.set(tagKeys[0], {value: 'value1'});
233+
tags.set(tagKeys[1], {value: 'value2'});
234+
const measurement = {measure, value: 1};
235+
tagger.withTagContext(tags, () => {
236+
globalStats.record([measurement]);
237+
});
238+
239+
assert.strictEqual(testExporter.recordedMeasurements.length, 1);
240+
assert.deepEqual(testExporter.recordedMeasurements[0], measurement);
241+
aggregationData =
242+
testExporter.registeredViews[0].getSnapshot(
243+
[{value: 'value1'}, {value: 'value2'}]) as LastValueData;
244+
assert.strictEqual(aggregationData.value, measurement.value);
245+
assert.deepStrictEqual(
246+
aggregationData.tagValues, [{value: 'value1'}, {value: 'value2'}]);
247+
});
248+
249+
it('should record against implicit context when not set or empty', () => {
250+
const UNKNOWN_TAG_VALUE: TagValue = null;
251+
globalStats.registerExporter(testExporter);
252+
const measurement = {measure, value: 2211};
253+
tagger.withTagContext(tagger.EMPTY_TAG_MAP, () => {
254+
globalStats.record([measurement]);
255+
});
256+
257+
aggregationData =
258+
testExporter.registeredViews[0].getSnapshot(
259+
[UNKNOWN_TAG_VALUE, UNKNOWN_TAG_VALUE]) as LastValueData;
260+
assert.strictEqual(aggregationData.value, measurement.value);
261+
assert.deepStrictEqual(
262+
aggregationData.tagValues, [UNKNOWN_TAG_VALUE, UNKNOWN_TAG_VALUE]);
263+
});
228264
});
229265
});

0 commit comments

Comments
 (0)