mirror of
https://github.com/chromium/crashpad.git
synced 2024-12-25 22:30:49 +08:00
[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:
parent
3215ed9086
commit
212b8f6b8c
@ -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",
|
||||
|
136
client/ring_buffer_annotation.h
Normal file
136
client/ring_buffer_annotation.h
Normal 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_
|
474
client/ring_buffer_annotation_load_test_main.cc
Normal file
474
client/ring_buffer_annotation_load_test_main.cc
Normal 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
|
188
client/ring_buffer_annotation_test.cc
Normal file
188
client/ring_buffer_annotation_test.cc
Normal 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
|
Loading…
x
Reference in New Issue
Block a user