diff --git a/apps/opentelemetry/README.md b/apps/opentelemetry/README.md index 00151cdc..bed24b1e 100644 --- a/apps/opentelemetry/README.md +++ b/apps/opentelemetry/README.md @@ -199,21 +199,23 @@ in the Resource. The default detectors read resource attributes from the OS environment variable `OTEL_RESOURCE_ATTRIBUTES` and Application environment variable `resource`. -### Span Limits +### Limits The number of Attributes, Events and Links on a Span are limited, as well as the length of an Attribute's value. When the limit is reached any additional Attributes, Events or Links are dropped and Attribute values larger than the length limit are truncated. -| OS | Application | Default | Type | -|:---------------------------------------|:-----------------------------|:---------|:------------------------| -| OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT | attribute_count_limit | 128 | integer | -| OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT | attribute_value_length_limit | infinity | integer | infinity | -| OTEL_SPAN_EVENT_COUNT_LIMIT | event_count_limit | 128 | integer | -| OTEL_SPAN_LINK_COUNT_LIMIT | link_count_limit | 128 | integer | -| OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT | attribute_per_event_limit | 128 | integer | -| OTEL_LINK_ATTRIBUTE_COUNT_LIMIT | attribute_per_link_limit | 128 | integer +| OS | Application | Default | Type | +|:---------------------------------------|:----------------------------------|:---------|:------------------------| +| OTEL_ATTRIBUTE_COUNT_LIMIT | attribute_count_limit | 128 | integer | +| OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT | attribute_value_length_limit | infinity | integer | infinity | +| OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT | span_attribute_count_limit | 128 | integer | +| OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT | span_attribute_value_length_limit | infinity | integer | infinity | +| OTEL_SPAN_EVENT_COUNT_LIMIT | event_count_limit | 128 | integer | +| OTEL_SPAN_LINK_COUNT_LIMIT | link_count_limit | 128 | integer | +| OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT | attribute_per_event_limit | 128 | integer | +| OTEL_LINK_ATTRIBUTE_COUNT_LIMIT | attribute_per_link_limit | 128 | integer Read more in the specification about [Span limits](https://github.com/open-telemetry/opentelemetry-specification/blob/main/specification/trace/sdk.md#span-limits) diff --git a/apps/opentelemetry/include/otel_span.hrl b/apps/opentelemetry/include/otel_span.hrl index 20707c4a..43fa3b39 100644 --- a/apps/opentelemetry/include/otel_span.hrl +++ b/apps/opentelemetry/include/otel_span.hrl @@ -62,13 +62,15 @@ instrumentation_scope :: opentelemetry:instrumentation_scope() | undefined | '_' }). --record(span_limits, { - attribute_count_limit = 128 :: integer(), %% Maximum allowed attribute count per span; - attribute_value_length_limit = infinity :: integer() | infinity, %% Maximum allowed attribute value length - event_count_limit = 128 :: integer(), %% Maximum allowed span event count - link_count_limit = 128 :: integer(), %% Maximum allowed span link count - attribute_per_event_limit = 128 :: integer(), %% Maximum allowed attribute per span event count - attribute_per_link_limit = 128 :: integer() %% Maximum allowed attribute per span link count +-record(limits, { + attribute_count_limit = 128 :: integer(), %% Maximum allowed attribute count; + attribute_value_length_limit = infinity :: integer() | infinity, %% Maximum allowed attribute value length + span_attribute_count_limit = undefined :: integer() | undefined, %% Maximum allowed attribute count per span; + span_attribute_value_length_limit = undefined :: integer() | infinity | undefined, %% Maximum allowed attribute value length on span attributes + event_count_limit = 128 :: integer(), %% Maximum allowed span event count + link_count_limit = 128 :: integer(), %% Maximum allowed span link count + attribute_per_event_limit = 128 :: integer(), %% Maximum allowed attribute per span event count + attribute_per_link_limit = 128 :: integer() %% Maximum allowed attribute per span link count }). -record(link, { diff --git a/apps/opentelemetry/src/opentelemetry_app.erl b/apps/opentelemetry/src/opentelemetry_app.erl index 57891747..29c27679 100644 --- a/apps/opentelemetry/src/opentelemetry_app.erl +++ b/apps/opentelemetry/src/opentelemetry_app.erl @@ -40,7 +40,7 @@ start(_StartType, _StartArgs) -> SupResult; _ -> %% set global span limits record based on configuration - otel_span_limits:set(Config), + otel_limits:set(Config), Resource = otel_resource_detector:get_resource(), _ = otel_tracer_provider_sup:start(?GLOBAL_TRACER_PROVIDER_NAME, Resource, Config), diff --git a/apps/opentelemetry/src/otel_configuration.erl b/apps/opentelemetry/src/otel_configuration.erl index 2b7eb53d..96396720 100644 --- a/apps/opentelemetry/src/otel_configuration.erl +++ b/apps/opentelemetry/src/otel_configuration.erl @@ -59,6 +59,8 @@ storage_size => integer() | infinity}, attribute_count_limit := integer(), attribute_value_length_limit := integer() | infinity, + span_attribute_count_limit := integer(), + span_attribute_value_length_limit := integer() | infinity, event_count_limit := integer(), link_count_limit := integer(), attribute_per_event_limit := integer(), @@ -97,6 +99,8 @@ new() -> storage_size => infinity}, attribute_count_limit => 128, attribute_value_length_limit => infinity, + span_attribute_count_limit => undefined, + span_attribute_value_length_limit => undefined, event_count_limit => 128, link_count_limit => 128, attribute_per_event_limit => 128, @@ -108,15 +112,15 @@ merge_with_os(AppEnv) -> lists:foldl(fun(F, Acc) -> F(AppEnv, Acc) - end, ConfigMap, [fun span_limits/2, + end, ConfigMap, [fun limits/2, fun general/2, fun sampler/2, fun processors/2, fun sweeper/2]). --spec span_limits(list(), t()) -> t(). -span_limits(AppEnv, ConfigMap) -> - merge_list_with_environment(config_mappings(span_limits), AppEnv, ConfigMap). +-spec limits(list(), t()) -> t(). +limits(AppEnv, ConfigMap) -> + merge_list_with_environment(config_mappings(limits), AppEnv, ConfigMap). -spec general(list(), t()) -> t(). general(AppEnv, ConfigMap) -> @@ -322,15 +326,15 @@ config_mappings(general_sdk) -> {"OTEL_SSP_EXPORT_TIMEOUT_MILLIS", ssp_exporting_timeout_ms, integer} ]; -config_mappings(span_limits) -> - [{"OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT", attribute_count_limit, integer}, - {"OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT", attribute_value_length_limit, integer_infinity}, +config_mappings(limits) -> + [{"OTEL_ATTRIBUTE_COUNT_LIMIT", attribute_count_limit, integer}, + {"OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT", attribute_value_length_limit, integer_infinity}, + {"OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT", span_attribute_count_limit, integer}, + {"OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT", span_attribute_value_length_limit, integer_infinity}, {"OTEL_SPAN_EVENT_COUNT_LIMIT", event_count_limit, integer}, {"OTEL_SPAN_LINK_COUNT_LIMIT", link_count_limit, integer}, {"OTEL_EVENT_ATTRIBUTE_COUNT_LIMIT", attribute_per_event_limit, integer}, - {"OTEL_LINK_ATTRIBUTE_COUNT_LIMIT", attribute_per_link_limit, integer}%% , - %% {"OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT", attribute_value_length_limit, integer}, - %% {"OTEL_ATTRIBUTE_COUNT_LIMIT", attribute_per_link_limit, integer} + {"OTEL_LINK_ATTRIBUTE_COUNT_LIMIT", attribute_per_link_limit, integer} ]; config_mappings(sweeper) -> [{"OTEL_SPAN_SWEEPER_INTERVAL", interval, integer_infinity}, diff --git a/apps/opentelemetry/src/otel_span_limits.erl b/apps/opentelemetry/src/otel_limits.erl similarity index 55% rename from apps/opentelemetry/src/otel_span_limits.erl rename to apps/opentelemetry/src/otel_limits.erl index 1fcd895b..50043d3b 100644 --- a/apps/opentelemetry/src/otel_span_limits.erl +++ b/apps/opentelemetry/src/otel_limits.erl @@ -13,15 +13,17 @@ %% limitations under the License. %% %% @doc Module for setting the global limits for the number of attributes, -%% events and links on a Span. +%% events and links. %% @end %%%------------------------------------------------------------------------- --module(otel_span_limits). +-module(otel_limits). -export([get/0, set/1, attribute_count_limit/0, attribute_value_length_limit/0, + span_attribute_count_limit/0, + span_attribute_value_length_limit/0, event_count_limit/0, link_count_limit/0, attribute_per_event_limit/0, @@ -29,26 +31,30 @@ -include("otel_span.hrl"). --define(SPAN_LIMITS_KEY, {?MODULE, span_limits}). +-define(LIMITS_KEY, {?MODULE, limits}). --spec get() -> #span_limits{}. +-spec get() -> #limits{}. get() -> - persistent_term:get(?SPAN_LIMITS_KEY). + persistent_term:get(?LIMITS_KEY). -spec set(otel_configuration:t()) -> ok. set(#{attribute_count_limit := AttributeCountLimit, attribute_value_length_limit := AttributeValueLengthLimit, + span_attribute_count_limit := SpanAttributeCountLimit, + span_attribute_value_length_limit := SpanAttributeValueLengthLimit, event_count_limit := EventCountLimit, link_count_limit := LinkCountLimit, attribute_per_event_limit := AttributePerEventLimit, attribute_per_link_limit := AttributePerLinkLimit}) -> - SpanLimits = #span_limits{attribute_count_limit=AttributeCountLimit, + Limits = #limits{attribute_count_limit=AttributeCountLimit, attribute_value_length_limit=AttributeValueLengthLimit, + span_attribute_count_limit=SpanAttributeCountLimit, + span_attribute_value_length_limit=SpanAttributeValueLengthLimit, event_count_limit=EventCountLimit, link_count_limit=LinkCountLimit, attribute_per_event_limit=AttributePerEventLimit, attribute_per_link_limit=AttributePerLinkLimit}, - persistent_term:put(?SPAN_LIMITS_KEY, SpanLimits). + persistent_term:put(?LIMITS_KEY, Limits). attribute_count_limit() -> get_limit(attribute_count_limit, ?MODULE:get()). @@ -56,6 +62,12 @@ attribute_count_limit() -> attribute_value_length_limit() -> get_limit(attribute_value_length_limit, ?MODULE:get()). +span_attribute_count_limit() -> + get_limit(span_attribute_count_limit, ?MODULE:get()). + +span_attribute_value_length_limit() -> + get_limit(span_attribute_value_length_limit, ?MODULE:get()). + event_count_limit() -> get_limit(event_count_limit, ?MODULE:get()). @@ -68,15 +80,23 @@ attribute_per_event_limit() -> attribute_per_link_limit() -> get_limit(attribute_per_link_limit, ?MODULE:get()). -get_limit(attribute_count_limit, #span_limits{attribute_count_limit=AttributeCountLimit}) -> +get_limit(attribute_count_limit, #limits{attribute_count_limit=AttributeCountLimit}) -> + AttributeCountLimit; +get_limit(attribute_value_length_limit, #limits{attribute_value_length_limit=AttributeValueLengthLimit}) -> + AttributeValueLengthLimit; +get_limit(span_attribute_count_limit, #limits{span_attribute_count_limit=undefined, attribute_count_limit=AttributeCountLimit}) -> AttributeCountLimit; -get_limit(attribute_value_length_limit, #span_limits{attribute_value_length_limit=AttributeValueLengthLimit}) -> +get_limit(span_attribute_count_limit, #limits{span_attribute_count_limit=SpanAttributeCountLimit}) -> + SpanAttributeCountLimit; +get_limit(span_attribute_value_length_limit, #limits{span_attribute_value_length_limit=undefined, attribute_value_length_limit=AttributeValueLengthLimit}) -> AttributeValueLengthLimit; -get_limit(event_count_limit, #span_limits{event_count_limit=EventCountLimit}) -> +get_limit(span_attribute_value_length_limit, #limits{span_attribute_value_length_limit=SpanAttributeValueLengthLimit}) -> + SpanAttributeValueLengthLimit; +get_limit(event_count_limit, #limits{event_count_limit=EventCountLimit}) -> EventCountLimit; -get_limit(link_count_limit, #span_limits{link_count_limit=LinkCountLimit}) -> +get_limit(link_count_limit, #limits{link_count_limit=LinkCountLimit}) -> LinkCountLimit; -get_limit(attribute_per_event_limit, #span_limits{attribute_per_event_limit=AttributePerEventLimit}) -> +get_limit(attribute_per_event_limit, #limits{attribute_per_event_limit=AttributePerEventLimit}) -> AttributePerEventLimit; -get_limit(attribute_per_link_limit, #span_limits{attribute_per_link_limit=AttributePerLinkLimit}) -> +get_limit(attribute_per_link_limit, #limits{attribute_per_link_limit=AttributePerLinkLimit}) -> AttributePerLinkLimit. diff --git a/apps/opentelemetry/src/otel_span_utils.erl b/apps/opentelemetry/src/otel_span_utils.erl index 0c9b16fd..f0a1f011 100644 --- a/apps/opentelemetry/src/otel_span_utils.erl +++ b/apps/opentelemetry/src/otel_span_utils.erl @@ -29,12 +29,12 @@ -spec start_span(otel_ctx:t(), opentelemetry:span_name(), otel_sampler:t(), otel_id_generator:t(), otel_span:start_opts()) -> {opentelemetry:span_ctx(), opentelemetry:span() | undefined}. start_span(Ctx, Name, Sampler, IdGenerator, Opts) -> - SpanAttributeCountLimit = otel_span_limits:attribute_count_limit(), - SpanAttributeValueLengthLimit= otel_span_limits:attribute_value_length_limit(), - EventCountLimit = otel_span_limits:event_count_limit(), - LinkCountLimit = otel_span_limits:link_count_limit(), - AttributePerEventLimit = otel_span_limits:attribute_per_event_limit(), - AttributePerLinkLimit = otel_span_limits:attribute_per_link_limit(), + SpanAttributeCountLimit = otel_limits:span_attribute_count_limit(), + SpanAttributeValueLengthLimit = otel_limits:span_attribute_value_length_limit(), + EventCountLimit = otel_limits:event_count_limit(), + LinkCountLimit = otel_limits:link_count_limit(), + AttributePerEventLimit = otel_limits:attribute_per_event_limit(), + AttributePerLinkLimit = otel_limits:attribute_per_link_limit(), Attributes = otel_attributes:new(maps:get(attributes, Opts, #{}), diff --git a/apps/opentelemetry/test/opentelemetry_SUITE.erl b/apps/opentelemetry/test/opentelemetry_SUITE.erl index c41db277..d45b7180 100644 --- a/apps/opentelemetry/test/opentelemetry_SUITE.erl +++ b/apps/opentelemetry/test/opentelemetry_SUITE.erl @@ -115,7 +115,9 @@ init_per_testcase(dropped_attributes, Config) -> Config1; init_per_testcase(too_many_attributes, Config) -> Config1 = set_batch_tab_processor(Config), - application:set_env(opentelemetry, attribute_count_limit, 2), + application:set_env(opentelemetry, attribute_count_limit, 5), + %% this will override the previous one + application:set_env(opentelemetry, span_attribute_count_limit, 2), {ok, _} = application:ensure_all_started(opentelemetry), Config1; init_per_testcase(tracer_instrumentation_scope, Config) -> diff --git a/apps/opentelemetry/test/otel_configuration_SUITE.erl b/apps/opentelemetry/test/otel_configuration_SUITE.erl index 4f292c12..19ae6153 100644 --- a/apps/opentelemetry/test/otel_configuration_SUITE.erl +++ b/apps/opentelemetry/test/otel_configuration_SUITE.erl @@ -20,7 +20,7 @@ all() -> sampler_trace_id, sampler_trace_id_default, sampler_parent_based_one, log_level, propagators, propagators_b3, propagators_b3multi, otlp_exporter, jaeger_exporter, zipkin_exporter, none_exporter, app_env_exporter, - otlp_metrics_exporter, none_metrics_exporter, span_limits, bad_span_limits, + otlp_metrics_exporter, none_metrics_exporter, limits, bad_limits, bad_app_config, deny_list, resource_detectors, span_processors]. init_per_testcase(empty_os_environment, Config) -> @@ -139,8 +139,10 @@ init_per_testcase(resource_detectors, Config) -> setup_env(Vars), [{os_vars, Vars} | Config]; -init_per_testcase(span_limits, Config) -> - Vars = [{"OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT", "111"}, +init_per_testcase(limits, Config) -> + Vars = [{"OTEL_ATTRIBUTE_COUNT_LIMIT", "123"}, + {"OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT", "4"}, + {"OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT", "111"}, {"OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT", "009"}, {"OTEL_SPAN_EVENT_COUNT_LIMIT", "200"}, {"OTEL_SPAN_LINK_COUNT_LIMIT", "1101"}, @@ -149,22 +151,28 @@ init_per_testcase(span_limits, Config) -> setup_env(Vars), - ExpectedOpts = #{attribute_count_limit => 111, - attribute_value_length_limit => 9, + ExpectedOpts = #{attribute_count_limit => 123, + attribute_value_length_limit => 4, + span_attribute_count_limit => 111, + span_attribute_value_length_limit => 9, event_count_limit => 200, link_count_limit => 1101, attribute_per_event_limit => 400, attribute_per_link_limit => 500}, - ExpectedRecord = #span_limits{attribute_count_limit=111, - attribute_value_length_limit=9, - event_count_limit=200, - link_count_limit=1101, - attribute_per_event_limit=400, - attribute_per_link_limit=500}, + ExpectedRecord = #limits{attribute_count_limit=123, + attribute_value_length_limit=4, + span_attribute_count_limit=111, + span_attribute_value_length_limit=9, + event_count_limit=200, + link_count_limit=1101, + attribute_per_event_limit=400, + attribute_per_link_limit=500}, [{expected_opts, ExpectedOpts}, {expected_record, ExpectedRecord}, {os_vars, Vars} | Config]; -init_per_testcase(bad_span_limits, Config) -> - Vars = [{"OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT", "aaa"}, +init_per_testcase(bad_limits, Config) -> + Vars = [{"OTEL_ATTRIBUTE_COUNT_LIMIT", "foo"}, + {"OTEL_ATTRIBUTE_VALUE_LENGTH_LIMIT", "4a"}, + {"OTEL_SPAN_ATTRIBUTE_COUNT_LIMIT", "aaa"}, {"OTEL_SPAN_ATTRIBUTE_VALUE_LENGTH_LIMIT", "bbb"}, {"OTEL_SPAN_EVENT_COUNT_LIMIT", "1d4"}, {"OTEL_SPAN_LINK_COUNT_LIMIT", "eee"}, @@ -174,12 +182,14 @@ init_per_testcase(bad_span_limits, Config) -> setup_env(Vars), ExpectedOpts = #{}, - ExpectedRecord = #span_limits{attribute_count_limit=128, - attribute_value_length_limit=infinity, - event_count_limit=128, - link_count_limit=128, - attribute_per_event_limit=128, - attribute_per_link_limit=128}, + ExpectedRecord = #limits{attribute_count_limit=128, + attribute_value_length_limit=infinity, + span_attribute_count_limit=undefined, + span_attribute_value_length_limit=undefined, + event_count_limit=128, + link_count_limit=128, + attribute_per_event_limit=128, + attribute_per_link_limit=128}, [{expected_opts, ExpectedOpts}, {expected_record, ExpectedRecord}, {os_vars, Vars} | Config]; init_per_testcase(bad_app_config, Config) -> @@ -344,11 +354,11 @@ resource_detectors(_Config) -> ok. -span_limits(Config) -> - compare_span_limits(Config). +limits(Config) -> + compare_limits(Config). -bad_span_limits(Config) -> - compare_span_limits(Config). +bad_limits(Config) -> + compare_limits(Config). bad_app_config(_Config) -> ?assertMatch(#{attribute_value_length_limit := infinity}, @@ -356,16 +366,16 @@ bad_app_config(_Config) -> ok. -compare_span_limits(Config) -> +compare_limits(Config) -> ExpectedRecord = ?config(expected_record, Config), ExpectedOpts = maps:to_list(?config(expected_opts, Config)), Opts = maps:to_list(otel_configuration:merge_with_os([])), ?assertIsSubset(ExpectedOpts, Opts), - otel_span_limits:set(maps:from_list(Opts)), + otel_limits:set(maps:from_list(Opts)), - SpanLimits = otel_span_limits:get(), + SpanLimits = otel_limits:get(), %% verifies the defaults because the base record has the defaults set as well ?assertEqual(ExpectedRecord, SpanLimits), diff --git a/apps/opentelemetry_api/include/opentelemetry.hrl b/apps/opentelemetry_api/include/opentelemetry.hrl index 79d036d2..0cea0d8e 100644 --- a/apps/opentelemetry_api/include/opentelemetry.hrl +++ b/apps/opentelemetry_api/include/opentelemetry.hrl @@ -78,3 +78,10 @@ %% developer-facing error message message = <<"">> :: unicode:unicode_binary() }). + +-record(attributes, { + count_limit :: integer(), + value_length_limit :: integer() | infinity, + dropped :: integer(), + map :: map() + }). \ No newline at end of file diff --git a/apps/opentelemetry_api/src/otel_attributes.erl b/apps/opentelemetry_api/src/otel_attributes.erl index c5dd21d2..3f0ac291 100644 --- a/apps/opentelemetry_api/src/otel_attributes.erl +++ b/apps/opentelemetry_api/src/otel_attributes.erl @@ -17,11 +17,14 @@ %%%------------------------------------------------------------------------- -module(otel_attributes). +-include("opentelemetry.hrl"). + -export([new/3, set/2, set/3, dropped/1, map/1, + filter/2, is_valid_attribute/2, process_attributes/1]). @@ -31,12 +34,6 @@ is_number(Value) orelse is_binary(Value) orelse is_list(Value))). --record(attributes, { - count_limit :: integer(), - value_length_limit :: integer() | infinity, - dropped :: integer(), - map :: map() - }). -type t() :: #attributes{}. @@ -73,6 +70,10 @@ map(#attributes{map=Map}) -> %% +filter(Attributes=#attributes{map=Map}, KeysToKeep) -> + KeptAttributes = maps:with(KeysToKeep, Map), + Attributes#attributes{map=KeptAttributes}. + update_attributes(List, Attributes) -> maps:fold(fun update_attribute/3, Attributes, List). diff --git a/apps/opentelemetry_api_experimental/src/otel_counter.erl b/apps/opentelemetry_api_experimental/src/otel_counter.erl index 36dd2257..ad78b57c 100644 --- a/apps/opentelemetry_api_experimental/src/otel_counter.erl +++ b/apps/opentelemetry_api_experimental/src/otel_counter.erl @@ -32,10 +32,10 @@ create(Meter, Name, Opts) -> otel_meter:create_counter(Meter, Name, Opts). --spec add(otel_meter:t(), otel_instrument:name(), pos_integer() |float(), opentelemetry:attributes_map()) -> ok. +-spec add(otel_meter:t(), otel_instrument:name(), pos_integer() | float(), opentelemetry:attributes_map() | otel_attributes:t()) -> ok. add(Meter, Name, Number, Attributes) -> otel_meter:record(Meter, Name, Number, Attributes). --spec add(otel_instrument:t(), pos_integer() |float(), opentelemetry:attributes_map()) -> ok. +-spec add(otel_instrument:t(), pos_integer() | float(), opentelemetry:attributes_map() | otel_attributes:t()) -> ok. add(Instrument=#instrument{module=Module}, Number, Attributes) -> Module:record(Instrument, Number, Attributes). diff --git a/apps/opentelemetry_api_experimental/src/otel_histogram.erl b/apps/opentelemetry_api_experimental/src/otel_histogram.erl index e16eef14..fff86512 100644 --- a/apps/opentelemetry_api_experimental/src/otel_histogram.erl +++ b/apps/opentelemetry_api_experimental/src/otel_histogram.erl @@ -33,10 +33,10 @@ create(Meter, Name, Opts) -> otel_meter:create_histogram(Meter, Name, Opts). --spec record(otel_meter:t(), otel_instrument:name(), pos_integer() | float(), opentelemetry:attributes_map()) -> ok. +-spec record(otel_meter:t(), otel_instrument:name(), pos_integer() | float(), opentelemetry:attributes_map() | otel_attributes:t()) -> ok. record(Meter, Name, Number, Attributes) -> otel_meter:record(Meter, Name, Number, Attributes). --spec record(otel_instrument:t(), pos_integer() | float(), opentelemetry:attributes_map()) -> ok. +-spec record(otel_instrument:t(), pos_integer() | float(), opentelemetry:attributes_map() | otel_attributes:t()) -> ok. record(Instrument=#instrument{module=Module}, Number, Attributes) -> Module:record(Instrument, Number, Attributes). diff --git a/apps/opentelemetry_api_experimental/src/otel_instrument.erl b/apps/opentelemetry_api_experimental/src/otel_instrument.erl index 6feac48a..87018b4d 100644 --- a/apps/opentelemetry_api_experimental/src/otel_instrument.erl +++ b/apps/opentelemetry_api_experimental/src/otel_instrument.erl @@ -29,7 +29,7 @@ -type kind() :: ?KIND_COUNTER | ?KIND_OBSERVABLE_COUNTER | ?KIND_HISTOGRAM | ?KIND_OBSERVABLE_GAUGE | ?KIND_UPDOWN_COUNTER | ?KIND_OBSERVABLE_UPDOWNCOUNTER. -type unit() :: atom(). %% latin1, maximum length of 63 characters --type observation() :: {number(), opentelemetry:attributes_map()}. +-type observation() :: {number(), opentelemetry:attributes_map() | otel_attributes:t()}. -type named_observations() :: {name(), [observation()]}. -type callback_args() :: term(). -type callback_result() :: [observation()] | diff --git a/apps/opentelemetry_api_experimental/src/otel_observable_counter.erl b/apps/opentelemetry_api_experimental/src/otel_observable_counter.erl index a4bbc67e..5dabd996 100644 --- a/apps/opentelemetry_api_experimental/src/otel_observable_counter.erl +++ b/apps/opentelemetry_api_experimental/src/otel_observable_counter.erl @@ -20,8 +20,10 @@ -export([create/3, create/5]). +-spec create(otel_meter:t(), otel_instrument:name(), otel_instrument:opts()) -> otel_instrument:t(). create(Meter, Name, Opts) -> otel_meter:create_observable_counter(Meter, Name, Opts). +-spec create(otel_meter:t(), otel_instrument:name(), otel_instrument:callback(), otel_instrument:callback_args(), otel_instrument:opts()) -> otel_instrument:t(). create(Meter, Name, Callback, CallbackArgs, Opts) -> otel_meter:create_observable_counter(Meter, Name, Callback, CallbackArgs, Opts). diff --git a/apps/opentelemetry_api_experimental/src/otel_observable_gauge.erl b/apps/opentelemetry_api_experimental/src/otel_observable_gauge.erl index 46f8c9b6..6816d352 100644 --- a/apps/opentelemetry_api_experimental/src/otel_observable_gauge.erl +++ b/apps/opentelemetry_api_experimental/src/otel_observable_gauge.erl @@ -27,8 +27,10 @@ -export([create/3, create/5]). +-spec create(otel_meter:t(), otel_instrument:name(), otel_instrument:opts()) -> otel_instrument:t(). create(Meter, Name, Opts) -> otel_meter:create_observable_gauge(Meter, Name, Opts). +-spec create(otel_meter:t(), otel_instrument:name(), otel_instrument:callback(), otel_instrument:callback_args(), otel_instrument:opts()) -> otel_instrument:t(). create(Meter, Name, Callback, CallbackArgs, Opts) -> otel_meter:create_observable_gauge(Meter, Name, Callback, CallbackArgs, Opts). diff --git a/apps/opentelemetry_api_experimental/src/otel_observable_updowncounter.erl b/apps/opentelemetry_api_experimental/src/otel_observable_updowncounter.erl index 565c821f..7315c851 100644 --- a/apps/opentelemetry_api_experimental/src/otel_observable_updowncounter.erl +++ b/apps/opentelemetry_api_experimental/src/otel_observable_updowncounter.erl @@ -25,8 +25,10 @@ -export([create/3, create/5]). +-spec create(otel_meter:t(), otel_instrument:name(), otel_instrument:opts()) -> otel_instrument:t(). create(Meter, Name, Opts) -> otel_meter:create_observable_updowncounter(Meter, Name, Opts). +-spec create(otel_meter:t(), otel_instrument:name(), otel_instrument:callback(), otel_instrument:callback_args(), otel_instrument:opts()) -> otel_instrument:t(). create(Meter, Name, Callback, CallbackArgs, Opts) -> otel_meter:create_observable_updowncounter(Meter, 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 64c73d0a..04b1b28d 100644 --- a/apps/opentelemetry_api_experimental/src/otel_updown_counter.erl +++ b/apps/opentelemetry_api_experimental/src/otel_updown_counter.erl @@ -32,10 +32,10 @@ create(Meter, Name, Opts) -> otel_meter:create_updown_counter(Meter, Name, Opts). --spec add(otel_meter:t(), otel_instrument:name(), number(), opentelemetry:attributes_map()) -> ok. +-spec add(otel_meter:t(), otel_instrument:name(), number(), opentelemetry:attributes_map() | otel_attributes:t()) -> ok. add(Meter, Name, Number, Attributes) -> otel_meter:record(Meter, Name, Number, Attributes). --spec add(otel_instrument:t(), number(), opentelemetry:attributes_map()) -> ok. +-spec add(otel_instrument:t(), number(), opentelemetry:attributes_map() | otel_attributes:t()) -> ok. add(Instrument=#instrument{module=Module}, Number, Attributes) -> Module:record(Instrument, Number, Attributes). diff --git a/apps/opentelemetry_experimental/include/otel_metrics.hrl b/apps/opentelemetry_experimental/include/otel_metrics.hrl index 96ad924c..401cca66 100644 --- a/apps/opentelemetry_experimental/include/otel_metrics.hrl +++ b/apps/opentelemetry_experimental/include/otel_metrics.hrl @@ -2,7 +2,7 @@ -define(DEFAULT_METER_PROVIDER, otel_meter_provider_default). --type key_inner_match_spec() :: {match_spec(atom()), match_spec(opentelemetry:attributes_map()), match_spec(reference())}. +-type key_inner_match_spec() :: {match_spec(atom()), match_spec(otel_attributes:t()), match_spec(reference())}. -type key_match_spec() :: match_spec(otel_aggregation:key()) | key_inner_match_spec() | {key_inner_match_spec()}. -record(meter, @@ -15,13 +15,6 @@ metrics_tab :: ets:table() | '_' }). --record(measurement, - { - instrument :: otel_instrument:t(), - value :: number(), - attributes :: opentelemetry:attributes_map() - }). - -record(drop_aggregation, {}). -record(sum_aggregation, @@ -74,7 +67,7 @@ -record(datapoint, { - attributes :: opentelemetry:attributes_map(), + attributes :: otel_attributes:t(), start_time_unix_nano :: integer(), time_unix_nano :: integer(), value :: number(), @@ -96,7 +89,7 @@ -record(histogram_datapoint, { - attributes :: opentelemetry:attributes_map(), + attributes :: otel_attributes:t(), start_time_unix_nano :: match_spec(integer()) | {const, eqwalizer:dynamic()} | undefined, time_unix_nano :: integer(), count :: number(), diff --git a/apps/opentelemetry_experimental/src/otel_aggregation.erl b/apps/opentelemetry_experimental/src/otel_aggregation.erl index 451e7c9f..909f509f 100644 --- a/apps/opentelemetry_experimental/src/otel_aggregation.erl +++ b/apps/opentelemetry_experimental/src/otel_aggregation.erl @@ -11,7 +11,7 @@ -type t() :: otel_aggregation_drop:t() | otel_aggregation_sum:t() | otel_aggregation_last_value:t() | otel_aggregation_histogram_explicit:t(). --type key() :: {atom(), opentelemetry:attributes_map(), reference()}. +-type key() :: {atom(), otel_attributes:t(), reference()}. -type options() :: map(). @@ -23,14 +23,14 @@ %% the aggregation module in the metrics table. -callback init(ViewAggregation, Attributes) -> Aggregation when ViewAggregation :: #view_aggregation{}, - Attributes :: opentelemetry:attributes_map(), + Attributes :: otel_attributes:t(), Aggregation :: t(). -callback aggregate(Table, ViewAggregation, Value, Attributes) -> boolean() when Table :: ets:table(), ViewAggregation :: #view_aggregation{}, Value :: number(), - Attributes :: opentelemetry:attributes_map(). + Attributes :: otel_attributes:t(). -callback checkpoint(Table, ViewAggregation, CollectionStartTime) -> ok when Table :: ets:table(), @@ -60,7 +60,7 @@ maybe_init_aggregate(MetricsTab, ViewAggregation=#view_aggregation{aggregation_m filter_attributes(undefined, Attributes) -> Attributes; filter_attributes(Keys, Attributes) -> - maps:with(Keys, Attributes). + otel_attributes:filter(Attributes, Keys). -spec default_mapping() -> #{otel_instrument:kind() => module()}. default_mapping() -> diff --git a/apps/opentelemetry_experimental/src/otel_meter_server.erl b/apps/opentelemetry_experimental/src/otel_meter_server.erl index 55e2f762..544c480d 100644 --- a/apps/opentelemetry_experimental/src/otel_meter_server.erl +++ b/apps/opentelemetry_experimental/src/otel_meter_server.erl @@ -145,14 +145,12 @@ add_view(Provider, Name, Criteria, Config) -> gen_server:call(Provider, {add_view, Name, Criteria, Config}). -spec record(ets:table(), ets:table(), otel_instrument:t(), number(), opentelemetry:attributes_map()) -> ok. -record(ViewAggregationsTab, MetricsTab, Instrument, Number, Attributes) -> - handle_measurement(#measurement{instrument=Instrument, - value=Number, - attributes=Attributes}, ViewAggregationsTab, MetricsTab). +record(ViewAggregationsTab, MetricsTab, #instrument{meter=Meter, name=Name}, Number, Attributes) -> + handle_measurement(Meter, Name, Number, Attributes, ViewAggregationsTab, MetricsTab). -spec record(otel_meter:t(), ets:table(), ets:table(), otel_instrument:t() | otel_instrument:name(), number(), opentelemetry:attributes_map()) -> ok. -record(Meter, ViewAggregationTab, MetricsTab, Name, Number, Attributes) -> - handle_measurement(Meter, Name, Number, Attributes, ViewAggregationTab, MetricsTab). +record(Meter, ViewAggregationsTab, MetricsTab, Name, Number, Attributes) -> + handle_measurement(Meter, Name, Number, Attributes, ViewAggregationsTab, MetricsTab). -spec force_flush() -> ok. force_flush() -> @@ -361,17 +359,15 @@ metric_reader(ReaderId, ReaderPid, DefaultAggregationMapping, Temporality) -> %% for each ViewAggregation a Measurement updates a Metric (`#metric') %% active metrics are indexed by the ViewAggregation name + the Measurement's Attributes -handle_measurement(#measurement{instrument=#instrument{meter=Meter, - name=Name}, - value=Value, - attributes=Attributes}, - ViewAggregationsTab, MetricsTab) -> +handle_measurement(Meter, Name, Number, AttributesRecord, ViewAggregationsTab, MetricsTab) when is_record(AttributesRecord, attributes) -> Matches = ets:match(ViewAggregationsTab, {{Meter, Name}, '$1'}), - update_aggregations(Value, Attributes, Matches, MetricsTab). + update_aggregations(Number, AttributesRecord, Matches, MetricsTab); handle_measurement(Meter, Name, Number, Attributes, ViewAggregationsTab, MetricsTab) -> - Matches = ets:match(ViewAggregationsTab, {{Meter, Name}, '$1'}), - update_aggregations(Number, Attributes, Matches, MetricsTab). + AttributeCountLimit = otel_limits:attribute_count_limit(), + AttributeValueLengthLimit = otel_limits:attribute_value_length_limit(), + AttributesRecord = otel_attributes:new(Attributes, AttributeCountLimit, AttributeValueLengthLimit), + handle_measurement(Meter, Name, Number, AttributesRecord, ViewAggregationsTab, MetricsTab). update_aggregations(Value, Attributes, ViewAggregations, MetricsTab) -> lists:foreach(fun([ViewAggregation=#view_aggregation{}]) -> diff --git a/apps/opentelemetry_experimental/src/otel_metric_exporter_console.erl b/apps/opentelemetry_experimental/src/otel_metric_exporter_console.erl index 08979745..98356c32 100644 --- a/apps/opentelemetry_experimental/src/otel_metric_exporter_console.erl +++ b/apps/opentelemetry_experimental/src/otel_metric_exporter_console.erl @@ -61,7 +61,7 @@ print_datapoint(Name, #datapoint{ exemplars=_, flags=_ }) -> - AttributesString = attributes_string(Attributes), + AttributesString = attributes_string(otel_attributes:map(Attributes)), io:format("~s{~s} ~p~n", [Name, AttributesString, Value]). print_histogram_datapoint(Name, #histogram_datapoint{ @@ -77,7 +77,7 @@ print_histogram_datapoint(Name, #histogram_datapoint{ min=_Min, max=_Max }) -> - AttributesString = attributes_string(Attributes), + AttributesString = attributes_string(otel_attributes:map(Attributes)), io:format("~s{~s} ~p~n", [Name, AttributesString, Buckets]). %% need to handle non-string values diff --git a/apps/opentelemetry_experimental/src/otel_observables.erl b/apps/opentelemetry_experimental/src/otel_observables.erl index 63ee93b0..3114a9f9 100644 --- a/apps/opentelemetry_experimental/src/otel_observables.erl +++ b/apps/opentelemetry_experimental/src/otel_observables.erl @@ -21,6 +21,7 @@ -include_lib("kernel/include/logger.hrl"). -include_lib("opentelemetry_api_experimental/include/otel_metrics.hrl"). +-include_lib("opentelemetry_api/include/opentelemetry.hrl"). -include("otel_metrics.hrl"). -include("otel_view.hrl"). @@ -92,10 +93,13 @@ handle_instruments_observations(Results, _Instruments, _ViewAggregationTab, _Met %% update aggregation for each observation handle_observations(_MetricsTab, _ViewAggregation, []) -> ok; -handle_observations(MetricsTab, ViewAggregation, [{Number, Attributes} | Rest]) - when is_number(Number), - is_map(Attributes) -> - _ = otel_aggregation:maybe_init_aggregate(MetricsTab, ViewAggregation, Number, Attributes), +handle_observations(MetricsTab, ViewAggregation, [{Number, Attributes} | Rest]) when is_number(Number), is_map(Attributes) -> + AttributeCountLimit = otel_limits:attribute_count_limit(), + AttributeValueLengthLimit = otel_limits:attribute_value_length_limit(), + AttributesRecord = otel_attributes:new(Attributes, AttributeCountLimit, AttributeValueLengthLimit), + handle_observations(MetricsTab, ViewAggregation, [{Number, AttributesRecord} | Rest]); +handle_observations(MetricsTab, ViewAggregation, [{Number, AttributesRecord} | Rest]) when is_number(Number), is_record(AttributesRecord, attributes) -> + _ = otel_aggregation:maybe_init_aggregate(MetricsTab, ViewAggregation, Number, AttributesRecord), handle_observations(MetricsTab, ViewAggregation, Rest); handle_observations(MetricsTab, ViewAggregation, [Result | Rest]) -> ?LOG_DEBUG("Each metric callback result must be of type {number(), map()} but got ~p", [Result]), diff --git a/apps/opentelemetry_experimental/test/otel_metrics_SUITE.erl b/apps/opentelemetry_experimental/test/otel_metrics_SUITE.erl index afd997e1..9b12bcf8 100644 --- a/apps/opentelemetry_experimental/test/otel_metrics_SUITE.erl +++ b/apps/opentelemetry_experimental/test/otel_metrics_SUITE.erl @@ -24,7 +24,7 @@ ?assertEqual(Description, MetricDescription), SortedDatapoints = - lists:sort([{MetricValue, MetricAttributes} || + lists:sort([{MetricValue, otel_attributes:map(MetricAttributes)} || #datapoint{value=MetricValue, attributes=MetricAttributes, start_time_unix_nano=StartTimeUnixNano, @@ -49,7 +49,7 @@ ?assertEqual(Description, MetricDescription), SortedDatapoints = - lists:sort([{MetricValue, MetricAttributes} || + lists:sort([{MetricValue, otel_attributes:map(MetricAttributes)} || #datapoint{value=MetricValue, attributes=MetricAttributes, start_time_unix_nano=StartTimeUnixNano, @@ -83,7 +83,8 @@ 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, histogram_aggregation_options, advisory_params + bad_observable_return, default_resource, histogram_aggregation_options, advisory_params, + too_many_attributes ]. init_per_suite(Config) -> @@ -174,6 +175,12 @@ init_per_testcase(delta_observable_counter, Config) -> {ok, _} = application:ensure_all_started(opentelemetry_experimental), Config; +init_per_testcase(too_many_attributes, Config) -> + application:stop(opentelemetry), + application:unload(opentelemetry), + application:load(opentelemetry), + application:set_env(opentelemetry, attribute_count_limit, 2), + init_per_testcase(undefined, Config); init_per_testcase(_, Config) -> application:load(opentelemetry_experimental), ok = application:set_env(opentelemetry_experimental, readers, [#{module => otel_metric_reader, @@ -185,6 +192,10 @@ init_per_testcase(_, Config) -> Config. +end_per_testcase(too_many_attributes, Config) -> + ok = application:stop(opentelemetry), + application:unload(opentelemetry), + end_per_testcase(undefined, Config); end_per_testcase(_, _Config) -> ok = application:stop(opentelemetry_experimental), application:unload(opentelemetry_experimental), @@ -320,7 +331,7 @@ float_histogram(_Config) -> {otel_metric, #metric{name=f_histogram, data=#histogram{datapoints=Datapoints}}} -> AttributeBuckets = - [{Attributes, Buckets, Min, Max, Sum} + [{otel_attributes:map(Attributes), Buckets, Min, Max, Sum} || #histogram_datapoint{bucket_counts=Buckets, attributes=Attributes, min=Min, @@ -571,11 +582,11 @@ explicit_histograms(_Config) -> {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]), + lists:sort([{otel_attributes:map(Attributes), Buckets, Min, Max, Sum} || #histogram_datapoint{bucket_counts=Buckets, + attributes=Attributes, + min=Min, + max=Max, + sum=Sum} <- Datapoints]), ?assertEqual([], [{#{<<"c">> => <<"b">>}, [0,0,0,1,1,0,1,0,0,0,0,0,0,0,0,1], 20, 20000, 20164}, {#{<<"a">> => <<"b">>, <<"d">> => <<"e">>}, [0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0], 30, 30, 30}] -- AttributeBuckets, AttributeBuckets) @@ -620,11 +631,11 @@ delta_explicit_histograms(_Config) -> {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]), + lists:sort([{otel_attributes:map(Attributes), Buckets, Min, Max, Sum} || #histogram_datapoint{bucket_counts=Buckets, + attributes=Attributes, + min=Min, + max=Max, + sum=Sum} <- Datapoints]), ?assertEqual([], [{#{<<"c">> => <<"b">>}, [0,0,0,1,1,0,1,0,0,0,0,0,0,0,0,0], 20, 100, 164}, {#{<<"a">> => <<"b">>, <<"d">> => <<"e">>}, [0,0,0,0,1,0,0,0,0,0,0,0,0,0,0,0], 30, 30, 30}] -- AttributeBuckets, AttributeBuckets) @@ -641,11 +652,11 @@ delta_explicit_histograms(_Config) -> {otel_metric, #metric{name=a_histogram, data=#histogram{datapoints=Datapoints1}}} -> AttributeBuckets1 = - [{Attributes, Buckets, Min, Max, Sum} || #histogram_datapoint{bucket_counts=Buckets, - attributes=Attributes, - min=Min, - max=Max, - sum=Sum} <- Datapoints1], + [{otel_attributes:map(Attributes), Buckets, Min, Max, Sum} || #histogram_datapoint{bucket_counts=Buckets, + attributes=Attributes, + min=Min, + max=Max, + sum=Sum} <- Datapoints1], ?assertEqual([], [{#{<<"c">> => <<"b">>}, [0,0,0,0,0,0,1,0,0,0,0,0,0,0,0,0], 88, 88, 88}, {#{<<"a">> => <<"b">>,<<"d">> => <<"e">>}, [0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0], @@ -1200,11 +1211,11 @@ advisory_params(_Config) -> {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]), + lists:sort([{otel_attributes:map(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) @@ -1232,11 +1243,11 @@ advisory_params(_Config) -> {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]), + lists:sort([{otel_attributes:map(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) @@ -1267,15 +1278,34 @@ histogram_aggregation_options(_Config) -> {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]), + lists:sort([{otel_attributes:map(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 + end. + +too_many_attributes(_Config) -> + CounterName = counter, + + Counter = ?create_counter(CounterName, #{}), + + ?assertEqual(ok, otel_counter:add(Counter, 2, #{<<"a">> => 1, <<"b">> => 2, <<"c">> => 3})), + ?assertEqual(ok, otel_counter:add(Counter, 5, #{<<"a">> => 4})), + + otel_meter_server:force_flush(), + + receive + {otel_metric, #metric{name=counter, data=#sum{datapoints=Datapoints}}} -> + Data = lists:sort([{MetricValue, otel_attributes:dropped(MetricAttributes)} || #datapoint{value=MetricValue, attributes=MetricAttributes} <- Datapoints]), + ?assertMatch([{2, 1}, {5, 0}], lists:sort(Data), Data) + after + 1000 -> + ct:fail(counter_receive_timeout) + end.