#include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace aethera::mcp::benchmarks { namespace { enum struct Input_Workload : std::uint8_t { steady, drag, wheel, mixed }; struct Benchmark_Configuration { std::vector 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(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("all")) { configuration_error = "the Gallery has no 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 { 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::vector> 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 = configuration.plot_ids[plot_index]; request.event.time_milliseconds = 1'000.0 + static_cast(sequence) * (1'000.0 / static_cast(configuration.input_rate_hz)); request.event.position = { 360.0 + static_cast( static_cast((sequence + plot_index * 7U) % 121U) - 60), 210.0 + static_cast( static_cast((sequence * 3U + plot_index * 11U) % 81U) - 40)}; request.event.global_position = request.event.position; if (workload == Input_Workload::wheel || (workload == Input_Workload::mixed && (plot_index & 1U) != 0U)) { request.event.type = Event_Type::wheel; request.event.pixel_delta_y = (sequence & 1U) != 0U ? 120.0 : -120.0; request.event.angle_delta_y = request.event.pixel_delta_y; return request; } request.event.type = Event_Type::pointer_move; request.event.button = Mouse_Button::left; request.event.buttons = 1; return request; } class Configured_Plots : public benchmark::Fixture { public: void SetUp(const benchmark::State&) override { if (shared_service) { service = shared_service; workload = shared_workload; plots = shared_plots; streams = shared_streams; return; } shared_service = Control_Service::create(); shared_workload = std::make_shared(); 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(0); shared_workload->completed.push_back(completed); const auto stream = plot->subscribe( [completed = std::move(completed)]( std::shared_ptr frame) { if (!frame || !frame->pixels) return web::Plot_Frame_Publication::ignored; completed->fetch_add( 1, std::memory_order_relaxed); return web::Plot_Frame_Publication::completed; }); plot->configure_stream( stream, configuration.width, configuration.height); shared_plots.push_back(std::move(plot)); shared_streams.push_back(stream); } service = shared_service; workload = shared_workload; plots = shared_plots; streams = shared_streams; } void TearDown(const benchmark::State&) override {} static void shutdown() { if (!shared_workload) return; for (std::size_t index = 0; index < shared_plots.size(); ++index) shared_plots[index]->unsubscribe(shared_streams[index]); shared_plots.clear(); shared_streams.clear(); shared_service.reset(); shared_workload.reset(); } protected: [[nodiscard]] bool submit_input_batch( Input_Workload input, std::uint64_t sequence, std::optional pointer_type = std::nullopt) { for (std::size_t plot_index = 0; 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; auto request = concurrent_input_request( input, plot_index, sequence); if (pointer_type) { request.event.type = *pointer_type; request.event.buttons = *pointer_type == Event_Type::pointer_release ? 0 : 1; } const auto result = service->call_tool( "aethera_plot_input", encode_protocol_value(request)); if (result.result != Tool_Call_Result::ok) return false; } return true; } void run(benchmark::State& state) { const auto input = configuration.input; for (auto& count : workload->completed) count->store(0, std::memory_order_relaxed); for (const auto& plot : plots) { plot->reset_diagnostics(); plot->request_taskflow_trace(1); } if (input == Input_Workload::drag || input == Input_Workload::mixed) { if (!submit_input_batch( input, 0, Event_Type::pointer_press)) { 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(); const auto deadline = started + std::chrono::duration_cast< std::chrono::steady_clock::duration>( std::chrono::duration{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) { 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); } } const auto finished = std::chrono::steady_clock::now(); if (input == Input_Workload::drag || input == Input_Workload::mixed) { static_cast(submit_input_batch( input, input_batches + 1U, Event_Type::pointer_release)); } const double elapsed_seconds = std::chrono::duration( finished - started).count(); std::uint64_t total_completed{}; double minimum_fps = std::numeric_limits::max(); double maximum_fps{}; for (std::size_t index = 0; index < plots.size(); ++index) { const auto count = workload->completed[index]->load( std::memory_order_relaxed); total_completed += count; const double fps = elapsed_seconds > 0.0 ? static_cast(count) / elapsed_seconds : 0.0; minimum_fps = std::min(minimum_fps, fps); maximum_fps = std::max(maximum_fps, fps); const auto diagnostics = plots[index]->diagnostics(); const auto& policy = diagnostics.at("frame_policy"); 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(); state.counters[prefix + "submitted"] = policy.at("observation").at("submitted_frame_count").get(); state.counters[prefix + "completed"] = policy.at("observation").at("completed_frame_count").get(); state.counters[prefix + "active"] = policy.at("observation").at("active_frame_count").get(); state.counters[prefix + "scene_rejected"] = policy.at("requests").at("scene_rejected").get(); state.counters[prefix + "slot_backpressure"] = policy.at("requests").at("frame_slot_backpressure").get(); state.counters[prefix + "completion_ms"] = policy.at("latency").at("average_completion_ms").get(); state.counters[prefix + "completion_max_ms"] = policy.at("latency").at("maximum_completion_ms").get(); const auto& frame_statistics = diagnostics.at("frame_statistics"); 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(field).get(); }; state.counters[prefix + "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_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"); constexpr std::array detailed_backend_statistics{ "backend_target_acquire_ms", "backend_structure_check_ms", "backend_query_ms", "backend_runtime_plan_ms", "backend_runtime_plan_cpu_ms", "backend_runtime_execute_ms", "backend_runtime_execute_cpu_ms", "backend_mvp_update_ms", "backend_frame_begin_ms", "backend_frame_plan_ms", "backend_frame_plan_cpu_ms", "backend_external_register_ms", "backend_frame_attach_ms", "backend_frame_execute_ms", "backend_frame_execute_cpu_ms", "backend_frame_finish_ms"}; constexpr std::array detailed_emit_statistics{ "backend_emit_replay_dirty_ms", "backend_emit_layout_ms", "backend_emit_prepare_ms", "backend_emit_plan_build_ms", "backend_emit_contract_ms", "backend_emit_stream_ms", "backend_emit_stream_freeze_ms", "backend_emit_commit_ms", "backend_emit_plan_reset_ms", "backend_emit_artifact_create_ms", "backend_emit_artifact_freeze_ms", "backend_emit_packet_encode_ms"}; constexpr std::array detailed_drp_statistics{ "backend_drp_validation_ms", "backend_drp_state_ms", "backend_drp_buffer_create_ms", "backend_drp_texture_create_ms", "backend_drp_shader_create_ms", "backend_drp_shader_compile_ms", "backend_drp_shader_module_create_ms", "backend_drp_pipeline_create_ms", "backend_drp_binding_create_ms", "backend_drp_upload_ms", "backend_drp_upload_decode_ms", "backend_drp_upload_vulkan_allocate_ms", "backend_drp_upload_host_copy_ms", "backend_drp_upload_command_allocate_ms", "backend_drp_upload_command_record_ms", "backend_drp_upload_fence_create_ms", "backend_drp_upload_submit_enqueue_ms", "backend_drp_upload_submit_queue_wait_ms", "backend_drp_upload_queue_submit_ms", "backend_drp_upload_fence_wait_ms", "backend_drp_upload_retire_ms", "backend_drp_transfer_ms", "backend_drp_record_ms"}; for (const std::string_view name : detailed_backend_statistics) { state.counters[prefix + std::string{name} + "/p95"] = statistic_value(name, "p95"); state.counters[prefix + std::string{name} + "/max"] = statistic_value(name, "maximum"); } for (const std::string_view name : detailed_emit_statistics) { state.counters[prefix + std::string{name} + "/p95"] = statistic_value(name, "p95"); state.counters[prefix + std::string{name} + "/max"] = statistic_value(name, "maximum"); } for (const std::string_view name : detailed_drp_statistics) { state.counters[prefix + std::string{name} + "/p95"] = statistic_value(name, "p95"); state.counters[prefix + std::string{name} + "/max"] = statistic_value(name, "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()); const auto runtime_delta = [&](std::string_view key) { if (runtime_begin.result != Tool_Call_Result::ok || runtime_end.result != Tool_Call_Result::ok) return 0.0; const auto begin = runtime_begin.content.at(key).get(); const auto end = runtime_end.content.at(key).get(); return static_cast(end >= begin ? end - begin : 0U); }; const double wall_ns = runtime_delta("observed_wall_time_ns"); const double busy_ns = runtime_delta("worker_busy_time_ns"); const double cpu_ns = runtime_delta("worker_cpu_time_ns"); const double cooperative_waits = runtime_delta( "cooperative_wait_count"); const double cooperative_wait_ns = runtime_delta( "cooperative_wait_time_ns"); const double worker_count = runtime_end.result == Tool_Call_Result::ok ? runtime_end.content.at("worker_count").get() : 0.0; state.counters["aggregate_fps"] = elapsed_seconds > 0.0 ? static_cast(total_completed) / elapsed_seconds : 0.0; state.counters["minimum_plot_fps"] = minimum_fps; state.counters["maximum_plot_fps"] = maximum_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 ? busy_ns / (wall_ns * worker_count) * 100.0 : 0.0; state.counters["worker_cpu_pct"] = wall_ns > 0.0 && worker_count > 0.0 ? cpu_ns / (wall_ns * worker_count) * 100.0 : 0.0; double longest_sampled_busy_ms{}; std::string longest_sampled_busy_node; std::string longest_sampled_busy_plot; for (std::size_t plot_index = 0; plot_index < plots.size(); ++plot_index) { const auto trace = plots[plot_index]->taskflow_trace(); if (!trace.value("complete", false)) continue; for (const auto& frame : trace.at("frames")) for (const auto& execution : frame.at("executions")) { const auto busy = std::max( 0.0, execution.value("duration_ms", 0.0) - execution.value("cooperative_wait_ms", 0.0)); if (busy <= longest_sampled_busy_ms) continue; longest_sampled_busy_ms = busy; longest_sampled_busy_node = execution.value( "node_id", std::string{}); longest_sampled_busy_plot = std::string{configuration.plot_ids[plot_index]}; } } state.counters["sampled_longest_busy_ms"] = longest_sampled_busy_ms; if (!longest_sampled_busy_node.empty()) state.SetLabel("busy=" + longest_sampled_busy_plot + ":" + longest_sampled_busy_node); state.counters["cooperative_yields"] = cooperative_waits; state.counters["cooperative_wait_ms"] = cooperative_wait_ns / 1'000'000.0; state.counters["input_batches"] = static_cast(input_batches); state.counters["input_requests"] = static_cast(input_requests); state.counters["input_request_rate"] = elapsed_seconds > 0.0 ? static_cast(input_requests) / elapsed_seconds : 0.0; state.SetItemsProcessed(static_cast(total_completed)); } private: inline static std::shared_ptr shared_service{}; inline static std::shared_ptr shared_workload{}; inline static std::vector> shared_plots{}; inline static std::vector shared_streams{}; std::shared_ptr service{}; std::shared_ptr workload{}; std::vector> plots{}; std::vector streams{}; }; BENCHMARK_DEFINE_F(Configured_Plots, Run)(benchmark::State& state) { run(state); } /* Fixture 静态持有所选 Plot;Google Benchmark 校准不会反复重建其渲染资源。 */ BENCHMARK_REGISTER_F(Configured_Plots, Run) ->Iterations(1) ->UseRealTime(); class Plot_Console_Reporter final : public benchmark::ConsoleReporter { public: bool ReportContext(const Context& context) override { return ConsoleReporter::ReportContext(context); } void ReportRuns(const std::vector& 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(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'; constexpr std::array detailed_backend_statistics{ std::pair{"acquire", "backend_target_acquire_ms"}, std::pair{"structure", "backend_structure_check_ms"}, std::pair{"query", "backend_query_ms"}, std::pair{"runtime-plan", "backend_runtime_plan_ms"}, std::pair{"runtime-plan-cpu", "backend_runtime_plan_cpu_ms"}, std::pair{"runtime-exec", "backend_runtime_execute_ms"}, std::pair{"runtime-exec-cpu", "backend_runtime_execute_cpu_ms"}, std::pair{"mvp", "backend_mvp_update_ms"}, std::pair{"frame-begin", "backend_frame_begin_ms"}, std::pair{"frame-plan", "backend_frame_plan_ms"}, std::pair{"frame-plan-cpu", "backend_frame_plan_cpu_ms"}, std::pair{"external", "backend_external_register_ms"}, std::pair{"attach", "backend_frame_attach_ms"}, std::pair{"frame-exec", "backend_frame_execute_ms"}, std::pair{"frame-exec-cpu", "backend_frame_execute_cpu_ms"}, std::pair{"finish", "backend_frame_finish_ms"}}; output << " backend-detail " << id; for (const auto& [label, statistic] : detailed_backend_statistics) output << " " << label << "95/max=" << counter(prefix + statistic + "/p95") << "/" << counter(prefix + statistic + "/max"); output << '\n'; constexpr std::array detailed_emit_statistics{ std::pair{"replay", "backend_emit_replay_dirty_ms"}, std::pair{"layout", "backend_emit_layout_ms"}, std::pair{"prepare", "backend_emit_prepare_ms"}, std::pair{"build", "backend_emit_plan_build_ms"}, std::pair{"contract", "backend_emit_contract_ms"}, std::pair{"stream", "backend_emit_stream_ms"}, std::pair{"stream-freeze", "backend_emit_stream_freeze_ms"}, std::pair{"commit", "backend_emit_commit_ms"}, std::pair{"reset", "backend_emit_plan_reset_ms"}, std::pair{"artifact", "backend_emit_artifact_create_ms"}, std::pair{"artifact-freeze", "backend_emit_artifact_freeze_ms"}, std::pair{"packet", "backend_emit_packet_encode_ms"}}; output << " emit-detail " << id; for (const auto& [label, statistic] : detailed_emit_statistics) output << " " << label << "95/max=" << counter(prefix + statistic + "/p95") << "/" << counter(prefix + statistic + "/max"); output << '\n'; constexpr std::array detailed_drp_statistics{ std::pair{"validate", "backend_drp_validation_ms"}, std::pair{"state", "backend_drp_state_ms"}, std::pair{"buffer", "backend_drp_buffer_create_ms"}, std::pair{"texture", "backend_drp_texture_create_ms"}, std::pair{"shader", "backend_drp_shader_create_ms"}, std::pair{"shader-compile", "backend_drp_shader_compile_ms"}, std::pair{"shader-module", "backend_drp_shader_module_create_ms"}, std::pair{"pipeline", "backend_drp_pipeline_create_ms"}, std::pair{"binding", "backend_drp_binding_create_ms"}, std::pair{"upload", "backend_drp_upload_ms"}, std::pair{"decode", "backend_drp_upload_decode_ms"}, std::pair{"vk-alloc", "backend_drp_upload_vulkan_allocate_ms"}, std::pair{"host-copy", "backend_drp_upload_host_copy_ms"}, std::pair{"cmd-alloc", "backend_drp_upload_command_allocate_ms"}, std::pair{"cmd-record", "backend_drp_upload_command_record_ms"}, std::pair{"fence-create", "backend_drp_upload_fence_create_ms"}, std::pair{"enqueue", "backend_drp_upload_submit_enqueue_ms"}, std::pair{"queue-wait", "backend_drp_upload_submit_queue_wait_ms"}, std::pair{"queue-submit", "backend_drp_upload_queue_submit_ms"}, std::pair{"fence-wait", "backend_drp_upload_fence_wait_ms"}, std::pair{"retire", "backend_drp_upload_retire_ms"}, std::pair{"transfer", "backend_drp_transfer_ms"}, std::pair{"record", "backend_drp_record_ms"}}; output << " drp-detail " << id; for (const auto& [label, statistic] : detailed_drp_statistics) output << " " << label << "95/max=" << counter(prefix + statistic + "/p95") << "/" << counter(prefix + statistic + "/max"); output << '\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") << "%" << " sampled_busy_max=" << counter("sampled_longest_busy_ms") << " ms" << " cooperative_yields=" << counter("cooperative_yields") << " cooperative_wait=" << counter("cooperative_wait_ms") << " ms\n"; if (!report.report_label.empty()) output << report.report_label << '\n'; } output.flags(flags); output.precision(precision); } }; 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"; } [[nodiscard]] Parse_Benchmark_Arguments_Result configure_benchmark( int& argc, char** argv) { return parse_arguments(argc, argv); } [[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; 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; }