Skip to content

Commit

Permalink
Add utility for generating random 64-bit numbers (open-telemetry#57)
Browse files Browse the repository at this point in the history
* Add fork utility

* Add random number generator

* Add test coverage for random

* Add cmake build files

* Format

* Add missing noexcept

* Fix macro

* Add FastRandomNumberGenerator
  • Loading branch information
rnburn authored Mar 27, 2020
1 parent 8b3dd9c commit 79d87c6
Show file tree
Hide file tree
Showing 18 changed files with 435 additions and 0 deletions.
5 changes: 5 additions & 0 deletions bazel/BUILD
Original file line number Diff line number Diff line change
@@ -1 +1,6 @@
package(default_visibility = ["//:__subpackages__"])

config_setting(
name = "windows",
constraint_values = ["@bazel_tools//platforms:windows"],
)
1 change: 1 addition & 0 deletions sdk/src/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
add_subdirectory(common)
add_subdirectory(trace)
29 changes: 29 additions & 0 deletions sdk/src/common/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
# Copyright 2020, 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.

package(default_visibility = ["//visibility:public"])

cc_library(
name = "random",
srcs = ["random.cc"],
hdrs = [
"fast_random_number_generator.h",
"random.h",
],
include_prefix = "src/common",
deps = [
"//api",
"//sdk/src/common/platform:fork",
],
)
9 changes: 9 additions & 0 deletions sdk/src/common/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
set(COMMON_SRCS random.cc)
if(WIN32)
list(APPEND COMMON_SRCS platform/fork_windows.cc)
else()
list(APPEND COMMON_SRCS platform/fork_unix.cc)
endif()

add_library(opentelemetry_common ${COMMON_SRCS})
target_link_libraries(opentelemetry_common Threads::Threads)
68 changes: 68 additions & 0 deletions sdk/src/common/fast_random_number_generator.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
#pragma once

#include <array>
#include <cstdint>
#include <limits>

#include "opentelemetry/version.h"

OPENTELEMETRY_BEGIN_NAMESPACE
namespace sdk
{
namespace common
{
/**
* Profiling shows that random number generation can be a significant cost of
* span generation. This provides a faster random number generator than
* std::mt19937_64; and since we don't care about the other beneficial random
* number properties that std:mt19937_64 provides for this application, it's a
* entirely appropriate replacement.
*/
class FastRandomNumberGenerator
{
public:
using result_type = uint64_t;

FastRandomNumberGenerator() noexcept = default;

template <class SeedSequence>
FastRandomNumberGenerator(SeedSequence &seed_sequence) noexcept
{
seed(seed_sequence);
}

uint64_t operator()() noexcept
{
// Uses the xorshift128p random number generation algorithm described in
// https://en.wikipedia.org/wiki/Xorshift
auto &state_a = state_[0];
auto &state_b = state_[1];
auto t = state_a;
auto s = state_b;
state_a = s;
t ^= t << 23; // a
t ^= t >> 17; // b
t ^= s ^ (s >> 26); // c
state_b = t;
return t + s;
}

// RandomNumberGenerator concept functions required from standard library.
// See http://www.cplusplus.com/reference/random/mt19937/
template <class SeedSequence>
void seed(SeedSequence &seed_sequence) noexcept
{
seed_sequence.generate(reinterpret_cast<uint32_t *>(state_.data()),
reinterpret_cast<uint32_t *>(state_.data() + state_.size()));
}

static constexpr uint64_t min() noexcept { return 0; }

static constexpr uint64_t max() noexcept { return std::numeric_limits<uint64_t>::max(); }

private:
std::array<uint64_t, 2> state_{};
};
} // namespace common
} // namespace sdk
OPENTELEMETRY_END_NAMESPACE
30 changes: 30 additions & 0 deletions sdk/src/common/platform/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
# Copyright 2020, 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.

package(default_visibility = ["//visibility:public"])

cc_library(
name = "fork",
srcs = select({
"//bazel:windows": ["fork_windows.cc"],
"//conditions:default": ["fork_unix.cc"],
}),
hdrs = [
"fork.h",
],
include_prefix = "src/common/platform",
deps = [
"//api",
],
)
21 changes: 21 additions & 0 deletions sdk/src/common/platform/fork.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#pragma once

#include "opentelemetry/version.h"

OPENTELEMETRY_BEGIN_NAMESPACE
namespace sdk
{
namespace common
{
namespace platform
{
/**
* Portable wrapper for pthread_atfork.
* See
* https://pubs.opengroup.org/onlinepubs/009695399/functions/pthread_atfork.html
*/
int AtFork(void (*prepare)(), void (*parent)(), void (*child)()) noexcept;
} // namespace platform
} // namespace common
} // namespace sdk
OPENTELEMETRY_END_NAMESPACE
19 changes: 19 additions & 0 deletions sdk/src/common/platform/fork_unix.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#include "src/common/platform/fork.h"

#include <pthread.h>

OPENTELEMETRY_BEGIN_NAMESPACE
namespace sdk
{
namespace common
{
namespace platform
{
int AtFork(void (*prepare)(), void (*parent)(), void (*child)()) noexcept
{
return ::pthread_atfork(prepare, parent, child);
}
} // namespace platform
} // namespace common
} // namespace sdk
OPENTELEMETRY_END_NAMESPACE
20 changes: 20 additions & 0 deletions sdk/src/common/platform/fork_windows.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#include "src/common/platform/fork.h"

OPENTELEMETRY_BEGIN_NAMESPACE
namespace sdk
{
namespace common
{
namespace platform
{
int AtFork(void (*prepare)(), void (*parent)(), void (*child)()) noexcept
{
(void)prepare;
(void)parent;
(void)child;
return 0;
}
} // namespace platform
} // namespace common
} // namespace sdk
OPENTELEMETRY_END_NAMESPACE
50 changes: 50 additions & 0 deletions sdk/src/common/random.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
#include "src/common/random.h"
#include "src/common/platform/fork.h"

OPENTELEMETRY_BEGIN_NAMESPACE
namespace sdk
{
namespace common
{
// Wraps a thread_local random number generator, but adds a fork handler so that
// the generator will be correctly seeded after forking.
//
// See https://stackoverflow.com/q/51882689/4447365 and
// https://github.com/opentracing-contrib/nginx-opentracing/issues/52
namespace
{
class TlsRandomNumberGenerator
{
public:
TlsRandomNumberGenerator() noexcept
{
Seed();
platform::AtFork(nullptr, nullptr, OnFork);
}

static FastRandomNumberGenerator &engine() noexcept { return engine_; }

private:
static thread_local FastRandomNumberGenerator engine_;

static void OnFork() noexcept { Seed(); }

static void Seed() noexcept
{
std::random_device random_device;
std::seed_seq seed_seq{random_device(), random_device(), random_device(), random_device()};
engine_.seed(seed_seq);
}
};

thread_local FastRandomNumberGenerator TlsRandomNumberGenerator::engine_{};
} // namespace

FastRandomNumberGenerator &GetRandomNumberGenerator() noexcept
{
static thread_local TlsRandomNumberGenerator random_number_generator{};
return TlsRandomNumberGenerator::engine();
}
} // namespace common
} // namespace sdk
OPENTELEMETRY_END_NAMESPACE
19 changes: 19 additions & 0 deletions sdk/src/common/random.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
#pragma once

#include <random>

#include "opentelemetry/version.h"
#include "src/common/fast_random_number_generator.h"

OPENTELEMETRY_BEGIN_NAMESPACE
namespace sdk
{
namespace common
{
/**
* @return a seeded thread-local random number generator.
*/
FastRandomNumberGenerator &GetRandomNumberGenerator() noexcept;
} // namespace common
} // namespace sdk
OPENTELEMETRY_END_NAMESPACE
1 change: 1 addition & 0 deletions sdk/test/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
add_subdirectory(common)
add_subdirectory(trace)
40 changes: 40 additions & 0 deletions sdk/test/common/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
load("//bazel:otel_cc_benchmark.bzl", "otel_cc_benchmark")

cc_test(
name = "random_test",
srcs = [
"random_test.cc",
],
deps = [
"//sdk/src/common:random",
"@com_google_googletest//:gtest_main",
],
)

cc_test(
name = "fast_random_number_generator_test",
srcs = [
"fast_random_number_generator_test.cc",
],
deps = [
"//sdk/src/common:random",
"@com_google_googletest//:gtest_main",
],
)

cc_test(
name = "random_fork_test",
srcs = [
"random_fork_test.cc",
],
deps = [
"//sdk/src/common:random",
"@com_google_googletest//:gtest_main",
],
)

otel_cc_benchmark(
name = "random_benchmark",
srcs = ["random_benchmark.cc"],
deps = ["//sdk/src/common:random"],
)
14 changes: 14 additions & 0 deletions sdk/test/common/CMakeLists.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
foreach(testname random_test fast_random_number_generator_test)
add_executable(${testname} "${testname}.cc")
target_link_libraries(${testname} ${GTEST_BOTH_LIBRARIES}
${CMAKE_THREAD_LIBS_INIT} opentelemetry_common)
gtest_add_tests(TARGET ${testname} TEST_PREFIX trace. TEST_LIST ${testname})
endforeach()

add_executable(random_fork_test random_fork_test.cc)
target_link_libraries(random_fork_test opentelemetry_common)
add_test(random_fork_test random_fork_test)

add_executable(random_benchmark random_benchmark.cc)
target_link_libraries(random_benchmark benchmark::benchmark
${CMAKE_THREAD_LIBS_INIT} opentelemetry_common)
16 changes: 16 additions & 0 deletions sdk/test/common/fast_random_number_generator_test.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
#include "src/common/random.h"

#include <gtest/gtest.h>
using opentelemetry::sdk::common::FastRandomNumberGenerator;

TEST(FastRandomNumberGeneratorTest, GenerateUniqueNumbers)
{
std::seed_seq seed_sequence{1, 2, 3};
FastRandomNumberGenerator random_number_generator;
random_number_generator.seed(seed_sequence);
std::set<uint64_t> values;
for (int i = 0; i < 1000; ++i)
{
EXPECT_TRUE(values.insert(random_number_generator()).second);
}
}
31 changes: 31 additions & 0 deletions sdk/test/common/random_benchmark.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
#include "src/common/random.h"

#include <benchmark/benchmark.h>
#include <cstdint>

namespace
{
using opentelemetry::sdk::common::GetRandomNumberGenerator;

void BM_RandomIdGeneration(benchmark::State &state)
{
auto &generator = GetRandomNumberGenerator();
while (state.KeepRunning())
{
benchmark::DoNotOptimize(generator());
}
}
BENCHMARK(BM_RandomIdGeneration);

void BM_RandomIdStdGeneration(benchmark::State &state)
{
std::mt19937_64 generator{0};
while (state.KeepRunning())
{
benchmark::DoNotOptimize(generator());
}
}
BENCHMARK(BM_RandomIdStdGeneration);

} // namespace
BENCHMARK_MAIN();
Loading

0 comments on commit 79d87c6

Please sign in to comment.