Current section

Files

Jump to
opentelemetry_experimental src otel_metric_exemplar_reservoir_aligned_histogram.erl
Raw

src/otel_metric_exemplar_reservoir_aligned_histogram.erl

%%%------------------------------------------------------------------------
%% Copyright 2024, 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
%%
%% 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.
%%
%% @doc Exemplar Reservoir used by the Explicit Histogram Aggregation. The
%% same bucket boundaries as the aggregation. Simple algorithm for adding
%% each new measurement as an exemplar:
%%
%% ```
%% bucket = find_histogram_bucket(measurement)
%% if bucket < num_buckets then
%% reservoir[bucket] = measurement
%% end
%%
%% def find_histogram_bucket(measurement):
%% for boundary, idx in bucket_boundaries do
%% if value <= boundary then
%% return idx
%% end
%% end
%% return boundaries.length
%%'''
%%
%% @end
%%%-------------------------------------------------------------------------
-module(otel_metric_exemplar_reservoir_aligned_histogram).
-export([new/1,
offer/6,
collect/3]).
-include_lib("opentelemetry_api/include/opentelemetry.hrl").
-record(state, {bucket_boundaries :: [float()]}).
-type config() :: #{explicit_bucket_boundaries => [float()]}.
-spec new(config()) -> #state{}.
new(#{explicit_bucket_boundaries := BucketBoundaries}) ->
#state{bucket_boundaries=BucketBoundaries};
new(_) ->
#state{bucket_boundaries=otel_aggregation_histogram_explicit:default_buckets()}.
offer(Ctx, ExemplarsTab, Key, Value, FilteredAttributes, #state{bucket_boundaries=BucketBoundaries}) ->
Bucket = find_bucket(BucketBoundaries, Value),
add_exemplar(Ctx, ExemplarsTab, Key, Bucket, Value, FilteredAttributes).
%% @doc Return all exemplars for a `Key' and then delete them.
-spec collect(ets:table(), term(), #state{}) -> [otel_metric_exemplar:exemplar()].
collect(ExemplarsTab, Key, _State) ->
Exemplars = ets:select(ExemplarsTab, [{{{'$1', '_'}, '$2'},
[{'==', '$1', {const, Key}}],
['$2']}]),
_ = ets:select_delete(ExemplarsTab, [{{'$1', '_'}, [{'==', '$1', {const, Key}}], [true]},
{{{'$1', '_'}, '_'}, [{'==', '$1', {const, Key}}], [true]}]),
Exemplars.
%%
add_exemplar(Ctx, ExemplarsTab, Key, Bucket, Value, FilteredAttributes) ->
Time = opentelemetry:timestamp(),
{TraceId, SpanId} = case otel_tracer:current_span_ctx(Ctx) of
#span_ctx{trace_id=TraceId0,
span_id=SpanId0} ->
{TraceId0, SpanId0};
_ ->
{undefined, undefined}
end,
Exemplar = otel_metric_exemplar:new(Value, Time, FilteredAttributes, TraceId, SpanId),
_ = ets:insert(ExemplarsTab, {{Key, Bucket}, Exemplar}),
ok.
find_bucket(Boundaries, Value) ->
find_bucket(Boundaries, Value, 1).
find_bucket([X | _Rest], Value, Pos) when Value =< X ->
Pos;
find_bucket([_X | Rest], Value, Pos) ->
find_bucket(Rest, Value, Pos+1);
find_bucket(_, _, Pos) ->
Pos.