mirror of
https://github.com/chromium/crashpad.git
synced 2024-12-27 07:14:10 +08:00
90bba04e22
StringToInt(string_piece) works because base::StringPiece is in namespace base, but when it is switched to std::string_view, this won't work anymore. Use the idiomatic spelling. Bug: chromium:691162 Change-Id: Ic45e0d2729fa5fc7c3e7a56fe159957b1bdcdf94 Reviewed-on: https://chromium-review.googlesource.com/c/crashpad/crashpad/+/4298113 Commit-Queue: David Benjamin <davidben@chromium.org> Reviewed-by: Robert Sesek <rsesek@chromium.org>
477 lines
16 KiB
C++
477 lines
16 KiB
C++
// 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 (!base::HexStringToInt(str, &next_value)) {
|
|
fprintf(stderr,
|
|
"Couldn't parse value: [%.*s]\n",
|
|
base::checked_cast<int>(bytes.size()),
|
|
bytes.data());
|
|
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
|