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

Commit c7226d6

Browse files
eduardoemerykjin
authored andcommitted
feat: add stats implementation (#92)
* feat: add stats implementation * refactor(fix): changes to address review comments
1 parent 4c4ce0f commit c7226d6

File tree

5 files changed

+237
-14
lines changed

5 files changed

+237
-14
lines changed

packages/opencensus-core/src/exporters/console-exporter.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,7 @@ export class ConsoleStatsExporter implements types.StatsEventListener {
9292
* @param view recorded view from measurement
9393
* @param measurement recorded measurement
9494
*/
95-
onRecord(view: View, measurement: Measurement) {
96-
console.log(`Measurement recorded: ${view.measure.name}`);
95+
onRecord(views: View[], measurement: Measurement) {
96+
console.log(`Measurement recorded: ${measurement.measure.name}`);
9797
}
9898
}

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

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,10 @@ export interface StatsEventListener {
3939
onRegisterView(view: View): void;
4040
/**
4141
* Is called whenever a new measurement is recorded.
42-
* @param view The view related to the measurement
42+
* @param views The views related to the measurement
4343
* @param measurement The recorded measurement
4444
*/
45-
onRecord(view: View, measurement: Measurement): void;
45+
onRecord(views: View[], measurement: Measurement): void;
4646
}
4747

4848
export type ExporterConfig = configTypes.BufferConfig;

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

Lines changed: 48 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,14 @@
1616

1717
import {StatsEventListener} from '../exporters/types';
1818

19-
import {AggregationType, Measure, Measurement, MeasureUnit, View} from './types';
19+
import {AggregationType, Measure, Measurement, MeasureType, MeasureUnit, View} from './types';
20+
import {BaseView} from './view';
2021

2122
export class Stats {
2223
/** A list of Stats exporters */
2324
private statsEventListeners: StatsEventListener[] = [];
2425
/** A map of Measures (name) to their corresponding Views */
25-
private registeredViews: {[key: string]: View[]};
26+
private registeredViews: {[key: string]: View[]} = {};
2627

2728
constructor() {}
2829

@@ -32,7 +33,18 @@ export class Stats {
3233
* @param view The view to be registered
3334
*/
3435
registerView(view: View) {
35-
throw new Error('Not Implemented');
36+
if (this.registeredViews[view.measure.name]) {
37+
this.registeredViews[view.measure.name].push(view);
38+
} else {
39+
this.registeredViews[view.measure.name] = [view];
40+
}
41+
42+
view.registered = true;
43+
44+
// Notifies all exporters
45+
for (const exporter of this.statsEventListeners) {
46+
exporter.onRegisterView(view);
47+
}
3648
}
3749

3850
/**
@@ -42,19 +54,31 @@ export class Stats {
4254
* @param aggregation The view aggregation type
4355
* @param tagKeys The view columns (tag keys)
4456
* @param description The view description
57+
* @param bucketBoundaries The view bucket boundaries for a distribution
58+
* aggregation type
4559
*/
4660
createView(
4761
name: string, measure: Measure, aggregation: AggregationType,
48-
tagKeys: string[], description?: string): View {
49-
throw new Error('Not Implemented');
62+
tagKeys: string[], description: string,
63+
bucketBoundaries?: number[]): View {
64+
const view = new BaseView(
65+
name, measure, aggregation, tagKeys, description, bucketBoundaries);
66+
this.registerView(view);
67+
return view;
5068
}
5169

5270
/**
5371
* Registers an exporter to send stats data to a service.
5472
* @param exporter An stats exporter
5573
*/
5674
registerExporter(exporter: StatsEventListener) {
57-
throw new Error('Not Implemented');
75+
this.statsEventListeners.push(exporter);
76+
77+
for (const measureName of Object.keys(this.registeredViews)) {
78+
for (const view of this.registeredViews[measureName]) {
79+
exporter.onRegisterView(view);
80+
}
81+
}
5882
}
5983

6084
/**
@@ -65,7 +89,7 @@ export class Stats {
6589
*/
6690
createMeasureDouble(name: string, unit: MeasureUnit, description?: string):
6791
Measure {
68-
throw new Error('Not Implemented');
92+
return {name, unit, type: MeasureType.DOUBLE, description};
6993
}
7094

7195
/**
@@ -77,14 +101,28 @@ export class Stats {
77101
*/
78102
createMeasureInt64(name: string, unit: MeasureUnit, description?: string):
79103
Measure {
80-
throw new Error('Not Implemented');
104+
return {name, unit, type: MeasureType.INT64, description};
81105
}
82106

83107
/**
84108
* Updates all views with the new measurements.
85109
* @param measurements A list of measurements to record
86110
*/
87-
record(measurements: Measurement[]) {
88-
throw new Error('Not Implemented');
111+
record(...measurements: Measurement[]) {
112+
for (const measurement of measurements) {
113+
const views = this.registeredViews[measurement.measure.name];
114+
if (!views) {
115+
break;
116+
}
117+
// Updates all views
118+
for (const view of views) {
119+
view.recordMeasurement(measurement);
120+
}
121+
122+
// Notifies all exporters
123+
for (const exporter of this.statsEventListeners) {
124+
exporter.onRecord(views, measurement);
125+
}
126+
}
89127
}
90128
}

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,8 @@ export interface View {
111111
* @param tags The desired data's tags
112112
*/
113113
getSnapshot(tags: Tags): AggregationData;
114+
/** Gets the view's tag keys */
115+
getColumns(): string[];
114116
}
115117

116118
/**
Lines changed: 183 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,183 @@
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 * as assert from 'assert';
18+
import * as mocha from 'mocha';
19+
20+
import {BaseView, Stats, StatsEventListener} from '../src';
21+
import {AggregationType, LastValueData, Measure, Measurement, MeasureType, MeasureUnit, View} from '../src/stats/types';
22+
23+
class TestExporter implements StatsEventListener {
24+
registeredViews: View[] = [];
25+
recordedMeasurements: Measurement[] = [];
26+
27+
onRegisterView(view: View) {
28+
this.registeredViews.push(view);
29+
}
30+
31+
onRecord(views: View[], measurement: Measurement) {
32+
this.recordedMeasurements.push(measurement);
33+
}
34+
35+
clean() {
36+
this.registeredViews = [];
37+
this.recordedMeasurements = [];
38+
}
39+
}
40+
41+
describe('Stats', () => {
42+
let stats: Stats;
43+
44+
beforeEach(() => {
45+
stats = new Stats();
46+
});
47+
48+
const viewName = 'testViewName';
49+
const tags = {tagKey1: 'tagValue1', tagKey2: 'tagValue2'};
50+
const tagKeys = Object.keys(tags);
51+
const measureName = 'testMeasureDouble';
52+
const measureUnit = MeasureUnit.UNIT;
53+
const description = 'test description';
54+
55+
describe('createMeasureDouble()', () => {
56+
it('should create a measure of type double', () => {
57+
const measureDouble =
58+
stats.createMeasureDouble(measureName, measureUnit, description);
59+
assert.strictEqual(measureDouble.type, MeasureType.DOUBLE);
60+
assert.strictEqual(measureDouble.name, measureName);
61+
assert.strictEqual(measureDouble.unit, measureUnit);
62+
assert.strictEqual(measureDouble.description, description);
63+
});
64+
});
65+
66+
describe('createMeasureInt64()', () => {
67+
it('should create a measure of type int64', () => {
68+
const measureDouble =
69+
stats.createMeasureInt64(measureName, measureUnit, description);
70+
assert.strictEqual(measureDouble.type, MeasureType.INT64);
71+
assert.strictEqual(measureDouble.name, measureName);
72+
assert.strictEqual(measureDouble.unit, measureUnit);
73+
assert.strictEqual(measureDouble.description, description);
74+
});
75+
});
76+
77+
describe('createView()', () => {
78+
const aggregationTypes = [
79+
AggregationType.COUNT, AggregationType.SUM, AggregationType.LAST_VALUE,
80+
AggregationType.DISTRIBUTION
81+
];
82+
let measure: Measure;
83+
84+
before(() => {
85+
measure = stats.createMeasureInt64(measureName, measureUnit);
86+
});
87+
88+
for (const aggregationType of aggregationTypes) {
89+
it(`should create a view with ${aggregationType} aggregation`, () => {
90+
const bucketBoundaries =
91+
AggregationType.DISTRIBUTION ? [1, 2, 3] : null;
92+
const view = stats.createView(
93+
viewName, measure, aggregationType, tagKeys, description,
94+
bucketBoundaries);
95+
96+
assert.strictEqual(view.name, viewName);
97+
assert.strictEqual(view.measure, measure);
98+
assert.strictEqual(view.description, description);
99+
assert.deepEqual(view.measure, measure);
100+
assert.strictEqual(view.aggregation, aggregationType);
101+
assert.ok(view.registered);
102+
});
103+
}
104+
105+
it('should not create a view with distribution aggregation when no bucket boundaries were given',
106+
() => {
107+
assert.throws(stats.createView, 'No bucketBoundaries specified');
108+
});
109+
});
110+
111+
describe('registerView()', () => {
112+
let measure: Measure;
113+
const testExporter = new TestExporter();
114+
115+
before(() => {
116+
measure = stats.createMeasureInt64(measureName, measureUnit);
117+
});
118+
119+
it('should register a view', () => {
120+
stats.registerExporter(testExporter);
121+
const view = new BaseView(
122+
viewName, measure, AggregationType.LAST_VALUE, tagKeys, description);
123+
124+
assert.ok(!view.registered);
125+
assert.strictEqual(testExporter.registeredViews.length, 0);
126+
127+
stats.registerView(view);
128+
129+
assert.ok(view.registered);
130+
assert.strictEqual(testExporter.registeredViews.length, 1);
131+
assert.deepEqual(testExporter.registeredViews[0], view);
132+
});
133+
});
134+
135+
describe('record()', () => {
136+
let measure: Measure;
137+
const testExporter = new TestExporter();
138+
139+
before(() => {
140+
measure = stats.createMeasureInt64(measureName, measureUnit);
141+
});
142+
143+
beforeEach(() => {
144+
testExporter.clean();
145+
});
146+
147+
it('should record a single measurement', () => {
148+
stats.registerExporter(testExporter);
149+
const view = stats.createView(
150+
viewName, measure, AggregationType.LAST_VALUE, tagKeys, description);
151+
const measurement = {measure, tags, value: 1};
152+
153+
assert.strictEqual(testExporter.recordedMeasurements.length, 0);
154+
155+
stats.record(measurement);
156+
const aggregationData =
157+
testExporter.registeredViews[0].getSnapshot(tags) as LastValueData;
158+
159+
assert.strictEqual(testExporter.recordedMeasurements.length, 1);
160+
assert.deepEqual(testExporter.recordedMeasurements[0], measurement);
161+
assert.strictEqual(aggregationData.value, measurement.value);
162+
});
163+
164+
it('should record multiple measurements', () => {
165+
stats.registerExporter(testExporter);
166+
const view = stats.createView(
167+
viewName, measure, AggregationType.LAST_VALUE, tagKeys, description);
168+
const measurement1 = {measure, tags, value: 1};
169+
const measurement2 = {measure, tags, value: 1};
170+
171+
assert.strictEqual(testExporter.recordedMeasurements.length, 0);
172+
173+
stats.record(measurement1, measurement2);
174+
const aggregationData =
175+
testExporter.registeredViews[0].getSnapshot(tags) as LastValueData;
176+
177+
assert.strictEqual(testExporter.recordedMeasurements.length, 2);
178+
assert.deepEqual(testExporter.recordedMeasurements[0], measurement1);
179+
assert.deepEqual(testExporter.recordedMeasurements[1], measurement2);
180+
assert.strictEqual(aggregationData.value, measurement2.value);
181+
});
182+
});
183+
});

0 commit comments

Comments
 (0)