From 88a4ab1e38c0bedd8649d7e223c0d92470d88b05 Mon Sep 17 00:00:00 2001 From: Mayur Kale Date: Fri, 10 May 2019 14:50:17 -0700 Subject: [PATCH] Plug-in Derived Cumulative into the Metric-Registry (#513) --- CHANGELOG.md | 2 +- .../opencensus-core/src/common/validations.ts | 4 +- .../src/metrics/metric-registry.ts | 96 ++++++-- .../test/test-metric-registry.ts | 217 ++++++++++++++++++ 4 files changed, 297 insertions(+), 22 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e63d71889..258196652 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,7 +5,7 @@ All notable changes to this project will be documented in this file. ## Unreleased - Add `defaultAttributes` config to `Tracer.start(config)` -- Add Cumulative (`DoubleCumulative`, `Int64Cumulative`) APIs. +- Add Cumulative (`DoubleCumulative`, `LongCumulative`, , `DerivedDoubleCumulative`, `DerivedLongCumulative`) APIs. **This release has a breaking change. Please test your code accordingly after upgrading.** diff --git a/packages/opencensus-core/src/common/validations.ts b/packages/opencensus-core/src/common/validations.ts index 945b33d91..01f61257b 100644 --- a/packages/opencensus-core/src/common/validations.ts +++ b/packages/opencensus-core/src/common/validations.ts @@ -47,8 +47,8 @@ export function validateArrayElementsNotNull( } /** Throws an error if any of the map elements is null. */ -export function validateMapElementNotNull( - map: Map, errorMessage: string) { +export function validateMapElementNotNull( + map: Map, errorMessage: string) { for (const [key, value] of map.entries()) { if (key == null || value == null) { throw new Error(`${errorMessage} elements should not be a NULL`); diff --git a/packages/opencensus-core/src/metrics/metric-registry.ts b/packages/opencensus-core/src/metrics/metric-registry.ts index ebb7f625b..914766558 100644 --- a/packages/opencensus-core/src/metrics/metric-registry.ts +++ b/packages/opencensus-core/src/metrics/metric-registry.ts @@ -14,11 +14,13 @@ * limitations under the License. */ +import {getTimestampWithProcessHRTime} from '../common/time-util'; import {validateArrayElementsNotNull, validateDuplicateKeys, validateMapElementNotNull, validateNotNull} from '../common/validations'; import {MeasureUnit} from '../stats/types'; import {Cumulative} from './cumulative/cumulative'; +import {DerivedCumulative} from './cumulative/derived-cumulative'; import {BaseMetricProducer} from './export/base-metric-producer'; -import {Metric, MetricDescriptorType, MetricProducer} from './export/types'; +import {LabelKey, LabelValue, Metric, MetricDescriptorType, MetricProducer, Timestamp} from './export/types'; import {DerivedGauge} from './gauges/derived-gauge'; import {Gauge} from './gauges/gauge'; import {Meter, MetricOptions} from './types'; @@ -61,9 +63,7 @@ export class MetricRegistry { MetricRegistry.DEFAULT_CONSTANT_LABEL; // TODO(mayurkale): Add support for resource - validateArrayElementsNotNull(labelKeys, MetricRegistry.LABEL_KEY); - validateMapElementNotNull(constantLabels, MetricRegistry.CONSTANT_LABELS); - validateDuplicateKeys(labelKeys, constantLabels); + this.validateLables(labelKeys, constantLabels); const labelKeysCopy = Object.assign([], labelKeys); const int64Gauge = new Gauge( @@ -92,9 +92,7 @@ export class MetricRegistry { MetricRegistry.DEFAULT_CONSTANT_LABEL; // TODO(mayurkale): Add support for resource - validateArrayElementsNotNull(labelKeys, MetricRegistry.LABEL_KEY); - validateMapElementNotNull(constantLabels, MetricRegistry.CONSTANT_LABELS); - validateDuplicateKeys(labelKeys, constantLabels); + this.validateLables(labelKeys, constantLabels); const labelKeysCopy = Object.assign([], labelKeys); const doubleGauge = new Gauge( @@ -123,9 +121,7 @@ export class MetricRegistry { MetricRegistry.DEFAULT_CONSTANT_LABEL; // TODO(mayurkale): Add support for resource - validateArrayElementsNotNull(labelKeys, MetricRegistry.LABEL_KEY); - validateMapElementNotNull(constantLabels, MetricRegistry.CONSTANT_LABELS); - validateDuplicateKeys(labelKeys, constantLabels); + this.validateLables(labelKeys, constantLabels); const labelKeysCopy = Object.assign([], labelKeys); const derivedInt64Gauge = new DerivedGauge( @@ -154,9 +150,7 @@ export class MetricRegistry { MetricRegistry.DEFAULT_CONSTANT_LABEL; // TODO(mayurkale): Add support for resource - validateArrayElementsNotNull(labelKeys, MetricRegistry.LABEL_KEY); - validateMapElementNotNull(constantLabels, MetricRegistry.CONSTANT_LABELS); - validateDuplicateKeys(labelKeys, constantLabels); + this.validateLables(labelKeys, constantLabels); const labelKeysCopy = Object.assign([], labelKeys); const derivedDoubleGauge = new DerivedGauge( @@ -185,9 +179,7 @@ export class MetricRegistry { MetricRegistry.DEFAULT_CONSTANT_LABEL; // TODO(mayurkale): Add support for resource - validateArrayElementsNotNull(labelKeys, MetricRegistry.LABEL_KEY); - validateMapElementNotNull(constantLabels, MetricRegistry.CONSTANT_LABELS); - validateDuplicateKeys(labelKeys, constantLabels); + this.validateLables(labelKeys, constantLabels); const labelKeysCopy = Object.assign([], labelKeys); const int64Cumulative = new Cumulative( @@ -216,9 +208,7 @@ export class MetricRegistry { MetricRegistry.DEFAULT_CONSTANT_LABEL; // TODO(mayurkale): Add support for resource - validateArrayElementsNotNull(labelKeys, MetricRegistry.LABEL_KEY); - validateMapElementNotNull(constantLabels, MetricRegistry.CONSTANT_LABELS); - validateDuplicateKeys(labelKeys, constantLabels); + this.validateLables(labelKeys, constantLabels); const labelKeysCopy = Object.assign([], labelKeys); const doubleCumulative = new Cumulative( @@ -228,6 +218,66 @@ export class MetricRegistry { return doubleCumulative; } + /** + * Builds a new derived Int64 Cumulative to be added to the registry. + * + * @param name The name of the metric. + * @param options The options for the metric. + * @returns A Int64 DerivedCumulative metric. + */ + addDerivedInt64Cumulative(name: string, options?: MetricOptions): + DerivedCumulative { + const description = + (options && options.description) || MetricRegistry.DEFAULT_DESCRIPTION; + const unit = (options && options.unit) || MetricRegistry.DEFAULT_UNIT; + const labelKeys = + (options && options.labelKeys) || MetricRegistry.DEFAULT_LABEL_KEYS; + const constantLabels = (options && options.constantLabels) || + MetricRegistry.DEFAULT_CONSTANT_LABEL; + // TODO(mayurkale): Add support for resource + + this.validateLables(labelKeys, constantLabels); + + const labelKeysCopy = Object.assign([], labelKeys); + const startTime: Timestamp = getTimestampWithProcessHRTime(); + const derivedInt64Cumulative = new DerivedCumulative( + validateNotNull(name, MetricRegistry.NAME), description, unit, + MetricDescriptorType.CUMULATIVE_INT64, labelKeysCopy, constantLabels, + startTime); + this.registerMetric(name, derivedInt64Cumulative); + return derivedInt64Cumulative; + } + + /** + * Builds a new derived Double Cumulative to be added to the registry. + * + * @param name The name of the metric. + * @param options The options for the metric. + * @returns A Double DerivedCumulative metric. + */ + addDerivedDoubleCumulative(name: string, options?: MetricOptions): + DerivedCumulative { + const description = + (options && options.description) || MetricRegistry.DEFAULT_DESCRIPTION; + const unit = (options && options.unit) || MetricRegistry.DEFAULT_UNIT; + const labelKeys = + (options && options.labelKeys) || MetricRegistry.DEFAULT_LABEL_KEYS; + const constantLabels = (options && options.constantLabels) || + MetricRegistry.DEFAULT_CONSTANT_LABEL; + // TODO(mayurkale): Add support for resource + + this.validateLables(labelKeys, constantLabels); + + const labelKeysCopy = Object.assign([], labelKeys); + const startTime: Timestamp = getTimestampWithProcessHRTime(); + const derivedDoubleCumulative = new DerivedCumulative( + validateNotNull(name, MetricRegistry.NAME), description, unit, + MetricDescriptorType.CUMULATIVE_DOUBLE, labelKeysCopy, constantLabels, + startTime); + this.registerMetric(name, derivedDoubleCumulative); + return derivedDoubleCumulative; + } + /** * Registers metric to register. * @@ -250,6 +300,14 @@ export class MetricRegistry { getMetricProducer(): MetricProducer { return this.metricProducer; } + + /** Validates labelKeys and constantLabels. */ + private validateLables( + labelKeys: LabelKey[], constantLabels: Map) { + validateArrayElementsNotNull(labelKeys, MetricRegistry.LABEL_KEY); + validateMapElementNotNull(constantLabels, MetricRegistry.CONSTANT_LABELS); + validateDuplicateKeys(labelKeys, constantLabels); + } } /** diff --git a/packages/opencensus-core/test/test-metric-registry.ts b/packages/opencensus-core/test/test-metric-registry.ts index c3604d948..2337e5706 100644 --- a/packages/opencensus-core/test/test-metric-registry.ts +++ b/packages/opencensus-core/test/test-metric-registry.ts @@ -534,6 +534,223 @@ describe('addDoubleCumulative', () => { }); }); +describe('addDerivedInt64Cumulative', () => { + let registry: MetricRegistry; + const realHrtimeFn = process.hrtime; + const realNowFn = Date.now; + const mockedTime: Timestamp = {seconds: 1450000100, nanos: 1e7}; + + beforeEach(() => { + registry = new MetricRegistry(); + + process.hrtime = () => [100, 1e7]; + Date.now = () => 1450000000000; + // Force the clock to recalibrate the time offset with the mocked time + TEST_ONLY.setHrtimeReference(); + }); + + afterEach(() => { + process.hrtime = realHrtimeFn; + Date.now = realNowFn; + // Reset the hrtime reference so that it uses a real clock again. + TEST_ONLY.resetHrtimeFunctionCache(); + }); + + it('should return a metric', () => { + const map = new Map(); + map.set('key', 'value'); + const derivedInt64Cumulative = + registry.addDerivedInt64Cumulative(METRIC_NAME, METRIC_OPTIONS); + derivedInt64Cumulative.createTimeSeries(LABEL_VALUES_200, map); + map.set('key1', 'value1'); + + const metrics = registry.getMetricProducer().getMetrics(); + assert.strictEqual(metrics.length, 1); + const [{descriptor, timeseries}] = metrics; + assert.deepStrictEqual(descriptor, { + name: METRIC_NAME, + description: METRIC_DESCRIPTION, + labelKeys: LABEL_KEYS, + unit: UNIT, + type: MetricDescriptorType.CUMULATIVE_INT64 + }); + assert.strictEqual(timeseries.length, 1); + assert.deepStrictEqual(timeseries, [{ + labelValues: LABEL_VALUES_200, + points: [{value: 2, timestamp: mockedTime}], + startTimestamp: mockedTime + }]); + }); + + it('should throw an error when the register same metric', () => { + registry.addDerivedInt64Cumulative(METRIC_NAME, METRIC_OPTIONS); + assert.throws(() => { + registry.addDerivedInt64Cumulative(METRIC_NAME, METRIC_OPTIONS); + }, /^Error: A metric with the name metric-name has already been registered.$/); + }); + + it('should throw an error when the duplicate keys in labelKeys and constantLabels', + () => { + const constantLabels = new Map(); + constantLabels.set({key: 'k1'}, {value: 'v1'}); + const labelKeys = [{key: 'k1', description: 'desc'}]; + assert.throws(() => { + registry.addDerivedInt64Cumulative( + METRIC_NAME, {constantLabels, labelKeys}); + }, /^Error: The keys from LabelKeys should not be present in constantLabels or LabelKeys should not contains duplicate keys$/); + }); + + it('should throw an error when the constant labels elements are null', () => { + const constantLabels = new Map(); + constantLabels.set({key: 'k1'}, null); + assert.throws(() => { + registry.addDerivedInt64Cumulative(METRIC_NAME, {constantLabels}); + }, /^Error: constantLabels elements should not be a NULL$/); + }); + + it('should return a metric without options', () => { + const map = new Map(); + map.set('key', 'value'); + const derivedInt64Cumulative = + registry.addDerivedInt64Cumulative(METRIC_NAME); + derivedInt64Cumulative.createTimeSeries([], map); + map.set('key1', 'value1'); + + const metrics = registry.getMetricProducer().getMetrics(); + assert.strictEqual(metrics.length, 1); + const [{descriptor, timeseries}] = metrics; + assert.deepStrictEqual(descriptor, { + name: METRIC_NAME, + description: '', + labelKeys: [], + unit: '1', + type: MetricDescriptorType.CUMULATIVE_INT64 + }); + assert.strictEqual(timeseries.length, 1); + assert.deepStrictEqual(timeseries, [{ + labelValues: [], + points: [{value: 2, timestamp: mockedTime}], + startTimestamp: mockedTime + }]); + }); +}); + +describe('addDerivedDoubleCumulative', () => { + let registry: MetricRegistry; + const realHrtimeFn = process.hrtime; + const realNowFn = Date.now; + const mockedTime: Timestamp = {seconds: 1450000100, nanos: 1e7}; + + beforeEach(() => { + registry = new MetricRegistry(); + + process.hrtime = () => [100, 1e7]; + Date.now = () => 1450000000000; + // Force the clock to recalibrate the time offset with the mocked time + TEST_ONLY.setHrtimeReference(); + }); + + afterEach(() => { + process.hrtime = realHrtimeFn; + Date.now = realNowFn; + // Reset the hrtime reference so that it uses a real clock again. + TEST_ONLY.resetHrtimeFunctionCache(); + }); + + it('should return a metric', () => { + class QueueManager { + get Value(): number { + return 45.5; + } + } + const queue = new QueueManager(); + const derivedDoubleCumulative = + registry.addDerivedDoubleCumulative(METRIC_NAME, METRIC_OPTIONS); + derivedDoubleCumulative.createTimeSeries(LABEL_VALUES_200, () => { + return queue.Value; + }); + + const metrics = registry.getMetricProducer().getMetrics(); + assert.strictEqual(metrics.length, 1); + const [{descriptor, timeseries}] = metrics; + assert.deepStrictEqual(descriptor, { + name: METRIC_NAME, + description: METRIC_DESCRIPTION, + labelKeys: LABEL_KEYS, + unit: UNIT, + type: MetricDescriptorType.CUMULATIVE_DOUBLE + }); + assert.strictEqual(timeseries.length, 1); + assert.deepStrictEqual(timeseries, [{ + labelValues: LABEL_VALUES_200, + points: [{value: 45.5, timestamp: mockedTime}], + startTimestamp: mockedTime + }]); + }); + + it('should throw an error when the register same metric', () => { + registry.addDerivedDoubleCumulative(METRIC_NAME, METRIC_OPTIONS); + assert.throws(() => { + registry.addDerivedDoubleCumulative(METRIC_NAME, METRIC_OPTIONS); + }, /^Error: A metric with the name metric-name has already been registered.$/); + }); + + it('should throw an error when the duplicate keys in labelKeys and constantLabels', + () => { + const constantLabels = new Map(); + constantLabels.set({key: 'k1'}, {value: 'v1'}); + const labelKeys = [{key: 'k1', description: 'desc'}]; + assert.throws(() => { + registry.addDerivedDoubleCumulative( + METRIC_NAME, {constantLabels, labelKeys}); + }, /^Error: The keys from LabelKeys should not be present in constantLabels or LabelKeys should not contains duplicate keys$/); + }); + + it('should throw an error when the constant labels elements are null', () => { + const constantLabels = new Map(); + constantLabels.set({key: 'k1'}, null); + assert.throws(() => { + registry.addDerivedDoubleCumulative(METRIC_NAME, {constantLabels}); + }, /^Error: constantLabels elements should not be a NULL$/); + }); + + it('should return a metric without options', () => { + class MemoryInfo { + current = 45.5; + get Value(): number { + return this.current; + } + inc() { + this.current++; + } + } + const mem = new MemoryInfo(); + const derivedDoubleCumulative = + registry.addDerivedDoubleCumulative(METRIC_NAME); + derivedDoubleCumulative.createTimeSeries([], () => { + return mem.Value; + }); + mem.inc(); + + const metrics = registry.getMetricProducer().getMetrics(); + assert.strictEqual(metrics.length, 1); + const [{descriptor, timeseries}] = metrics; + assert.deepStrictEqual(descriptor, { + name: METRIC_NAME, + description: '', + labelKeys: [], + unit: '1', + type: MetricDescriptorType.CUMULATIVE_DOUBLE + }); + assert.strictEqual(timeseries.length, 1); + assert.deepStrictEqual(timeseries, [{ + labelValues: [], + points: [{value: 46.5, timestamp: mockedTime}], + startTimestamp: mockedTime + }]); + }); +}); + describe('Add multiple gauges', () => { let registry: MetricRegistry; const realHrtimeFn = process.hrtime;