Current section

Files

Jump to
Raw

src/otel_view.erl

%%%------------------------------------------------------------------------
%% Copyright 2022, 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
%% @end
%%%-------------------------------------------------------------------------
-module(otel_view).
-export([new/2,
new/3,
match_instrument_to_views/2]).
-include_lib("opentelemetry_api_experimental/include/otel_metrics.hrl").
-include_lib("opentelemetry_api/include/opentelemetry.hrl").
-include_lib("opentelemetry_experimental/include/otel_metrics.hrl").
-include("otel_view.hrl").
-type name() :: atom().
-type criteria() :: #{instrument_name => otel_instrument:name(),
instrument_kind => otel_instrument:kind(),
meter_name => unicode:unicode_binary() | undefined,
meter_version => unicode:unicode_binary() | undefined,
meter_schema_url => unicode:unicode_binary() | undefined}.
-type config() :: #{description => unicode:unicode_binary(),
attribute_keys => [atom()],
aggregation_module => module() | default,
aggregation_options => map()
%% exemplar_reservoir
}.
-type t() :: #view{}.
-export_type([t/0,
name/0,
criteria/0,
config/0]).
-include_lib("opentelemetry_api/include/gradualizer.hrl").
%% no name means Instrument name is used
%% must reject wildcard Criteria in this case
-dialyzer({nowarn_function,new/2}).
-spec new(criteria() | undefined, config()) -> t().
new(Criteria, Config) ->
CriteriaInstrumentName = view_name_from_criteria(Criteria),
Matchspec = criteria_to_instrument_matchspec(Criteria),
%% no name given so use the name of the instrument in the selection
%% if no instrument name is given then it'll stay `undefined'
#view{name=CriteriaInstrumentName,
instrument_matchspec=Matchspec,
description=maps:get(description, Config, undefined),
attribute_keys=maps:get(attribute_keys, Config, []),
aggregation_module=maps:get(aggregation_module, Config, undefined),
aggregation_options=maps:get(aggregation_options, Config, #{})}.
-dialyzer({nowarn_function,new/3}).
-spec new(name(), criteria() | undefined, config()) -> t().
new(undefined, Criteria, Config) ->
new(Criteria, Config);
new(Name, Criteria, Config) ->
View = new(Criteria, Config),
View#view{name=Name}.
-dialyzer({nowarn_function,match_instrument_to_views/2}).
-spec match_instrument_to_views(otel_instrument:t(), [otel_view:t()]) ->
[{otel_view:t() | undefined, #view_aggregation{}}].
match_instrument_to_views(Instrument=#instrument{name=InstrumentName,
meter=Meter,
description=Description}, Views) ->
IsMonotonic = otel_instrument:is_monotonic(Instrument),
Temporality = otel_aggregation:instrument_temporality(Instrument),
Scope = otel_meter:scope(Meter),
case lists:filtermap(fun(View=#view{name=ViewName,
description=ViewDescription,
aggregation_options=AggregationOptions,
instrument_matchspec=Matchspec}) ->
case ets:match_spec_run([Instrument], Matchspec) of
[] ->
false;
_ ->
{true, {View, #view_aggregation{name=value_or(ViewName,
InstrumentName),
scope=Scope,
instrument=Instrument,
temporality=Temporality,
is_monotonic=IsMonotonic,
aggregation_options=AggregationOptions,
description=value_or(ViewDescription,
Description)
}}}
end
end, Views) of
[] ->
[{undefined, #view_aggregation{name=InstrumentName,
scope=Scope,
instrument=Instrument,
temporality=Temporality,
is_monotonic=IsMonotonic,
aggregation_options=#{},
description=Description}}];
Aggs ->
Aggs
end.
%%
value_or(undefined, Other) ->
Other;
value_or(Value, _Other) ->
Value.
-dialyzer({nowarn_function,criteria_to_instrument_matchspec/1}).
-spec criteria_to_instrument_matchspec(map() | undefined) -> ets:compiled_match_spec().
criteria_to_instrument_matchspec(Criteria) when is_map(Criteria) ->
Instrument =
maps:fold(fun(instrument_name, InstrumentName, InstrumentAcc) ->
InstrumentAcc#instrument{name=InstrumentName};
(instrument_kind, Kind, InstrumentAcc) ->
InstrumentAcc#instrument{kind=Kind};
(meter_name, MeterName, InstrumentAcc) ->
Meter = maybe_init_meter(InstrumentAcc),
Meter1 = update_meter_name(MeterName, Meter),
InstrumentAcc#instrument{meter=Meter1};
(meter_version, MeterVersion, InstrumentAcc) ->
Meter = maybe_init_meter(InstrumentAcc),
Meter1 = update_meter_version(MeterVersion, Meter),
InstrumentAcc#instrument{meter=Meter1};
(meter_schema_url, SchemaUrl, InstrumentAcc) ->
Meter = maybe_init_meter(InstrumentAcc),
Meter1 = update_meter_schema_url(SchemaUrl, Meter),
InstrumentAcc#instrument{meter=Meter1}
%% eqwalizer:ignore building a matchspec and don't want '_' polluting the type
end, #instrument{_='_'}, Criteria),
ets:match_spec_compile([{Instrument, [], [true]}]);
criteria_to_instrument_matchspec(_) ->
%% eqwalizer:ignore building a matchspec and don't want '_' polluting the type
ets:match_spec_compile([{#instrument{_='_'}, [], [true]}]).
-dialyzer({nowarn_function,maybe_init_meter/1}).
maybe_init_meter(#instrument{meter='_'}) ->
{'_', #meter{instrumentation_scope=#instrumentation_scope{_='_'},
_='_'}}.
-dialyzer({nowarn_function,update_meter_name/2}).
update_meter_name(MeterName, {_, Meter=#meter{instrumentation_scope=Scope}}) ->
{'_', Meter#meter{instrumentation_scope=Scope#instrumentation_scope{name=MeterName}}}.
-dialyzer({nowarn_function,update_meter_version/2}).
update_meter_version(MeterVersion, {_, Meter=#meter{instrumentation_scope=Scope}}) ->
{'_', Meter#meter{instrumentation_scope=Scope#instrumentation_scope{version=MeterVersion}}}.
-dialyzer({nowarn_function,update_meter_schema_url/2}).
update_meter_schema_url(SchemaUrl, {_, Meter=#meter{instrumentation_scope=Scope}}) ->
{'_', Meter#meter{instrumentation_scope=Scope#instrumentation_scope{schema_url=SchemaUrl}}}.
view_name_from_criteria(Criteria) when is_map(Criteria) ->
maps:get(instrument_name, Criteria, undefined);
view_name_from_criteria(_) ->
undefined.