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

Commit

Permalink
feat: add stats implementation (#92)
Browse files Browse the repository at this point in the history
* feat: add stats implementation

* refactor(fix): changes to address review comments
  • Loading branch information
eduardoemery authored and kjin committed Aug 16, 2018
1 parent 4c4ce0f commit c7226d6
Show file tree
Hide file tree
Showing 5 changed files with 237 additions and 14 deletions.
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) {
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);

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);
});
});
});

0 comments on commit c7226d6

Please sign in to comment.