#include <atomic>
#include "base/compiler_specific.h"
#include "base/containers/span.h"
#include "base/logging.h"
#include "base/metrics/histogram_functions.h"
#include "base/metrics/persistent_histogram_allocator.h"
#include "base/metrics/statistics_recorder.h"
#include "base/strings/strcat.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h"
#include "base/threading/platform_thread.h"
#include "base/threading/simple_thread.h"
#include "base/time/time.h"
#include "build/build_config.h"
#include "testing/gtest/include/gtest/gtest.h"
namespace base {
namespace {
constexpr TimeDelta kTestRunningTime = Seconds(5);
constexpr char kHistogramNamePrefix[] = "SRStarvationTest.";
class BaseThread : public SimpleThread {
public:
explicit BaseThread(const std::string& thread_name)
: SimpleThread(thread_name, Options()), thread_name_(thread_name) {}
BaseThread(const BaseThread&) = delete;
BaseThread& operator=(const BaseThread&) = delete;
~BaseThread() override = default;
const std::string& thread_name() const LIFETIME_BOUND { return thread_name_; }
void SetStartTime() { start_time_ = Time::Now(); }
bool ShouldStop() { return stop_.load(std::memory_order_relaxed); }
void Stop() {
stop_.store(true, std::memory_order_relaxed);
end_time_ = Time::Now();
}
void IncrementIterCount() { ++iter_count_; }
size_t iter_count() { return iter_count_; }
TimeDelta running_time() { return end_time_ - start_time_; }
TimeDelta average_time_per_iter() { return running_time() / iter_count_; }
private:
std::string thread_name_;
std::atomic<bool> stop_ = false;
size_t iter_count_ = 0;
Time start_time_;
Time end_time_;
};
class ReadThread : public BaseThread {
public:
explicit ReadThread(size_t id)
: BaseThread(/*thread_name=*/StrCat({"ReadThread", NumberToString(id)})) {
}
void Run() override {
SetStartTime();
const std::string histogram_name =
StrCat({kHistogramNamePrefix, "ReadThreadHistogram"});
while (!ShouldStop()) {
UmaHistogramBoolean(histogram_name, false);
IncrementIterCount();
}
}
};
class WriteThread : public BaseThread {
public:
explicit WriteThread(size_t id)
: BaseThread(
/*thread_name=*/StrCat({"WriteThread", NumberToString(id)})) {}
void Run() override {
SetStartTime();
const std::string base_name =
StrCat({kHistogramNamePrefix, thread_name(), ".Iteration"});
while (!ShouldStop()) {
UmaHistogramBoolean(StrCat({base_name, NumberToString(iter_count())}),
false);
IncrementIterCount();
}
}
};
}
struct StarvationTestParams {
size_t num_read_threads;
size_t num_write_threads;
};
enum class FirstThreadsToStart {
kReaders,
kWriters,
};
class StatisticsRecorderStarvationTest
: public testing::TestWithParam<
std::tuple<StarvationTestParams, FirstThreadsToStart>> {
public:
StatisticsRecorderStarvationTest() = default;
StatisticsRecorderStarvationTest(const StatisticsRecorderStarvationTest&) =
delete;
StatisticsRecorderStarvationTest& operator=(
const StatisticsRecorderStarvationTest&) = delete;
~StatisticsRecorderStarvationTest() override = default;
void SetUp() override {
sr_ = StatisticsRecorder::CreateTemporaryForTesting();
for (size_t i = 0; i < 10000; ++i) {
UmaHistogramBoolean(
StrCat({kHistogramNamePrefix, "Dummy", NumberToString(i)}), false);
}
}
void TearDown() override {
auto histograms = sr_->GetHistograms();
sr_.reset();
for (auto* histogram : histograms) {
if (StartsWith(histogram->histogram_name(), kHistogramNamePrefix)) {
delete histogram;
}
}
}
void StartThreads() {
for (size_t i = 0; i < num_read_threads(); ++i) {
read_threads_.emplace_back(std::make_unique<ReadThread>(i));
}
for (size_t i = 0; i < num_write_threads(); ++i) {
write_threads_.emplace_back(std::make_unique<WriteThread>(i));
}
span<std::unique_ptr<BaseThread>> start_first_threads;
span<std::unique_ptr<BaseThread>> start_second_threads;
switch (GetFirstThreadsToStart()) {
case FirstThreadsToStart::kReaders:
start_first_threads = read_threads_;
start_second_threads = write_threads_;
break;
case FirstThreadsToStart::kWriters:
start_first_threads = write_threads_;
start_second_threads = read_threads_;
break;
}
for (auto& thread : start_first_threads) {
thread->Start();
}
PlatformThread::Sleep(Milliseconds(100));
for (auto& thread : start_second_threads) {
thread->Start();
}
}
void StopThreads() {
for (auto* thread : GetAllThreads()) {
thread->Stop();
thread->Join();
}
}
std::vector<BaseThread*> GetAllThreads() {
std::vector<BaseThread*> threads;
threads.reserve(num_read_threads() + num_write_threads());
for (auto& read_thread : read_threads_) {
threads.push_back(read_thread.get());
}
for (auto& write_thread : write_threads_) {
threads.push_back(write_thread.get());
}
return threads;
}
size_t num_read_threads() { return std::get<0>(GetParam()).num_read_threads; }
size_t num_write_threads() {
return std::get<0>(GetParam()).num_write_threads;
}
FirstThreadsToStart GetFirstThreadsToStart() {
return std::get<1>(GetParam());
}
protected:
std::vector<std::unique_ptr<BaseThread>> read_threads_;
std::vector<std::unique_ptr<BaseThread>> write_threads_;
private:
std::unique_ptr<StatisticsRecorder> sr_;
};
TEST_P(StatisticsRecorderStarvationTest, StatisticsRecorderNoStarvation) {
ASSERT_FALSE(GlobalHistogramAllocator::Get());
StartThreads();
PlatformThread::Sleep(kTestRunningTime);
StopThreads();
static constexpr TimeDelta kStarvationThreshold = Milliseconds(50);
std::vector<BaseThread*> threads = GetAllThreads();
for (auto* thread : threads) {
EXPECT_LT(thread->average_time_per_iter(), kStarvationThreshold);
}
LOG(INFO) << "Params: num_read_threads=" << num_read_threads()
<< ", num_write_threads=" << num_write_threads()
<< ", FirstThreadsToStart="
<< static_cast<int>(GetFirstThreadsToStart());
for (auto* thread : threads) {
LOG(INFO) << thread->thread_name() << " iter_count=" << thread->iter_count()
<< ", running_time=" << thread->running_time()
<< ", average_time_per_iter=" << thread->average_time_per_iter();
}
}
INSTANTIATE_TEST_SUITE_P(
All,
StatisticsRecorderStarvationTest,
testing::Combine(
testing::Values(StarvationTestParams{.num_read_threads = 10,
.num_write_threads = 1},
StarvationTestParams{.num_read_threads = 1,
.num_write_threads = 10},
StarvationTestParams{.num_read_threads = 1,
.num_write_threads = 1},
StarvationTestParams{.num_read_threads = 5,
.num_write_threads = 5}),
testing::Values(FirstThreadsToStart::kReaders,
FirstThreadsToStart::kWriters)));
}