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

feat: add stats implementation #92

Merged
merged 2 commits into from
Aug 16, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions packages/opencensus-core/src/exporters/console-exporter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ export class ConsoleStatsExporter implements types.StatsEventListener {
* @param view recorded view from measurement
* @param measurement recorded measurement
*/
onRecord(view: View, measurement: Measurement) {
console.log(`Measurement recorded: ${view.measure.name}`);
onRecord(views: View[], measurement: Measurement) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update JSDocs here and elsewhere.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated, thanks for noticing.

console.log(`Measurement recorded: ${measurement.measure.name}`);
}
}
4 changes: 2 additions & 2 deletions packages/opencensus-core/src/exporters/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,10 +39,10 @@ export interface StatsEventListener {
onRegisterView(view: View): void;
/**
* Is called whenever a new measurement is recorded.
* @param view The view related to the measurement
* @param views The views related to the measurement
* @param measurement The recorded measurement
*/
onRecord(view: View, measurement: Measurement): void;
onRecord(views: View[], measurement: Measurement): void;
}

export type ExporterConfig = configTypes.BufferConfig;
58 changes: 48 additions & 10 deletions packages/opencensus-core/src/stats/stats.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@

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

import {AggregationType, Measure, Measurement, MeasureUnit, View} from './types';
import {AggregationType, Measure, Measurement, MeasureType, MeasureUnit, View} from './types';
import {BaseView} from './view';

export class Stats {
/** A list of Stats exporters */
private statsEventListeners: StatsEventListener[] = [];
/** A map of Measures (name) to their corresponding Views */
private registeredViews: {[key: string]: View[]};
private registeredViews: {[key: string]: View[]} = {};

constructor() {}

Expand All @@ -32,7 +33,18 @@ export class Stats {
* @param view The view to be registered
*/
registerView(view: View) {
throw new Error('Not Implemented');
if (this.registeredViews[view.measure.name]) {
this.registeredViews[view.measure.name].push(view);
} else {
this.registeredViews[view.measure.name] = [view];
}

view.registered = true;

// Notifies all exporters
for (const exporter of this.statsEventListeners) {
exporter.onRegisterView(view);
}
}

/**
Expand All @@ -42,19 +54,31 @@ export class Stats {
* @param aggregation The view aggregation type
* @param tagKeys The view columns (tag keys)
* @param description The view description
* @param bucketBoundaries The view bucket boundaries for a distribution
* aggregation type
*/
createView(
name: string, measure: Measure, aggregation: AggregationType,
tagKeys: string[], description?: string): View {
throw new Error('Not Implemented');
tagKeys: string[], description: string,
bucketBoundaries?: number[]): View {
const view = new BaseView(
name, measure, aggregation, tagKeys, description, bucketBoundaries);
this.registerView(view);
return view;
}

/**
* Registers an exporter to send stats data to a service.
* @param exporter An stats exporter
*/
registerExporter(exporter: StatsEventListener) {
throw new Error('Not Implemented');
this.statsEventListeners.push(exporter);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we immediately notify it of all currently registered views?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely! I hadn't considered this case but it is really important! Thanks for the suggestion.


for (const measureName of Object.keys(this.registeredViews)) {
for (const view of this.registeredViews[measureName]) {
exporter.onRegisterView(view);
}
}
}

/**
Expand All @@ -65,7 +89,7 @@ export class Stats {
*/
createMeasureDouble(name: string, unit: MeasureUnit, description?: string):
Measure {
throw new Error('Not Implemented');
return {name, unit, type: MeasureType.DOUBLE, description};
}

/**
Expand All @@ -77,14 +101,28 @@ export class Stats {
*/
createMeasureInt64(name: string, unit: MeasureUnit, description?: string):
Measure {
throw new Error('Not Implemented');
return {name, unit, type: MeasureType.INT64, description};
}

/**
* Updates all views with the new measurements.
* @param measurements A list of measurements to record
*/
record(measurements: Measurement[]) {
throw new Error('Not Implemented');
record(...measurements: Measurement[]) {
for (const measurement of measurements) {
const views = this.registeredViews[measurement.measure.name];
if (!views) {
break;
}
// Updates all views
for (const view of views) {
view.recordMeasurement(measurement);
}

// Notifies all exporters
for (const exporter of this.statsEventListeners) {
exporter.onRecord(views, measurement);
}
}
}
}
2 changes: 2 additions & 0 deletions packages/opencensus-core/src/stats/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,8 @@ export interface View {
* @param tags The desired data's tags
*/
getSnapshot(tags: Tags): AggregationData;
/** Gets the view's tag keys */
getColumns(): string[];
}

/**
Expand Down
183 changes: 183 additions & 0 deletions packages/opencensus-core/test/test-stats.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,183 @@
/**
* Copyright 2018, OpenCensus Authors
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

import * as assert from 'assert';
import * as mocha from 'mocha';

import {BaseView, Stats, StatsEventListener} from '../src';
import {AggregationType, LastValueData, Measure, Measurement, MeasureType, MeasureUnit, View} from '../src/stats/types';

class TestExporter implements StatsEventListener {
registeredViews: View[] = [];
recordedMeasurements: Measurement[] = [];

onRegisterView(view: View) {
this.registeredViews.push(view);
}

onRecord(views: View[], measurement: Measurement) {
this.recordedMeasurements.push(measurement);
}

clean() {
this.registeredViews = [];
this.recordedMeasurements = [];
}
}

describe('Stats', () => {
let stats: Stats;

beforeEach(() => {
stats = new Stats();
});

const viewName = 'testViewName';
const tags = {tagKey1: 'tagValue1', tagKey2: 'tagValue2'};
const tagKeys = Object.keys(tags);
const measureName = 'testMeasureDouble';
const measureUnit = MeasureUnit.UNIT;
const description = 'test description';

describe('createMeasureDouble()', () => {
it('should create a measure of type double', () => {
const measureDouble =
stats.createMeasureDouble(measureName, measureUnit, description);
assert.strictEqual(measureDouble.type, MeasureType.DOUBLE);
assert.strictEqual(measureDouble.name, measureName);
assert.strictEqual(measureDouble.unit, measureUnit);
assert.strictEqual(measureDouble.description, description);
});
});

describe('createMeasureInt64()', () => {
it('should create a measure of type int64', () => {
const measureDouble =
stats.createMeasureInt64(measureName, measureUnit, description);
assert.strictEqual(measureDouble.type, MeasureType.INT64);
assert.strictEqual(measureDouble.name, measureName);
assert.strictEqual(measureDouble.unit, measureUnit);
assert.strictEqual(measureDouble.description, description);
});
});

describe('createView()', () => {
const aggregationTypes = [
AggregationType.COUNT, AggregationType.SUM, AggregationType.LAST_VALUE,
AggregationType.DISTRIBUTION
];
let measure: Measure;

before(() => {
measure = stats.createMeasureInt64(measureName, measureUnit);
});

for (const aggregationType of aggregationTypes) {
it(`should create a view with ${aggregationType} aggregation`, () => {
const bucketBoundaries =
AggregationType.DISTRIBUTION ? [1, 2, 3] : null;
const view = stats.createView(
viewName, measure, aggregationType, tagKeys, description,
bucketBoundaries);

assert.strictEqual(view.name, viewName);
assert.strictEqual(view.measure, measure);
assert.strictEqual(view.description, description);
assert.deepEqual(view.measure, measure);
assert.strictEqual(view.aggregation, aggregationType);
assert.ok(view.registered);
});
}

it('should not create a view with distribution aggregation when no bucket boundaries were given',
() => {
assert.throws(stats.createView, 'No bucketBoundaries specified');
});
});

describe('registerView()', () => {
let measure: Measure;
const testExporter = new TestExporter();

before(() => {
measure = stats.createMeasureInt64(measureName, measureUnit);
});

it('should register a view', () => {
stats.registerExporter(testExporter);
const view = new BaseView(
viewName, measure, AggregationType.LAST_VALUE, tagKeys, description);

assert.ok(!view.registered);
assert.strictEqual(testExporter.registeredViews.length, 0);

stats.registerView(view);

assert.ok(view.registered);
assert.strictEqual(testExporter.registeredViews.length, 1);
assert.deepEqual(testExporter.registeredViews[0], view);
});
});

describe('record()', () => {
let measure: Measure;
const testExporter = new TestExporter();

before(() => {
measure = stats.createMeasureInt64(measureName, measureUnit);
});

beforeEach(() => {
testExporter.clean();
});

it('should record a single measurement', () => {
stats.registerExporter(testExporter);
const view = stats.createView(
viewName, measure, AggregationType.LAST_VALUE, tagKeys, description);
const measurement = {measure, tags, value: 1};

assert.strictEqual(testExporter.recordedMeasurements.length, 0);

stats.record(measurement);
const aggregationData =
testExporter.registeredViews[0].getSnapshot(tags) as LastValueData;

assert.strictEqual(testExporter.recordedMeasurements.length, 1);
assert.deepEqual(testExporter.recordedMeasurements[0], measurement);
assert.strictEqual(aggregationData.value, measurement.value);
});

it('should record multiple measurements', () => {
stats.registerExporter(testExporter);
const view = stats.createView(
viewName, measure, AggregationType.LAST_VALUE, tagKeys, description);
const measurement1 = {measure, tags, value: 1};
const measurement2 = {measure, tags, value: 1};

assert.strictEqual(testExporter.recordedMeasurements.length, 0);

stats.record(measurement1, measurement2);
const aggregationData =
testExporter.registeredViews[0].getSnapshot(tags) as LastValueData;

assert.strictEqual(testExporter.recordedMeasurements.length, 2);
assert.deepEqual(testExporter.recordedMeasurements[0], measurement1);
assert.deepEqual(testExporter.recordedMeasurements[1], measurement2);
assert.strictEqual(aggregationData.value, measurement2.value);
});
});
});