diff --git a/CHANGELOG.md b/CHANGELOG.md index d0cd8b70..4bf793f4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ### Changes - [Allow to create observable instruments without passing callback arguments](https://github.com/open-telemetry/opentelemetry-erlang/pull/604) +- [Allow to give `advisory_params` to instrument creation functions](https://github.com/open-telemetry/opentelemetry-erlang/pull/628) ## Experimental SDK @@ -37,6 +38,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - [Add `instrument_unit` to view criteria](https://github.com/open-telemetry/opentelemetry-erlang/pull/604) - [Validate instrument name](https://github.com/open-telemetry/opentelemetry-erlang/pull/604) +- [Handle `explict_bucket_boundaries` advisory parameter](https://github.com/open-telemetry/opentelemetry-erlang/pull/628) +- [Rename `boundaries` to `explict_bucket_boundaries` in histogram explicit aggregation options](https://github.com/open-telemetry/opentelemetry-erlang/pull/628) ### Changes diff --git a/apps/opentelemetry_api_experimental/include/otel_metrics.hrl b/apps/opentelemetry_api_experimental/include/otel_metrics.hrl index 5446cf2e..75716b1b 100644 --- a/apps/opentelemetry_api_experimental/include/otel_metrics.hrl +++ b/apps/opentelemetry_api_experimental/include/otel_metrics.hrl @@ -1,12 +1,13 @@ --record(instrument, {module :: module(), - meter :: otel_meter:t(), - name :: otel_instrument:name(), - description :: otel_instrument:description() | undefined, - kind :: otel_instrument:kind(), - unit :: otel_instrument:unit() | undefined, - temporality :: otel_instrument:temporality(), - callback :: otel_instrument:callback() | undefined, - callback_args :: otel_instrument:callback_args() | undefined}). +-record(instrument, {module :: module(), + meter :: otel_meter:t(), + name :: otel_instrument:name(), + description :: otel_instrument:description() | undefined, + kind :: otel_instrument:kind(), + unit :: otel_instrument:unit() | undefined, + temporality :: otel_instrument:temporality(), + callback :: otel_instrument:callback() | undefined, + callback_args :: otel_instrument:callback_args() | undefined, + advisory_params :: otel_instrument:advisory_params() | undefined}). -define(TEMPORALITY_DELTA, temporality_delta). -define(TEMPORALITY_CUMULATIVE, temporality_cumulative). diff --git a/apps/opentelemetry_api_experimental/src/otel_counter.erl b/apps/opentelemetry_api_experimental/src/otel_counter.erl index 3c0e9728..36dd2257 100644 --- a/apps/opentelemetry_api_experimental/src/otel_counter.erl +++ b/apps/opentelemetry_api_experimental/src/otel_counter.erl @@ -28,7 +28,7 @@ -spec create(Meter, Name, Opts) -> otel_instrument:t() when Meter :: otel_meter:t(), Name :: otel_instrument:name(), - Opts :: otel_meter:opts(). + Opts :: otel_instrument:opts(). create(Meter, Name, Opts) -> otel_meter:create_counter(Meter, Name, Opts). diff --git a/apps/opentelemetry_api_experimental/src/otel_histogram.erl b/apps/opentelemetry_api_experimental/src/otel_histogram.erl index f82a55ea..e16eef14 100644 --- a/apps/opentelemetry_api_experimental/src/otel_histogram.erl +++ b/apps/opentelemetry_api_experimental/src/otel_histogram.erl @@ -29,7 +29,7 @@ -spec create(Meter, Name, Opts) -> otel_instrument:t() when Meter :: otel_meter:t(), Name :: otel_instrument:name(), - Opts :: otel_meter:opts(). + Opts :: otel_instrument:opts(). create(Meter, Name, Opts) -> otel_meter:create_histogram(Meter, Name, Opts). diff --git a/apps/opentelemetry_api_experimental/src/otel_instrument.erl b/apps/opentelemetry_api_experimental/src/otel_instrument.erl index 3d37b4f7..6feac48a 100644 --- a/apps/opentelemetry_api_experimental/src/otel_instrument.erl +++ b/apps/opentelemetry_api_experimental/src/otel_instrument.erl @@ -17,8 +17,8 @@ %%%------------------------------------------------------------------------- -module(otel_instrument). --export([new/6, - new/8, +-export([new/5, + new/7, is_monotonic/1, temporality/1]). @@ -38,6 +38,12 @@ -type temporality() :: ?TEMPORALITY_DELTA | ?TEMPORALITY_CUMULATIVE. +-type advisory_params() :: #{explicit_bucket_boundaries => [number(), ...]}. + +-type opts() :: #{description => description(), + unit => unit(), + advisory_params => advisory_params()}. + -type t() :: #instrument{}. -export_type([t/0, @@ -48,29 +54,39 @@ temporality/0, callback/0, callback_args/0, - callback_result/0]). + callback_result/0, + advisory_params/0, + opts/0]). --spec new(module(), otel_meter:t(), kind(), name(), description() | undefined, unit() | undefined) -> t(). -new(Module, Meter, Kind, Name, Description, Unit) -> - #instrument{module = Module, - meter = Meter, - name = Name, - description = Description, - temporality = ?TEMPORALITY_DELTA, - kind = Kind, - unit = Unit}. +-spec new(module(), otel_meter:t(), kind(), name(), opts()) -> t(). +new(Module, Meter, Kind, Name, Opts) -> + Description = maps:get(description, Opts, undefined), + Unit = maps:get(unit, Opts, undefined), + AdvisoryParams = maps:get(advisory_params, Opts, undefined), + #instrument{module = Module, + meter = Meter, + name = Name, + description = Description, + temporality = ?TEMPORALITY_DELTA, + kind = Kind, + unit = Unit, + advisory_params = AdvisoryParams}. --spec new(module(), otel_meter:t(), kind(), name(), description() | undefined, unit() | undefined, callback(), callback_args()) -> t(). -new(Module, Meter, Kind, Name, Description, Unit, Callback, CallbackArgs) -> - #instrument{module = Module, - meter = Meter, - name = Name, - description = Description, - kind = Kind, - unit = Unit, - temporality = ?TEMPORALITY_CUMULATIVE, - callback = Callback, - callback_args = CallbackArgs}. +-spec new(module(), otel_meter:t(), kind(), name(), callback(), callback_args(), opts()) -> t(). +new(Module, Meter, Kind, Name, Callback, CallbackArgs, Opts) -> + Description = maps:get(description, Opts, undefined), + Unit = maps:get(unit, Opts, undefined), + AdvisoryParams = maps:get(advisory_params, Opts, undefined), + #instrument{module = Module, + meter = Meter, + name = Name, + description = Description, + kind = Kind, + unit = Unit, + temporality = ?TEMPORALITY_CUMULATIVE, + callback = Callback, + callback_args = CallbackArgs, + advisory_params = AdvisoryParams}. is_monotonic(#instrument{kind=?KIND_COUNTER}) -> true; diff --git a/apps/opentelemetry_api_experimental/src/otel_meter.erl b/apps/opentelemetry_api_experimental/src/otel_meter.erl index fbda6063..2b9cb9dc 100644 --- a/apps/opentelemetry_api_experimental/src/otel_meter.erl +++ b/apps/opentelemetry_api_experimental/src/otel_meter.erl @@ -42,7 +42,7 @@ Meter :: t(), Name :: otel_instrument:name(), Kind :: otel_instrument:kind(), - Opts :: opts(). + Opts :: otel_instrument:opts(). -callback create_instrument(Meter, Name, Kind, Callback, CallbackArgs, Opts) -> otel_instrument:t() when Meter :: t(), @@ -50,7 +50,7 @@ Kind :: otel_instrument:kind(), Callback :: otel_instrument:callback(), CallbackArgs :: otel_instrument:callback_args(), - Opts :: opts(). + Opts :: otel_instrument:opts(). -callback register_callback(Meter, Instruments, Callback, CallbackArgs) -> ok when Meter :: t(), @@ -58,39 +58,35 @@ Callback :: otel_instrument:callback(), CallbackArgs :: otel_instrument:callback_args(). --type opts() :: #{description => otel_instrument:description(), - unit => otel_instrument:unit()}. - -type t() :: {module(), term()}. --export_type([t/0, - opts/0]). +-export_type([t/0]). -spec create_counter(Meter, Name, Opts) -> otel_instrument:t() when Meter :: t(), Name :: otel_instrument:name(), - Opts :: opts(). + Opts :: otel_instrument:opts(). create_counter(Meter, Name, Opts) -> create_instrument(Meter, Name, ?KIND_COUNTER, Opts). -spec create_updown_counter(Meter, Name, Opts) -> otel_instrument:t() when Meter :: t(), Name :: otel_instrument:name(), - Opts :: opts(). + Opts :: otel_instrument:opts(). create_updown_counter(Meter, Name, Opts) -> create_instrument(Meter, Name, ?KIND_UPDOWN_COUNTER, Opts). -spec create_histogram(Meter, Name, Opts) -> otel_instrument:t() when Meter :: t(), Name :: otel_instrument:name(), - Opts :: opts(). + Opts :: otel_instrument:opts(). create_histogram(Meter, Name, Opts) -> create_instrument(Meter, Name, ?KIND_HISTOGRAM, Opts). -spec create_observable_counter(Meter, Name, Opts) -> otel_instrument:t() when Meter :: t(), Name :: otel_instrument:name(), - Opts :: opts(). + Opts :: otel_instrument:opts(). create_observable_counter(Meter, Name, Opts) -> create_instrument(Meter, Name, ?KIND_OBSERVABLE_COUNTER, Opts). @@ -99,14 +95,14 @@ create_observable_counter(Meter, Name, Opts) -> Name :: otel_instrument:name(), Callback :: otel_instrument:callback(), CallbackArgs :: otel_instrument:callback_args(), - Opts :: opts(). + Opts :: otel_instrument:opts(). create_observable_counter(Meter, Name, Callback, CallbackArgs, Opts) -> create_instrument(Meter, Name, ?KIND_OBSERVABLE_COUNTER, Callback, CallbackArgs, Opts). -spec create_observable_gauge(Meter, Name, Opts) -> otel_instrument:t() when Meter :: t(), Name :: otel_instrument:name(), - Opts :: opts(). + Opts :: otel_instrument:opts(). create_observable_gauge(Meter, Name, Opts) -> create_instrument(Meter, Name, ?KIND_OBSERVABLE_GAUGE, Opts). @@ -115,14 +111,14 @@ create_observable_gauge(Meter, Name, Opts) -> Name :: otel_instrument:name(), Callback :: otel_instrument:callback(), CallbackArgs :: otel_instrument:callback_args(), - Opts :: opts(). + Opts :: otel_instrument:opts(). create_observable_gauge(Meter, Name, Callback, CallbackArgs, Opts) -> create_instrument(Meter, Name, ?KIND_OBSERVABLE_GAUGE, Callback, CallbackArgs, Opts). -spec create_observable_updowncounter(Meter, Name, Opts) -> otel_instrument:t() when Meter :: t(), Name :: otel_instrument:name(), - Opts :: opts(). + Opts :: otel_instrument:opts(). create_observable_updowncounter(Meter, Name, Opts) -> create_instrument(Meter, Name, ?KIND_OBSERVABLE_UPDOWNCOUNTER, Opts). @@ -131,7 +127,7 @@ create_observable_updowncounter(Meter, Name, Opts) -> Name :: otel_instrument:name(), Callback :: otel_instrument:callback(), CallbackArgs :: otel_instrument:callback_args(), - Opts :: opts(). + Opts :: otel_instrument:opts(). create_observable_updowncounter(Meter, Name, Callback, CallbackArgs, Opts) -> create_instrument(Meter, Name, ?KIND_OBSERVABLE_UPDOWNCOUNTER, Callback, CallbackArgs, Opts). @@ -145,7 +141,7 @@ scope(Meter={Module, _}) -> Meter :: t(), Name :: otel_instrument:name(), Kind :: otel_instrument:kind(), - Opts :: opts(). + Opts :: otel_instrument:opts(). create_instrument(Meter={Module, _}, Name, Kind, Opts) -> Module:create_instrument(Meter, Name, Kind, Opts). @@ -155,7 +151,7 @@ create_instrument(Meter={Module, _}, Name, Kind, Opts) -> Kind :: otel_instrument:kind(), Callback :: otel_instrument:callback(), CallbackArgs :: otel_instrument:callback_args(), - Opts :: opts(). + Opts :: otel_instrument:opts(). create_instrument(Meter={Module, _}, Name, Kind, Callback, CallbackArgs, Opts) -> Module:create_instrument(Meter, Name, Kind, Callback, CallbackArgs, Opts). diff --git a/apps/opentelemetry_api_experimental/src/otel_meter_noop.erl b/apps/opentelemetry_api_experimental/src/otel_meter_noop.erl index e9b01048..775822f9 100644 --- a/apps/opentelemetry_api_experimental/src/otel_meter_noop.erl +++ b/apps/opentelemetry_api_experimental/src/otel_meter_noop.erl @@ -41,9 +41,7 @@ register_callback(_Meter, _Instruments, _Callback, _CallbackArgs) -> ok. create_instrument(Meter, Name, Kind, Opts) -> - otel_instrument:new(?MODULE, Meter, Kind, Name, maps:get(description, Opts, undefined), - maps:get(unit, Opts, undefined)). + otel_instrument:new(?MODULE, Meter, Kind, Name, Opts). create_instrument(Meter, Name, Kind, Callback, CallbackArgs, Opts) -> - otel_instrument:new(?MODULE, Meter, Kind, Name, maps:get(description, Opts, undefined), - maps:get(unit, Opts, undefined), Callback, CallbackArgs). + otel_instrument:new(?MODULE, Meter, Kind, Name, Callback, CallbackArgs, Opts). diff --git a/apps/opentelemetry_api_experimental/src/otel_updown_counter.erl b/apps/opentelemetry_api_experimental/src/otel_updown_counter.erl index f143a929..64c73d0a 100644 --- a/apps/opentelemetry_api_experimental/src/otel_updown_counter.erl +++ b/apps/opentelemetry_api_experimental/src/otel_updown_counter.erl @@ -28,7 +28,7 @@ -spec create(Meter, Name, Opts) -> otel_instrument:t() when Meter :: otel_meter:t(), Name :: otel_instrument:name(), - Opts :: otel_meter:opts(). + Opts :: otel_instrument:opts(). create(Meter, Name, Opts) -> otel_meter:create_updown_counter(Meter, Name, Opts). diff --git a/apps/opentelemetry_experimental/include/otel_metrics.hrl b/apps/opentelemetry_experimental/include/otel_metrics.hrl index ba48230c..16c008f2 100644 --- a/apps/opentelemetry_experimental/include/otel_metrics.hrl +++ b/apps/opentelemetry_experimental/include/otel_metrics.hrl @@ -62,7 +62,7 @@ start_time_unix_nano :: integer() | {const, eqwalizer:dynamic()} | '$9' | '$2' | undefined, %% instrument_temporality :: otel_aggregation:temporality(), %% default: [0.0, 5.0, 10.0, 25.0, 50.0, 75.0, 100.0, 250.0, 500.0, 1000.0] - boundaries :: [float()] | match_spec([float()]), + explicit_bucket_boundaries :: [float()] | match_spec([float()]), record_min_max :: boolean() | match_spec(boolean()), checkpoint :: #explicit_histogram_checkpoint{} | match_spec(#explicit_histogram_checkpoint{}) | {#explicit_histogram_checkpoint{}}, bucket_counts :: counters:counters_ref() | match_spec(undefined), diff --git a/apps/opentelemetry_experimental/src/otel_aggregation_histogram_explicit.erl b/apps/opentelemetry_experimental/src/otel_aggregation_histogram_explicit.erl index 7981e556..cd8ade8c 100644 --- a/apps/opentelemetry_experimental/src/otel_aggregation_histogram_explicit.erl +++ b/apps/opentelemetry_experimental/src/otel_aggregation_histogram_explicit.erl @@ -136,12 +136,12 @@ init(#view_aggregation{name=Name, reader=ReaderId, aggregation_options=Options}, Attributes) -> Key = {Name, Attributes, ReaderId}, - Boundaries = maps:get(boundaries, Options, ?DEFAULT_BOUNDARIES), + ExplicitBucketBoundaries = maps:get(explicit_bucket_boundaries, Options, ?DEFAULT_BOUNDARIES), RecordMinMax = maps:get(record_min_max, Options, true), #explicit_histogram_aggregation{key=Key, start_time_unix_nano=erlang:system_time(nanosecond), - boundaries=Boundaries, - bucket_counts=new_bucket_counts(Boundaries), + explicit_bucket_boundaries=ExplicitBucketBoundaries, + bucket_counts=new_bucket_counts(ExplicitBucketBoundaries), checkpoint=undefined, record_min_max=RecordMinMax, min=infinity, %% works because any atom is > any integer @@ -153,17 +153,17 @@ aggregate(Table, #view_aggregation{name=Name, reader=ReaderId, aggregation_options=Options}, Value, Attributes) -> Key = {Name, Attributes, ReaderId}, - Boundaries = maps:get(boundaries, Options, ?DEFAULT_BOUNDARIES), + ExplicitBucketBoundaries = maps:get(explicit_bucket_boundaries, Options, ?DEFAULT_BOUNDARIES), try ets:lookup_element(Table, Key, #explicit_histogram_aggregation.bucket_counts) of BucketCounts0 -> BucketCounts = case BucketCounts0 of undefined -> - new_bucket_counts(Boundaries); + new_bucket_counts(ExplicitBucketBoundaries); _ -> BucketCounts0 end, - BucketIdx = find_bucket(Boundaries, Value), + BucketIdx = find_bucket(ExplicitBucketBoundaries, Value), counters:add(BucketCounts, BucketIdx, 1), MS = ?AGGREATE_MATCH_SPEC(Key, Value, BucketCounts), @@ -181,7 +181,7 @@ checkpoint(Tab, #view_aggregation{name=Name, temporality=?TEMPORALITY_DELTA}, CollectionStartNano) -> MS = [{#explicit_histogram_aggregation{key='$1', start_time_unix_nano='$9', - boundaries='$2', + explicit_bucket_boundaries='$2', record_min_max='$3', checkpoint='_', bucket_counts='$5', @@ -193,7 +193,7 @@ checkpoint(Tab, #view_aggregation{name=Name, {'=:=', {element, 3, '$1'}, {const, ReaderId}}], [{#explicit_histogram_aggregation{key='$1', start_time_unix_nano={const, CollectionStartNano}, - boundaries='$2', + explicit_bucket_boundaries='$2', record_min_max='$3', checkpoint={#explicit_histogram_checkpoint{bucket_counts='$5', min='$6', @@ -229,7 +229,7 @@ collect(Tab, #view_aggregation{name=Name, datapoint(CollectionStartNano, #explicit_histogram_aggregation{ key={_, Attributes, _}, - boundaries=Boundaries, + explicit_bucket_boundaries=Boundaries, start_time_unix_nano=StartTimeUnixNano, checkpoint=undefined, bucket_counts=BucketCounts, @@ -253,7 +253,7 @@ datapoint(CollectionStartNano, #explicit_histogram_aggregation{ }; datapoint(CollectionStartNano, #explicit_histogram_aggregation{ key={_, Attributes, _}, - boundaries=Boundaries, + explicit_bucket_boundaries=Boundaries, checkpoint=#explicit_histogram_checkpoint{bucket_counts=BucketCounts, min=Min, max=Max, diff --git a/apps/opentelemetry_experimental/src/otel_meter_default.erl b/apps/opentelemetry_experimental/src/otel_meter_default.erl index 05a67fc4..656aaeda 100644 --- a/apps/opentelemetry_experimental/src/otel_meter_default.erl +++ b/apps/opentelemetry_experimental/src/otel_meter_default.erl @@ -34,12 +34,12 @@ -define(INSTRUMENT_NAME_REGEX, "^[A-Za-z]+[A-Za-z0-9/_.\-]{0,254}$"). --spec create_instrument(otel_meter:t(), otel_instrument:name(), otel_instrument:kind(), otel_meter:opts()) -> otel_instrument:t(). +-spec create_instrument(otel_meter:t(), otel_instrument:name(), otel_instrument:kind(), otel_instrument:opts()) -> otel_instrument:t(). create_instrument(Meter, Name, Kind, Opts) -> validate_name(Name), + ValidatedOpts = validate_opts(Name, Kind, Opts), Instrument=#instrument{meter={_, #meter{provider=Provider}}} = - otel_instrument:new(?MODULE, Meter, Kind, Name, maps:get(description, Opts, undefined), - maps:get(unit, Opts, undefined)), + otel_instrument:new(?MODULE, Meter, Kind, Name, ValidatedOpts), _ = otel_meter_server:add_instrument(Provider, Instrument), Instrument. @@ -52,12 +52,12 @@ lookup_instrument(Meter={_, #meter{instruments_tab=Tab}}, Name) -> undefined end. --spec create_instrument(otel_meter:t(), otel_instrument:name(), otel_instrument:kind(), otel_instrument:callback(), otel_instrument:callback_args(), otel_meter:opts()) -> otel_instrument:t(). +-spec create_instrument(otel_meter:t(), otel_instrument:name(), otel_instrument:kind(), otel_instrument:callback(), otel_instrument:callback_args(), otel_instrument:opts()) -> otel_instrument:t(). create_instrument(Meter, Name, Kind, Callback, CallbackArgs, Opts) -> validate_name(Name), + ValidatedOpts = validate_opts(Name, Kind, Opts), Instrument=#instrument{meter={_, #meter{provider=Provider}}} = - otel_instrument:new(?MODULE, Meter, Kind, Name, maps:get(description, Opts, undefined), - maps:get(unit, Opts, undefined), Callback, CallbackArgs), + otel_instrument:new(?MODULE, Meter, Kind, Name, Callback, CallbackArgs, ValidatedOpts), _ = otel_meter_server:add_instrument(Provider, Instrument), Instrument. @@ -81,6 +81,34 @@ validate_name(Name) when is_atom(Name) -> validate_name(Name) -> ?LOG_ERROR("Invalid instrument name, should be an atom matching '~s', but got ~p", [?INSTRUMENT_NAME_REGEX, Name]), ok. + +validate_opts(Name, Kind, #{advisory_params := AdvisoryParams} = Opts) -> + % switch to maps:filtermap when we support only 24 onwards + ValidatedAdvisoryParams = maps:from_list(lists:filtermap(fun({Key, Value}) -> validate_advisory_param(Name, Kind, Key, Value) end, maps:to_list(AdvisoryParams))), + maps:put(advisory_params, ValidatedAdvisoryParams, Opts); +validate_opts(_Name, _Kind, Opts) -> + Opts. + +validate_advisory_param(Name, ?KIND_HISTOGRAM, explicit_bucket_boundaries, Value) -> + validate_explicit_bucket_boundaries(Name, Value); +validate_advisory_param(Name, _Kind, explicit_bucket_boundaries, _Value) -> + ?LOG_WARNING("[instrument '~s'] 'explicit_bucket_boundaries' advisory parameter is allowed only for histograms, ignoring", [Name]), + false; +validate_advisory_param(Name, _Kind, Opt, _Value) -> + ?LOG_WARNING("[instrument '~s'] '~s' advisory parameter is not supported, ignoring", [Name, Opt]), + false. + +validate_explicit_bucket_boundaries(Name, [_ | _] = Value) -> + case lists:all(fun is_number/1, Value) and (lists:sort(Value) == Value) of + true -> + {true, {explicit_bucket_boundaries, Value}}; + false -> + ?LOG_WARNING("[instrument '~s'] 'explicit_bucket_boundaries' advisory parameter should be a not empty ordered list of numbers, got ~p", [Name, Value]), + false + end; +validate_explicit_bucket_boundaries(Name, Value) -> + ?LOG_WARNING("[instrument '~s'] 'explicit_bucket_boundaries' advisory parameter should be a not empty ordered list of numbers, got ~p", [Name, Value]), + false. %% record(Instrument=#instrument{meter={_, #meter{view_aggregations_tab=ViewAggregationTab, diff --git a/apps/opentelemetry_experimental/src/otel_meter_server.erl b/apps/opentelemetry_experimental/src/otel_meter_server.erl index e99cd82b..d297e4f6 100644 --- a/apps/opentelemetry_experimental/src/otel_meter_server.erl +++ b/apps/opentelemetry_experimental/src/otel_meter_server.erl @@ -392,7 +392,6 @@ view_aggregation_for_reader(Instrument=#instrument{kind=Kind}, ViewAggregation, reader=Id, attribute_keys=AttributeKeys, aggregation_module=AggregationModule, - aggregation_options=#{}, temporality=Temporality}; view_aggregation_for_reader(Instrument=#instrument{kind=Kind}, ViewAggregation, View, Reader=#reader{id=Id, @@ -404,7 +403,6 @@ view_aggregation_for_reader(Instrument=#instrument{kind=Kind}, ViewAggregation, reader=Id, attribute_keys=undefined, aggregation_module=AggregationModule, - aggregation_options=#{}, temporality=Temporality}. diff --git a/apps/opentelemetry_experimental/src/otel_view.erl b/apps/opentelemetry_experimental/src/otel_view.erl index c2e9e960..9eaa24ac 100644 --- a/apps/opentelemetry_experimental/src/otel_view.erl +++ b/apps/opentelemetry_experimental/src/otel_view.erl @@ -78,7 +78,8 @@ new(Name, Criteria, Config) -> -spec match_instrument_to_views(otel_instrument:t(), [t()]) -> [{t() | undefined, #view_aggregation{}}]. match_instrument_to_views(Instrument=#instrument{name=InstrumentName, meter=Meter, - description=Description}, Views) -> + description=Description, + advisory_params=AdvisoryParams}, Views) -> IsMonotonic = otel_instrument:is_monotonic(Instrument), Temporality = otel_instrument:temporality(Instrument), Scope = otel_meter:scope(Meter), @@ -91,6 +92,7 @@ match_instrument_to_views(Instrument=#instrument{name=InstrumentName, [] -> false; _ -> + AggregationOptions1 = aggragation_options(AggregationOptions, AdvisoryParams), %% `reader' needs to be undefined and is set %% for each in `otel_meter_server' %% eqwalizer:ignore see above @@ -101,20 +103,21 @@ match_instrument_to_views(Instrument=#instrument{name=InstrumentName, temporality=Temporality, is_monotonic=IsMonotonic, attribute_keys=AttributeKeys, - aggregation_options=AggregationOptions, + aggregation_options=AggregationOptions1, description=value_or(ViewDescription, Description) }}} end end, Views) of [] -> + AggregationOptions1 = aggragation_options(#{}, AdvisoryParams), [{undefined, #view_aggregation{name=InstrumentName, scope=Scope, instrument=Instrument, temporality=Temporality, is_monotonic=IsMonotonic, attribute_keys=undefined, - aggregation_options=#{}, + aggregation_options=AggregationOptions1, description=Description}}]; Aggs -> Aggs @@ -122,6 +125,13 @@ match_instrument_to_views(Instrument=#instrument{name=InstrumentName, %% +aggragation_options(#{explicit_bucket_boundaries := _} = AggregationOptions, _AdvisoryParams) -> + AggregationOptions; +aggragation_options(AggregationOptions, #{explicit_bucket_boundaries := Boundaries}) -> + maps:put(explicit_bucket_boundaries, Boundaries, AggregationOptions); +aggragation_options(AggregationOptions, _AdvisoryParams) -> + AggregationOptions. + value_or(undefined, Other) -> Other; value_or(Value, _Other) -> diff --git a/apps/opentelemetry_experimental/test/otel_metrics_SUITE.erl b/apps/opentelemetry_experimental/test/otel_metrics_SUITE.erl index 73638f13..6308472f 100644 --- a/apps/opentelemetry_experimental/test/otel_metrics_SUITE.erl +++ b/apps/opentelemetry_experimental/test/otel_metrics_SUITE.erl @@ -83,7 +83,7 @@ all() -> kill_reader, kill_server, observable_counter, observable_updown_counter, observable_gauge, multi_instrument_callback, using_macros, float_counter, float_updown_counter, float_histogram, sync_filtered_attributes, async_filtered_attributes, delta_observable_counter, - bad_observable_return, default_resource + bad_observable_return, default_resource, histogram_aggregation_options, advisory_params ]. init_per_suite(Config) -> @@ -1130,3 +1130,122 @@ bad_observable_return(_Config) -> ?assertSumReceive(CounterName2, <<"observable counter 2 description">>, kb, [{8, #{}}]), ok. + +advisory_params(_Config) -> + DefaultMeter = otel_meter_default, + + Meter = opentelemetry_experimental:get_meter(), + ?assertMatch({DefaultMeter, _}, Meter), + + % explicit_bucket_boundaries allowed only for histograms + Counter = otel_counter:create(Meter, invalid_1, + #{advisory_params => #{explicit_bucket_boundaries => [1, 2, 3]}}), + ?assertEqual(Counter#instrument.advisory_params, #{}), + + % advisory parameters different from explicit_bucket_boundaries are not allowed + Counter1 = otel_counter:create(Meter, invalid_2, #{advisory_params => #{invalid => invalid}}), + ?assertEqual(Counter1#instrument.advisory_params, #{}), + + % explicit_bucket_boundaries should be an ordered list of numbers + Histo1 = otel_histogram:create(Meter, invalid_3, + #{advisory_params => #{explicit_bucket_boundaries => invalid}}), + ?assertEqual(Histo1#instrument.advisory_params, #{}), + + Histo2 = otel_histogram:create(Meter, invalid_4, + #{advisory_params => #{explicit_bucket_boundaries => [2,1,4]}}), + ?assertEqual(Histo2#instrument.advisory_params, #{}), + + % when valid use the explicit_bucket_boundaries from advisory_params if not set in a view + Histogram = otel_histogram:create(Meter, a_histogram, + #{advisory_params => #{explicit_bucket_boundaries => [10, 20, 30]}}), + ?assertEqual(Histogram#instrument.advisory_params, #{explicit_bucket_boundaries => [10, 20, 30]}), + + ?assertEqual(ok, otel_histogram:record(Histogram, 15, #{<<"a">> => <<"1">>})), + ?assertEqual(ok, otel_histogram:record(Histogram, 50, #{<<"a">> => <<"1">>})), + ?assertEqual(ok, otel_histogram:record(Histogram, 26, #{<<"a">> => <<"2">>})), + + otel_meter_server:force_flush(), + + receive + {otel_metric, #metric{name=a_histogram, + data=#histogram{datapoints=Datapoints}}} -> + AttributeBuckets = + lists:sort([{Attributes, Buckets, Min, Max, Sum} || #histogram_datapoint{bucket_counts=Buckets, + attributes=Attributes, + min=Min, + max=Max, + sum=Sum} <- Datapoints]), + ?assertEqual([], [{#{<<"a">> => <<"1">>}, [0,1,0,1], 15, 50, 65}, + {#{<<"a">> => <<"2">>}, [0,0,1,0], 26, 26, 26}] + -- AttributeBuckets, AttributeBuckets) + after + 5000 -> + ct:fail(histogram_receive_timeout) + end, + + % explicit_bucket_boundaries from view have precedence + ?assert(otel_meter_server:add_view(view, #{instrument_name => b_histogram}, #{ + aggregation_module => otel_aggregation_histogram_explicit, + aggregation_options => #{explicit_bucket_boundaries => [10, 100]}})), + + HistogramB = otel_histogram:create(Meter, b_histogram, + #{advisory_params => #{explicit_bucket_boundaries => [10, 20, 30]}}), + ?assertEqual(HistogramB#instrument.advisory_params, #{explicit_bucket_boundaries => [10, 20, 30]}), + + ?assertEqual(ok, otel_histogram:record(HistogramB, 15, #{<<"a">> => <<"1">>})), + ?assertEqual(ok, otel_histogram:record(HistogramB, 50, #{<<"a">> => <<"1">>})), + ?assertEqual(ok, otel_histogram:record(HistogramB, 26, #{<<"a">> => <<"2">>})), + + otel_meter_server:force_flush(), + + receive + {otel_metric, #metric{name=view, + data=#histogram{datapoints=DatapointsB}}} -> + AttributeBucketsB = + lists:sort([{Attributes, Buckets, Min, Max, Sum} || #histogram_datapoint{bucket_counts=Buckets, + attributes=Attributes, + min=Min, + max=Max, + sum=Sum} <- DatapointsB]), + ?assertEqual([], [{#{<<"a">> => <<"1">>}, [0,2,0], 15, 50, 65}, + {#{<<"a">> => <<"2">>}, [0,1,0], 26, 26, 26}] + -- AttributeBucketsB, AttributeBucketsB) + after + 1000 -> + ct:fail(histogram_receive_timeout) + end. + +histogram_aggregation_options(_Config) -> + DefaultMeter = otel_meter_default, + + Meter = opentelemetry_experimental:get_meter(), + ?assertMatch({DefaultMeter, _}, Meter), + + ?assert(otel_meter_server:add_view(view, #{instrument_name => histogram}, #{ + aggregation_module => otel_aggregation_histogram_explicit, + aggregation_options => #{explicit_bucket_boundaries => [10, 100]}})), + + Histogram = otel_histogram:create(Meter, histogram, #{}), + + ?assertEqual(ok, otel_histogram:record(Histogram, 15, #{<<"a">> => <<"1">>})), + ?assertEqual(ok, otel_histogram:record(Histogram, 50, #{<<"a">> => <<"1">>})), + ?assertEqual(ok, otel_histogram:record(Histogram, 26, #{<<"a">> => <<"2">>})), + + otel_meter_server:force_flush(), + + receive + {otel_metric, #metric{name=view, + data=#histogram{datapoints=DatapointsB}}} -> + AttributeBucketsB = + lists:sort([{Attributes, Buckets, Min, Max, Sum} || #histogram_datapoint{bucket_counts=Buckets, + attributes=Attributes, + min=Min, + max=Max, + sum=Sum} <- DatapointsB]), + ?assertEqual([], [{#{<<"a">> => <<"1">>}, [0,2,0], 15, 50, 65}, + {#{<<"a">> => <<"2">>}, [0,1,0], 26, 26, 26}] + -- AttributeBucketsB, AttributeBucketsB) + after + 1000 -> + ct:fail(histogram_receive_timeout) + end. \ No newline at end of file