改结构
This commit is contained in:
@@ -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 静态持有所选 Plot;Google 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 个 Plot;MinTime 校准不会重建 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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user