This commit is contained in:
Yao
2024-12-20 17:49:45 +08:00
parent 86b0363ce1
commit 654d456c7d
7011 changed files with 1705926 additions and 7 deletions

View File

@@ -0,0 +1,93 @@
// Copyright 2019 The Abseil 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
//
// https://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 "absl/profiling/internal/exponential_biased.h"
#include <stdint.h>
#include <algorithm>
#include <atomic>
#include <cmath>
#include <limits>
#include "absl/base/attributes.h"
#include "absl/base/optimization.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace profiling_internal {
// The algorithm generates a random number between 0 and 1 and applies the
// inverse cumulative distribution function for an exponential. Specifically:
// Let m be the inverse of the sample period, then the probability
// distribution function is m*exp(-mx) so the CDF is
// p = 1 - exp(-mx), so
// q = 1 - p = exp(-mx)
// log_e(q) = -mx
// -log_e(q)/m = x
// log_2(q) * (-log_e(2) * 1/m) = x
// In the code, q is actually in the range 1 to 2**26, hence the -26 below
int64_t ExponentialBiased::GetSkipCount(int64_t mean) {
if (ABSL_PREDICT_FALSE(!initialized_)) {
Initialize();
}
uint64_t rng = NextRandom(rng_);
rng_ = rng;
// Take the top 26 bits as the random number
// (This plus the 1<<58 sampling bound give a max possible step of
// 5194297183973780480 bytes.)
// The uint32_t cast is to prevent a (hard-to-reproduce) NAN
// under piii debug for some binaries.
double q = static_cast<uint32_t>(rng >> (kPrngNumBits - 26)) + 1.0;
// Put the computed p-value through the CDF of a geometric.
double interval = bias_ + (std::log2(q) - 26) * (-std::log(2.0) * mean);
// Very large values of interval overflow int64_t. To avoid that, we will
// cheat and clamp any huge values to (int64_t max)/2. This is a potential
// source of bias, but the mean would need to be such a large value that it's
// not likely to come up. For example, with a mean of 1e18, the probability of
// hitting this condition is about 1/1000. For a mean of 1e17, standard
// calculators claim that this event won't happen.
if (interval > static_cast<double>(std::numeric_limits<int64_t>::max() / 2)) {
// Assume huge values are bias neutral, retain bias for next call.
return std::numeric_limits<int64_t>::max() / 2;
}
double value = std::rint(interval);
bias_ = interval - value;
return value;
}
int64_t ExponentialBiased::GetStride(int64_t mean) {
return GetSkipCount(mean - 1) + 1;
}
void ExponentialBiased::Initialize() {
// We don't get well distributed numbers from `this` so we call NextRandom() a
// bunch to mush the bits around. We use a global_rand to handle the case
// where the same thread (by memory address) gets created and destroyed
// repeatedly.
ABSL_CONST_INIT static std::atomic<uint32_t> global_rand(0);
uint64_t r = reinterpret_cast<uint64_t>(this) +
global_rand.fetch_add(1, std::memory_order_relaxed);
for (int i = 0; i < 20; ++i) {
r = NextRandom(r);
}
rng_ = r;
initialized_ = true;
}
} // namespace profiling_internal
ABSL_NAMESPACE_END
} // namespace absl

View File

@@ -0,0 +1,130 @@
// Copyright 2019 The Abseil 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
//
// https://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.
#ifndef ABSL_PROFILING_INTERNAL_EXPONENTIAL_BIASED_H_
#define ABSL_PROFILING_INTERNAL_EXPONENTIAL_BIASED_H_
#include <stdint.h>
#include "absl/base/config.h"
#include "absl/base/macros.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace profiling_internal {
// ExponentialBiased provides a small and fast random number generator for a
// rounded exponential distribution. This generator manages very little state,
// and imposes no synchronization overhead. This makes it useful in specialized
// scenarios requiring minimum overhead, such as stride based periodic sampling.
//
// ExponentialBiased provides two closely related functions, GetSkipCount() and
// GetStride(), both returning a rounded integer defining a number of events
// required before some event with a given mean probability occurs.
//
// The distribution is useful to generate a random wait time or some periodic
// event with a given mean probability. For example, if an action is supposed to
// happen on average once every 'N' events, then we can get a random 'stride'
// counting down how long before the event to happen. For example, if we'd want
// to sample one in every 1000 'Frobber' calls, our code could look like this:
//
// Frobber::Frobber() {
// stride_ = exponential_biased_.GetStride(1000);
// }
//
// void Frobber::Frob(int arg) {
// if (--stride == 0) {
// SampleFrob(arg);
// stride_ = exponential_biased_.GetStride(1000);
// }
// ...
// }
//
// The rounding of the return value creates a bias, especially for smaller means
// where the distribution of the fraction is not evenly distributed. We correct
// this bias by tracking the fraction we rounded up or down on each iteration,
// effectively tracking the distance between the cumulative value, and the
// rounded cumulative value. For example, given a mean of 2:
//
// raw = 1.63076, cumulative = 1.63076, rounded = 2, bias = -0.36923
// raw = 0.14624, cumulative = 1.77701, rounded = 2, bias = 0.14624
// raw = 4.93194, cumulative = 6.70895, rounded = 7, bias = -0.06805
// raw = 0.24206, cumulative = 6.95101, rounded = 7, bias = 0.24206
// etc...
//
// Adjusting with rounding bias is relatively trivial:
//
// double value = bias_ + exponential_distribution(mean)();
// double rounded_value = std::rint(value);
// bias_ = value - rounded_value;
// return rounded_value;
//
// This class is thread-compatible.
class ExponentialBiased {
public:
// The number of bits set by NextRandom.
static constexpr int kPrngNumBits = 48;
// `GetSkipCount()` returns the number of events to skip before some chosen
// event happens. For example, randomly tossing a coin, we will on average
// throw heads once before we get tails. We can simulate random coin tosses
// using GetSkipCount() as:
//
// ExponentialBiased eb;
// for (...) {
// int number_of_heads_before_tail = eb.GetSkipCount(1);
// for (int flips = 0; flips < number_of_heads_before_tail; ++flips) {
// printf("head...");
// }
// printf("tail\n");
// }
//
int64_t GetSkipCount(int64_t mean);
// GetStride() returns the number of events required for a specific event to
// happen. See the class comments for a usage example. `GetStride()` is
// equivalent to `GetSkipCount(mean - 1) + 1`. When to use `GetStride()` or
// `GetSkipCount()` depends mostly on what best fits the use case.
int64_t GetStride(int64_t mean);
// Computes a random number in the range [0, 1<<(kPrngNumBits+1) - 1]
//
// This is public to enable testing.
static uint64_t NextRandom(uint64_t rnd);
private:
void Initialize();
uint64_t rng_{0};
double bias_{0};
bool initialized_{false};
};
// Returns the next prng value.
// pRNG is: aX+b mod c with a = 0x5DEECE66D, b = 0xB, c = 1<<48
// This is the lrand64 generator.
inline uint64_t ExponentialBiased::NextRandom(uint64_t rnd) {
const uint64_t prng_mult = uint64_t{0x5DEECE66D};
const uint64_t prng_add = 0xB;
const uint64_t prng_mod_power = 48;
const uint64_t prng_mod_mask =
~((~static_cast<uint64_t>(0)) << prng_mod_power);
return (prng_mult * rnd + prng_add) & prng_mod_mask;
}
} // namespace profiling_internal
ABSL_NAMESPACE_END
} // namespace absl
#endif // ABSL_PROFILING_INTERNAL_EXPONENTIAL_BIASED_H_

View File

@@ -0,0 +1,253 @@
// Copyright 2018 The Abseil 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
//
// https://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.
//
// -----------------------------------------------------------------------------
// File: sample_recorder.h
// -----------------------------------------------------------------------------
//
// This header file defines a lock-free linked list for recording samples
// collected from a random/stochastic process.
//
// This utility is internal-only. Use at your own risk.
#ifndef ABSL_PROFILING_INTERNAL_SAMPLE_RECORDER_H_
#define ABSL_PROFILING_INTERNAL_SAMPLE_RECORDER_H_
#include <atomic>
#include <cstddef>
#include <functional>
#include "absl/base/config.h"
#include "absl/base/thread_annotations.h"
#include "absl/synchronization/mutex.h"
#include "absl/time/time.h"
namespace absl {
ABSL_NAMESPACE_BEGIN
namespace profiling_internal {
// Sample<T> that has members required for linking samples in the linked list of
// samples maintained by the SampleRecorder. Type T defines the sampled data.
template <typename T>
struct Sample {
// Guards the ability to restore the sample to a pristine state. This
// prevents races with sampling and resurrecting an object.
absl::Mutex init_mu;
T* next = nullptr;
T* dead ABSL_GUARDED_BY(init_mu) = nullptr;
int64_t weight; // How many sampling events were required to sample this one.
};
// Holds samples and their associated stack traces with a soft limit of
// `SetHashtablezMaxSamples()`.
//
// Thread safe.
template <typename T>
class SampleRecorder {
public:
SampleRecorder();
~SampleRecorder();
// Registers for sampling. Returns an opaque registration info.
template <typename... Targs>
T* Register(Targs&&... args);
// Unregisters the sample.
void Unregister(T* sample);
// The dispose callback will be called on all samples the moment they are
// being unregistered. Only affects samples that are unregistered after the
// callback has been set.
// Returns the previous callback.
using DisposeCallback = void (*)(const T&);
DisposeCallback SetDisposeCallback(DisposeCallback f);
// Iterates over all the registered `StackInfo`s. Returning the number of
// samples that have been dropped.
int64_t Iterate(const std::function<void(const T& stack)>& f);
size_t GetMaxSamples() const;
void SetMaxSamples(size_t max);
private:
void PushNew(T* sample);
void PushDead(T* sample);
template <typename... Targs>
T* PopDead(Targs... args);
std::atomic<size_t> dropped_samples_;
std::atomic<size_t> size_estimate_;
std::atomic<size_t> max_samples_{1 << 20};
// Intrusive lock free linked lists for tracking samples.
//
// `all_` records all samples (they are never removed from this list) and is
// terminated with a `nullptr`.
//
// `graveyard_.dead` is a circular linked list. When it is empty,
// `graveyard_.dead == &graveyard`. The list is circular so that
// every item on it (even the last) has a non-null dead pointer. This allows
// `Iterate` to determine if a given sample is live or dead using only
// information on the sample itself.
//
// For example, nodes [A, B, C, D, E] with [A, C, E] alive and [B, D] dead
// looks like this (G is the Graveyard):
//
// +---+ +---+ +---+ +---+ +---+
// all -->| A |--->| B |--->| C |--->| D |--->| E |
// | | | | | | | | | |
// +---+ | | +->| |-+ | | +->| |-+ | |
// | G | +---+ | +---+ | +---+ | +---+ | +---+
// | | | | | |
// | | --------+ +--------+ |
// +---+ |
// ^ |
// +--------------------------------------+
//
std::atomic<T*> all_;
T graveyard_;
std::atomic<DisposeCallback> dispose_;
};
template <typename T>
typename SampleRecorder<T>::DisposeCallback
SampleRecorder<T>::SetDisposeCallback(DisposeCallback f) {
return dispose_.exchange(f, std::memory_order_relaxed);
}
template <typename T>
SampleRecorder<T>::SampleRecorder()
: dropped_samples_(0), size_estimate_(0), all_(nullptr), dispose_(nullptr) {
absl::MutexLock l(&graveyard_.init_mu);
graveyard_.dead = &graveyard_;
}
template <typename T>
SampleRecorder<T>::~SampleRecorder() {
T* s = all_.load(std::memory_order_acquire);
while (s != nullptr) {
T* next = s->next;
delete s;
s = next;
}
}
template <typename T>
void SampleRecorder<T>::PushNew(T* sample) {
sample->next = all_.load(std::memory_order_relaxed);
while (!all_.compare_exchange_weak(sample->next, sample,
std::memory_order_release,
std::memory_order_relaxed)) {
}
}
template <typename T>
void SampleRecorder<T>::PushDead(T* sample) {
if (auto* dispose = dispose_.load(std::memory_order_relaxed)) {
dispose(*sample);
}
absl::MutexLock graveyard_lock(&graveyard_.init_mu);
absl::MutexLock sample_lock(&sample->init_mu);
sample->dead = graveyard_.dead;
graveyard_.dead = sample;
}
template <typename T>
template <typename... Targs>
T* SampleRecorder<T>::PopDead(Targs... args) {
absl::MutexLock graveyard_lock(&graveyard_.init_mu);
// The list is circular, so eventually it collapses down to
// graveyard_.dead == &graveyard_
// when it is empty.
T* sample = graveyard_.dead;
if (sample == &graveyard_) return nullptr;
absl::MutexLock sample_lock(&sample->init_mu);
graveyard_.dead = sample->dead;
sample->dead = nullptr;
sample->PrepareForSampling(std::forward<Targs>(args)...);
return sample;
}
template <typename T>
template <typename... Targs>
T* SampleRecorder<T>::Register(Targs&&... args) {
size_t size = size_estimate_.fetch_add(1, std::memory_order_relaxed);
if (size > max_samples_.load(std::memory_order_relaxed)) {
size_estimate_.fetch_sub(1, std::memory_order_relaxed);
dropped_samples_.fetch_add(1, std::memory_order_relaxed);
return nullptr;
}
T* sample = PopDead(args...);
if (sample == nullptr) {
// Resurrection failed. Hire a new warlock.
sample = new T();
{
absl::MutexLock sample_lock(&sample->init_mu);
// If flag initialization happens to occur (perhaps in another thread)
// while in this block, it will lock `graveyard_` which is usually always
// locked before any sample. This will appear as a lock inversion.
// However, this code is run exactly once per sample, and this sample
// cannot be accessed until after it is returned from this method. This
// means that this lock state can never be recreated, so we can safely
// inform the deadlock detector to ignore it.
sample->init_mu.ForgetDeadlockInfo();
sample->PrepareForSampling(std::forward<Targs>(args)...);
}
PushNew(sample);
}
return sample;
}
template <typename T>
void SampleRecorder<T>::Unregister(T* sample) {
PushDead(sample);
size_estimate_.fetch_sub(1, std::memory_order_relaxed);
}
template <typename T>
int64_t SampleRecorder<T>::Iterate(
const std::function<void(const T& stack)>& f) {
T* s = all_.load(std::memory_order_acquire);
while (s != nullptr) {
absl::MutexLock l(&s->init_mu);
if (s->dead == nullptr) {
f(*s);
}
s = s->next;
}
return dropped_samples_.load(std::memory_order_relaxed);
}
template <typename T>
void SampleRecorder<T>::SetMaxSamples(size_t max) {
max_samples_.store(max, std::memory_order_release);
}
template <typename T>
size_t SampleRecorder<T>::GetMaxSamples() const {
return max_samples_.load(std::memory_order_acquire);
}
} // namespace profiling_internal
ABSL_NAMESPACE_END
} // namespace absl
#endif // ABSL_PROFILING_INTERNAL_SAMPLE_RECORDER_H_