[client] New RingBufferAnnotation

This CL integrates the new ScopedSpinGuard with the new
LengthDelimitedRingBuffer into a new class, RingBufferAnnotation.

RingBufferAnnotation is thread-safe both for reading and writing, and is
suitable for streaming logs, trace events, and other high-throughput
data streams.

I included a load test (ring_buffer_annotation_load_test) which launches
two threads which simultaneously write to and read from the
RingBufferAnnotation.

By default, reads and writes are serialized using ScopedSpinGuard, but
passing the flag "--disable_spin_guard" to the test disables the spin
guard on the reading side (which is expected to make the test fail).

Change-Id: Ic8e28866d085d57e778c4f86bcb7492ef0638ab9
Bug: crashpad:437
Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/4023619
Reviewed-by: Robert Sesek <rsesek@chromium.org>
Reviewed-by: Mark Mentovai <mark@chromium.org>
Commit-Queue: Ben Hamilton <benhamilton@google.com>
This commit is contained in:
Ben Hamilton 2023-01-31 16:18:37 -07:00 committed by Crashpad LUCI CQ
parent 3215ed9086
commit 212b8f6b8c
4 changed files with 812 additions and 0 deletions

View File

@ -120,6 +120,7 @@ static_library("common") {
"crashpad_info.cc",
"crashpad_info.h",
"length_delimited_ring_buffer.h",
"ring_buffer_annotation.h",
"settings.cc",
"settings.h",
"simple_address_range_bag.h",
@ -148,6 +149,18 @@ static_library("common") {
configs += [ "../build:flock_always_supported_defines" ]
}
crashpad_executable("ring_buffer_annotation_load_test") {
testonly = true
sources = [
"ring_buffer_annotation_load_test_main.cc",
]
deps = [
":client",
"../tools:tool_support",
"$mini_chromium_source_parent:base",
]
}
source_set("client_test") {
testonly = true
@ -157,6 +170,7 @@ source_set("client_test") {
"crash_report_database_test.cc",
"length_delimited_ring_buffer_test.cc",
"prune_crash_reports_test.cc",
"ring_buffer_annotation_test.cc",
"settings_test.cc",
"simple_address_range_bag_test.cc",
"simple_string_dictionary_test.cc",

View File

@ -0,0 +1,136 @@
// Copyright 2023 The Crashpad 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.
#ifndef CRASHPAD_CLIENT_RING_BUFFER_ANNOTATION_H_
#define CRASHPAD_CLIENT_RING_BUFFER_ANNOTATION_H_
#include <stdio.h>
#include "client/annotation.h"
#include "client/length_delimited_ring_buffer.h"
namespace crashpad {
//! \brief Capacity of `RingBufferAnnotation`, in bytes.
using RingBufferAnnotationCapacity = RingBufferCapacity;
namespace internal {
//! \brief Default capacity of `RingBufferAnnotation`, in bytes.
inline constexpr RingBufferAnnotationCapacity
kDefaultRingBufferAnnotationCapacity = 8192;
} // namespace internal
//! \brief An `Annotation` which wraps a `LengthDelimitedRingBuffer`
//! of up to `Capacity` bytes in length.
//!
//! Supports writing variable-length data via `Push()`. When the ring buffer is
//! full, it will drop old data items in FIFO order until enough space is
//! available for the write.
//!
//! Supports guarding concurrent reads from writes via `ScopedSpinGuard`, so
//! writing to this object is thread-safe.
//!
//! Clients which read this `Annotation`'s memory can optionally invoke
//! `TryCreateScopedSpinGuard()` on this object to ensure any pending write
//! finishes before the memory is read.
//!
//! Each item in this ring buffer is delimited by its length encoded in
//! little-endian Base 128 varint encoding.
//!
//! `RingBufferAnnotation` uses varint-encoded delimiters to enable
//! zero-copy deserialization of the ringbuffer's contents when storing
//! protobufs inside the ringbuffer, e.g. via
//! `google::protobuf::util::ParseDelimitedFromZeroCopyStream()` or similar.
//!
//! \sa
//! https://github.com/protocolbuffers/protobuf/blob/3202b9da88ceb75b65bbabaf4033c95e872f828d/src/google/protobuf/util/delimited_message_util.h#L85
//! \sa
//! https://github.com/protocolbuffers/protobuf/blob/8bd49dea5e167a389d94b71d24c981d8f9fa0c99/src/google/protobuf/io/zero_copy_stream_impl_lite.h#L68
//! \sa
//! https://github.com/protocolbuffers/protobuf/blob/8bd49dea5e167a389d94b71d24c981d8f9fa0c99/src/google/protobuf/io/coded_stream.h#L171
//!
//! To deserialize the items stored in this annotation, use
//! `LengthDelimitedRingBufferReader`.
template <RingBufferAnnotationCapacity Capacity =
internal::kDefaultRingBufferAnnotationCapacity>
class RingBufferAnnotation final : public Annotation {
public:
//! \brief Constructs a `RingBufferAnnotation`.
//! \param[in] type A unique identifier for the type of data in the ring
//! buffer.
//! \param[in] name The name of the annotation.
constexpr RingBufferAnnotation(Annotation::Type type, const char name[])
: Annotation(type,
name,
reinterpret_cast<void* const>(&ring_buffer_data_),
ConcurrentAccessGuardMode::kScopedSpinGuard),
ring_buffer_data_(),
ring_buffer_writer_(ring_buffer_data_) {}
RingBufferAnnotation(const RingBufferAnnotation&) = delete;
RingBufferAnnotation& operator=(const RingBufferAnnotation&) = delete;
RingBufferAnnotation(RingBufferAnnotation&&) = default;
RingBufferAnnotation& operator=(RingBufferAnnotation&&) = default;
//! \brief Pushes data onto this annotation's ring buffer.
//!
//! If the ring buffer does not have enough space to store `buffer_length`
//! bytes of data, old data items are dropped in FIFO order until
//! enough space is available to store the new data.
bool Push(const void* const buffer,
RingBufferAnnotationCapacity buffer_length) {
// Use a zero timeout so the operation immediately fails if another thread
// or process is currently reading this Annotation.
constexpr uint64_t kSpinGuardTimeoutNanoseconds = 0;
auto spin_guard = TryCreateScopedSpinGuard(kSpinGuardTimeoutNanoseconds);
if (!spin_guard) {
return false;
}
bool success = ring_buffer_writer_.Push(buffer, buffer_length);
if (success) {
SetSize(ring_buffer_data_.GetRingBufferLength());
}
return success;
}
//! \brief Reset the annotation (e.g., for testing).
//! This method is not thread-safe.
void ResetForTesting() {
ring_buffer_data_.ResetForTesting();
ring_buffer_writer_.ResetForTesting();
}
private:
using RingBufferWriter =
LengthDelimitedRingBufferWriter<RingBufferData<Capacity>>;
//! \brief The ring buffer data stored in this Anotation.
RingBufferData<Capacity> ring_buffer_data_;
//! \brief The writer which wraps `ring_buffer_data_`.
RingBufferWriter ring_buffer_writer_;
};
// Allow just `RingBufferAnnotation foo;` to be declared without template
// arguments using C++17 class template argument deduction.
template <RingBufferAnnotationCapacity Capacity =
internal::kDefaultRingBufferAnnotationCapacity>
RingBufferAnnotation(Annotation::Type type, const char name[])
-> RingBufferAnnotation<Capacity>;
} // namespace crashpad
#endif // CRASHPAD_CLIENT_RING_BUFFER_ANNOTATION_H_

View File

@ -0,0 +1,474 @@
// Copyright 2023 The Crashpad 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 <getopt.h>
#include <inttypes.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <chrono>
#include <condition_variable>
#include <functional>
#include <mutex>
#include <optional>
#include <random>
#include <ratio>
#include <string>
#include "base/notreached.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_piece.h"
#include "base/strings/stringprintf.h"
#include "build/build_config.h"
#include "client/annotation.h"
#include "client/length_delimited_ring_buffer.h"
#include "client/ring_buffer_annotation.h"
#include "tools/tool_support.h"
#include "util/stdlib/string_number_conversion.h"
#include "util/synchronization/scoped_spin_guard.h"
#include "util/thread/thread.h"
#if BUILDFLAG(IS_WIN)
#include <windows.h>
#else
#include <signal.h>
#endif // BUILDFLAG(IS_WIN)
namespace crashpad {
namespace test {
namespace {
constexpr Annotation::Type kRingBufferLoadTestType =
Annotation::UserDefinedType(0x0042);
std::atomic<bool> g_should_exit = false;
struct RingBufferAnnotationSnapshotParams final {
enum class Mode {
kUseScopedSpinGuard = 1,
kDoNotUseSpinGuard = 2,
};
Mode mode = Mode::kUseScopedSpinGuard;
using Duration = std::chrono::duration<uint64_t, std::nano>;
Duration producer_thread_min_run_duration = std::chrono::milliseconds(1);
Duration producer_thread_max_run_duration = std::chrono::milliseconds(10);
Duration producer_thread_sleep_duration = std::chrono::nanoseconds(10);
Duration consumer_thread_min_run_duration = std::chrono::milliseconds(5);
Duration consumer_thread_max_run_duration = std::chrono::milliseconds(100);
Duration quiesce_timeout = std::chrono::microseconds(500);
uint64_t num_loops = std::numeric_limits<uint64_t>::max();
std::optional<Duration> main_thread_run_duration = std::nullopt;
};
template <uint32_t RingBufferCapacity>
class RingBufferAnnotationSnapshot final {
using RingBufferAnnotationType = RingBufferAnnotation<RingBufferCapacity>;
struct State final {
State()
: ring_buffer_annotation(kRingBufferLoadTestType,
"ring-buffer-load-test"),
ring_buffer_ready(false),
producer_thread_running(false),
producer_thread_finished(false),
consumer_thread_finished(false),
should_exit(false) {}
State(const State&) = delete;
State& operator=(const State&) = delete;
RingBufferAnnotationType ring_buffer_annotation;
bool ring_buffer_ready;
bool producer_thread_running;
bool producer_thread_finished;
bool consumer_thread_finished;
bool should_exit;
};
class Thread final : public crashpad::Thread {
public:
Thread(std::function<void()> thread_main)
: thread_main_(std::move(thread_main)) {}
private:
void ThreadMain() override { thread_main_(); }
const std::function<void()> thread_main_;
};
public:
RingBufferAnnotationSnapshot(const RingBufferAnnotationSnapshotParams& params)
: params_(params),
main_loop_thread_([this]() { MainLoopThreadMain(); }),
producer_thread_([this]() { ProducerThreadMain(); }),
consumer_thread_([this]() { ConsumerThreadMain(); }),
mutex_(),
state_changed_condition_(),
state_() {}
RingBufferAnnotationSnapshot(const RingBufferAnnotationSnapshot&) = delete;
RingBufferAnnotationSnapshot& operator=(const RingBufferAnnotationSnapshot&) =
delete;
void Start() {
main_loop_thread_.Start();
producer_thread_.Start();
consumer_thread_.Start();
}
void Stop() {
consumer_thread_.Join();
producer_thread_.Join();
main_loop_thread_.Join();
}
private:
void MainLoopThreadMain() {
std::chrono::steady_clock::time_point main_thread_end_time;
if (params_.main_thread_run_duration) {
main_thread_end_time =
std::chrono::steady_clock::now() + *params_.main_thread_run_duration;
} else {
main_thread_end_time = std::chrono::steady_clock::time_point::max();
}
for (uint64_t i = 0;
i < params_.num_loops &&
std::chrono::steady_clock::now() < main_thread_end_time;
i++) {
{
std::unique_lock<std::mutex> start_lock(mutex_);
state_.ring_buffer_annotation.ResetForTesting();
state_.ring_buffer_ready = true;
state_changed_condition_.notify_all();
}
{
std::unique_lock<std::mutex> lock(mutex_);
state_changed_condition_.wait(lock, [this] {
return state_.producer_thread_finished &&
state_.consumer_thread_finished;
});
state_.ring_buffer_ready = false;
if (g_should_exit) {
printf("Exiting on Control-C.\n");
break;
}
printf(".");
fflush(stdout);
state_changed_condition_.notify_all();
}
}
state_.should_exit = true;
state_changed_condition_.notify_all();
}
void ProducerThreadMain() {
while (true) {
{
std::unique_lock<std::mutex> lock(mutex_);
state_changed_condition_.wait(lock, [this] {
return state_.should_exit || state_.ring_buffer_ready;
});
if (state_.should_exit) {
return;
}
state_.producer_thread_running = true;
state_.producer_thread_finished = false;
state_changed_condition_.notify_all();
}
auto min_run_duration_micros =
std::chrono::duration_cast<std::chrono::microseconds>(
params_.producer_thread_min_run_duration);
auto max_run_duration_micros =
std::chrono::duration_cast<std::chrono::microseconds>(
params_.producer_thread_max_run_duration);
std::uniform_int_distribution<std::chrono::microseconds::rep>
run_duration_distribution(min_run_duration_micros.count(),
max_run_duration_micros.count());
static thread_local std::mt19937 random_number_generator;
auto run_duration = std::chrono::microseconds(
run_duration_distribution(random_number_generator));
auto end_time = std::chrono::steady_clock::now() + run_duration;
uint64_t next_value = 0;
while (std::chrono::steady_clock::now() < end_time) {
if (!Produce(next_value++)) {
// The consumer thread interrupted this.
break;
}
}
{
std::unique_lock<std::mutex> lock(mutex_);
state_changed_condition_.wait(
lock, [this] { return state_.consumer_thread_finished; });
state_.producer_thread_running = false;
state_.producer_thread_finished = true;
state_changed_condition_.notify_all();
}
}
}
bool Produce(uint64_t value) {
std::string hex_value = base::StringPrintf("0x%08" PRIx64, value);
if (!state_.ring_buffer_annotation.Push(
hex_value.data(), static_cast<uint32_t>(hex_value.size()))) {
fprintf(stderr,
"Ignoring failed call to Push(0x%" PRIx64
") (ScopedSpinGuard was held by snapshot thread)\n",
value);
return false;
}
return true;
}
void ConsumerThreadMain() {
while (true) {
{
std::unique_lock<std::mutex> lock(mutex_);
state_changed_condition_.wait(lock, [this] {
return state_.should_exit ||
(state_.ring_buffer_ready && state_.producer_thread_running);
});
if (state_.should_exit) {
return;
}
state_.consumer_thread_finished = false;
state_changed_condition_.notify_all();
}
auto min_run_duration_micros =
std::chrono::duration_cast<std::chrono::microseconds>(
params_.consumer_thread_min_run_duration);
auto max_run_duration_micros =
std::chrono::duration_cast<std::chrono::microseconds>(
params_.consumer_thread_max_run_duration);
std::uniform_int_distribution<std::chrono::microseconds::rep>
run_duration_distribution(min_run_duration_micros.count(),
max_run_duration_micros.count());
static thread_local std::mt19937 random_number_generator;
auto run_duration = std::chrono::microseconds(
run_duration_distribution(random_number_generator));
auto end_time = std::chrono::steady_clock::now() + run_duration;
while (std::chrono::steady_clock::now() < end_time) {
constexpr uint64_t kSleepTimeNs = 10000; // 10 us
SleepNanoseconds(kSleepTimeNs);
}
Snapshot();
{
std::unique_lock<std::mutex> lock(mutex_);
state_.consumer_thread_finished = true;
state_.ring_buffer_ready = false;
state_changed_condition_.notify_all();
}
}
}
void Snapshot() {
int64_t timeout_ns = static_cast<int64_t>(
std::chrono::duration_cast<std::chrono::nanoseconds>(
params_.quiesce_timeout)
.count());
uint8_t serialized_ring_buffer[sizeof(state_.ring_buffer_annotation)];
Annotation::ValueSizeType ring_buffer_size;
{
std::optional<ScopedSpinGuard> scoped_spin_guard;
if (params_.mode ==
RingBufferAnnotationSnapshotParams::Mode::kUseScopedSpinGuard) {
scoped_spin_guard =
state_.ring_buffer_annotation.TryCreateScopedSpinGuard(timeout_ns);
}
if (params_.mode ==
RingBufferAnnotationSnapshotParams::Mode::kUseScopedSpinGuard &&
!scoped_spin_guard) {
fprintf(stderr,
"Could not quiesce writes within %" PRIi64 " ns\n",
timeout_ns);
abort();
}
ring_buffer_size = state_.ring_buffer_annotation.size();
memcpy(&serialized_ring_buffer[0],
state_.ring_buffer_annotation.value(),
ring_buffer_size);
}
RingBufferData ring_buffer;
if (!ring_buffer.DeserializeFromBuffer(serialized_ring_buffer,
ring_buffer_size)) {
fprintf(stderr, "Could not deserialize ring buffer\n");
abort();
}
LengthDelimitedRingBufferReader ring_buffer_reader(ring_buffer);
int value = std::numeric_limits<int>::max();
std::vector<uint8_t> bytes;
while (ring_buffer_reader.Pop(bytes)) {
int next_value;
base::StringPiece str(reinterpret_cast<const char*>(&bytes[0]),
bytes.size());
if (!HexStringToInt(str, &next_value)) {
fprintf(
stderr, "Couldn't parse value: [%s]\n", str.as_string().c_str());
abort();
}
if (value == std::numeric_limits<int>::max()) {
// First value in buffer.
} else if (value + 1 != next_value) {
fprintf(stderr,
"Expected value 0x%08x, got 0x%08x\n",
value + 1,
next_value);
abort();
}
value = next_value;
bytes.clear();
}
}
const RingBufferAnnotationSnapshotParams params_;
Thread main_loop_thread_;
Thread producer_thread_;
Thread consumer_thread_;
std::mutex mutex_;
// Fired whenever `state_` changes.
std::condition_variable state_changed_condition_;
// Protected by `mutex_`.
State state_;
};
void Usage(const base::FilePath& me) {
// clang-format off
fprintf(stderr,
"Usage: %" PRFilePath " [OPTION]...\n"
"Runs a load test for concurrent I/O to RingBufferAnnotation.\n"
"\n"
"By default, enables the annotation spin guard and runs indefinitely\n"
"until interrupted (e.g., with Control-C or SIGINT).\n"
"\n"
" -d,--disable-spin-guard Disables the annotation spin guard\n"
" (the test is expected to crash in this case)\n"
" -n,--num-loops=N Runs the test for N iterations, not indefinitely\n"
" -s,--duration-secs=SECS Runs the test for SECS seconds, not indefinitely\n",
me.value().c_str());
// clang-format on
ToolSupport::UsageTail(me);
}
int TestMain(int argc, char** argv) {
const base::FilePath argv0(
ToolSupport::CommandLineArgumentToFilePathStringType(argv[0]));
const base::FilePath me(argv0.BaseName());
#if BUILDFLAG(IS_WIN)
auto handler_routine = [](DWORD type) -> BOOL {
if (type == CTRL_C_EVENT) {
g_should_exit = true;
return TRUE;
}
return FALSE;
};
if (!SetConsoleCtrlHandler(handler_routine, /*Add=*/TRUE)) {
fprintf(stderr, "Couldn't set Control-C handler\n");
return EXIT_FAILURE;
}
#else
signal(SIGINT, [](int signal) { g_should_exit = true; });
#endif // BUILDFLAG(IS_WIN)
RingBufferAnnotationSnapshotParams params;
enum OptionFlags {
// "Short" (single-character) options.
kOptionDisableSpinGuard = 'd',
kOptionNumLoops = 'n',
kOptionDurationSecs = 's',
// Standard options.
kOptionHelp = -2,
kOptionVersion = -3,
};
static constexpr option long_options[] = {
{"disable-spin-guard", no_argument, nullptr, kOptionDisableSpinGuard},
{"num-loops", required_argument, nullptr, kOptionNumLoops},
{"duration-secs", required_argument, nullptr, kOptionDurationSecs},
{"help", no_argument, nullptr, kOptionHelp},
{"version", no_argument, nullptr, kOptionVersion},
{nullptr, 0, nullptr, 0},
};
int opt;
while ((opt = getopt_long(argc, argv, "dn:s:", long_options, nullptr)) !=
-1) {
switch (opt) {
case kOptionDisableSpinGuard:
printf("Disabling spin guard logic (this test will fail!)\n");
params.mode =
RingBufferAnnotationSnapshotParams::Mode::kDoNotUseSpinGuard;
break;
case kOptionNumLoops: {
std::string num_loops(optarg);
uint64_t num_loops_value;
if (!StringToNumber(num_loops, &num_loops_value)) {
ToolSupport::UsageHint(me, "--num-loops requires integer value");
return EXIT_FAILURE;
}
params.num_loops = num_loops_value;
break;
}
case kOptionDurationSecs: {
std::string duration_secs(optarg);
uint64_t duration_secs_value;
if (!StringToNumber(duration_secs, &duration_secs_value)) {
ToolSupport::UsageHint(me, "--duration-secs requires integer value");
return EXIT_FAILURE;
}
params.main_thread_run_duration =
std::chrono::seconds(duration_secs_value);
break;
}
case kOptionHelp:
Usage(me);
return EXIT_SUCCESS;
case kOptionVersion:
ToolSupport::Version(me);
return EXIT_SUCCESS;
default:
ToolSupport::UsageHint(me, nullptr);
return EXIT_FAILURE;
}
}
RingBufferAnnotationSnapshot<8192> test_producer_snapshot(params);
printf("Starting test (Control-C to exit)...\n");
test_producer_snapshot.Start();
test_producer_snapshot.Stop();
printf("Test finished.\n");
return EXIT_SUCCESS;
}
} // namespace
} // namespace test
} // namespace crashpad
#if BUILDFLAG(IS_POSIX)
int main(int argc, char** argv) {
return crashpad::test::TestMain(argc, argv);
}
#elif BUILDFLAG(IS_WIN)
int wmain(int argc, wchar_t* argv[]) {
return crashpad::ToolSupport::Wmain(argc, argv, crashpad::test::TestMain);
}
#endif

View File

@ -0,0 +1,188 @@
// Copyright 2023 The Crashpad 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 "client/ring_buffer_annotation.h"
#include "client/length_delimited_ring_buffer.h"
#include <array>
#include <string>
#include "client/annotation_list.h"
#include "client/crashpad_info.h"
#include "gtest/gtest.h"
#include "test/gtest_death.h"
namespace crashpad {
namespace test {
namespace {
constexpr uint32_t kRingBufferHeaderSize = 16;
constexpr uint32_t kLengthDelimiter1ByteSize = 1;
class RingBufferAnnotationTest : public testing::Test {
public:
void SetUp() override {
CrashpadInfo::GetCrashpadInfo()->set_annotations_list(&annotations_);
}
void TearDown() override {
CrashpadInfo::GetCrashpadInfo()->set_annotations_list(nullptr);
}
size_t AnnotationsCount() {
size_t result = 0;
for (auto* annotation : annotations_) {
if (annotation->is_set())
++result;
}
return result;
}
protected:
AnnotationList annotations_;
};
TEST_F(RingBufferAnnotationTest, Basics) {
constexpr Annotation::Type kType = Annotation::UserDefinedType(1);
constexpr char kName[] = "annotation 1";
RingBufferAnnotation annotation(kType, kName);
EXPECT_FALSE(annotation.is_set());
EXPECT_EQ(0u, AnnotationsCount());
EXPECT_EQ(kType, annotation.type());
EXPECT_EQ(0u, annotation.size());
EXPECT_EQ(std::string(kName), annotation.name());
EXPECT_TRUE(
annotation.Push(reinterpret_cast<const uint8_t*>("0123456789"), 10));
EXPECT_TRUE(annotation.is_set());
EXPECT_EQ(1u, AnnotationsCount());
constexpr Annotation::ValueSizeType kExpectedSize =
kRingBufferHeaderSize + kLengthDelimiter1ByteSize + 10u;
EXPECT_EQ(kExpectedSize, annotation.size());
EXPECT_EQ(&annotation, *annotations_.begin());
RingBufferData data;
EXPECT_TRUE(
data.DeserializeFromBuffer(annotation.value(), annotation.size()));
EXPECT_EQ(kExpectedSize, data.GetRingBufferLength());
std::vector<uint8_t> popped_value;
LengthDelimitedRingBufferReader reader(data);
EXPECT_TRUE(reader.Pop(popped_value));
const std::vector<uint8_t> expected = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
EXPECT_EQ(expected, popped_value);
annotation.Clear();
EXPECT_FALSE(annotation.is_set());
EXPECT_EQ(0u, AnnotationsCount());
EXPECT_EQ(0u, annotation.size());
}
TEST_F(RingBufferAnnotationTest, MultiplePushesWithoutWrapping) {
constexpr Annotation::Type kType = Annotation::UserDefinedType(1);
constexpr char kName[] = "annotation 1";
RingBufferAnnotation annotation(kType, kName);
EXPECT_TRUE(
annotation.Push(reinterpret_cast<const uint8_t*>("0123456789"), 10));
EXPECT_TRUE(annotation.Push(reinterpret_cast<const uint8_t*>("ABCDEF"), 6));
EXPECT_TRUE(annotation.is_set());
EXPECT_EQ(1u, AnnotationsCount());
constexpr Annotation::ValueSizeType kExpectedSize =
kRingBufferHeaderSize + kLengthDelimiter1ByteSize + 10u +
kLengthDelimiter1ByteSize + 6u;
EXPECT_EQ(kExpectedSize, annotation.size());
EXPECT_EQ(&annotation, *annotations_.begin());
RingBufferData data;
EXPECT_TRUE(
data.DeserializeFromBuffer(annotation.value(), annotation.size()));
EXPECT_EQ(kExpectedSize, data.GetRingBufferLength());
std::vector<uint8_t> popped_value;
LengthDelimitedRingBufferReader reader(data);
EXPECT_TRUE(reader.Pop(popped_value));
const std::vector<uint8_t> expected1 = {
'0', '1', '2', '3', '4', '5', '6', '7', '8', '9'};
EXPECT_EQ(expected1, popped_value);
popped_value.clear();
EXPECT_TRUE(reader.Pop(popped_value));
const std::vector<uint8_t> expected2 = {'A', 'B', 'C', 'D', 'E', 'F'};
EXPECT_EQ(expected2, popped_value);
}
TEST_F(RingBufferAnnotationTest,
MultiplePushCallsWithWrappingShouldOverwriteInFIFOOrder) {
constexpr Annotation::Type kType = Annotation::UserDefinedType(1);
constexpr char kName[] = "annotation 1";
RingBufferAnnotation<10> annotation(kType, kName);
// Each Push() call will push 1 byte for the varint 128-encoded length,
// then the number of bytes specified.
constexpr char kFirst[] = "AAA";
constexpr char kSecond[] = "BBB";
constexpr char kThird[] = "CCC";
// This takes up bytes 0-3 of the 10-byte RingBufferAnnotation.
ASSERT_TRUE(annotation.Push(reinterpret_cast<const uint8_t*>(kFirst), 3));
// This takes up bytes 4-7 of the 10-byte RingBufferAnnotation.
ASSERT_TRUE(annotation.Push(reinterpret_cast<const uint8_t*>(kSecond), 3));
// This should wrap around the end of the array and overwrite kFirst since it
// needs 4 bytes but there are only 2 left.
ASSERT_TRUE(annotation.Push(reinterpret_cast<const uint8_t*>(kThird), 3));
// The size of the annotation should include the header and the full 10 bytes
// of the ring buffer, since the third write wrapped around the end.
ASSERT_EQ(kRingBufferHeaderSize + 10u, annotation.size());
// This data size needs to match the size in the RingBufferAnnotation above.
RingBufferData<10> data;
ASSERT_TRUE(
data.DeserializeFromBuffer(annotation.value(), annotation.size()));
std::vector<uint8_t> popped_value;
LengthDelimitedRingBufferReader reader(data);
ASSERT_TRUE(reader.Pop(popped_value));
// "AAA" has been overwritten, so the first thing popped should be "BBB".
const std::vector<uint8_t> expected_b = {'B', 'B', 'B'};
EXPECT_EQ(expected_b, popped_value);
popped_value.clear();
ASSERT_TRUE(reader.Pop(popped_value));
const std::vector<uint8_t> expected_c = {'C', 'C', 'C'};
EXPECT_EQ(expected_c, popped_value);
}
} // namespace
} // namespace test
} // namespace crashpad