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

Commit

Permalink
Add support for derived cumulative metrics
Browse files Browse the repository at this point in the history
  • Loading branch information
mayurkale22 committed May 8, 2019
1 parent d4c3b19 commit 525cfa4
Show file tree
Hide file tree
Showing 6 changed files with 529 additions and 86 deletions.
3 changes: 3 additions & 0 deletions packages/opencensus-core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ export * from './trace/instrumentation/types';
export * from './trace/propagation/types';
export * from './exporters/types';
export * from './common/types';
export * from './metrics/types';
export * from './metrics/cumulative/types';
export * from './metrics/gauges/types';
export {Metric, MetricDescriptor, TimeSeries, MetricDescriptorType, LabelKey, LabelValue, Point as TimeSeriesPoint, DistributionValue, BucketOptions, Bucket as DistributionBucket, SummaryValue, Explicit, Exemplar, Timestamp, Snapshot, ValueAtPercentile, MetricProducerManager, MetricProducer} from './metrics/export/types';

Expand Down Expand Up @@ -74,6 +76,7 @@ export * from './metrics/metric-registry';

// Cumulative CLASSES
export * from './metrics/cumulative/cumulative';
export * from './metrics/cumulative/derived-cumulative';

// GAUGES CLASSES
export * from './metrics/gauges/derived-gauge';
Expand Down
170 changes: 170 additions & 0 deletions packages/opencensus-core/src/metrics/cumulative/derived-cumulative.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
/**
* Copyright 2019, 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 {getTimestampWithProcessHRTime} from '../../common/time-util';
import {validateArrayElementsNotNull, validateNotNull} from '../../common/validations';
import {LabelKey, LabelValue, Metric, MetricDescriptor, MetricDescriptorType, TimeSeries, Timestamp} from '../export/types';
import {Meter} from '../types';
import {AccessorInterface} from '../types';
import * as util from '../utils';

type ValueExtractor = () => number;

interface CumulativeEntry {
readonly labelValues: LabelValue[];
readonly extractor: ValueExtractor;
prevValue: number;
}

/**
* DerivedCumulative metric is used to record aggregated metrics that
* represents a single numerical value accumulated over a time interval.
*/
export class DerivedCumulative implements Meter {
private metricDescriptor: MetricDescriptor;
private labelKeysLength: number;
private registeredPoints: Map<string, CumulativeEntry> = new Map();
private extractor?: ValueExtractor;
private readonly constantLabelValues: LabelValue[];
private startTime: Timestamp;

/**
* Constructs a new DerivedCumulative instance.
*
* @param {string} name The name of the metric.
* @param {string} description The description of the metric.
* @param {string} unit The unit of the metric.
* @param {MetricDescriptorType} type The type of metric.
* @param {LabelKey[]} labelKeys The list of the label keys.
* @param {Map<LabelKey, LabelValue>} constantLabels The map of constant
* labels for the Metric.
* @param {Timestamp} startTime The time when the cumulative metric start
* measuring the value.
*/
constructor(
name: string, description: string, unit: string,
type: MetricDescriptorType, labelKeys: LabelKey[],
readonly constantLabels: Map<LabelKey, LabelValue>,
startTime: Timestamp) {
this.labelKeysLength = labelKeys.length;
const keysAndConstantKeys = [...labelKeys, ...constantLabels.keys()];
this.constantLabelValues = [...constantLabels.values()];

this.metricDescriptor =
{name, description, unit, type, labelKeys: keysAndConstantKeys};
this.startTime = startTime;
}

/**
* Creates a TimeSeries. The value of a single point in the TimeSeries is
* observed from a obj. The ValueExtractor is invoked whenever
* metrics are collected, meaning the reported value is up-to-date.
*
* @param {LabelValue[]} labelValues The list of the label values.
* @param objOrFn obj The obj to get the size or length or value from. If
* multiple options are available, the value (ToValueInterface) takes
* precedence first, followed by length and size. e.g value -> length ->
* size.
* fn is the function that will be called to get the current value
* of the cumulative.
*/
createTimeSeries(labelValues: LabelValue[], objOrFn: AccessorInterface):
void {
validateArrayElementsNotNull(
validateNotNull(labelValues, 'labelValues'), 'labelValue');
validateNotNull(objOrFn, 'obj');

const hash = util.hashLabelValues(labelValues);
if (this.registeredPoints.has(hash)) {
throw new Error(
'A different time series with the same labels already exists.');
}
if (this.labelKeysLength !== labelValues.length) {
throw new Error('Label Keys and Label Values don\'t have same size');
}

if (objOrFn instanceof Function) {
this.extractor = objOrFn;
} else if (util.isToValueInterface(objOrFn)) {
this.extractor = () => objOrFn.getValue();
} else if (util.isLengthAttributeInterface(objOrFn)) {
this.extractor = () => objOrFn.length;
} else if (util.isLengthMethodInterface(objOrFn)) {
this.extractor = () => objOrFn.length();
} else if (util.isSizeAttributeInterface(objOrFn)) {
this.extractor = () => objOrFn.size;
} else if (util.isSizeMethodInterface(objOrFn)) {
this.extractor = () => objOrFn.size();
} else {
throw new Error('Unknown interface/object type');
}

this.registeredPoints.set(
hash, {labelValues, extractor: this.extractor, prevValue: 0});
}

/**
* Removes the TimeSeries from the cumulative metric, if it is present. i.e.
* references to previous Point objects are invalid (not part of the
* metric).
*
* @param {LabelValue[]} labelValues The list of label values.
*/
removeTimeSeries(labelValues: LabelValue[]): void {
validateNotNull(labelValues, 'labelValues');
this.registeredPoints.delete(util.hashLabelValues(labelValues));
}

/**
* Removes all TimeSeries from the cumulative metric. i.e. references to all
* previous Point objects are invalid (not part of the metric).
*/
clear(): void {
this.registeredPoints.clear();
}

/**
* Provides a Metric with one or more TimeSeries.
*
* @returns {Metric} The Metric, or null if TimeSeries is not present in
* Metric.
*/
getMetric(): Metric|null {
if (this.registeredPoints.size === 0) {
return null;
}
const timestamp: Timestamp = getTimestampWithProcessHRTime();
return {
descriptor: this.metricDescriptor,
timeseries: Array.from(
this.registeredPoints,
([_, cumulativeEntry]) => {
const newValue = cumulativeEntry.extractor();
const value = newValue > cumulativeEntry.prevValue ?
newValue :
cumulativeEntry.prevValue;
cumulativeEntry.prevValue = value;

return {
labelValues:
[...cumulativeEntry.labelValues, ...this.constantLabelValues],
points: [{value, timestamp}],
startTimestamp: this.startTime
} as TimeSeries;
})
};
}
}
95 changes: 9 additions & 86 deletions packages/opencensus-core/src/metrics/gauges/derived-gauge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,42 +18,8 @@ import {getTimestampWithProcessHRTime} from '../../common/time-util';
import {validateArrayElementsNotNull, validateNotNull} from '../../common/validations';
import {LabelKey, LabelValue, Metric, MetricDescriptor, MetricDescriptorType, TimeSeries, Timestamp} from '../export/types';
import * as types from '../types';
import {hashLabelValues} from '../utils';

/**
* Interface for objects with "length()" method.
*/
export interface LengthMethodInterface {
length(): number;
}

/**
* Interface for objects with "length" attribute (e.g. Array).
*/
export interface LengthAttributeInterface {
length: number;
}

/**
* Interface for objects with "size" method.
*/
export interface SizeMethodInterface {
size(): number;
}

/**
* Interface for objects with "size" attribute (e.g. Map, Set).
*/
export interface SizeAttributeInterface {
size: number;
}

/**
* Interface for objects with "getValue" method.
*/
export interface ToValueInterface {
getValue(): number;
}
import {AccessorInterface} from '../types';
import * as util from '../utils';

type ValueExtractor = () => number;

Expand All @@ -62,14 +28,6 @@ interface GaugeEntry {
readonly extractor: ValueExtractor;
}

interface AccessorFunction {
(): number;
}

export type AccessorInterface =
LengthAttributeInterface|LengthMethodInterface|SizeAttributeInterface|
SizeMethodInterface|ToValueInterface|AccessorFunction;

/**
* DerivedGauge metric
*/
Expand All @@ -83,8 +41,6 @@ export class DerivedGauge implements types.Meter {
private static readonly LABEL_VALUE = 'labelValue';
private static readonly LABEL_VALUES = 'labelValues';
private static readonly OBJECT = 'obj';
private static readonly NUMBER = 'number';
private static readonly FUNCTION = 'function';
private static readonly ERROR_MESSAGE_INVALID_SIZE =
'Label Keys and Label Values don\'t have same size';
private static readonly ERROR_MESSAGE_DUPLICATE_TIME_SERIES =
Expand Down Expand Up @@ -115,39 +71,6 @@ export class DerivedGauge implements types.Meter {
{name, description, unit, type, labelKeys: keysAndConstantKeys};
}

// Checks if the specified collection is a LengthAttributeInterface.
// tslint:disable-next-line:no-any
protected static isLengthAttributeInterface(obj: any):
obj is LengthAttributeInterface {
return obj && typeof obj.length === DerivedGauge.NUMBER;
}

// Checks if the specified collection is a LengthMethodInterface.
// tslint:disable-next-line:no-any
protected static isLengthMethodInterface(obj: any):
obj is LengthMethodInterface {
return obj && typeof obj.length === DerivedGauge.FUNCTION;
}

// Checks if the specified collection is a SizeAttributeInterface.
// tslint:disable-next-line:no-any
protected static isSizeAttributeInterface(obj: any):
obj is SizeAttributeInterface {
return obj && typeof obj.size === DerivedGauge.NUMBER;
}

// Checks if the specified collection is a SizeMethodInterface.
// tslint:disable-next-line:no-any
protected static isSizeMethodInterface(obj: any): obj is SizeMethodInterface {
return obj && typeof obj.size === DerivedGauge.FUNCTION;
}

// Checks if the specified callbackFn is a ToValueInterface.
// tslint:disable-next-line:no-any
protected static isToValueInterface(obj: any): obj is ToValueInterface {
return obj && typeof obj.getValue === DerivedGauge.FUNCTION;
}

/**
* Creates a TimeSeries. The value of a single point in the TimeSeries is
* observed from a obj or a function. The ValueExtractor is invoked whenever
Expand All @@ -168,7 +91,7 @@ export class DerivedGauge implements types.Meter {
DerivedGauge.LABEL_VALUE);
validateNotNull(objOrFn, DerivedGauge.OBJECT);

const hash = hashLabelValues(labelValues);
const hash = util.hashLabelValues(labelValues);
if (this.registeredPoints.has(hash)) {
throw new Error(DerivedGauge.ERROR_MESSAGE_DUPLICATE_TIME_SERIES);
}
Expand All @@ -178,15 +101,15 @@ export class DerivedGauge implements types.Meter {

if (objOrFn instanceof Function) {
this.extractor = objOrFn;
} else if (DerivedGauge.isToValueInterface(objOrFn)) {
} else if (util.isToValueInterface(objOrFn)) {
this.extractor = () => objOrFn.getValue();
} else if (DerivedGauge.isLengthAttributeInterface(objOrFn)) {
} else if (util.isLengthAttributeInterface(objOrFn)) {
this.extractor = () => objOrFn.length;
} else if (DerivedGauge.isLengthMethodInterface(objOrFn)) {
} else if (util.isLengthMethodInterface(objOrFn)) {
this.extractor = () => objOrFn.length();
} else if (DerivedGauge.isSizeAttributeInterface(objOrFn)) {
} else if (util.isSizeAttributeInterface(objOrFn)) {
this.extractor = () => objOrFn.size;
} else if (DerivedGauge.isSizeMethodInterface(objOrFn)) {
} else if (util.isSizeMethodInterface(objOrFn)) {
this.extractor = () => objOrFn.size();
} else {
throw new Error(DerivedGauge.ERROR_MESSAGE_UNKNOWN_INTERFACE);
Expand All @@ -204,7 +127,7 @@ export class DerivedGauge implements types.Meter {
*/
removeTimeSeries(labelValues: LabelValue[]): void {
validateNotNull(labelValues, DerivedGauge.LABEL_VALUES);
this.registeredPoints.delete(hashLabelValues(labelValues));
this.registeredPoints.delete(util.hashLabelValues(labelValues));
}

/**
Expand Down
33 changes: 33 additions & 0 deletions packages/opencensus-core/src/metrics/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,3 +42,36 @@ export interface MetricOptions {
// TODO(mayurkale): Add resource information.
// https://github.com/census-instrumentation/opencensus-specs/pull/248
}

/** Interface for objects with "length()" method. */
export interface LengthMethodInterface {
length(): number;
}

/** Interface for objects with "length" attribute (e.g. Array). */
export interface LengthAttributeInterface {
length: number;
}

/** Interface for objects with "size" method. */
export interface SizeMethodInterface {
size(): number;
}

/** Interface for objects with "size" attribute (e.g. Map, Set). */
export interface SizeAttributeInterface {
size: number;
}

/** Interface for objects with "getValue" method. */
export interface ToValueInterface {
getValue(): number;
}

export interface AccessorFunction {
(): number;
}

export type AccessorInterface =
LengthAttributeInterface|LengthMethodInterface|SizeAttributeInterface|
SizeMethodInterface|ToValueInterface|AccessorFunction;
Loading

0 comments on commit 525cfa4

Please sign in to comment.