blob: f4424c59eca2beb900b96c092a0aaae4c3d28634 [file] [log] [blame]
// Copyright 2018 The Fuchsia Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
#include <cobalt-client/cpp/histogram.h>
#include <lib/fit/function.h>
#include <lib/sync/completion.h>
#include <lib/zx/time.h>
#include <algorithm>
#include <array>
#include <atomic>
#include <cstdint>
#include <mutex>
#include <string_view>
#include <thread>
#include <cobalt-client/cpp/collector.h>
#include <cobalt-client/cpp/histogram_internal.h>
#include <cobalt-client/cpp/in_memory_logger.h>
#include <cobalt-client/cpp/metric_options.h>
#include <zxtest/zxtest.h>
namespace cobalt_client {
namespace internal {
namespace {
constexpr uint32_t kBucketCount = 20;
using TestBaseHistogram = BaseHistogram<kBucketCount>;
TEST(BaseHistogramTest, BucketCountStartsAtZero) {
TestBaseHistogram histogram;
ASSERT_EQ(kBucketCount, histogram.size());
for (uint32_t i = 0; i < kBucketCount; ++i) {
EXPECT_EQ(0, histogram.GetCount(i));
}
}
TEST(BaseHistogramTest, IncrementCountByDefaultIncrementsBucketCountByOne) {
constexpr uint32_t kTargetBucket = 2;
TestBaseHistogram histogram;
histogram.IncrementCount(kTargetBucket);
for (uint32_t i = 0; i < kBucketCount; ++i) {
if (i == kTargetBucket) {
continue;
}
EXPECT_EQ(0, histogram.GetCount(i));
}
EXPECT_EQ(1, histogram.GetCount(kTargetBucket));
}
TEST(BaseHistogramTest, IncrementCountWithValueIncrementsBucketCountByValue) {
constexpr uint32_t kTargetBucket = 2;
constexpr uint64_t kValue = 123456;
TestBaseHistogram histogram;
histogram.IncrementCount(kTargetBucket, kValue);
for (uint32_t i = 0; i < kBucketCount; ++i) {
if (i == kTargetBucket) {
continue;
}
EXPECT_EQ(0, histogram.GetCount(i));
}
EXPECT_EQ(kValue, histogram.GetCount(kTargetBucket));
}
TEST(BaseHistogramTest, IncrementCountIsIsolated) {
constexpr std::array<uint32_t, 5> kTargetBuckets = {2, 4, 6, 8, 10};
constexpr uint64_t kValue = 123456;
TestBaseHistogram histogram;
for (auto bucket : kTargetBuckets) {
histogram.IncrementCount(bucket, kValue + bucket);
}
for (uint32_t i = 0; i < kBucketCount; ++i) {
if (std::find(kTargetBuckets.begin(), kTargetBuckets.end(), i) != kTargetBuckets.end()) {
EXPECT_EQ(kValue + i, histogram.GetCount(i));
continue;
}
EXPECT_EQ(0, histogram.GetCount(i));
}
}
TEST(BaseHistogramTest, IncrementCountFromMultipleTheadsIsConsistent) {
constexpr uint64_t kThreadCount = 20;
constexpr uint64_t kTimes = 200;
TestBaseHistogram histogram;
std::array<std::thread, kThreadCount> incrementing_threads;
sync_completion_t start_signal;
auto increment_fn = [&histogram, &start_signal]() {
sync_completion_wait(&start_signal, zx::duration::infinite().get());
for (uint64_t i = 0; i < kTimes; ++i) {
for (uint32_t bucket_index = 0; bucket_index < histogram.size(); ++bucket_index) {
histogram.IncrementCount(bucket_index);
}
}
};
for (uint64_t i = 0; i < kThreadCount; i++) {
incrementing_threads[i] = std::thread(increment_fn);
}
sync_completion_signal(&start_signal);
for (auto& thread : incrementing_threads) {
thread.join();
}
constexpr uint64_t kExpectedCount = kTimes * kThreadCount;
for (uint32_t bucket_index = 0; bucket_index < histogram.size(); ++bucket_index) {
EXPECT_EQ(kExpectedCount, histogram.GetCount(bucket_index));
}
}
using TestRemoteHistogram = RemoteHistogram<kBucketCount>;
// Default id for the histogram.
constexpr uint64_t kMetricId = 1;
// Default component name.
constexpr std::string_view kComponentName = "RemoteHisotgramComponentName";
// Default event codes.
constexpr std::array<uint32_t, MetricOptions::kMaxEventCodes> kEventCodes = {1, 2, 3, 4, 5};
HistogramOptions MakeHistogramOptions() {
HistogramOptions options = HistogramOptions::CustomizedExponential(kBucketCount, 2, 1, 0);
options.metric_id = kMetricId;
options.component = kComponentName;
options.event_codes = kEventCodes;
return options;
}
TestRemoteHistogram MakeRemoteHistogram() { return TestRemoteHistogram(MakeHistogramOptions()); }
TEST(RemoteHistogramTest, FlushSetsBucketsToZeroAndReturnsTrueIfLogSucceeds) {
TestRemoteHistogram histogram = MakeRemoteHistogram();
InMemoryLogger logger;
logger.fail_logging(false);
for (uint32_t bucket_index = 0; bucket_index < histogram.size(); ++bucket_index) {
histogram.IncrementCount(bucket_index, bucket_index + 1);
}
ASSERT_TRUE(histogram.Flush(&logger));
for (uint32_t bucket_index = 0; bucket_index < histogram.size(); ++bucket_index) {
EXPECT_EQ(0, histogram.GetCount(bucket_index));
}
const auto& logged_histograms = logger.histograms();
auto logged_histogram_itr = logged_histograms.find(histogram.metric_options());
ASSERT_NE(logged_histograms.end(), logged_histogram_itr);
const auto& logged_histogram = logged_histogram_itr->second;
for (uint32_t bucket_index = 0; bucket_index < histogram.size(); ++bucket_index) {
const uint64_t kExpectedCount = bucket_index + 1;
ASSERT_NE(logged_histogram.end(), logged_histogram.find(bucket_index));
EXPECT_EQ(kExpectedCount, logged_histogram.at(bucket_index));
}
}
TEST(RemoteHistogramTest, FlushSetsBucketsToZeroAndReturnsFalseIfLogFails) {
TestRemoteHistogram histogram = MakeRemoteHistogram();
InMemoryLogger logger;
logger.fail_logging(true);
for (uint32_t bucket_index = 0; bucket_index < histogram.size(); ++bucket_index) {
histogram.IncrementCount(bucket_index, bucket_index + 1);
}
ASSERT_FALSE(histogram.Flush(&logger));
for (uint32_t bucket_index = 0; bucket_index < histogram.size(); ++bucket_index) {
EXPECT_EQ(0, histogram.GetCount(bucket_index));
}
const auto& logged_histograms = logger.histograms();
auto logged_histogram_itr = logged_histograms.find(histogram.metric_options());
ASSERT_EQ(logged_histograms.end(), logged_histogram_itr);
}
TEST(RemoteHistogramTest, UndoFlushSetsCounterToPreviousValue) {
TestRemoteHistogram histogram = MakeRemoteHistogram();
InMemoryLogger logger;
logger.fail_logging(true);
for (uint32_t bucket_index = 0; bucket_index < histogram.size(); ++bucket_index) {
histogram.IncrementCount(bucket_index, bucket_index + 1);
}
ASSERT_FALSE(histogram.Flush(&logger));
histogram.UndoFlush();
for (uint32_t bucket_index = 0; bucket_index < histogram.size(); ++bucket_index) {
EXPECT_EQ(bucket_index + 1, histogram.GetCount(bucket_index));
}
}
using TestHistogram = Histogram<kBucketCount>;
TEST(HistogramTest, ConstructFromOptionsIsOk) {
ASSERT_NO_DEATH([] { [[maybe_unused]] TestHistogram histogram(MakeHistogramOptions()); });
}
TEST(HistogramTest, ConstructFromOptionsWithCollectorIsOk) {
ASSERT_NO_DEATH([] {
std::unique_ptr<InMemoryLogger> logger = std::make_unique<InMemoryLogger>();
Collector collector(std::move(logger));
[[maybe_unused]] TestHistogram histogram(MakeHistogramOptions(), &collector);
});
}
TEST(HistogramTest, InitilizeAlreadyInitializedHistogramIsAssertionError) {
std::unique_ptr<InMemoryLogger> logger = std::make_unique<InMemoryLogger>();
Collector collector(std::move(logger));
HistogramOptions histogram_options = MakeHistogramOptions();
[[maybe_unused]] TestHistogram histogram(histogram_options, &collector);
ASSERT_DEATH([&]() { histogram.Initialize(histogram_options, &collector); });
}
void InMemoryLoggerContainsHistogramWithBucketCount(const TestHistogram& histogram,
const InMemoryLogger& logger,
int64_t logged_value, uint64_t logged_count) {
const auto& logged_histograms = logger.histograms();
auto logged_histogram_itr = logged_histograms.find(histogram.GetOptions());
ASSERT_NE(logged_histograms.end(), logged_histogram_itr);
const auto& logged_histogram = logged_histogram_itr->second;
uint32_t bucket_index = histogram.GetOptions().map_fn(static_cast<double>(logged_value),
histogram.size(), histogram.GetOptions());
auto bucket_itr = logged_histogram.find(bucket_index);
ASSERT_NE(logged_histogram.end(), bucket_itr);
uint64_t actual_count = bucket_itr->second;
EXPECT_EQ(logged_count, actual_count);
}
fit::function<void(uint64_t, uint64_t)> MakeLoggedHistogramContainsChecker(
const TestHistogram& histogram, const InMemoryLogger& logger) {
return [&histogram, &logger](uint64_t value, uint64_t count) {
InMemoryLoggerContainsHistogramWithBucketCount(histogram, logger, value, count);
};
}
TEST(HistogramTest, AddIncreasesCorrectBucketCount) {
constexpr uint64_t kValue = 25;
std::unique_ptr<InMemoryLogger> logger = std::make_unique<InMemoryLogger>();
logger->fail_logging(false);
auto* logger_ptr = logger.get();
Collector collector(std::move(logger));
TestHistogram histogram(MakeHistogramOptions(), &collector);
histogram.Add(kValue);
ASSERT_EQ(1, histogram.GetCount(kValue));
ASSERT_TRUE(collector.Flush());
ASSERT_EQ(0, histogram.GetCount(kValue));
auto logged_histogram_contains = MakeLoggedHistogramContainsChecker(histogram, *logger_ptr);
ASSERT_NO_FAILURES(logged_histogram_contains(kValue, 1));
}
TEST(HistogramTest, AddWithCountIncreasesCorrectBucketCount) {
constexpr uint64_t kValue = 25;
constexpr uint64_t kCount = 25678;
std::unique_ptr<InMemoryLogger> logger = std::make_unique<InMemoryLogger>();
logger->fail_logging(false);
auto* logger_ptr = logger.get();
Collector collector(std::move(logger));
TestHistogram histogram(MakeHistogramOptions(), &collector);
histogram.Add(kValue, kCount);
ASSERT_EQ(kCount, histogram.GetCount(kValue));
ASSERT_TRUE(collector.Flush());
ASSERT_EQ(0, histogram.GetCount(kValue));
auto logged_histogram_contains = MakeLoggedHistogramContainsChecker(histogram, *logger_ptr);
ASSERT_NO_FAILURES(logged_histogram_contains(kValue, kCount));
}
TEST(HistogramTest, AddIncreasesCountByOne) {
constexpr uint64_t kValue = 25;
TestHistogram histogram(MakeHistogramOptions());
histogram.Add(kValue);
EXPECT_EQ(1, histogram.GetCount(kValue));
}
TEST(HistogramTest, AddValueIncreasesCountByValue) {
constexpr uint64_t kValue = 25;
constexpr uint64_t kTimes = 100;
TestHistogram histogram(MakeHistogramOptions());
histogram.Add(kValue, kTimes);
EXPECT_EQ(kTimes, histogram.GetCount(kValue));
}
TEST(HistogramTest, AddOnMultipleThreadsWithSynchronizedFlushingIsConsistent) {
constexpr uint64_t kTimes = 1;
constexpr uint64_t kThreadCount = 20;
std::unique_ptr<InMemoryLogger> logger = std::make_unique<InMemoryLogger>();
auto* logger_ptr = logger.get();
std::mutex logger_mutex;
Collector collector(std::move(logger));
TestHistogram histogram(MakeHistogramOptions(), &collector);
sync_completion_t start_signal;
std::array<std::thread, kThreadCount> spamming_threads = {};
auto get_value_for_bucket = [&histogram](uint32_t bucket_index) {
const double bucket_value = histogram.GetOptions().reverse_map_fn(
bucket_index, histogram.size(), histogram.GetOptions());
return static_cast<int64_t>(
std::max<double>(std::numeric_limits<int64_t>::min(), bucket_value));
};
auto increment_fn = [&histogram, &start_signal, &get_value_for_bucket]() {
sync_completion_wait(&start_signal, zx::duration::infinite().get());
for (uint64_t i = 0; i < kTimes; ++i) {
for (uint32_t bucket_index = 0; bucket_index < histogram.size(); ++bucket_index) {
const int64_t kValue = get_value_for_bucket(bucket_index);
histogram.Add(kValue, bucket_index + 1);
}
}
};
auto flushing_fn = [&collector, &logger_ptr, &logger_mutex, &start_signal]() {
sync_completion_wait(&start_signal, zx::duration::infinite().get());
for (uint64_t i = 0; i < kTimes; ++i) {
bool result = collector.Flush();
{
std::lock_guard lock(logger_mutex);
logger_ptr->fail_logging(!result);
}
}
};
for (uint64_t thread = 0; thread < kThreadCount; ++thread) {
if (thread % 2 == 0) {
spamming_threads[thread] = std::thread(increment_fn);
} else {
spamming_threads[thread] = std::thread(flushing_fn);
}
}
sync_completion_signal(&start_signal);
for (auto& thread : spamming_threads) {
thread.join();
}
logger_ptr->fail_logging(false);
ASSERT_TRUE(collector.Flush());
constexpr uint64_t kBaseExpectedCount = (kThreadCount / 2) * kTimes;
auto logged_histogram_contains = MakeLoggedHistogramContainsChecker(histogram, *logger_ptr);
for (uint32_t bucket_index = 0; bucket_index < histogram.size(); ++bucket_index) {
const uint64_t kExpectedCount = kBaseExpectedCount * (bucket_index + 1);
const int64_t kValue = get_value_for_bucket(bucket_index);
ASSERT_NO_FAILURES(logged_histogram_contains(kValue, kExpectedCount));
}
}
} // namespace
} // namespace internal
} // namespace cobalt_client