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

update samplers to latest spec: parent_based & trace_id_ratio_based #127

Merged
merged 1 commit into from
Oct 28, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
114 changes: 63 additions & 51 deletions apps/opentelemetry/src/otel_sampler.erl
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@
-export([setup/2,
always_on/7,
always_off/7,
parent_or_else/7,
probability/7]).
parent_based/7,
trace_id_ratio_based/7]).

-include_lib("kernel/include/logger.hrl").
-include_lib("opentelemetry_api/include/opentelemetry.hrl").
Expand All @@ -45,34 +45,26 @@
t/0]).

-define(MAX_VALUE, 9223372036854775807). %% 2^63 - 1
-define(DEFAULT_PROBABILITY, 0.5).

-spec setup(atom() | module(), map()) -> t().
setup(always_on, _Opts) ->
{fun ?MODULE:always_on/7, []};
setup(always_off, _Opts) ->
{fun ?MODULE:always_off/7, []};
setup(parent_or_else, #{delegate_sampler := {DelegateSampler, DelegateOpts}})
when is_atom(DelegateSampler) ->
{fun ?MODULE:parent_or_else/7, setup(DelegateSampler, DelegateOpts)};
setup(parent_or_else, _Opts) ->
?LOG_INFO("no delegate_sampler opt found for sampler parent_or_else. always_on will be used for root spans"),
{fun ?MODULE:parent_or_else/7, setup(always_on, #{})};
setup(probability, Opts) ->
IdUpperBound =
case maps:get(probability, Opts, ?DEFAULT_PROBABILITY) of
P when P =:= 0.0 ->
0;
P when P =:= 1.0 ->
?MAX_VALUE;
P when P >= 0.0 andalso P =< 1.0 ->
P * ?MAX_VALUE
end,
setup(parent_based, Opts) ->
Config = parent_based_config(Opts),
{fun ?MODULE:parent_based/7, Config};
setup(trace_id_ratio_based, Probability) ->
IdUpperBound = case Probability of
P when P =:= 0.0 ->
0;
P when P =:= 1.0 ->
?MAX_VALUE;
P when P >= 0.0 andalso P =< 1.0 ->
P * ?MAX_VALUE
end,

IgnoreParentFlag = maps:get(ignore_parent_flag, Opts, false),
OnlyRoot = maps:get(only_rootel_spans, Opts, true),

{fun ?MODULE:probability/7, {IdUpperBound, IgnoreParentFlag, OnlyRoot}};
{fun ?MODULE:trace_id_ratio_based/7, IdUpperBound};
setup(Sampler, Opts) ->
Sampler:setup(Opts).

Expand All @@ -82,40 +74,60 @@ always_on(_TraceId, _SpanCtx, _Links, _SpanName, _Kind, _Attributes, _Opts) ->
always_off(_TraceId, _SpanCtx, _Links, _SpanName, _Kind, _Attributes, _Opts) ->
{?NOT_RECORD, []}.

parent_or_else(_, #span_ctx{trace_flags=TraceFlags}, _, _, _, _, _) when ?IS_SPAN_ENABLED(TraceFlags) ->
{?RECORD_AND_SAMPLED, []};
parent_or_else(_, #span_ctx{trace_flags=_TraceFlags}, _, _, _, _, _) ->
{?NOT_RECORD, []};
parent_or_else(TraceId, SpanCtx, Links, SpanName, Kind, Attributes, {DelegateSampler, DelegateOpts}) ->
DelegateSampler(TraceId, SpanCtx, Links, SpanName, Kind, Attributes, DelegateOpts).
parent_based_config(Opts=#{root := {RootSampler, RootOpts}})
when is_atom(RootSampler)->
{RemoteParentSampled, RemoteParentSampledOpts}
= maps:get(remote_parent_sampled, Opts, {always_on, #{}}),
{RemoteParentNotSampled, RemoteParentNotSampledOpts}
= maps:get(remote_parent_not_sampled, Opts, {always_off, #{}}),
{LocalParentSampled, LocalParentSampledOpts}
= maps:get(local_parent_sampled, Opts, {always_on, #{}}),
{LocalParentNotSampled, LocalParentNotSampledOpts}
= maps:get(local_parent_not_sampled, Opts, {always_off, #{}}),

#{root => setup(RootSampler, RootOpts),
remote_parent_sampled =>
setup(RemoteParentSampled, RemoteParentSampledOpts),
remote_parent_not_sampled =>
setup(RemoteParentNotSampled, RemoteParentNotSampledOpts),
local_parent_sampled =>
setup(LocalParentSampled, LocalParentSampledOpts),
local_parent_not_sampled =>
setup(LocalParentNotSampled, LocalParentNotSampledOpts)};
parent_based_config(Opts) ->
?LOG_INFO("no root opt found for sampler parent_based. always_on will be used for root spans"),
parent_based_config(Opts#{root => {always_on, #{}}}).

parent_based(TraceId, SpanCtx, Links, SpanName, Kind, Attributes, Opts) ->
{Sampler, SamplerOpts} = parent_based_sampler(SpanCtx, Opts),
Sampler(TraceId, SpanCtx, Links, SpanName, Kind, Attributes, SamplerOpts).

probability(TraceId, Parent, _, _, _, _, {IdUpperBound,
IgnoreParentFlag,
_OnlyRoot})
when IgnoreParentFlag =:= true orelse Parent =:= undefined ->
{do_probability_sample(TraceId, IdUpperBound), []};
probability(_, #span_ctx{trace_flags=TraceFlags}, _, _, _, _, {_IdUpperBound,
_IgnoreParentFlag,
_OnlyRoot})
%% remote parent sampled
parent_based_sampler(#span_ctx{trace_flags=TraceFlags,
is_remote=true}, #{remote_parent_sampled := SamplerAndOpts})
when ?IS_SPAN_ENABLED(TraceFlags) ->
{?RECORD_AND_SAMPLED, []};
probability(TraceId, #span_ctx{is_remote=IsRemote}, _, _, _, _, {IdUpperBound,
_IgnoreParentFlag,
OnlyRoot}) ->
case OnlyRoot =:= false andalso IsRemote =:= true
andalso do_probability_sample(TraceId, IdUpperBound) of
?RECORD_AND_SAMPLED ->
{?RECORD_AND_SAMPLED, []};
_ ->
{?NOT_RECORD, []}
end.
SamplerAndOpts;
%% remote parent not sampled
parent_based_sampler(#span_ctx{is_remote=true}, #{remote_parent_not_sampled := SamplerAndOpts}) ->
SamplerAndOpts;
%% local parent sampled
parent_based_sampler(#span_ctx{trace_flags=TraceFlags,
is_remote=false}, #{local_parent_sampled := SamplerAndOpts})
when ?IS_SPAN_ENABLED(TraceFlags) ->
SamplerAndOpts;
%% local parent not sampled
parent_based_sampler(#span_ctx{is_remote=false}, #{local_parent_not_sampled := SamplerAndOpts}) ->
SamplerAndOpts;
%% root
parent_based_sampler(_SpanCtx, #{root := SamplerAndOpts}) ->
SamplerAndOpts.

do_probability_sample(TraceId, IdUpperBound) ->
trace_id_ratio_based(TraceId, _, _, _, _, _, IdUpperBound) ->
Lower64Bits = TraceId band ?MAX_VALUE,
case erlang:abs(Lower64Bits) < IdUpperBound of
true ->
?RECORD_AND_SAMPLED;
{?RECORD_AND_SAMPLED, []};
false ->
?NOT_RECORD
{?NOT_RECORD, []}
end.

61 changes: 23 additions & 38 deletions apps/opentelemetry/test/otel_samplers_SUITE.erl
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
-include("otel_sampler.hrl").

all() ->
[probability_sampler, parent_or_else].
[trace_id_ratio_based, parent_based].

init_per_suite(Config) ->
application:load(opentelemetry),
Expand All @@ -26,15 +26,14 @@ init_per_testcase(_, Config) ->
end_per_testcase(_, _Config) ->
ok.

probability_sampler(_Config) ->
trace_id_ratio_based(_Config) ->
SpanName = <<"span-prob-sampled">>,
Probability = 0.5,
DoSample = 120647249294066572380176333851662846319,
DoNotSample = 53020601517903903921384168845238205400,

%% sampler that runs on all spans
{Sampler, Opts} = otel_sampler:setup(probability, #{probability => Probability,
only_rootel_spans => false}),
{Sampler, Opts} = otel_sampler:setup(trace_id_ratio_based, Probability),

%% checks the trace id is is under the upper bound
?assertMatch({?RECORD_AND_SAMPLED, []},
Expand All @@ -44,79 +43,65 @@ probability_sampler(_Config) ->
?assertMatch({?NOT_RECORD, []},
Sampler(DoNotSample, undefined, [], SpanName, undefined, [], Opts)),

%% uses the value from the parent span context
?assertMatch({?RECORD_AND_SAMPLED, []},
%% ignores the parent span context trace flags
?assertMatch({?NOT_RECORD, []},
Sampler(DoNotSample, #span_ctx{trace_flags=1,
is_remote=true},
[], SpanName, undefined, [], Opts)),

%% since parent is not remote it uses the value from the parent span context
?assertMatch({?NOT_RECORD, []},
%% ignores the parent span context trace flags
?assertMatch({?RECORD_AND_SAMPLED, []},
Sampler(DoSample, #span_ctx{trace_flags=0,
is_remote=false},
[], SpanName, undefined, [], Opts)),

%% since parent is remote it checks the trace id and it is under the upper bound
%% trace id is under the upper bound
?assertMatch({?RECORD_AND_SAMPLED, []},
Sampler(DoSample, #span_ctx{trace_flags=0,
is_remote=true},
[], SpanName, undefined, [], Opts)),

{Sampler1, Opts1} = otel_sampler:setup(probability, #{probability => Probability,
only_rootel_spans => false,
ignore_parent_flag => false}),

?assertMatch({?RECORD_AND_SAMPLED, []},
Sampler1(DoSample, #span_ctx{trace_flags=0, is_remote=true},
[], SpanName, undefined, [], Opts1)),

{Sampler2, Opts3} = otel_sampler:setup(probability, #{probability => Probability,
ignore_parent_flag => false}),

%% parent not ignored but is 0 and sampler hint RECORD is ignored by default
?assertMatch({?NOT_RECORD, []},
Sampler2(DoNotSample, #span_ctx{trace_flags=0, is_remote=true},
[], SpanName, undefined, [], Opts3)),

ok.

parent_or_else(_Config) ->
parent_based(_Config) ->
SpanName = <<"span-prob-sampled">>,
Probability = 0.5,
DoSample = 120647249294066572380176333851662846319,
DoNotSample = 53020601517903903921384168845238205400,

{Sampler, Opts} = otel_sampler:setup(parent_or_else,
#{delegate_sampler => {probability, #{probability => Probability,
only_rootel_spans => false}}}),
{Sampler, Opts} = otel_sampler:setup(parent_based,
#{root => {trace_id_ratio_based, Probability}}),

%% with no parent it will run the probability sampler
?assertMatch({?RECORD_AND_SAMPLED, []},
Sampler(DoSample, undefined, [], SpanName, undefined, [], Opts)),
?assertMatch({?NOT_RECORD, []},
Sampler(DoNotSample, undefined, [], SpanName, undefined, [], Opts)),
Sampler(DoNotSample, undefined, [], SpanName, undefined, [], Opts)),

%% with parent it will use the parents value
?assertMatch({?RECORD_AND_SAMPLED, []},
Sampler(DoNotSample, #span_ctx{trace_flags=1},
Sampler(DoNotSample, #span_ctx{trace_flags=1,
is_remote=true},
[], SpanName, undefined, [], Opts)),
?assertMatch({?NOT_RECORD, []},
Sampler(DoNotSample, #span_ctx{trace_flags=0},
Sampler(DoNotSample, #span_ctx{trace_flags=0,
is_remote=true},
[], SpanName, undefined, [], Opts)),

%% with no delegate_sampler in setup opts the default sampler always_on is used
{DefaultParentOrElse, Opts1} = otel_sampler:setup(parent_or_else, #{}),
%% with no root sampler in setup opts the default sampler always_on is used
{DefaultParentOrElse, Opts1} = otel_sampler:setup(parent_based, #{}),

?assertMatch({?RECORD_AND_SAMPLED, []},
DefaultParentOrElse(DoSample, undefined, [], SpanName, undefined, [], Opts1)),
?assertMatch({?RECORD_AND_SAMPLED, []},
DefaultParentOrElse(DoNotSample, undefined, [], SpanName, undefined, [], Opts1)),
DefaultParentOrElse(DoNotSample, undefined, [], SpanName, undefined, [], Opts1)),

?assertMatch({?RECORD_AND_SAMPLED, []},
DefaultParentOrElse(DoNotSample, #span_ctx{trace_flags=1},
[], SpanName, undefined, [], Opts1)),
[], SpanName, undefined, [], Opts1)),
?assertMatch({?NOT_RECORD, []},
DefaultParentOrElse(DoNotSample, #span_ctx{trace_flags=0},
[], SpanName, undefined, [], Opts1)),
DefaultParentOrElse(DoNotSample, #span_ctx{trace_flags=0,
is_remote=true},
[], SpanName, undefined, [], Opts1)),

ok.