Skip to content

Commit

Permalink
Merge pull request #261 from qdentity/dvic-refactor-sampler
Browse files Browse the repository at this point in the history
Refactor otel_sampler to use callbacks for setup, should_sample, and decription
  • Loading branch information
tsloughter authored Aug 19, 2021
2 parents 24804a7 + cd4ae4d commit 8710301
Show file tree
Hide file tree
Showing 16 changed files with 576 additions and 262 deletions.
2 changes: 1 addition & 1 deletion apps/opentelemetry/src/opentelemetry.app.src
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
stdlib,
opentelemetry_api
]},
{env, [{sampler, {parent_based, #{root => {always_on, #{}}}}}, % default sampler
{env, [{sampler, {parent_based, #{root => always_on}}}, % default sampler

{text_map_propagators, [fun otel_baggage:get_text_map_propagators/0,
fun otel_tracer_default:w3c_propagators/0]},
Expand Down
15 changes: 7 additions & 8 deletions apps/opentelemetry/src/otel_configuration.erl
Original file line number Diff line number Diff line change
Expand Up @@ -45,7 +45,7 @@ processors(AppEnvOpts) ->
%% sampler configuration is unique since it has the _ARG that is a sort of
%% sub-configuration of the sampler config, and isn't a list.
sampler(AppEnvOpts) ->
Sampler = proplists:get_value(sampler, AppEnvOpts, {parent_based, #{root => {always_on, #{}}}}),
Sampler = proplists:get_value(sampler, AppEnvOpts, {parent_based, #{root => always_on}}),

Sampler1 = case os:getenv("OTEL_TRACES_SAMPLER") of
false ->
Expand Down Expand Up @@ -143,22 +143,21 @@ transform(url, Value) ->
uri_string:parse(Value);
%% convert sampler string to usable configuration term
transform(sampler, {"parentbased_always_on", _}) ->
{parent_based, #{root => {always_on, #{}}}};
{parent_based, #{root => always_on}};
transform(sampler, {"parentbased_always_off", _}) ->
{parent_based, #{root => {always_off, #{}}}};
{parent_based, #{root => always_off}};
transform(sampler, {"always_on", _}) ->
{always_on, #{}};
always_on;
transform(sampler, {"always_off", _}) ->
{always_off, #{}};
always_off;
transform(sampler, {"traceidratio", false}) ->
{trace_id_ratio_based, 1.0};
transform(sampler, {"traceidratio", Probability}) ->
{trace_id_ratio_based, probability_string_to_float(Probability)};
transform(sampler, {"parentbased_traceidratio", false}) ->
{parentbased_traceidratio, 1.0};
{parent_based, #{root => {trace_id_ratio_based, 1.0}}};
transform(sampler, {"parentbased_traceidratio", Probability}) ->
{parent_based,
#{root => {trace_id_ratio_based, probability_string_to_float(Probability)}}};
{parent_based, #{root => {trace_id_ratio_based, probability_string_to_float(Probability)}}};
transform(sampler, Value) ->
Value;

Expand Down
233 changes: 70 additions & 163 deletions apps/opentelemetry/src/otel_sampler.erl
Original file line number Diff line number Diff line change
Expand Up @@ -19,171 +19,78 @@
%%%-------------------------------------------------------------------------
-module(otel_sampler).

-export([new/3,
setup/1,
get_description/1,
always_on/7,
always_off/7,
parent_based/7,
trace_id_ratio_based/7]).
-export([description/1, new/1, should_sample/7]).

-export_type([
description/0,
sampler_config/0,
sampler_opts/0,
sampling_decision/0,
sampling_result/0,
t/0
]).

-callback setup(sampler_opts()) -> sampler_config().

-callback description(sampler_config()) -> description().

-callback should_sample(
otel_ctx:t(),
opentelemetry:trace_id(),
opentelemetry:links(),
opentelemetry:span_name(),
opentelemetry:span_kind(),
opentelemetry:attributes(),
sampler_config()
) -> sampling_result().

-include_lib("kernel/include/logger.hrl").
-include_lib("opentelemetry_api/include/opentelemetry.hrl").
-include("otel_sampler.hrl").
-include("otel_span.hrl").

-callback setup(sampler_opts()) -> t().

-type sampling_decision() :: ?DROP | ?RECORD_ONLY | ?RECORD_AND_SAMPLE.
-type sampling_result() :: {sampling_decision(), opentelemetry:attributes(), opentelemetry:tracestate()}.
-type description() :: unicode:unicode_binary().
-type sampler_fun() :: fun((otel_ctx:t(),
opentelemetry:trace_id(),
opentelemetry:links(),
opentelemetry:span_name(),
opentelemetry:span_kind(),
opentelemetry:attributes(),
term()) -> sampling_result()).
-type sampler() :: {sampler_fun(), description(), sampler_opts()}.
-type sampler_config() :: term().
-type sampler_opts() :: term().
-opaque t() :: sampler().
-export_type([sampler_fun/0,
description/0,
sampling_result/0,
sampling_decision/0,
sampler_opts/0,
t/0]).

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

-spec new(sampler_fun(), description(), sampler_opts()) -> t().
new(DecisionFunction, Description, SamplerOpts) ->
{DecisionFunction, Description, SamplerOpts}.

-spec setup(atom() | {atom() | module(), sampler_opts()}) -> t().
setup({Sampler, Opts}) ->
setup(Sampler, Opts);
setup(Sampler) when is_atom(Sampler) ->
setup(Sampler, #{}).

setup(always_on, Opts) ->
{fun ?MODULE:always_on/7, description(always_on, Opts), []};
setup(always_off, Opts) ->
{fun ?MODULE:always_off/7, description(always_off, Opts), []};
setup(parent_based, Opts) ->
{Config, Description} = parent_based_config(Opts),
{fun ?MODULE:parent_based/7, Description, 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,
{fun ?MODULE:trace_id_ratio_based/7, description(trace_id_ratio_based, Probability), IdUpperBound};
setup(Sampler, Opts) ->
Sampler:setup(Opts).

always_on(Ctx, _TraceId, _Links, _SpanName, _Kind, _Attributes, _Opts) ->
{?RECORD_AND_SAMPLE, [], tracestate(Ctx)}.

always_off(Ctx, _TraceId, _Links, _SpanName, _Kind, _Attributes, _Opts) ->
{?DROP, [], tracestate(Ctx)}.

-spec get_description(sampler()) -> description().
get_description({_Fun, Description, _Opts}) ->
Description.

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, #{}}),

ParentBasedConfig = #{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)},
{ParentBasedConfig, description(parent_based, ParentBasedConfig)};
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(Ctx, TraceId, Links, SpanName, Kind, Attributes, Opts) ->
ParentSpanCtx = otel_tracer:current_span_ctx(Ctx),
{Sampler, _Description, SamplerOpts} = parent_based_sampler(ParentSpanCtx, Opts),
Sampler(Ctx, TraceId, Links, SpanName, Kind, Attributes, SamplerOpts).

%% remote parent sampled
parent_based_sampler(#span_ctx{trace_flags=TraceFlags,
is_remote=true}, #{remote_parent_sampled := SamplerAndOpts})
when ?IS_SAMPLED(TraceFlags) ->
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_SAMPLED(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.


trace_id_ratio_based(Ctx, undefined, _, _, _, _, _IdUpperBound) ->
{?DROP, [], tracestate(Ctx)};
trace_id_ratio_based(Ctx, 0, _, _, _, _, _IdUpperBound) ->
{?DROP, [], tracestate(Ctx)};
trace_id_ratio_based(Ctx, TraceId, _, _, _, _, IdUpperBound) ->
Lower64Bits = TraceId band ?MAX_VALUE,
case erlang:abs(Lower64Bits) < IdUpperBound of
true ->
{?RECORD_AND_SAMPLE, [], tracestate(Ctx)};
false ->
{?DROP, [], tracestate(Ctx)}
end.

tracestate(Ctx) ->
tracestate_(otel_tracer:current_span_ctx(Ctx)).

tracestate_(#span_ctx{tracestate=undefined}) ->
[];
tracestate_(#span_ctx{tracestate=TraceState}) ->
TraceState;
tracestate_(undefined) ->
[].

description(always_on, _) ->
<<"AlwaysOnSampler">>;
description(always_off, _) ->
<<"AlwaysOffSampler">>;
description(trace_id_ratio_based, Probability) ->
unicode:characters_to_binary(io_lib:format("TraceIdRatioBased{~.6f}", [Probability]));
description(parent_based, #{root := RootSampler,
remote_parent_sampled := RemoteParentSampler,
remote_parent_not_sampled := RemoteParentNotSampler,
local_parent_sampled := LocalParentSampler,
local_parent_not_sampled := LocalParentNotSampler}) ->
<<"ParentBased{root:", (get_description(RootSampler))/binary,
",remoteParentSampled:", (get_description(RemoteParentSampler))/binary,
",remoteParentNotSampled:", (get_description(RemoteParentNotSampler))/binary,
",localParentSampled:", (get_description(LocalParentSampler))/binary,
",localParentNotSampled:", (get_description(LocalParentNotSampler))/binary,
"}">>.
-type builtin_sampler() ::
always_on
| always_off
| {trace_id_ratio_based, float()}
| {parent_based, #{
remote_parent_sampled => sampler_spec(),
remote_parent_not_sampled => sampler_spec(),
local_parent_sampled => sampler_spec(),
local_parent_not_sampled => sampler_spec(),
root => sampler_spec()
}}.
-type sampler_spec() :: builtin_sampler() | {module(), sampler_opts()}.
-type sampling_decision() :: ?DROP | ?RECORD_ONLY | ?RECORD_AND_SAMPLE.
-type sampling_result() :: {
sampling_decision(), opentelemetry:attributes(), opentelemetry:tracestate()
}.
-opaque t() :: {module(), description(), sampler_opts()}.

-spec new(sampler_spec()) -> t().
new(always_on) ->
new({otel_sampler_always_on, #{}});
new(always_off) ->
new({otel_sampler_always_off, #{}});
new({trace_id_ratio_based, Opts}) ->
new({otel_sampler_trace_id_ratio_based, Opts});
new({parent_based, Opts}) ->
new({otel_sampler_parent_based, Opts});
new({Sampler, Opts}) ->
Config = Sampler:setup(Opts),
{Sampler, Sampler:description(Config), Config}.

-spec should_sample(
t(),
otel_ctx:t(),
opentelemetry:trace_id(),
opentelemetry:links(),
opentelemetry:span_name(),
opentelemetry:span_kind(),
opentelemetry:attributes()
) -> sampling_result().
should_sample({Sampler, _, Config}, Ctx, TraceId, Links, SpanName, Kind, Attributes) ->
Sampler:should_sample(Ctx, TraceId, Links, SpanName, Kind, Attributes, Config).

-spec description(t()) -> description().
description({_, Description, _}) -> Description.
34 changes: 34 additions & 0 deletions apps/opentelemetry/src/otel_sampler_always_off.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
%%%------------------------------------------------------------------------
%% Copyright 2019, 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
%% This sampler always decides to neither record nor sample each span.
%% @end
%%%-------------------------------------------------------------------------
-module(otel_sampler_always_off).

-behavior(otel_sampler).

-export([description/1, setup/1, should_sample/7]).

-include_lib("opentelemetry_api/include/opentelemetry.hrl").
-include("otel_sampler.hrl").

setup(_Opts) -> [].

description(_) -> <<"AlwaysOffSampler">>.

should_sample(Ctx, _TraceId, _Links, _SpanName, _SpanKind, _Attributes, _Opts) ->
SpanCtx = otel_tracer:current_span_ctx(Ctx),
{?DROP, [], otel_span:tracestate(SpanCtx)}.
34 changes: 34 additions & 0 deletions apps/opentelemetry/src/otel_sampler_always_on.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
%%%------------------------------------------------------------------------
%% Copyright 2019, 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
%% This sampler always decides to record and sample each span.
%% @end
%%%-------------------------------------------------------------------------
-module(otel_sampler_always_on).

-behavior(otel_sampler).

-export([description/1, setup/1, should_sample/7]).

-include_lib("opentelemetry_api/include/opentelemetry.hrl").
-include("otel_sampler.hrl").

setup(_Opts) -> [].

description(_) -> <<"AlwaysOnSampler">>.

should_sample(Ctx, _TraceId, _Links, _SpanName, _SpanKind, _Attributes, _Opts) ->
SpanCtx = otel_tracer:current_span_ctx(Ctx),
{?RECORD_AND_SAMPLE, [], otel_span:tracestate(SpanCtx)}.
Loading

0 comments on commit 8710301

Please sign in to comment.