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

Commit 2df1bc4

Browse files
authored
Adds 0 count for underflow/first bucket to [StackdriverStatsExporter] (#181)
* Drops 0 bound, moves [BucketBoundaries] logic to separate class * Fixes boundaries for [prometeus] * Removes redundant [BucketBondaries] tests from [opencensus-exporter-zpages] * Adds 0 count for underflow/first bucket to [StackdriverStatsExporter]
1 parent 18e8981 commit 2df1bc4

File tree

14 files changed

+204
-170
lines changed

14 files changed

+204
-170
lines changed

packages/opencensus-core/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,7 @@ export * from './exporters/console-exporter';
4646
export * from './stats/stats';
4747
export * from './stats/view';
4848
export * from './stats/recorder';
49+
export * from './stats/bucket-boundaries';
4950

5051
// interfaces
5152
export * from './stats/types';
Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
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 defaultLogger from '../common/console-logger';
18+
import * as loggerTypes from '../common/types';
19+
import {Bucket} from './types';
20+
21+
export class BucketBoundaries {
22+
readonly buckets: Bucket[];
23+
readonly bucketCounts: number[];
24+
/** An object to log information to */
25+
private logger: loggerTypes.Logger;
26+
27+
constructor(boundaries: number[], logger = defaultLogger) {
28+
this.logger = logger.logger();
29+
this.buckets = this.dropNegativeBucketBounds(boundaries);
30+
this.bucketCounts = this.getBucketCounts(this.buckets);
31+
}
32+
33+
/**
34+
* Gets bucket boundaries
35+
*/
36+
getBoundaries(): Bucket[] {
37+
return this.buckets;
38+
}
39+
40+
/**
41+
* Gets initial bucket counts
42+
*/
43+
getCounts(): number[] {
44+
return this.bucketCounts;
45+
}
46+
47+
/**
48+
* Drops negative (BucketBounds) are currently not supported by
49+
* any of the backends that OC supports
50+
* @param bucketBoundaries a list with the bucket boundaries
51+
*/
52+
private dropNegativeBucketBounds(bucketBoundaries: number[]): Bucket[] {
53+
let negative = 0;
54+
if (!bucketBoundaries) return [];
55+
const result = bucketBoundaries.reduce((accumulator, boundary, index) => {
56+
if (boundary > 0) {
57+
const nextBoundary = bucketBoundaries[index + 1];
58+
this.validateBoundary(boundary, nextBoundary);
59+
accumulator.push(boundary);
60+
} else {
61+
negative++;
62+
}
63+
return accumulator;
64+
}, []);
65+
if (negative) {
66+
this.logger.warn(`Dropping ${
67+
negative} negative bucket boundaries, the values must be strictly > 0.`);
68+
}
69+
return result;
70+
}
71+
72+
/**
73+
* Gets initial list of bucket counters
74+
* @param buckets Bucket boundaries
75+
*/
76+
private getBucketCounts(buckets: Bucket[]): number[] {
77+
if (!buckets) return [];
78+
const bucketsCount = new Array(buckets.length + 1);
79+
bucketsCount.fill(0);
80+
return bucketsCount;
81+
}
82+
83+
/**
84+
* Checks boundaries order and duplicates
85+
* @param current Boundary
86+
* @param next Next boundary
87+
*/
88+
private validateBoundary(current: number, next: number) {
89+
if (next) {
90+
if (current > next) {
91+
this.logger.error('Bucket boundaries not sorted.');
92+
}
93+
if (current === next) {
94+
this.logger.error('Bucket boundaries not unique.');
95+
}
96+
}
97+
}
98+
}

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

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,10 +44,14 @@ export class Recorder {
4444
distributionData: DistributionData, value: number): DistributionData {
4545
distributionData.count += 1;
4646

47-
const inletBucket = distributionData.buckets.find((bucket) => {
48-
return bucket.lowBoundary <= value && value < bucket.highBoundary;
49-
});
50-
inletBucket.count += 1;
47+
let bucketIndex =
48+
distributionData.buckets.findIndex(bucket => bucket > value);
49+
50+
if (bucketIndex < 0) {
51+
bucketIndex = distributionData.buckets.length;
52+
}
53+
54+
distributionData.bucketCounts[bucketIndex] += 1;
5155

5256
if (value > distributionData.max) {
5357
distributionData.max = value;

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

Lines changed: 3 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -192,16 +192,9 @@ export interface DistributionData extends AggregationMetadata {
192192
sumSquaredDeviations: number;
193193
/** Bucket distribution of the histogram */
194194
buckets: Bucket[];
195+
/** Buckets count */
196+
bucketCounts: number[];
195197
}
196198

197-
/** A simple histogram bucket interface. */
198-
export interface Bucket {
199-
/** Number of occurrences in the domain */
200-
count: number;
201-
/** The maximum possible value for a data point to fall in this bucket */
202-
readonly highBoundary: number;
203-
/** The minimum possible value for a data point to fall in this bucket */
204-
readonly lowBoundary: number;
205-
}
206-
199+
export type Bucket = number;
207200
export type AggregationData = SumData|CountData|LastValueData|DistributionData;

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

Lines changed: 7 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -17,8 +17,9 @@
1717
import * as defaultLogger from '../common/console-logger';
1818
import * as loggerTypes from '../common/types';
1919

20+
import {BucketBoundaries} from './bucket-boundaries';
2021
import {Recorder} from './recorder';
21-
import {AggregationData, AggregationMetadata, AggregationType, Bucket, CountData, DistributionData, LastValueData, Measure, Measurement, MeasureType, SumData, Tags, View} from './types';
22+
import {AggregationData, AggregationMetadata, AggregationType, CountData, DistributionData, LastValueData, Measure, Measurement, MeasureType, SumData, Tags, View} from './types';
2223

2324
const RECORD_SEPARATOR = String.fromCharCode(30);
2425
const UNIT_SEPARATOR = String.fromCharCode(31);
@@ -55,7 +56,7 @@ export class BaseView implements View {
5556
/** The start time for this view */
5657
readonly startTime: number;
5758
/** The bucket boundaries in a Distribution Aggregation */
58-
private bucketBoundaries?: number[];
59+
private bucketBoundaries: BucketBoundaries;
5960
/**
6061
* The end time for this view - represents the last time a value was recorded
6162
*/
@@ -91,7 +92,7 @@ export class BaseView implements View {
9192
this.columns = tagsKeys;
9293
this.aggregation = aggregation;
9394
this.startTime = Date.now();
94-
this.bucketBoundaries = bucketBoundaries;
95+
this.bucketBoundaries = new BucketBoundaries(bucketBoundaries);
9596
}
9697

9798
/** Gets the view's tag keys */
@@ -156,7 +157,7 @@ export class BaseView implements View {
156157
*/
157158
private createAggregationData(tags: Tags): AggregationData {
158159
const aggregationMetadata = {tags, timestamp: Date.now()};
159-
160+
const {buckets, bucketCounts} = this.bucketBoundaries;
160161
switch (this.aggregation) {
161162
case AggregationType.DISTRIBUTION:
162163
return {
@@ -170,7 +171,8 @@ export class BaseView implements View {
170171
mean: null as number,
171172
stdDeviation: null as number,
172173
sumSquaredDeviations: null as number,
173-
buckets: this.createBuckets(this.bucketBoundaries)
174+
buckets,
175+
bucketCounts
174176
};
175177
case AggregationType.SUM:
176178
return {...aggregationMetadata, type: AggregationType.SUM, value: 0};
@@ -185,64 +187,6 @@ export class BaseView implements View {
185187
}
186188
}
187189

188-
/**
189-
* Creates empty Buckets, given a list of bucket boundaries.
190-
* @param bucketBoundaries a list with the bucket boundaries
191-
*/
192-
private createBuckets(bucketBoundaries: number[]): Bucket[] {
193-
let negative = 0;
194-
const result = bucketBoundaries.reduce((accumulator, boundary, index) => {
195-
if (boundary >= 0) {
196-
const nextBoundary = bucketBoundaries[index + 1];
197-
this.validateBoundary(boundary, nextBoundary);
198-
const len = bucketBoundaries.length - negative;
199-
const position = index - negative;
200-
const bucket = this.createBucket(boundary, nextBoundary, position, len);
201-
accumulator.push(bucket);
202-
} else {
203-
negative++;
204-
}
205-
return accumulator;
206-
}, []);
207-
if (negative) {
208-
this.logger.warn(`Dropping ${
209-
negative} negative bucket boundaries, the values must be strictly > 0.`);
210-
}
211-
return result;
212-
}
213-
214-
/**
215-
* Checks boundaries order and duplicates
216-
* @param current Boundary
217-
* @param next Next boundary
218-
*/
219-
private validateBoundary(current: number, next: number) {
220-
if (next) {
221-
if (current > next) {
222-
this.logger.error('Bucket boundaries not sorted.');
223-
}
224-
if (current === next) {
225-
this.logger.error('Bucket boundaries not unique.');
226-
}
227-
}
228-
}
229-
230-
/**
231-
* Creates empty bucket boundary.
232-
* @param current Current boundary
233-
* @param next Next boundary
234-
* @param position Index of boundary
235-
* @param max Maximum length of boundaries
236-
*/
237-
private createBucket(
238-
current: number, next: number, position: number, max: number): Bucket {
239-
return {
240-
count: 0,
241-
lowBoundary: position ? current : -Infinity,
242-
highBoundary: (position === max - 1) ? Infinity : next
243-
};
244-
}
245-
246190
/**
247191
* Returns a snapshot of an AggregationData for that tags/labels values.
248192
* @param tags The desired data's tags
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
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 {BucketBoundaries} from '../src';
19+
20+
describe('BucketBoundaries', () => {
21+
it('should return boundaries', () => {
22+
const buckets = new BucketBoundaries([1, 2, 3]);
23+
assert.deepStrictEqual(buckets.getBoundaries(), [1, 2, 3]);
24+
});
25+
26+
it('should has bucket counts', () => {
27+
const buckets = new BucketBoundaries([1, 2, 3]);
28+
assert.deepStrictEqual(buckets.getCounts(), [0, 0, 0, 0]);
29+
});
30+
31+
describe('Drop negative and 0 boundaries', () => {
32+
const buckets = new BucketBoundaries([-Infinity, -3, -2, -1, 0, 1, 2, 3]);
33+
it('should drop negative and 0 boundaries', () => {
34+
assert.deepStrictEqual(buckets.getBoundaries(), [1, 2, 3]);
35+
});
36+
it('should has bucket counts', () => {
37+
assert.deepStrictEqual(buckets.getCounts(), [0, 0, 0, 0]);
38+
});
39+
});
40+
});

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

Lines changed: 2 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -42,15 +42,6 @@ function assertDistributionData(
4242
assert.strictEqual(distributionData.count, values.length);
4343
assert.strictEqual(distributionData.sum, valuesSum);
4444

45-
for (const bucket of distributionData.buckets) {
46-
const expectedBucketCount = values
47-
.filter(
48-
value => bucket.lowBoundary <= value &&
49-
value < bucket.highBoundary)
50-
.length;
51-
assert.strictEqual(bucket.count, expectedBucketCount);
52-
}
53-
5445
const expectedMean = valuesSum / values.length;
5546
assert.ok(isAlmostEqual(distributionData.mean, expectedMean, EPSILON));
5647

@@ -173,13 +164,8 @@ describe('Recorder', () => {
173164
mean: 0,
174165
stdDeviation: 0,
175166
sumSquaredDeviations: 0,
176-
buckets: [
177-
{highBoundary: 0, lowBoundary: -Infinity, count: 0},
178-
{highBoundary: 2, lowBoundary: 0, count: 0},
179-
{highBoundary: 4, lowBoundary: 2, count: 0},
180-
{highBoundary: 6, lowBoundary: 4, count: 0},
181-
{highBoundary: Infinity, lowBoundary: 6, count: 0}
182-
]
167+
buckets: [2, 4, 6],
168+
bucketCounts: [0, 0, 0, 0]
183169
};
184170
const sentValues = [];
185171
for (const value of testCase.values) {

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

Lines changed: 3 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -48,15 +48,6 @@ function assertDistributionData(
4848
assert.strictEqual(distributionData.count, values.length);
4949
assert.strictEqual(distributionData.sum, valuesSum);
5050

51-
for (const bucket of distributionData.buckets) {
52-
const expectedBucketCount = values
53-
.filter(
54-
value => bucket.lowBoundary <= value &&
55-
value < bucket.highBoundary)
56-
.length;
57-
assert.strictEqual(bucket.count, expectedBucketCount);
58-
}
59-
6051
const expectedMean = valuesSum / values.length;
6152
assert.ok(isAlmostEqual(distributionData.mean, expectedMean, EPSILON));
6253

@@ -125,7 +116,7 @@ describe('BaseView', () => {
125116

126117
describe('recordMeasurement()', () => {
127118
const measurementValues = [1.1, 2.3, 3.2, 4.3, 5.2];
128-
const bucketBoundaries = [0, 2, 4, 6];
119+
const bucketBoundaries = [2, 4, 6];
129120
const emptyAggregation = {};
130121
const tags: Tags = {testKey1: 'testValue', testKey2: 'testValue'};
131122

@@ -161,16 +152,8 @@ describe('BaseView', () => {
161152
view.recordMeasurement(measurement);
162153
}
163154
const data = view.getSnapshot(tags) as DistributionData;
164-
const expectedBuckets = [
165-
{count: 1, lowBoundary: -Infinity, highBoundary: 2},
166-
{count: 2, lowBoundary: 2, highBoundary: 4},
167-
{count: 2, lowBoundary: 4, highBoundary: 6},
168-
{count: 0, lowBoundary: 6, highBoundary: Infinity}
169-
];
170-
assert.equal(data.buckets.length, expectedBuckets.length);
171-
expectedBuckets.forEach((bucket, index) => {
172-
assert.deepStrictEqual(data.buckets[index], bucket);
173-
});
155+
assert.deepStrictEqual(data.buckets, [2, 4, 6]);
156+
assert.deepStrictEqual(data.bucketCounts, [1, 2, 2, 0]);
174157
});
175158

176159
const view = new BaseView(

packages/opencensus-exporter-prometheus/src/prometheus-stats.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -189,7 +189,7 @@ export class PrometheusStatsExporter implements StatsEventListener {
189189
*/
190190
private getBoundaries(view: View, tags: Tags): number[] {
191191
const data = view.getSnapshot(tags) as DistributionData;
192-
return data.buckets.map(b => b.lowBoundary).filter(b => b !== -Infinity);
192+
return data.buckets;
193193
}
194194

195195
/**

0 commit comments

Comments
 (0)