diff --git a/CHANGELOG.md b/CHANGELOG.md index dc9a2e749..f535a0063 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ All notable changes to this project will be documented in this file. ## Unreleased +- Add Cumulative (`DoubleCumulative`, `Int64Cumulative`) APIs. + **This release has a breaking change. Please test your code accordingly after upgrading.** - removing Tracer's `startChildSpan(name?: string, kind?: types.SpanKind)` interface diff --git a/packages/opencensus-core/src/metrics/metric-registry.ts b/packages/opencensus-core/src/metrics/metric-registry.ts index 2cf21eebe..ebb7f625b 100644 --- a/packages/opencensus-core/src/metrics/metric-registry.ts +++ b/packages/opencensus-core/src/metrics/metric-registry.ts @@ -16,6 +16,7 @@ import {validateArrayElementsNotNull, validateDuplicateKeys, validateMapElementNotNull, validateNotNull} from '../common/validations'; import {MeasureUnit} from '../stats/types'; +import {Cumulative} from './cumulative/cumulative'; import {BaseMetricProducer} from './export/base-metric-producer'; import {Metric, MetricDescriptorType, MetricProducer} from './export/types'; import {DerivedGauge} from './gauges/derived-gauge'; @@ -46,9 +47,9 @@ export class MetricRegistry { * convenient form when you want to manually increase and decrease values as * per your service requirements. * - * @param {string} name The name of the metric. - * @param {MetricOptions} options The options for the metric. - * @returns {Gauge} A Int64 Gauge metric. + * @param name The name of the metric. + * @param options The options for the metric. + * @returns A Int64 Gauge metric. */ addInt64Gauge(name: string, options?: MetricOptions): Gauge { const description = @@ -77,9 +78,9 @@ export class MetricRegistry { * convenient form when you want to manually increase and decrease values as * per your service requirements. * - * @param {string} name The name of the metric. - * @param {MetricOptions} options The options for the metric. - * @returns {Gauge} A Double Gauge metric. + * @param name The name of the metric. + * @param options The options for the metric. + * @returns A Double Gauge metric. */ addDoubleGauge(name: string, options?: MetricOptions): Gauge { const description = @@ -108,9 +109,9 @@ export class MetricRegistry { * convenient form when you want to manually increase and decrease values as * per your service requirements. * - * @param {string} name The name of the metric. - * @param {MetricOptions} options The options for the metric. - * @returns {DerivedGauge} A Int64 DerivedGauge metric. + * @param name The name of the metric. + * @param options The options for the metric. + * @returns A Int64 DerivedGauge metric. */ addDerivedInt64Gauge(name: string, options?: MetricOptions): DerivedGauge { const description = @@ -139,9 +140,9 @@ export class MetricRegistry { * convenient form when you want to manually increase and decrease values as * per your service requirements. * - * @param {string} name The name of the metric. - * @param {MetricOptions} options The options for the metric. - * @returns {DerivedGauge} A Double DerivedGauge metric. + * @param name The name of the metric. + * @param options The options for the metric. + * @returns A Double DerivedGauge metric. */ addDerivedDoubleGauge(name: string, options?: MetricOptions): DerivedGauge { const description = @@ -165,11 +166,73 @@ export class MetricRegistry { return derivedDoubleGauge; } + /** + * Builds a new Int64 cumulative to be added to the registry. This API is + * useful when you want to manually increase and reset values as per service + * requirements. + * + * @param name The name of the metric. + * @param options The options for the metric. + * @returns A Int64 Cumulative metric. + */ + addInt64Cumulative(name: string, options?: MetricOptions): Cumulative { + 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 + + validateArrayElementsNotNull(labelKeys, MetricRegistry.LABEL_KEY); + validateMapElementNotNull(constantLabels, MetricRegistry.CONSTANT_LABELS); + validateDuplicateKeys(labelKeys, constantLabels); + + const labelKeysCopy = Object.assign([], labelKeys); + const int64Cumulative = new Cumulative( + validateNotNull(name, MetricRegistry.NAME), description, unit, + MetricDescriptorType.CUMULATIVE_INT64, labelKeysCopy, constantLabels); + this.registerMetric(name, int64Cumulative); + return int64Cumulative; + } + + /** + * Builds a new double cumulative to be added to the registry. This API is + * useful when you want to manually increase and reset values as per service + * requirements. + * + * @param name The name of the metric. + * @param options The options for the metric. + * @returns A Double Cumulative metric. + */ + addDoubleCumulative(name: string, options?: MetricOptions): Cumulative { + 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 + + validateArrayElementsNotNull(labelKeys, MetricRegistry.LABEL_KEY); + validateMapElementNotNull(constantLabels, MetricRegistry.CONSTANT_LABELS); + validateDuplicateKeys(labelKeys, constantLabels); + + const labelKeysCopy = Object.assign([], labelKeys); + const doubleCumulative = new Cumulative( + validateNotNull(name, MetricRegistry.NAME), description, unit, + MetricDescriptorType.CUMULATIVE_DOUBLE, labelKeysCopy, constantLabels); + this.registerMetric(name, doubleCumulative); + return doubleCumulative; + } + /** * Registers metric to register. * - * @param {string} name The name of the metric. - * @param {Meter} meter The metric to register. + * @param name The name of the metric. + * @param meter The metric to register. */ private registerMetric(name: string, meter: Meter): void { if (this.registeredMetrics.has(name)) { @@ -182,7 +245,7 @@ export class MetricRegistry { /** * Gets a metric producer for registry. * - * @returns {MetricProducer} The metric producer. + * @returns The metric producer. */ getMetricProducer(): MetricProducer { return this.metricProducer; @@ -204,7 +267,7 @@ class MetricProducerForRegistry extends BaseMetricProducer { /** * Gets a collection of produced Metric`s to be exported. * - * @returns {Metric[]} The list of metrics. + * @returns The list of metrics. */ getMetrics(): Metric[] { return Array.from(this.registeredMetrics.values()) diff --git a/packages/opencensus-core/test/test-metric-registry.ts b/packages/opencensus-core/test/test-metric-registry.ts index 8d2e7cc05..c3604d948 100644 --- a/packages/opencensus-core/test/test-metric-registry.ts +++ b/packages/opencensus-core/test/test-metric-registry.ts @@ -370,6 +370,170 @@ describe('addDerivedDoubleGauge', () => { }); }); +describe('addInt64Cumulative', () => { + 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 int64Gauge = registry.addInt64Cumulative(METRIC_NAME, METRIC_OPTIONS); + const pointEntry = int64Gauge.getOrCreateTimeSeries(LABEL_VALUES_200); + pointEntry.inc(); + + 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); + const [{points}] = timeseries; + const [point] = points; + assert.equal(point.value, 1); + assert.deepStrictEqual( + point.timestamp, + {seconds: mockedTime.seconds, nanos: mockedTime.nanos}); + }); + + it('should return a metric without options', () => { + const int64Gauge = registry.addInt64Cumulative(METRIC_NAME); + const pointEntry = int64Gauge.getDefaultTimeSeries(); + pointEntry.inc(100); + + const metrics = registry.getMetricProducer().getMetrics(); + assert.strictEqual(metrics.length, 1); + const [{descriptor, timeseries}] = metrics; + assert.deepStrictEqual(descriptor, { + name: METRIC_NAME, + description: '', + labelKeys: [], + unit: UNIT, + type: MetricDescriptorType.CUMULATIVE_INT64 + }); + assert.strictEqual(timeseries.length, 1); + const [{points}] = timeseries; + const [point] = points; + assert.equal(point.value, 100); + assert.deepStrictEqual( + point.timestamp, + {seconds: mockedTime.seconds, nanos: mockedTime.nanos}); + }); + + 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.addInt64Cumulative(METRIC_NAME, {constantLabels, labelKeys}); + }, /^Error: The keys from LabelKeys should not be present in constantLabels or LabelKeys should not contains duplicate keys$/); + }); +}); + +describe('addDoubleCumulative', () => { + 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 int64Gauge = + registry.addDoubleCumulative(METRIC_NAME, METRIC_OPTIONS); + const pointEntry = int64Gauge.getOrCreateTimeSeries(LABEL_VALUES_200); + pointEntry.inc(1.1); + + 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); + const [{points}] = timeseries; + const [point] = points; + assert.equal(point.value, 1.1); + assert.deepStrictEqual( + point.timestamp, + {seconds: mockedTime.seconds, nanos: mockedTime.nanos}); + }); + + it('should return a metric without options', () => { + const int64Gauge = registry.addDoubleCumulative(METRIC_NAME); + const pointEntry = int64Gauge.getDefaultTimeSeries(); + pointEntry.inc(); + pointEntry.inc(100.12); + + const metrics = registry.getMetricProducer().getMetrics(); + assert.strictEqual(metrics.length, 1); + const [{descriptor, timeseries}] = metrics; + assert.deepStrictEqual(descriptor, { + name: METRIC_NAME, + description: '', + labelKeys: [], + unit: UNIT, + type: MetricDescriptorType.CUMULATIVE_DOUBLE + }); + assert.strictEqual(timeseries.length, 1); + const [{points}] = timeseries; + const [point] = points; + assert.equal(point.value, 101.12); + assert.deepStrictEqual( + point.timestamp, + {seconds: mockedTime.seconds, nanos: mockedTime.nanos}); + }); + + 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.addDoubleCumulative(METRIC_NAME, {constantLabels, labelKeys}); + }, /^Error: The keys from LabelKeys should not be present in constantLabels or LabelKeys should not contains duplicate keys$/); + }); +}); + describe('Add multiple gauges', () => { let registry: MetricRegistry; const realHrtimeFn = process.hrtime; @@ -406,9 +570,13 @@ describe('Add multiple gauges', () => { size: () => arr.length, }); + const int64Cumulative = + registry.addInt64Cumulative('metric-name4', METRIC_OPTIONS); + int64Cumulative.getOrCreateTimeSeries(LABEL_VALUES_200).inc(); + const metrics = registry.getMetricProducer().getMetrics(); - assert.strictEqual(metrics.length, 3); - const [{descriptor: descriptor1, timeseries: timeseries1}, {descriptor: descriptor2, timeseries: timeseries2}, {descriptor: descriptor3, timeseries: timeseries3}] = metrics; + assert.strictEqual(metrics.length, 4); + const [{descriptor: descriptor1, timeseries: timeseries1}, {descriptor: descriptor2, timeseries: timeseries2}, {descriptor: descriptor3, timeseries: timeseries3}, {descriptor: descriptor4, timeseries: timeseries4}] = metrics; assert.deepStrictEqual(descriptor1, { name: 'metric-name1', description: METRIC_DESCRIPTION, @@ -430,6 +598,13 @@ describe('Add multiple gauges', () => { unit: UNIT, type: MetricDescriptorType.GAUGE_INT64 }); + assert.deepStrictEqual(descriptor4, { + name: 'metric-name4', + description: METRIC_DESCRIPTION, + labelKeys: LABEL_KEYS, + unit: UNIT, + type: MetricDescriptorType.CUMULATIVE_INT64 + }); assert.strictEqual(timeseries1.length, 1); assert.strictEqual(timeseries1[0].points.length, 1); assert.equal(timeseries1[0].points[0].value, 100); @@ -446,5 +621,8 @@ describe('Add multiple gauges', () => { timeseries1[0].points[0].timestamp, timeseries2[0].points[0].timestamp); assert.deepStrictEqual( timeseries2[0].points[0].timestamp, timeseries3[0].points[0].timestamp); + assert.strictEqual(timeseries4.length, 1); + assert.strictEqual(timeseries4[0].points.length, 1); + assert.equal(timeseries4[0].points[0].value, 1); }); });