Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(sdk-metrics-base): bootstrap metrics exemplars #2641

Merged
merged 26 commits into from
Dec 28, 2021
Merged
Show file tree
Hide file tree
Changes from 14 commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
075ed44
chore(opentelemetry-sdk-metrics-base): initial exemplar work
srikanthccv Nov 22, 2021
9cf4335
chore: update tests and implementation
srikanthccv Nov 23, 2021
0c38902
chore: eol
srikanthccv Nov 23, 2021
a615c71
chore: update tests
srikanthccv Nov 23, 2021
e67e0e1
Merge branch 'main' into issue-2594
srikanthccv Nov 23, 2021
34cc9f6
chore: address review suggestions
srikanthccv Nov 24, 2021
15ea346
Merge branch 'issue-2594' of github.com:lonewolf3739/opentelemetry-js…
srikanthccv Nov 24, 2021
2c7d4d6
chore: change name
srikanthccv Nov 24, 2021
433374a
chore: indent
srikanthccv Nov 24, 2021
d31c50e
chore: string only attributes
srikanthccv Nov 24, 2021
9eeadce
chore: allow null for attributes until common attributes is used
srikanthccv Nov 24, 2021
c057dbb
Merge remote-tracking branch 'upstream/main' into issue-2594
srikanthccv Nov 27, 2021
32d3f29
chore: use wip and address comments
srikanthccv Nov 28, 2021
e41a60e
chore: update more refs
srikanthccv Nov 28, 2021
fa2fe5b
Merge remote-tracking branch 'upstream/main' into issue-2594
srikanthccv Dec 7, 2021
25995e9
chore: address review comments
srikanthccv Dec 7, 2021
3f3f4f7
Merge branch 'main' into issue-2594
srikanthccv Dec 12, 2021
3c33479
Merge branch 'main' into issue-2594
srikanthccv Dec 19, 2021
a55df26
Merge branch 'main' into issue-2594
srikanthccv Dec 22, 2021
043b5f0
chore: address comments
srikanthccv Dec 22, 2021
d11d0cf
Merge branch 'main' into issue-2594
srikanthccv Dec 22, 2021
09ddbe0
chore: address review comments
srikanthccv Dec 23, 2021
a6e318d
Merge branch 'issue-2594' of github.com:lonewolf3739/opentelemetry-js…
srikanthccv Dec 23, 2021
1dcef56
Merge branch 'main' into issue-2594
srikanthccv Dec 24, 2021
f46cda4
Merge branch 'main' into issue-2594
dyladan Dec 27, 2021
45d86b5
Merge branch 'main' into issue-2594
srikanthccv Dec 28, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
/*
* Copyright The OpenTelemetry 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
*
* https://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 { Context, HrTime } from '@opentelemetry/api';
import { ValueType, Attributes } from '@opentelemetry/api-metrics-wip';
import { FixedSizeExemplarReservoirBase } from './ExemplarReservoir';


/**
* AlignedHistogramBucketExemplarReservoir takes the same boundaries
* configuration of a Histogram. This alogorithm keeps the last seen measurement
* that falls within a histogram bucket.
* https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/metrics/sdk.md#exemplar-defaults
*/
export class AlignedHistogramBucketExemplarReservoir extends FixedSizeExemplarReservoirBase {
private _boundaries: number[];
constructor(boundaries: number[]) {
super(boundaries.length+1);
this._boundaries = boundaries;
}

findBucketIndex(value: ValueType, _timestamp: HrTime, _attributes: Attributes, _ctx: Context) {
for(let i = 0; i < this._boundaries.length; i++) {
if (value <= this._boundaries[i]) {
return i;
}
}
return this._boundaries.length;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright The OpenTelemetry 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
*
* https://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 { ValueType, Attributes } from '@opentelemetry/api-metrics-wip'
import { Context, HrTime } from '@opentelemetry/api'
import { ExemplarFilter } from './ExemplarFilter';


export class AlwaysSampleExemplarFilter implements ExemplarFilter {

shouldSample(
_value: ValueType,
_timestamp: HrTime,
_attributes: Attributes,
_ctx: Context
): boolean {
return true;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
/*
* Copyright The OpenTelemetry 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
*
* https://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 { HrTime } from '@opentelemetry/api';
import { ValueType, Attributes } from '@opentelemetry/api-metrics-wip';

// A representation of an exemplar, which is a sample input measurement.
srikanthccv marked this conversation as resolved.
Show resolved Hide resolved
// Exemplars also hold information about the environment when the measurement
// was recorded, for example the span and trace ID of the active span when the
// exemplar was recorded.
export type Exemplar = {
// The set of key/value pairs that were filtered out by the aggregator, but
// recorded alongside the original measurement. Only key/value pairs that were
// filtered out by the aggregator should be included
filteredAttributes: Attributes;

// The value of the measurement that was recorded.
value: ValueType;

// timestamp is the exact time when this exemplar was recorded
timestamp: HrTime;

// (Optional) Span ID of the exemplar trace.
// span_id may be missing if the measurement is not recorded inside a trace
// or if the trace is not sampled.
spanId?: string;

// (Optional) Trace ID of the exemplar trace.
// trace_id may be missing if the measurement is not recorded inside a trace
// or if the trace is not sampled.
traceId?: string;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
/*
* Copyright The OpenTelemetry 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
*
* https://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 { ValueType, Attributes } from '@opentelemetry/api-metrics-wip'
import { Context, HrTime } from '@opentelemetry/api'

/**
* This interface represents a ExemplarFilter. Exemplar filters are
* used to filter measurements before attempting to store them in a
* reservoir.
*/
export interface ExemplarFilter {
/**
* Returns whether or not a reservoir should attempt to filter a measurement.
*
* @param value The value of the measurement
* @param timestamp A timestamp that best represents when the measurement was taken
* @param attributes The complete set of Attributes of the measurement
* @param context The Context of the measurement
*/
shouldSample(
value: ValueType,
srikanthccv marked this conversation as resolved.
Show resolved Hide resolved
timestamp: HrTime,
attributes: Attributes,
ctx: Context
): boolean;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,134 @@
/*
* Copyright The OpenTelemetry 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
*
* https://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 { ValueType, Attributes } from '@opentelemetry/api-metrics-wip'
import { Context, HrTime, isSpanContextValid, trace } from '@opentelemetry/api'
import { Exemplar } from './Exemplar'


/**
* An interface for an exemplar reservoir of samples.
*/
export interface ExemplarReservoir {

/** Offers a measurement to be sampled. */
offer(
value: ValueType,
timestamp: HrTime,
attributes: Attributes,
ctx: Context
): void;
/**
* Returns accumulated Exemplars and also resets the reservoir
* for the next sampling period
*
* @param pointAttributes The attributes associated with metric point.
*/
collectAndReset(pointAttributes: Attributes): Exemplar[];
srikanthccv marked this conversation as resolved.
Show resolved Hide resolved
}


class ExamplarBucket {
srikanthccv marked this conversation as resolved.
Show resolved Hide resolved
private value: ValueType = 0;
srikanthccv marked this conversation as resolved.
Show resolved Hide resolved
private attributes: Attributes = {};
private timestamp: HrTime = [0, 0];
private spanId?: string;
private traceId?: string;
private _offered: boolean = false;

constructor() {}
srikanthccv marked this conversation as resolved.
Show resolved Hide resolved

offer(value: ValueType, timestamp: HrTime, attributes: Attributes, ctx: Context) {
this.value = value;
this.timestamp = timestamp;
this.attributes = attributes;
const spanContext = trace.getSpanContext(ctx);
if (spanContext && isSpanContextValid(spanContext)) {
this.spanId = spanContext.spanId;
this.traceId = spanContext.traceId;
}
this._offered = true;
}

collectAndReset(pointAttributes: Attributes): Exemplar | null {
srikanthccv marked this conversation as resolved.
Show resolved Hide resolved
if (!this._offered) return null;
const currentAttriubtes = this.attributes;
srikanthccv marked this conversation as resolved.
Show resolved Hide resolved
// filter attributes
Object.keys(pointAttributes).forEach(key => {
if (pointAttributes[key] === currentAttriubtes[key]) {
delete currentAttriubtes[key];
}
});
const retVal: Exemplar = {
filteredAttributes: currentAttriubtes,
value: this.value,
timestamp: this.timestamp,
spanId: this.spanId,
traceId: this.traceId
};
this.attributes = {};
this.value = 0;
this.timestamp = [0, 0];
this.spanId = undefined;
this.traceId = undefined;
this._offered = false;
return retVal;
}
}


export abstract class FixedSizeExemplarReservoirBase implements ExemplarReservoir {
private _reservoirStorage: ExamplarBucket[];
protected _size: number;

constructor(size: number) {
this._size = size;
this._reservoirStorage = new Array<ExamplarBucket>(size);
for(let i = 0; i < this._size; i++) {
this._reservoirStorage[i] = new ExamplarBucket();
}
}

abstract findBucketIndex(value: ValueType, timestamp: HrTime, attributes: Attributes, ctx: Context): number;

maxSize(): number {
return this._size;
}

offer(value: ValueType, timestamp: HrTime, attributes: Attributes, ctx: Context): void {
const index = this.findBucketIndex(value, timestamp, attributes, ctx);
if (index !== -1) {
srikanthccv marked this conversation as resolved.
Show resolved Hide resolved
this._reservoirStorage[index].offer(value, timestamp, attributes, ctx)
}
}

/**
* Resets the reservoir
*/
reset(): void {}
srikanthccv marked this conversation as resolved.
Show resolved Hide resolved

collectAndReset(pointAttributes: Attributes): Exemplar[] {
const exemplars: Exemplar[] = [];
this._reservoirStorage.forEach(storageItem => {
const res = storageItem.collectAndReset(pointAttributes);
if (res !== null) {
exemplars.push(res);
}
});
this.reset();
return exemplars;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*
* Copyright The OpenTelemetry 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
*
* https://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 { ValueType, Attributes } from '@opentelemetry/api-metrics-wip'
import { Context, HrTime } from '@opentelemetry/api'
import { ExemplarFilter } from './ExemplarFilter';

export class NeverSampleExemplarFilter implements ExemplarFilter {

shouldSample(
_value: ValueType,
_timestamp: HrTime,
_attributes: Attributes,
_ctx: Context
): boolean {
return false;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
/*
* Copyright The OpenTelemetry 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
*
* https://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 { Context, HrTime } from '@opentelemetry/api';
import { ValueType, Attributes } from '@opentelemetry/api-metrics-wip';
import { FixedSizeExemplarReservoirBase } from './ExemplarReservoir';

/**
* Fixed size reservoir that uses equivalent of naive reservoir sampling
* algorithm to accept measurements.
*
*/
export class SimpleFixedSizeExemplarReservoir extends FixedSizeExemplarReservoirBase {
srikanthccv marked this conversation as resolved.
Show resolved Hide resolved
private _counter: number;
srikanthccv marked this conversation as resolved.
Show resolved Hide resolved
constructor(size: number) {
super(size);
this._counter = 0;
}

getRandomInt(min: number, max: number) { //[min, max)
srikanthccv marked this conversation as resolved.
Show resolved Hide resolved
min = Math.ceil(min);
max = Math.floor(max);
srikanthccv marked this conversation as resolved.
Show resolved Hide resolved
return Math.floor(Math.random() * (max - min) + min);
}

findBucketIndex(_value: ValueType, _timestamp: HrTime, _attributes: Attributes, _ctx: Context) {
const index = this.getRandomInt(0, this._counter+1);
this._counter += 1;
srikanthccv marked this conversation as resolved.
Show resolved Hide resolved
return index < this._size ? index: -1;
}

override reset() {
this._counter = 0;
}
}
Loading