改结构

This commit is contained in:
2026-08-29 03:03:34 +08:00
parent 31c8076822
commit 05a79fc351
23 changed files with 840 additions and 332 deletions
+416 -180
View File
@@ -1,80 +1,27 @@
#include <mcp/core/Control_Requests.hpp>
#include <mcp/core/Control_Service.hpp>
#include <mcp/core/Protocol_Type.hpp>
#include <mcp/core/runtime/Gallery_Plots.hpp>
#include <benchmark/benchmark.h>
#include <render_common.hpp>
#include <algorithm>
#include <array>
#include <atomic>
#include <charconv>
#include <chrono>
#include <cstdint>
#include <iomanip>
#include <iostream>
#include <limits>
#include <memory>
#include <optional>
#include <ranges>
#include <string>
#include <string_view>
#include <vector>
namespace aethera::mcp::benchmarks {
namespace {
[[nodiscard]] Plot_Input_Request timestamped_drag_request(
Event_Type type = Event_Type::pointer_move) {
Plot_Input_Request request;
request.plot = "datoviz_point";
request.event.type = type;
request.event.time_milliseconds = 1'000.0;
request.event.position = {240.0, 180.0};
request.event.global_position = {240.0, 180.0};
request.event.button = Mouse_Button::left;
request.event.buttons = 1;
return request;
}
void decode_timestamped_drag(benchmark::State& state) {
const auto encoded = encode_protocol_value(timestamped_drag_request());
for ([[maybe_unused]] auto iteration : state) {
Plot_Input_Request decoded;
decode_protocol_value(decoded, encoded);
benchmark::DoNotOptimize(decoded.event.time_milliseconds);
benchmark::ClobberMemory();
}
state.SetItemsProcessed(state.iterations());
}
void decode_timed_render(benchmark::State& state) {
const auto encoded = encode_protocol_value(
Plot_Render_Request{"datoviz_point", 1'000.0, 720, 420});
for ([[maybe_unused]] auto iteration : state) {
Plot_Render_Request decoded;
decode_protocol_value(decoded, encoded);
benchmark::DoNotOptimize(decoded.time_milliseconds);
benchmark::ClobberMemory();
}
state.SetItemsProcessed(state.iterations());
}
class Control_Path : public benchmark::Fixture {
public:
void SetUp(const benchmark::State&) override {
service = Control_Service::create();
}
void TearDown(const benchmark::State&) override {
service.reset();
}
protected:
std::shared_ptr<Control_Service> service;
};
constexpr std::array<std::string_view, 16> plot_3d_ids{
"datoviz_point", "datoviz_splat", "datoviz_pixel", "datoviz_marker",
"datoviz_sphere", "datoviz_segment", "datoviz_vector",
"datoviz_primitive", "datoviz_mesh", "datoviz_spectrogram",
"datoviz_path", "datoviz_image", "datoviz_labels", "datoviz_glyph",
"datoviz_text", "datoviz_volume"};
constexpr std::size_t splat_plot_index{1};
enum struct Input_Workload : std::uint8_t {
steady,
drag,
@@ -82,17 +29,156 @@ enum struct Input_Workload : std::uint8_t {
mixed
};
struct Benchmark_Configuration {
std::vector<std::string_view> plot_ids{}; /* 本进程并发打开的 Gallery Plot ID,直接引用 Gallery 权威定义。 */
Input_Workload input{Input_Workload::mixed}; /* 本进程向所选图提交的交互负载。 */
std::uint32_t width{720}; /* 每个输出流的像素宽度。 */
std::uint32_t height{420}; /* 每个输出流的像素高度。 */
std::uint32_t input_rate_hz{120}; /* 输入批次目标频率;零表示 steady。 */
double duration_seconds{10.0}; /* 单次测量的明确墙钟时长。 */
};
enum struct Parse_Benchmark_Arguments_Result : std::uint8_t {
configured,
help_requested,
invalid_argument
};
Benchmark_Configuration configuration{};
std::string configuration_error{};
[[nodiscard]] bool append_plot_id(std::string_view id) {
if (std::ranges::find(configuration.plot_ids, id) !=
configuration.plot_ids.end()) return true;
if (!web::find_gallery_plot_definition(id)) return false;
configuration.plot_ids.push_back(id);
return true;
}
void append_dimension(web::Plot_Dimension dimension) {
for (const auto& definition : web::gallery_plot_definitions())
if (definition.dimension == dimension)
static_cast<void>(append_plot_id(definition.id));
}
[[nodiscard]] bool select_plots(std::string_view specification) {
configuration.plot_ids.clear();
while (!specification.empty()) {
const auto separator = specification.find(',');
const auto token = specification.substr(0, separator);
if (token == "2d") append_dimension(web::Plot_Dimension::two_d);
else if (token == "3d") append_dimension(web::Plot_Dimension::three_d);
else if (token == "all") {
append_dimension(web::Plot_Dimension::two_d);
append_dimension(web::Plot_Dimension::three_d);
}
else if (token.empty() || !append_plot_id(token)) return false;
if (separator == std::string_view::npos) break;
specification.remove_prefix(separator + 1U);
}
return !configuration.plot_ids.empty();
}
[[nodiscard]] bool parse_unsigned(std::string_view text,
std::uint32_t& value) {
const auto* begin = text.data();
const auto* end = begin + text.size();
const auto result = std::from_chars(begin, end, value);
return result.ec == std::errc{} && result.ptr == end;
}
[[nodiscard]] bool parse_duration(std::string_view text, double& value) {
const auto* begin = text.data();
const auto* end = begin + text.size();
const auto result = std::from_chars(begin, end, value);
return result.ec == std::errc{} && result.ptr == end &&
value > 0.0 && value <= 3'600.0;
}
[[nodiscard]] Parse_Benchmark_Arguments_Result parse_arguments(
int& argc, char** argv) {
configuration = {};
if (!select_plots("3d")) {
configuration_error = "the Gallery has no 3D plots";
return Parse_Benchmark_Arguments_Result::invalid_argument;
}
int retained_count{1};
for (int index = 1; index < argc; ++index) {
const std::string_view argument{argv[index]};
if (argument == "--aethera_help")
return Parse_Benchmark_Arguments_Result::help_requested;
const auto value_after = [&](std::string_view prefix)
-> std::optional<std::string_view> {
if (!argument.starts_with(prefix)) return std::nullopt;
return argument.substr(prefix.size());
};
if (const auto value = value_after("--aethera_plots=")) {
if (!select_plots(*value)) {
configuration_error = "invalid --aethera_plots selection: " +
std::string{*value};
return Parse_Benchmark_Arguments_Result::invalid_argument;
}
continue;
}
if (const auto value = value_after("--aethera_input=")) {
if (*value == "steady") configuration.input = Input_Workload::steady;
else if (*value == "drag") configuration.input = Input_Workload::drag;
else if (*value == "wheel") configuration.input = Input_Workload::wheel;
else if (*value == "mixed") configuration.input = Input_Workload::mixed;
else {
configuration_error = "invalid --aethera_input value: " +
std::string{*value};
return Parse_Benchmark_Arguments_Result::invalid_argument;
}
continue;
}
if (const auto value = value_after("--aethera_size=")) {
const auto separator = value->find('x');
if (separator == std::string_view::npos ||
!parse_unsigned(value->substr(0, separator), configuration.width) ||
!parse_unsigned(value->substr(separator + 1U), configuration.height) ||
configuration.width == 0 || configuration.height == 0) {
configuration_error = "invalid --aethera_size value: " +
std::string{*value};
return Parse_Benchmark_Arguments_Result::invalid_argument;
}
continue;
}
if (const auto value = value_after("--aethera_input_rate=")) {
if (!parse_unsigned(*value, configuration.input_rate_hz) ||
configuration.input_rate_hz == 0) {
configuration_error = "invalid --aethera_input_rate value: " +
std::string{*value};
return Parse_Benchmark_Arguments_Result::invalid_argument;
}
continue;
}
if (const auto value = value_after("--aethera_duration=")) {
if (!parse_duration(*value, configuration.duration_seconds)) {
configuration_error = "invalid --aethera_duration value: " +
std::string{*value};
return Parse_Benchmark_Arguments_Result::invalid_argument;
}
continue;
}
argv[retained_count++] = argv[index];
}
argc = retained_count;
return Parse_Benchmark_Arguments_Result::configured;
}
struct Concurrent_Workload_State {
std::array<std::atomic_uint64_t, plot_3d_ids.size()> completed{}; /* Per-Plot publish counts for this benchmark interval. */
std::vector<std::shared_ptr<std::atomic_uint64_t>> completed{}; /* 所选 Plot 各自的发布计数器;回调共享稳定所有权。 */
};
[[nodiscard]] Plot_Input_Request concurrent_input_request(
Input_Workload workload, std::size_t plot_index,
std::uint64_t sequence) {
Plot_Input_Request request;
request.plot = plot_3d_ids[plot_index];
request.plot = configuration.plot_ids[plot_index];
request.event.time_milliseconds = 1'000.0 +
static_cast<double>(sequence) * (1'000.0 / 120.0);
static_cast<double>(sequence) *
(1'000.0 / static_cast<double>(configuration.input_rate_hz));
request.event.position = {
360.0 + static_cast<double>(
static_cast<std::int64_t>((sequence + plot_index * 7U) % 121U) - 60),
@@ -112,7 +198,7 @@ struct Concurrent_Workload_State {
return request;
}
class Concurrent_3D : public benchmark::Fixture {
class Configured_Plots : public benchmark::Fixture {
public:
void SetUp(const benchmark::State&) override {
if (shared_service) {
@@ -124,19 +210,25 @@ public:
}
shared_service = Control_Service::create();
shared_workload = std::make_shared<Concurrent_Workload_State>();
shared_plots.reserve(plot_3d_ids.size());
shared_streams.reserve(plot_3d_ids.size());
for (std::size_t index = 0; index < plot_3d_ids.size(); ++index) {
auto plot = shared_service->find_plot(plot_3d_ids[index]);
if (!plot) throw std::logic_error("concurrent 3D benchmark Plot is unavailable");
shared_workload->completed.reserve(configuration.plot_ids.size());
shared_plots.reserve(configuration.plot_ids.size());
shared_streams.reserve(configuration.plot_ids.size());
for (std::size_t index = 0; index < configuration.plot_ids.size();
++index) {
auto plot = shared_service->find_plot(configuration.plot_ids[index]);
if (!plot)
throw std::logic_error("configured benchmark Plot is unavailable");
auto completed = std::make_shared<std::atomic_uint64_t>(0);
shared_workload->completed.push_back(completed);
const auto stream = plot->subscribe(
[workload = shared_workload, index](
[completed = std::move(completed)](
std::shared_ptr<const web::Plot_Stream_Frame> frame) {
if (!frame || !frame->pixels) return;
workload->completed[index].fetch_add(
completed->fetch_add(
1, std::memory_order_relaxed);
});
plot->configure_stream(stream, 720, 420);
plot->configure_stream(
stream, configuration.width, configuration.height);
shared_plots.push_back(std::move(plot));
shared_streams.push_back(stream);
}
@@ -163,7 +255,7 @@ protected:
Input_Workload input, std::uint64_t sequence,
std::optional<Event_Type> pointer_type = std::nullopt) {
for (std::size_t plot_index = 0;
plot_index < plot_3d_ids.size(); ++plot_index) {
plot_index < configuration.plot_ids.size(); ++plot_index) {
const bool wheel = input == Input_Workload::wheel ||
(input == Input_Workload::mixed && (plot_index & 1U) != 0U);
if (pointer_type && wheel) continue;
@@ -181,42 +273,49 @@ protected:
return true;
}
void run(benchmark::State& state, Input_Workload input) {
void run(benchmark::State& state) {
const auto input = configuration.input;
for (auto& count : workload->completed)
count.store(0, std::memory_order_relaxed);
count->store(0, std::memory_order_relaxed);
for (const auto& plot : plots) plot->reset_diagnostics();
if (input == Input_Workload::drag || input == Input_Workload::mixed) {
if (!submit_input_batch(
input, 0, Event_Type::pointer_press)) {
state.SkipWithError("16-Plot pointer press batch was rejected");
state.SkipWithError("configured Plot pointer press batch was rejected");
return;
}
}
const auto runtime_begin = service->call_tool(
"aethera_task_runtime", nlohmann::json::object());
const auto started = std::chrono::steady_clock::now();
constexpr auto input_period = std::chrono::nanoseconds{
1'000'000'000 / 120};
const auto deadline = started + std::chrono::duration_cast<
std::chrono::steady_clock::duration>(
std::chrono::duration<double>{configuration.duration_seconds});
const auto input_period = std::chrono::nanoseconds{
1'000'000'000 / configuration.input_rate_hz};
auto next_input = started + input_period;
std::uint64_t input_batches{};
std::uint64_t input_requests{};
std::uint32_t input_poll{};
for ([[maybe_unused]] auto iteration : state) {
benchmark::DoNotOptimize(
workload->completed[splat_plot_index].load(
std::memory_order_relaxed));
benchmark::ClobberMemory();
if ((++input_poll & 0x3FFU) != 0U) continue;
const auto now = std::chrono::steady_clock::now();
if (input == Input_Workload::steady) continue;
if (now < next_input) continue;
++input_batches;
if (!submit_input_batch(input, input_batches)) {
state.SkipWithError("16-Plot interaction batch was rejected");
break;
while (std::chrono::steady_clock::now() < deadline) {
benchmark::DoNotOptimize(
workload->completed.front()->load(
std::memory_order_relaxed));
benchmark::ClobberMemory();
if ((++input_poll & 0x3FFU) != 0U) continue;
const auto now = std::chrono::steady_clock::now();
if (input == Input_Workload::steady || now < next_input)
continue;
++input_batches;
if (!submit_input_batch(input, input_batches)) {
state.SkipWithError(
"configured Plot interaction batch was rejected");
break;
}
input_requests += configuration.plot_ids.size();
do next_input += input_period; while (next_input <= now);
}
input_requests += plot_3d_ids.size();
do next_input += input_period; while (next_input <= now);
}
const auto finished = std::chrono::steady_clock::now();
if (input == Input_Workload::drag || input == Input_Workload::mixed) {
@@ -229,20 +328,17 @@ protected:
std::uint64_t total_completed{};
double minimum_fps = std::numeric_limits<double>::max();
double maximum_fps{};
double splat_fps{};
for (std::size_t index = 0; index < plots.size(); ++index) {
const auto count = workload->completed[index].load(
const auto count = workload->completed[index]->load(
std::memory_order_relaxed);
total_completed += count;
const double fps = elapsed_seconds > 0.0
? static_cast<double>(count) / elapsed_seconds : 0.0;
minimum_fps = std::min(minimum_fps, fps);
maximum_fps = std::max(maximum_fps, fps);
if (index == splat_plot_index) splat_fps = fps;
const auto diagnostics = plots[index]->diagnostics();
const auto& policy = diagnostics.at("frame_policy");
const auto prefix = "p" + std::to_string(index) + "_";
const auto prefix = std::string{configuration.plot_ids[index]} + "/";
state.counters[prefix + "fps"] = fps;
state.counters[prefix + "requested"] =
policy.at("observation").at("request_count").get<double>();
@@ -258,16 +354,63 @@ protected:
policy.at("requests").at("frame_slot_backpressure").get<double>();
state.counters[prefix + "completion_ms"] =
policy.at("latency").at("average_completion_ms").get<double>();
state.counters[prefix + "completion_max_ms"] =
policy.at("latency").at("maximum_completion_ms").get<double>();
const auto& frame_statistics = diagnostics.at("frame_statistics");
const auto statistic_average = [&](std::string_view name) {
const auto statistic_value = [&](std::string_view name,
std::string_view field) {
const auto found = frame_statistics.find(name);
return found == frame_statistics.end()
? 0.0 : found->at("average").get<double>();
? 0.0 : found->at(field).get<double>();
};
state.counters[prefix + "backend_queue_ms"] =
statistic_average("backend_queue_ms");
statistic_value("backend_queue_ms", "average");
state.counters[prefix + "backend_queue_p95_ms"] =
statistic_value("backend_queue_ms", "p95");
state.counters[prefix + "backend_queue_max_ms"] =
statistic_value("backend_queue_ms", "maximum");
state.counters[prefix + "backend_apply_ms"] =
statistic_value("backend_apply_ms", "average");
state.counters[prefix + "backend_apply_p95_ms"] =
statistic_value("backend_apply_ms", "p95");
state.counters[prefix + "backend_apply_max_ms"] =
statistic_value("backend_apply_ms", "maximum");
state.counters[prefix + "backend_plan_ms"] =
statistic_average("backend_plan_ms");
statistic_value("backend_plan_ms", "average");
state.counters[prefix + "backend_plan_p95_ms"] =
statistic_value("backend_plan_ms", "p95");
state.counters[prefix + "backend_plan_max_ms"] =
statistic_value("backend_plan_ms", "maximum");
state.counters[prefix + "backend_execute_ms"] =
statistic_value("backend_execute_ms", "average");
state.counters[prefix + "backend_execute_p95_ms"] =
statistic_value("backend_execute_ms", "p95");
state.counters[prefix + "backend_execute_max_ms"] =
statistic_value("backend_execute_ms", "maximum");
state.counters[prefix + "backend_submit_queue_ms"] =
statistic_value("backend_submit_queue_ms", "average");
state.counters[prefix + "backend_submit_queue_p95_ms"] =
statistic_value("backend_submit_queue_ms", "p95");
state.counters[prefix + "backend_submit_queue_max_ms"] =
statistic_value("backend_submit_queue_ms", "maximum");
state.counters[prefix + "backend_submit_ms"] =
statistic_value("backend_submit_ms", "average");
state.counters[prefix + "backend_submit_p95_ms"] =
statistic_value("backend_submit_ms", "p95");
state.counters[prefix + "backend_submit_max_ms"] =
statistic_value("backend_submit_ms", "maximum");
state.counters[prefix + "gpu_total_ms"] =
statistic_value("gpu_total_ms", "average");
state.counters[prefix + "gpu_total_p95_ms"] =
statistic_value("gpu_total_ms", "p95");
state.counters[prefix + "readback_ms"] =
statistic_value("readback_ms", "average");
state.counters[prefix + "scene_render_ms"] =
statistic_value("scene_render_ms", "average");
state.counters[prefix + "event_dispatch_ms"] =
statistic_value("event_dispatch_ms", "average");
state.counters[prefix + "frame_interval_p95_ms"] =
statistic_value("frame_interval_ms", "p95");
}
const auto runtime_end = service->call_tool(
"aethera_task_runtime", nlohmann::json::object());
@@ -287,7 +430,6 @@ protected:
? static_cast<double>(total_completed) / elapsed_seconds : 0.0;
state.counters["minimum_plot_fps"] = minimum_fps;
state.counters["maximum_plot_fps"] = maximum_fps;
state.counters["splat_fps"] = splat_fps;
state.counters["fairness_pct"] = maximum_fps > 0.0
? minimum_fps / maximum_fps * 100.0 : 0.0;
state.counters["worker_busy_pct"] = wall_ns > 0.0 && worker_count > 0.0
@@ -312,105 +454,199 @@ private:
std::vector<web::Plot::Stream_Id> streams{};
};
BENCHMARK_DEFINE_F(Concurrent_3D, Warmup)(benchmark::State& state) {
run(state, Input_Workload::steady);
BENCHMARK_DEFINE_F(Configured_Plots, Run)(benchmark::State& state) {
run(state);
}
BENCHMARK_DEFINE_F(Concurrent_3D, Steady)(benchmark::State& state) {
run(state, Input_Workload::steady);
}
BENCHMARK_DEFINE_F(Concurrent_3D, All_Plots_Drag)(benchmark::State& state) {
run(state, Input_Workload::drag);
}
BENCHMARK_DEFINE_F(Concurrent_3D, All_Plots_Wheel)(benchmark::State& state) {
run(state, Input_Workload::wheel);
}
BENCHMARK_DEFINE_F(Concurrent_3D, All_Plots_Mixed)(benchmark::State& state) {
run(state, Input_Workload::mixed);
}
/* Fixture 静态持有所选 PlotGoogle Benchmark 校准不会反复重建其渲染资源。 */
BENCHMARK_REGISTER_F(Configured_Plots, Run)
->Iterations(1)
->UseRealTime();
BENCHMARK_DEFINE_F(Control_Path, Timestamped_Drag_Admission)(
benchmark::State& state) {
auto request = timestamped_drag_request(Event_Type::pointer_press);
auto arguments = encode_protocol_value(request);
const auto press = service->call_tool("aethera_plot_input", arguments);
if (press.result != Tool_Call_Result::ok) {
state.SkipWithError("timestamped pointer press was rejected");
return;
class Plot_Console_Reporter final : public benchmark::ConsoleReporter {
public:
bool ReportContext(const Context& context) override {
return ConsoleReporter::ReportContext(context);
}
arguments["event"]["type"] = "pointer_move";
double time_milliseconds = request.event.time_milliseconds;
for ([[maybe_unused]] auto iteration : state) {
time_milliseconds += 1'000.0 / 120.0;
arguments["event"]["time_milliseconds"] = time_milliseconds;
const auto result = service->call_tool(
"aethera_plot_input", arguments);
if (result.result != Tool_Call_Result::ok) {
state.SkipWithError("timestamped pointer move was rejected");
break;
void ReportRuns(const std::vector<Run>& reports) override {
auto& output = GetOutputStream();
const auto flags = output.flags();
const auto precision = output.precision();
output << std::fixed << std::setprecision(2);
for (const auto& report : reports) {
if (report.skipped != benchmark::internal::NotSkipped) {
output << report.benchmark_name() << ": "
<< report.skip_message << '\n';
continue;
}
const auto counter = [&](std::string_view name) {
const auto found = report.counters.find(std::string{name});
return found == report.counters.end() ? 0.0 : found->second.value;
};
output << "\n" << report.benchmark_name()
<< " elapsed=" << report.real_accumulated_time
<< " s frames="
<< static_cast<std::uint64_t>(counter("aggregate_fps") *
report.real_accumulated_time)
<< "\n";
output << std::left
<< std::setw(23) << "Plot"
<< std::right
<< std::setw(9) << "FPS"
<< std::setw(11) << "Done ms"
<< std::setw(11) << "Max ms"
<< std::setw(11) << "Scene ms"
<< std::setw(11) << "Event ms"
<< std::setw(11) << "Queue95"
<< std::setw(11) << "QueueMax"
<< std::setw(11) << "Apply95"
<< std::setw(11) << "ApplyMax"
<< std::setw(11) << "Plan95"
<< std::setw(11) << "PlanMax"
<< std::setw(11) << "Exec95"
<< std::setw(11) << "ExecMax"
<< std::setw(11) << "SubQ95"
<< std::setw(11) << "SubQMax"
<< std::setw(11) << "Submit95"
<< std::setw(11) << "SubmitMax"
<< std::setw(10) << "GPU ms"
<< std::setw(10) << "Read ms"
<< std::setw(10) << "Backpr."
<< std::setw(9) << "Reject" << '\n';
for (const auto id : configuration.plot_ids) {
const auto prefix = std::string{id} + "/";
output << std::left << std::setw(23) << id << std::right
<< std::setw(9) << counter(prefix + "fps")
<< std::setw(11) << counter(prefix + "completion_ms")
<< std::setw(11) << counter(prefix + "completion_max_ms")
<< std::setw(11) << counter(prefix + "scene_render_ms")
<< std::setw(11) << counter(prefix + "event_dispatch_ms")
<< std::setw(11) << counter(prefix + "backend_queue_p95_ms")
<< std::setw(11) << counter(prefix + "backend_queue_max_ms")
<< std::setw(11) << counter(prefix + "backend_apply_p95_ms")
<< std::setw(11) << counter(prefix + "backend_apply_max_ms")
<< std::setw(11) << counter(prefix + "backend_plan_p95_ms")
<< std::setw(11) << counter(prefix + "backend_plan_max_ms")
<< std::setw(11) << counter(prefix + "backend_execute_p95_ms")
<< std::setw(11) << counter(prefix + "backend_execute_max_ms")
<< std::setw(11) << counter(prefix + "backend_submit_queue_p95_ms")
<< std::setw(11) << counter(prefix + "backend_submit_queue_max_ms")
<< std::setw(11) << counter(prefix + "backend_submit_p95_ms")
<< std::setw(11) << counter(prefix + "backend_submit_max_ms")
<< std::setw(10) << counter(prefix + "gpu_total_ms")
<< std::setw(10) << counter(prefix + "readback_ms")
<< std::setw(10) << counter(prefix + "slot_backpressure")
<< std::setw(9) << counter(prefix + "scene_rejected")
<< '\n';
}
output << "aggregate_fps=" << counter("aggregate_fps")
<< " min_fps=" << counter("minimum_plot_fps")
<< " max_fps=" << counter("maximum_plot_fps")
<< " fairness=" << counter("fairness_pct") << "%"
<< " input_batches=" << counter("input_batches")
<< " input_requests/s=" << counter("input_request_rate")
<< " worker_busy=" << counter("worker_busy_pct") << "%"
<< " worker_cpu=" << counter("worker_cpu_pct") << "%\n";
}
benchmark::DoNotOptimize(result.result);
output.flags(flags);
output.precision(precision);
}
state.SetItemsProcessed(state.iterations());
};
const auto runtime = service->call_tool(
"aethera_task_runtime", nlohmann::json::object());
if (runtime.result != Tool_Call_Result::ok) {
state.SkipWithError("Taskflow runtime diagnostics were rejected");
return;
}
const auto milliseconds = [](const nlohmann::json& value,
std::string_view key) {
return static_cast<double>(value.at(key).get<std::uint64_t>()) /
1'000'000.0;
};
state.counters["taskflow_wall_ms"] =
milliseconds(runtime.content, "observed_wall_time_ns");
state.counters["worker_busy_ms"] =
milliseconds(runtime.content, "worker_busy_time_ns");
state.counters["worker_cpu_ms"] =
milliseconds(runtime.content, "worker_cpu_time_ns");
state.counters["worker_utilization_pct"] =
runtime.content.at("worker_utilization").get<double>();
state.counters["worker_cpu_utilization_pct"] =
runtime.content.at("worker_cpu_utilization").get<double>();
state.counters["active_taskflows"] =
static_cast<double>(runtime.content.at("active_taskflows").get<std::size_t>());
state.counters["completed_taskflows"] =
static_cast<double>(runtime.content.at("completed_taskflows").get<std::size_t>());
state.SetLabel(runtime.content.at("longest_task").at("name").get<std::string>());
void print_help() {
std::cout <<
"Aethera concurrent Plot benchmark options:\n"
" --aethera_plots=3d|2d|all|id[,id...]\n"
" Groups and IDs may be mixed, for example: 2d,datoviz_mesh\n"
" --aethera_input=steady|drag|wheel|mixed\n"
" --aethera_size=WIDTHxHEIGHT\n"
" --aethera_input_rate=HZ\n"
" --aethera_duration=SECONDS (default: 10, maximum: 3600)\n"
" --aethera_help\n"
"Google Benchmark output options remain available, for example:\n"
" --benchmark_format=json\n\n"
"Available Gallery Plot IDs:\n";
for (const auto& definition : web::gallery_plot_definitions())
std::cout << " " << definition.id << " ("
<< web::plot_dimension_name(definition.dimension) << ")\n";
}
BENCHMARK(decode_timestamped_drag);
BENCHMARK(decode_timed_render);
BENCHMARK_REGISTER_F(Control_Path, Timestamped_Drag_Admission)
->Iterations(512);
/* Fixture 静态持有 16 个 PlotMinTime 校准不会重建 Datoviz Scene。 */
BENCHMARK_REGISTER_F(Concurrent_3D, Warmup)
->MinTime(10.0)->UseRealTime();
BENCHMARK_REGISTER_F(Concurrent_3D, Steady)
->MinTime(10.0)->UseRealTime();
BENCHMARK_REGISTER_F(Concurrent_3D, All_Plots_Drag)
->MinTime(10.0)->UseRealTime();
BENCHMARK_REGISTER_F(Concurrent_3D, All_Plots_Wheel)
->MinTime(10.0)->UseRealTime();
BENCHMARK_REGISTER_F(Concurrent_3D, All_Plots_Mixed)
->MinTime(10.0)->UseRealTime();
[[nodiscard]] Parse_Benchmark_Arguments_Result configure_benchmark(
int& argc, char** argv) {
return parse_arguments(argc, argv);
}
void shutdown_concurrent_3d_benchmark() {
Concurrent_3D::shutdown();
[[nodiscard]] const std::string& benchmark_configuration_error() {
return configuration_error;
}
void describe_configuration() {
std::string plots;
for (const auto id : configuration.plot_ids) {
if (!plots.empty()) plots += ',';
plots += id;
}
const auto input = [&] {
switch (configuration.input) {
case Input_Workload::steady: return "steady";
case Input_Workload::drag: return "drag";
case Input_Workload::wheel: return "wheel";
case Input_Workload::mixed: return "mixed";
}
return "unknown";
}();
benchmark::AddCustomContext("aethera_plots", std::move(plots));
benchmark::AddCustomContext("aethera_input", input);
benchmark::AddCustomContext(
"aethera_size", std::to_string(configuration.width) + "x" +
std::to_string(configuration.height));
benchmark::AddCustomContext(
"aethera_input_rate_hz", std::to_string(configuration.input_rate_hz));
benchmark::AddCustomContext(
"aethera_duration_seconds",
std::to_string(configuration.duration_seconds));
}
void shutdown_configured_benchmark() {
Configured_Plots::shutdown();
}
}
}
int main(int argc, char** argv) {
bool structured_display{};
for (int index = 1; index < argc; ++index) {
const std::string_view argument{argv[index]};
constexpr std::string_view format_prefix{"--benchmark_format="};
if (argument.starts_with(format_prefix) &&
argument.substr(format_prefix.size()) != "console")
structured_display = true;
}
const auto configured =
aethera::mcp::benchmarks::configure_benchmark(argc, argv);
if (configured == aethera::mcp::benchmarks::
Parse_Benchmark_Arguments_Result::help_requested) {
aethera::mcp::benchmarks::print_help();
return 0;
}
if (configured == aethera::mcp::benchmarks::
Parse_Benchmark_Arguments_Result::invalid_argument) {
std::cerr << aethera::mcp::benchmarks::benchmark_configuration_error()
<< "\nUse --aethera_help to list valid selections.\n";
return 2;
}
aethera::initialize_runtime({});
benchmark::Initialize(&argc, argv);
if (benchmark::ReportUnrecognizedArguments(argc, argv)) return 2;
benchmark::RunSpecifiedBenchmarks();
aethera::mcp::benchmarks::shutdown_concurrent_3d_benchmark();
aethera::mcp::benchmarks::describe_configuration();
if (structured_display) benchmark::RunSpecifiedBenchmarks();
else {
aethera::mcp::benchmarks::Plot_Console_Reporter reporter;
benchmark::RunSpecifiedBenchmarks(&reporter);
}
aethera::mcp::benchmarks::shutdown_configured_benchmark();
benchmark::Shutdown();
return 0;
}