Skip to content

Commit

Permalink
Probability Sampler (gabime#136)
Browse files Browse the repository at this point in the history
  • Loading branch information
nholbrook authored Jul 14, 2020
1 parent 04c48a1 commit ab2a5c1
Show file tree
Hide file tree
Showing 6 changed files with 402 additions and 3 deletions.
50 changes: 50 additions & 0 deletions sdk/include/opentelemetry/sdk/trace/samplers/probability.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#pragma once

#include "opentelemetry/sdk/trace/sampler.h"

OPENTELEMETRY_BEGIN_NAMESPACE
namespace sdk
{
namespace trace
{
namespace trace_api = opentelemetry::trace;
/**
* The probability sampler, based on it's configuration, should either defer the
* decision to sample to it's parent, or compute and return a decision based on
* the provided trace_id and probability.
*/
class ProbabilitySampler : public Sampler
{
public:
/**
* @param probability a required value, 1.0 >= probability >= 0.0, that given any
* random trace_id, ShouldSample will return RECORD_AND_SAMPLE
* @throws invalid_argument if probability is out of bounds [0.0, 1.0]
*/
explicit ProbabilitySampler(double probability);

/**
* @return Returns either RECORD_AND_SAMPLE or NOT_RECORD based on current
* sampler configuration and provided parent_context / tracer_id. tracer_id
* is used as a pseudorandom value in conjunction with the predefined
* threshold to determine whether this trace should be sampled
*/
SamplingResult ShouldSample(
const trace_api::SpanContext *parent_context,
trace_api::TraceId trace_id,
nostd::string_view /*name*/,
trace_api::SpanKind /*span_kind*/,
const trace_api::KeyValueIterable & /*attributes*/) noexcept override;

/**
* @return Description MUST be ProbabilitySampler{0.000100}
*/
std::string GetDescription() const noexcept override;

private:
std::string sampler_description_;
const uint64_t threshold_;
};
} // namespace trace
} // namespace sdk
OPENTELEMETRY_END_NAMESPACE
3 changes: 2 additions & 1 deletion sdk/src/trace/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
add_library(opentelemetry_trace tracer_provider.cc tracer.cc span.cc samplers/parent_or_else.cc)
add_library(opentelemetry_trace tracer_provider.cc tracer.cc span.cc
samplers/parent_or_else.cc samplers/probability.cc)
114 changes: 114 additions & 0 deletions sdk/src/trace/samplers/probability.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// Copyright 2020, Open Telemetry Authors
// Copyright 2017, OpenCensus 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.

#include "opentelemetry/sdk/trace/samplers/probability.h"

#include <cmath>
#include <cstdint>
#include <stdexcept>

namespace trace_api = opentelemetry::trace;

namespace
{
/**
* Converts a probability in [0, 1] to a threshold in [0, UINT64_MAX]
*
* @param probability a required value top be converted to uint64_t. is
* bounded by 1 >= probability >= 0.
* @return Returns threshold value computed after converting probability to
* uint64_t datatype
*/
uint64_t CalculateThreshold(double probability) noexcept
{
if (probability <= 0.0) return 0;
if (probability >= 1.0) return UINT64_MAX;

// We can't directly return probability * UINT64_MAX.
//
// UINT64_MAX is (2^64)-1, but as a double rounds up to 2^64.
// For probabilities >= 1-(2^-54), the product wraps to zero!
// Instead, calculate the high and low 32 bits separately.
const double product = UINT32_MAX * probability;
double hi_bits, lo_bits = ldexp(modf(product, &hi_bits), 32) + product;
return (static_cast<uint64_t>(hi_bits) << 32) +
static_cast<uint64_t>(lo_bits);
}

/**
* @param trace_id a required value to be converted to uint64_t. trace_id must
* at least 8 bytes long
* @return Returns threshold value computed after converting trace_id to
* uint64_t datatype
*/
uint64_t CalculateThresholdFromBuffer(const trace_api::TraceId& trace_id) noexcept
{
// We only use the first 8 bytes of TraceId.
static_assert(trace_api::TraceId::kSize >= 8, "TraceID must be at least 8 bytes long.");

uint64_t res = 0;
std::memcpy(&res, &trace_id, 8);

double probability = (double) res / UINT64_MAX;

return CalculateThreshold(probability);
}
} // namespace

OPENTELEMETRY_BEGIN_NAMESPACE
namespace sdk
{
namespace trace
{
ProbabilitySampler::ProbabilitySampler(double probability)
: threshold_(CalculateThreshold(probability))
{
if (probability > 1.0) probability = 1.0;
if (probability < 0.0) probability = 0.0;
sampler_description_ = "ProbabilitySampler{" + std::to_string(probability) + "}";
}

SamplingResult ProbabilitySampler::ShouldSample(
const trace_api::SpanContext *parent_context,
trace_api::TraceId trace_id,
nostd::string_view /*name*/,
trace_api::SpanKind /*span_kind*/,
const trace_api::KeyValueIterable & /*attributes*/) noexcept
{
if (parent_context && !parent_context->HasRemoteParent()) {
if (parent_context->IsSampled()) {
return { Decision::RECORD_AND_SAMPLE, nullptr };
} else {
return { Decision::NOT_RECORD, nullptr };
}
}

if (threshold_ == 0) return { Decision::NOT_RECORD, nullptr };

if (CalculateThresholdFromBuffer(trace_id) <= threshold_)
{
return { Decision::RECORD_AND_SAMPLE, nullptr };
}

return { Decision::NOT_RECORD, nullptr };
}

std::string ProbabilitySampler::GetDescription() const noexcept
{
return sampler_description_;
}
} // namespace trace
} // namespace sdk
OPENTELEMETRY_END_NAMESPACE
12 changes: 12 additions & 0 deletions sdk/test/trace/BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -74,3 +74,15 @@ cc_test(
"@com_google_googletest//:gtest_main",
],
)

cc_test(
name = "probability_sampler_test",
srcs = [
"probability_sampler_test.cc",
],
deps = [
"//sdk/src/trace",
"//sdk/src/common:random",
"@com_google_googletest//:gtest_main",
],
)
4 changes: 2 additions & 2 deletions sdk/test/trace/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
foreach(testname tracer_provider_test span_data_test simple_processor_test
tracer_test always_off_sampler_test always_on_sampler_test
parent_or_else_sampler_test)
parent_or_else_sampler_test probability_sampler_test)
add_executable(${testname} "${testname}.cc")
target_link_libraries(${testname} ${GTEST_BOTH_LIBRARIES}
${CMAKE_THREAD_LIBS_INIT} opentelemetry_trace)
${CMAKE_THREAD_LIBS_INIT} opentelemetry_common opentelemetry_trace)
gtest_add_tests(TARGET ${testname} TEST_PREFIX trace. TEST_LIST ${testname})
endforeach()
Loading

0 comments on commit ab2a5c1

Please sign in to comment.