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 API (#503)
Browse files Browse the repository at this point in the history
* Add support for derived cumulative metrics

* fix review comments

* Move the type to the arrow function signature

* Add JSDoc for CumulativeEntry interface and its members

* Add TODO to use unknown type instead of any

* fix review comment
  • Loading branch information
mayurkale22 committed May 9, 2019
1 parent 111223e commit 89f20bb
Show file tree
Hide file tree
Showing 6 changed files with 542 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
177 changes: 177 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,177 @@
/**
* 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;

/**
* An interface that describes the entry for every TimeSeries (Point) added to
* the Cumulative metric.
*/
interface CumulativeEntry {
/** The list of the label values. */
readonly labelValues: LabelValue[];
/** The function to get the actual value of point. */
readonly extractor: ValueExtractor;
/** The previous value of the point. */
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 name The name of the metric.
* @param description The description of the metric.
* @param unit The unit of the metric.
* @param type The type of metric.
* @param labelKeys The list of the label keys.
* @param constantLabels The map of constant labels for the Metric.
* @param 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 an object or function. The ValueExtractor is invoked whenever
* metrics are collected, meaning the reported value is up-to-date.
*
* @param 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 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 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]):
TimeSeries => {
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
};
})
};
}
}
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 @@ -114,39 +70,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 @@ -167,7 +90,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 @@ -177,15 +100,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 @@ -203,7 +126,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 @@ -41,3 +41,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 89f20bb

Please sign in to comment.