Files
Renderive/web_server/app/Gallery_Capture_Json.h
2026-08-12 21:31:02 +08:00

364 lines
15 KiB
C++

#pragma once
#include "render_2D/renderable/Renderable.h"
#include <renderive/scene/base/Scene_Base.hpp>
#include <nlohmann/json.hpp>
#include <algorithm>
#include <array>
#include <cmath>
#include <map>
#include <numeric>
#include <set>
#include <string>
#include <unordered_map>
#include <vector>
namespace renderive::web {
namespace gallery_capture_detail {
using Json = nlohmann::json;
inline const char* node_kind(Render_Node_Kind kind) noexcept {
switch (kind) {
case Render_Node_Kind::prepare:
return "prepare";
case Render_Node_Kind::paint:
return "paint";
case Render_Node_Kind::composite:
return "composite";
}
return "prepare";
}
inline const char* execution_status(Node_Execution_Status status) noexcept {
switch (status) {
case Node_Execution_Status::pending:
return "pending";
case Node_Execution_Status::running:
return "running";
case Node_Execution_Status::complete:
return "complete";
case Node_Execution_Status::failed:
return "failed";
}
return "pending";
}
inline Json metrics_json(const Node_Execution_Metrics& metrics) {
Json result = Json::object();
constexpr std::array names{"input_count", "chunk_size", "prepared_cells",
"pixel_count", "primitive_count"};
for (std::size_t index = 0; index < names.size(); ++index) {
const auto kind = static_cast<Node_Metric_Kind>(index);
if (metrics.contains(kind))
result[names[index]] = metrics.get(kind);
}
return result;
}
inline std::unordered_map<std::uint64_t, std::string> owner_names(
const Scene_Base& scene) {
std::unordered_map<std::uint64_t, std::string> result;
const auto topology = scene.topology_snapshot();
result.reserve(topology.renderables.size());
for (const auto& base : topology.renderables) {
std::string name;
if (const auto* renderable = dynamic_cast<const renderive::Renderable*>(base.get()))
name = renderable->object_name();
if (name.empty())
name = "Renderable " + std::to_string(base->renderable_id());
result.emplace(base->renderable_id(), std::move(name));
}
return result;
}
inline Json plan_json(const Scene_Base& scene, const Render_Plan& plan) {
const auto names = owner_names(scene);
Json nodes = Json::array();
Json edges = Json::array();
std::map<std::uint64_t, std::pair<bool, bool>> active_stages;
std::set<std::uint64_t> owners;
for (const auto& node : plan.graph.nodes) {
const std::string owner = node.owner_id == 0
? "Scene"
: names.contains(node.owner_id)
? names.at(node.owner_id)
: "Renderable " + std::to_string(node.owner_id);
nodes.push_back({
{"id", std::to_string(node.node_id)},
{"node_id", node.node_id},
{"owner_id", node.owner_id},
{"owner", owner},
{"label", owner + " / " + node.name},
{"name", node.name},
{"kind", node_kind(node.kind)},
{"execution_index", node.execution_index}
});
if (node.owner_id != 0) {
owners.insert(node.owner_id);
auto& stages = active_stages[node.owner_id];
stages.first = stages.first || node.kind == Render_Node_Kind::prepare;
stages.second = stages.second || node.kind == Render_Node_Kind::paint;
}
}
for (const auto& edge : plan.graph.edges) {
edges.push_back({{"from", std::to_string(edge.from)},
{"to", std::to_string(edge.to)},
{"kind", "dependency"}});
}
Json renderables = Json::array();
for (const std::uint64_t owner_id : owners) {
const auto stages = active_stages.find(owner_id);
const bool has_prepare = stages != active_stages.end() && stages->second.first;
const bool has_paint = stages != active_stages.end() && stages->second.second;
renderables.push_back({
{"owner_id", owner_id},
{"name", names.contains(owner_id)
? names.at(owner_id)
: "Renderable " + std::to_string(owner_id)},
{"prepare_cache", has_prepare ? "MISS" : "HIT"},
{"paint_cache", has_paint ? "MISS" : "HIT"},
{"prepare_in_plan", has_prepare},
{"paint_in_plan", has_paint}
});
}
return {{"version", plan.version},
{"topology_id", "plan-" + std::to_string(plan.version)},
{"nodes", std::move(nodes)},
{"edges", std::move(edges)},
{"renderables", std::move(renderables)}};
}
inline Json execution_json(const Node_Execution& execution,
const Frame_Snapshot& frame) {
return {
{"node_id", execution.node_id},
{"start_time_ns", execution.start_time_ns},
{"end_time_ns", execution.end_time_ns},
{"start_offset_ns", execution.start_time_ns >= frame.render_start_ns
? execution.start_time_ns - frame.render_start_ns : 0},
{"end_offset_ns", execution.end_time_ns >= frame.render_start_ns
? execution.end_time_ns - frame.render_start_ns : 0},
{"duration_ns", execution.duration_ns()},
{"worker_id", execution.worker_id},
{"status", execution_status(execution.status)},
{"metrics", metrics_json(execution.metrics)}
};
}
inline Json node_analysis_json(const Node_Frame_Analysis& node) {
return {
{"node_id", node.node_id},
{"duration_ns", node.duration_ns},
{"start_offset_ns", node.start_offset_ns},
{"end_offset_ns", node.end_offset_ns},
{"dependency_ready_time_ns", node.dependency_ready_time_ns},
{"scheduler_wait_ns", node.scheduler_wait_ns},
{"work_contribution", node.work_contribution},
{"critical_path_contribution", node.critical_path_contribution},
{"on_critical_path", node.on_critical_path}
};
}
inline Json frame_json(const Captured_Frame& frame) {
Json executions = Json::array();
for (const auto& execution : frame.snapshot->node_executions)
executions.push_back(execution_json(execution, *frame.snapshot));
Json nodes = Json::array();
for (const auto& node : frame.analysis.nodes)
nodes.push_back(node_analysis_json(node));
Json workers = Json::array();
for (const auto& worker : frame.analysis.workers) {
workers.push_back({{"worker_id", worker.worker_id},
{"work_duration_ns", worker.work_duration_ns},
{"utilization", worker.utilization}});
}
return {
{"frame_id", frame.snapshot->frame_id},
{"render_plan_version", frame.snapshot->render_plan_version},
{"render_start_ns", frame.snapshot->render_start_ns},
{"render_end_ns", frame.snapshot->render_end_ns},
{"render_duration_ns", frame.snapshot->render_duration_ns()},
{"node_executions", std::move(executions)},
{"analysis", {
{"total_render_duration_ns", frame.analysis.total_render_duration_ns},
{"critical_path_duration_ns", frame.analysis.critical_path_duration_ns},
{"total_work_duration_ns", frame.analysis.total_work_duration_ns},
{"parallel_overlap_ns", frame.analysis.parallel_overlap_ns},
{"peak_parallelism", frame.analysis.peak_parallelism},
{"average_parallelism", frame.analysis.average_parallelism},
{"critical_path", frame.analysis.critical_path},
{"bottleneck_nodes", frame.analysis.bottleneck_nodes},
{"nodes", std::move(nodes)},
{"workers", std::move(workers)}
}}
};
}
inline Json node_statistics_json(const Node_Statistics& statistics) {
return {
{"node_id", statistics.node_id},
{"execution_count", statistics.execution_count},
{"average_ns", statistics.average_ns},
{"moving_average_ns", statistics.moving_average_ns},
{"p50_ns", statistics.p50_ns},
{"p95_ns", statistics.p95_ns},
{"p99_ns", statistics.p99_ns},
{"minimum_ns", statistics.minimum_ns},
{"maximum_ns", statistics.maximum_ns},
{"critical_path_frequency", statistics.critical_path_frequency},
{"average_scheduler_wait_ns", statistics.average_scheduler_wait_ns}
};
}
inline Json plan_statistics_json(const Plan_Version_Statistics& statistics) {
Json nodes = Json::array();
for (const auto& node : statistics.nodes)
nodes.push_back(node_statistics_json(node));
return {
{"render_plan_version", statistics.render_plan_version},
{"frame_count", statistics.frame_count},
{"render_average_ns", statistics.render_average_ns},
{"render_p50_ns", statistics.render_p50_ns},
{"render_p95_ns", statistics.render_p95_ns},
{"render_maximum_ns", statistics.render_maximum_ns},
{"average_parallelism", statistics.average_parallelism},
{"peak_parallelism", statistics.peak_parallelism},
{"scheduler_wait_average_ns", statistics.scheduler_wait_average_ns},
{"scheduler_wait_p95_ns", statistics.scheduler_wait_p95_ns},
{"nodes", std::move(nodes)}
};
}
inline double percentile(std::vector<std::uint64_t> values, double quantile) {
if (values.empty())
return 0.0;
std::sort(values.begin(), values.end());
const double position = quantile * static_cast<double>(values.size() - 1);
const auto lower = static_cast<std::size_t>(std::floor(position));
const auto upper = static_cast<std::size_t>(std::ceil(position));
const double fraction = position - static_cast<double>(lower);
return static_cast<double>(values[lower]) * (1.0 - fraction) +
static_cast<double>(values[upper]) * fraction;
}
inline Json session_summary_json(const Capture_Session& session) {
std::vector<std::uint64_t> durations;
std::vector<std::uint64_t> waits;
std::map<Render_Node_Id, std::size_t> critical_frequency;
double parallelism{};
std::size_t peak{};
for (const auto& frame : session.frames) {
durations.push_back(frame.analysis.total_render_duration_ns);
parallelism += frame.analysis.average_parallelism;
peak = std::max(peak, frame.analysis.peak_parallelism);
for (const auto& node : frame.analysis.nodes)
waits.push_back(node.scheduler_wait_ns);
for (Render_Node_Id node_id : frame.analysis.critical_path)
++critical_frequency[node_id];
}
const double duration_sum = std::accumulate(
durations.begin(), durations.end(), 0.0);
const double wait_sum = std::accumulate(waits.begin(), waits.end(), 0.0);
Json critical = Json::array();
for (const auto& [node_id, frequency] : critical_frequency)
critical.push_back({{"node_id", node_id}, {"frequency", frequency}});
return {
{"render_average_ns", durations.empty() ? 0.0 : duration_sum / durations.size()},
{"render_p50_ns", percentile(durations, 0.50)},
{"render_p95_ns", percentile(durations, 0.95)},
{"render_maximum_ns", durations.empty() ? 0 : *std::max_element(durations.begin(), durations.end())},
{"average_parallelism", session.frames.empty() ? 0.0 : parallelism / session.frames.size()},
{"peak_parallelism", peak},
{"scheduler_wait_average_ns", waits.empty() ? 0.0 : wait_sum / waits.size()},
{"scheduler_wait_p95_ns", percentile(waits, 0.95)},
{"critical_path_frequency", std::move(critical)}
};
}
inline Json plan_difference_json(const Render_Plan_Difference& difference) {
Json added_edges = Json::array();
Json removed_edges = Json::array();
for (const auto& edge : difference.added_edges)
added_edges.push_back({{"from", edge.from}, {"to", edge.to}});
for (const auto& edge : difference.removed_edges)
removed_edges.push_back({{"from", edge.from}, {"to", edge.to}});
return {{"added_nodes", difference.added_nodes},
{"removed_nodes", difference.removed_nodes},
{"added_edges", std::move(added_edges)},
{"removed_edges", std::move(removed_edges)}};
}
} // namespace gallery_capture_detail
inline nlohmann::json gallery_render_plan_json(const Scene_Base& scene) {
const auto plan = scene.render_plan_snapshot();
return plan ? gallery_capture_detail::plan_json(scene, *plan)
: nlohmann::json{{"version", 0}, {"nodes", nlohmann::json::array()},
{"edges", nlohmann::json::array()},
{"renderables", nlohmann::json::array()}};
}
inline nlohmann::json gallery_performance_capture_json(const Scene_Base& scene) {
using namespace gallery_capture_detail;
const auto controller = scene.capture_state();
Json sessions = Json::array();
std::set<Render_Plan_Version> referenced_plans;
for (Capture_Session_Id id : scene.capture_sessions()) {
const auto session = scene.capture_session(id);
if (!session)
continue;
Json frames = Json::array();
for (const auto& frame : session->frames) {
frames.push_back(frame_json(frame));
referenced_plans.insert(frame.snapshot->render_plan_version);
}
Json node_statistics = Json::array();
for (const auto& node : scene.node_statistics(id))
node_statistics.push_back(node_statistics_json(node));
Json plan_statistics = Json::array();
for (const auto& plan : scene.plan_version_statistics(id))
plan_statistics.push_back(plan_statistics_json(plan));
sessions.push_back({
{"session_id", session->session_id},
{"requested_count", session->requested_count},
{"captured_count", session->captured_count()},
{"active", session->active()},
{"frames", std::move(frames)},
{"summary", session_summary_json(*session)},
{"node_statistics", std::move(node_statistics)},
{"plan_statistics", std::move(plan_statistics)}
});
}
if (const auto current = scene.render_plan_snapshot())
referenced_plans.insert(current->version);
Json plans = Json::array();
std::shared_ptr<const Render_Plan> previous;
for (Render_Plan_Version version : referenced_plans) {
const auto plan = scene.find_render_plan(version);
if (!plan)
continue;
Json value = plan_json(scene, *plan);
value["difference"] = previous
? plan_difference_json(compare_render_plans(*previous, *plan))
: Json{{"added_nodes", Json::array()}, {"removed_nodes", Json::array()},
{"added_edges", Json::array()}, {"removed_edges", Json::array()}};
plans.push_back(std::move(value));
previous = plan;
}
return {
{"controller", {
{"enabled", controller.enabled()},
{"session_id", controller.session_id},
{"remaining_frame_count", controller.remaining_frame_count}
}},
{"sessions", std::move(sessions)},
{"plans", std::move(plans)}
};
}
} // namespace renderive::web