diff --git a/kernel/src/kernel/Taskflow_Frame_Access.hpp b/kernel/src/kernel/Taskflow_Frame_Access.hpp index 0b680c2..c028fb9 100644 --- a/kernel/src/kernel/Taskflow_Frame_Access.hpp +++ b/kernel/src/kernel/Taskflow_Frame_Access.hpp @@ -23,7 +23,9 @@ struct Taskflow_Frame_Access { std::size_t queue_size, std::size_t queue_capacity, Clock::time_point entered, Clock::time_point started, Clock::time_point finished, std::uint64_t cpu_entered_ns, - std::uint64_t cpu_started_ns, std::uint64_t cpu_finished_ns); + std::uint64_t cpu_started_ns, std::uint64_t cpu_duration_ns, + std::uint64_t cpu_cycles, std::uint64_t cooperative_wait_ns, + bool cpu_time_coarse); static void finish_task_observer( Render_Frame& frame, std::size_t worker, std::size_t task, Clock::time_point completed, std::uint64_t cpu_finished_ns, diff --git a/kernel/src/kernel/frame.cpp b/kernel/src/kernel/frame.cpp index ce398e9..cef80b2 100644 --- a/kernel/src/kernel/frame.cpp +++ b/kernel/src/kernel/frame.cpp @@ -434,7 +434,9 @@ std::size_t detail::Taskflow_Frame_Access::append_task( std::size_t queue_size, std::size_t queue_capacity, Clock::time_point entered, Clock::time_point started, Clock::time_point finished, std::uint64_t cpu_entered_ns, - std::uint64_t cpu_started_ns, std::uint64_t cpu_finished_ns) { + std::uint64_t cpu_started_ns, std::uint64_t cpu_duration_ns, + std::uint64_t cpu_cycles, std::uint64_t cooperative_wait_ns, + bool cpu_time_coarse) { auto& data = *frame.d; if (worker >= data.taskflow_workers.size()) return std::numeric_limits::max(); @@ -451,9 +453,10 @@ std::size_t detail::Taskflow_Frame_Access::append_task( trace.finished_ms = elapsed_ms(finished); trace.completed_ms = trace.finished_ms; trace.duration_ms = std::max(0.0, trace.finished_ms - trace.started_ms); - trace.cpu_duration_ms = cpu_finished_ns >= cpu_started_ns - ? static_cast(cpu_finished_ns - cpu_started_ns) / 1'000'000.0 - : 0.0; + trace.cpu_duration_ms = static_cast(cpu_duration_ns) / 1'000'000.0; + trace.cpu_cycles = cpu_cycles; + trace.cooperative_wait_ms = static_cast(cooperative_wait_ns) / 1'000'000.0; + trace.cpu_time_coarse = cpu_time_coarse; trace.observer_entry_ms = std::max(0.0, trace.started_ms - trace.entered_ms); trace.observer_entry_cpu_ms = cpu_started_ns >= cpu_entered_ns ? static_cast(cpu_started_ns - cpu_entered_ns) / 1'000'000.0 diff --git a/kernel/src/kernel/frame.hpp b/kernel/src/kernel/frame.hpp index 4bba1d7..10fa914 100644 --- a/kernel/src/kernel/frame.hpp +++ b/kernel/src/kernel/frame.hpp @@ -100,7 +100,10 @@ struct Taskflow_Task_Trace { double finished_ms{}; /* Observer on_exit 进入,即任务体已经结束的时间。 */ double completed_ms{}; /* Observer on_exit 与按帧追踪写入全部结束的时间。 */ double duration_ms{}; /* 仅任务体 started 到 finished 的持续时间。 */ - double cpu_duration_ms{}; /* 任务体在当前 worker 线程上实际消耗的 CPU 时间。 */ + double cpu_duration_ms{}; /* 任务体独占当前 worker 片段的线程 CPU 时间;cooperative corun 期间不计入。 */ + std::uint64_t cpu_cycles{}; /* Windows QueryThreadCycleTime 的独占 CPU 周期;用于短任务 CPU 活动判定,不直接换算秒。 */ + double cooperative_wait_ms{}; /* Task_Graph::corun/corun_until 主动让出 Worker 的墙钟时间。 */ + bool cpu_time_coarse{}; /* 当前平台的线程 CPU 时间源是否为低分辨率计费时钟(Windows GetThreadTimes)。 */ double observer_entry_ms{}; /* on_entry 诊断本身的耗时。 */ double observer_exit_ms{}; /* on_exit 诊断与按帧追踪写入的耗时。 */ double observer_entry_cpu_ms{}; /* on_entry 诊断实际消耗的 worker CPU 时间。 */ diff --git a/kernel/src/kernel/render_common.cpp b/kernel/src/kernel/render_common.cpp index 620a45b..89f1c0b 100644 --- a/kernel/src/kernel/render_common.cpp +++ b/kernel/src/kernel/render_common.cpp @@ -70,6 +70,20 @@ std::uint64_t current_thread_cpu_ns() noexcept { return 0; #endif } +std::uint64_t current_thread_cpu_cycles() noexcept { +#if defined(_WIN32) + return thread_cpu_cycles(GetCurrentThread()); +#else + return 0; +#endif +} +constexpr bool thread_cpu_time_is_coarse() noexcept { +#if defined(_WIN32) + return true; +#else + return false; +#endif +} struct Task_Observer : public tf::ObserverInterface { private: using Clock = std::chrono::steady_clock; @@ -122,7 +136,14 @@ private: Clock::time_point segment_started{}; /* 当前连续独占 Worker 片段的起点。 */ std::uint64_t maximum_segment_ns{}; /* 已结束连续独占片段的最大墙钟。 */ std::uint64_t cpu_entered_ns{}; /* on_entry 进入时的 worker CPU 时间。 */ - std::uint64_t cpu_started_ns{}; /* 任务体开始前的 worker CPU 时间。 */ + std::uint64_t cpu_started_ns{}; /* 任务体开始前的 worker CPU 时间,仅用于 Observer entry 统计。 */ + std::uint64_t cpu_segment_started_ns{}; /* 当前任务独占 Worker 片段的线程 CPU 起点。 */ + bool cpu_segment_active{}; /* CPU 累计值本身允许为 0,不能拿 0 当未启动哨兵。 */ + std::uint64_t cpu_duration_ns{}; /* 已累计的任务独占 Worker CPU;嵌套 corun/子任务不计入。 */ + std::uint64_t cpu_cycle_segment_started{}; /* Windows 当前独占片段的 QueryThreadCycleTime 起点。 */ + std::uint64_t cpu_cycles{}; /* Windows 已累计的任务独占 CPU 周期。 */ + Clock::time_point cooperative_wait_started{}; /* 主动 corun 让出 Worker 的墙钟起点。 */ + std::uint64_t cooperative_wait_ns{}; /* 已累计 cooperative wait 墙钟。 */ Render_Frame* frame{}; /* 进入任务时唯一活动的按帧捕获。 */ std::size_t queue_size{}; /* 进入任务时 worker 队列深度。 */ std::size_t queue_capacity{}; /* 进入任务时 worker 队列容量。 */ @@ -245,6 +266,41 @@ private: } } } + static void close_cpu_segment( + Start_Record& active, std::uint64_t cpu_now_ns, + std::uint64_t cycle_now) noexcept { + if (!active.frame || !active.cpu_segment_active) return; + if (cpu_now_ns >= active.cpu_segment_started_ns) + active.cpu_duration_ns += + cpu_now_ns - active.cpu_segment_started_ns; +#if defined(_WIN32) + if (cycle_now >= active.cpu_cycle_segment_started) + active.cpu_cycles += cycle_now - active.cpu_cycle_segment_started; +#else + static_cast(cycle_now); +#endif + active.cpu_segment_started_ns = 0; + active.cpu_cycle_segment_started = 0; + active.cpu_segment_active = false; + } + static void close_cpu_segment(Start_Record& active) noexcept { + if (!active.frame || !active.cpu_segment_active) return; + close_cpu_segment(active, current_thread_cpu_ns(), + current_thread_cpu_cycles()); + } + static void open_cpu_segment( + Start_Record& active, std::uint64_t cpu_now_ns, + std::uint64_t cycle_now) noexcept { + if (!active.frame) return; + active.cpu_segment_started_ns = cpu_now_ns; + active.cpu_cycle_segment_started = cycle_now; + active.cpu_segment_active = true; + } + static void open_cpu_segment(Start_Record& active) noexcept { + if (!active.frame) return; + open_cpu_segment(active, current_thread_cpu_ns(), + current_thread_cpu_cycles()); + } void pause_worker(std::size_t worker) noexcept { if (worker >= starts.size() || starts[worker].empty()) return; const auto now = Clock::now(); @@ -256,7 +312,10 @@ private: active.maximum_segment_ns = std::max( active.maximum_segment_ns, elapsed); } + close_cpu_segment(active); active.segment_started = {}; + if (active.cooperative_wait_started == Clock::time_point{}) + active.cooperative_wait_started = now; active.cooperatively_suspended = true; worker_statistics[worker].active_segment_started_ns.store( 0, std::memory_order_release); @@ -273,8 +332,15 @@ private: if (worker >= starts.size() || starts[worker].empty()) return; const auto now = Clock::now(); auto& active = starts[worker].back(); + if (active.cooperative_wait_started != Clock::time_point{}) { + active.cooperative_wait_ns += static_cast( + std::chrono::duration_cast( + now - active.cooperative_wait_started).count()); + active.cooperative_wait_started = {}; + } active.cooperatively_suspended = false; active.segment_started = now; + open_cpu_segment(active); worker_statistics[worker].active_segment_started_ns.store( clock_ns(now), std::memory_order_release); worker_statistics[worker].active_segment_cpu_started_ns.store( @@ -351,6 +417,7 @@ public: now - parent.segment_started).count()); parent.maximum_segment_ns = std::max( parent.maximum_segment_ns, elapsed); + close_cpu_segment(parent); parent.segment_started = {}; } } @@ -358,11 +425,14 @@ public: worker_busy_starts[worker.id()] = now; worker_cpu_starts[worker.id()] = current_thread_cpu_ns(); } - worker_starts.push_back(Start_Record{ - now, {}, {}, 0, current_thread_cpu_ns(), 0, nullptr, - worker.queue_size(), worker.queue_capacity(), - static_cast(task.hash_value()), task.type(), false - }); + Start_Record record{}; + record.entered = now; + record.cpu_entered_ns = current_thread_cpu_ns(); + record.queue_size = worker.queue_size(); + record.queue_capacity = worker.queue_capacity(); + record.native_id = static_cast(task.hash_value()); + record.type = task.type(); + worker_starts.push_back(std::move(record)); auto* frame = trace_frame.load(std::memory_order_acquire); /* * 帧租约从 on_entry 持续到对应 on_exit。只在退出时登记写入者会留下 @@ -413,7 +483,15 @@ public: task.num_weak_dependencies()); if (!task.name().empty()) worker_state.named_task_count.fetch_add(1, std::memory_order_relaxed); update_first(worker_state.first_task_time_ns, clock_ns(now)); - worker_starts.back().cpu_started_ns = current_thread_cpu_ns(); + const auto task_cpu_started_ns = current_thread_cpu_ns(); + const auto task_cpu_cycle_started = worker_starts.back().frame + ? current_thread_cpu_cycles() : 0; + worker_starts.back().cpu_started_ns = task_cpu_started_ns; + worker_starts.back().cpu_segment_started_ns = task_cpu_started_ns; + worker_starts.back().cpu_cycle_segment_started = + task_cpu_cycle_started; + worker_starts.back().cpu_segment_active = + worker_starts.back().frame != nullptr; worker_starts.back().started = Clock::now(); worker_starts.back().segment_started = worker_starts.back().started; } @@ -421,7 +499,17 @@ public: const auto finished = Clock::now(); const auto cpu_finished_ns = current_thread_cpu_ns(); auto& worker_starts = starts[worker.id()]; - auto start = worker_starts.back(); + auto& active = worker_starts.back(); + const auto cpu_finished_cycles = active.frame + ? current_thread_cpu_cycles() : 0; + close_cpu_segment(active, cpu_finished_ns, cpu_finished_cycles); + if (active.cooperative_wait_started != Clock::time_point{}) { + active.cooperative_wait_ns += static_cast( + std::chrono::duration_cast( + finished - active.cooperative_wait_started).count()); + active.cooperative_wait_started = {}; + } + auto start = active; worker_starts.pop_back(); const auto elapsed = static_cast( std::chrono::duration_cast( @@ -467,7 +555,9 @@ public: static_cast(task.hash_value()), start.queue_size, start.queue_capacity, start.entered, start.started, finished, start.cpu_entered_ns, - start.cpu_started_ns, cpu_finished_ns); + start.cpu_started_ns, start.cpu_duration_ns, + start.cpu_cycles, start.cooperative_wait_ns, + thread_cpu_time_is_coarse()); } catch (...) { /* Observer 不能让按需诊断分配失败改变渲染任务的完成语义。 */ @@ -526,20 +616,22 @@ public: #endif } else { - parent.segment_started = completed; + const auto parent_cpu_started_ns = current_thread_cpu_ns(); + const auto parent_cpu_cycle_started = parent.frame + ? current_thread_cpu_cycles() : 0; + const auto parent_resumed = Clock::now(); + parent.segment_started = parent_resumed; + open_cpu_segment(parent, parent_cpu_started_ns, + parent_cpu_cycle_started); worker_state.active_segment_started_ns.store( - clock_ns(completed), std::memory_order_release); + clock_ns(parent_resumed), std::memory_order_release); worker_state.active_segment_cpu_started_ns.store( - cpu_completed_ns, std::memory_order_release); + parent_cpu_started_ns, std::memory_order_release); #if defined(_WIN32) - const auto native_handle = - worker_state.native_thread_handle.load( - std::memory_order_acquire); worker_state.active_cpu_cycles.store( - thread_cpu_cycles(reinterpret_cast(native_handle)), - std::memory_order_release); + parent_cpu_cycle_started, std::memory_order_release); worker_state.active_cpu_progress_ns.store( - clock_ns(completed), std::memory_order_release); + clock_ns(parent_resumed), std::memory_order_release); #endif } worker_state.active_task_type.store(parent.type, std::memory_order_relaxed); diff --git a/web_server/src/Gallery_Video_Stream.cpp b/web_server/src/Gallery_Video_Stream.cpp index 8d137bd..c5ea78a 100644 --- a/web_server/src/Gallery_Video_Stream.cpp +++ b/web_server/src/Gallery_Video_Stream.cpp @@ -2,17 +2,14 @@ #include #include #include "detail/Gallery_Frame_Atlas.hpp" -#include #include #include #include #include #include -#include #include #include #include -#include #include namespace aethera::web { namespace { @@ -46,71 +43,7 @@ std::string exception_description(const std::exception_ptr& failure) { return "empty gallery video failure"; } } -namespace detail { -/* - * FFmpeg 硬件编码 API 可能在驱动内部等待硬件队列。它仍是 Scene completion - * DAG 的一个业务节点,但不能占住 Kernel Taskflow Worker。每个图集只有一个 - * H264_Encoder,因此用一个串行编码域维持 codec context 的唯一执行位置; - * 调用节点通过 Task_Graph::corun_until 协作让出,完成后恢复同一帧 DAG。 - */ -struct Video_Encode_Domain final { - struct Work { - std::function function; - std::shared_ptr> completion; - }; -public: - Video_Encode_Domain() : thread_([this] { - run(); - }) {} - ~Video_Encode_Domain() { - stopping_.store(true, std::memory_order_release); - if (thread_.joinable()) thread_.join(); - } - Video_Encode_Domain(const Video_Encode_Domain&) = delete; - Video_Encode_Domain& operator=(const Video_Encode_Domain&) = delete; - void invoke(std::function function) { - if (!function) throw std::invalid_argument("video encode work is empty"); - if (stopping_.load(std::memory_order_acquire)) throw std::runtime_error("video encode domain is stopping"); - auto completion = std::make_shared>(); - auto completed = completion->get_future(); - auto work = std::make_unique( - Work{std::move(function), std::move(completion)}); - if (!queue_.enqueue(std::move(work))) throw std::bad_alloc{}; - Task_Graph::corun_until([&completed] { - return completed.wait_for(std::chrono::seconds(0)) == - std::future_status::ready; - }); - completed.get(); - } -private: - void run() noexcept { - for (;;) { - std::unique_ptr work; - const bool received = queue_.wait_dequeue_timed( - work, std::chrono::milliseconds(1)); - if (received && work) { - try { - work->function(); - work->completion->set_value(); - } - catch (...) { - try { - work->completion->set_exception( - std::current_exception()); - } - catch (...) {} - } - } - if (!received && stopping_.load(std::memory_order_acquire)) return; - } - } - moodycamel::BlockingConcurrentQueue> queue_{8}; - std::atomic_bool stopping_{}; - std::thread thread_; -}; -} -Gallery_Video_Stream::Private::Private() : encoder(gallery_frame_rate), - encode_domain(std::make_unique()) {} +Gallery_Video_Stream::Private::Private() : encoder(gallery_frame_rate) {} Gallery_Video_Stream::Private::~Private() = default; void Gallery_Video_Stream::Private::initialize( std::vector plots) { @@ -331,27 +264,32 @@ void Gallery_Video_Stream::Private::compose_media_frame() { } void Gallery_Video_Stream::Private::encode_media_frame() { if (!active_composition) return; - encode_domain->invoke([this] { - const auto started = std::chrono::steady_clock::now(); - if (key_frame_requested.exchange(false, - std::memory_order_acq_rel)) - encoder.request_key_frame(); - active_video = encoder.encode( - active_composition->pixels, active_composition->width, - active_composition->height, - active_composition->layout == Plot_Pixel_Layout::bgra8 - ? Video_Pixel_Layout::bgra - : Video_Pixel_Layout::rgba, - active_encode_tick.sequence, - std::chrono::microseconds{ - static_cast( - std::llround(active_encode_tick.time_milliseconds * - 1'000.0)) - }); - static_cast(encode_ms.submit( - std::chrono::duration( - std::chrono::steady_clock::now() - started).count())); - }); + /* + * 直接在 FFmpeg.H264.encode Taskflow 节点中调用 FFmpeg。这样实际 + * avcodec_send_frame/avcodec_receive_packet(以及硬件后端等待)全部落在 + * 同一个业务节点的 wall/CPU 统计中,不再由额外编码线程隐藏真实耗时。 + * gallery.video.frame 本身串行 compose -> encode -> publish,页面唯一 + * H264_Encoder 因而仍只有一个执行位置,不需要额外 mutex 或专用线程。 + */ + const auto started = std::chrono::steady_clock::now(); + if (key_frame_requested.exchange(false, + std::memory_order_acq_rel)) + encoder.request_key_frame(); + active_video = encoder.encode( + active_composition->pixels, active_composition->width, + active_composition->height, + active_composition->layout == Plot_Pixel_Layout::bgra8 + ? Video_Pixel_Layout::bgra + : Video_Pixel_Layout::rgba, + active_encode_tick.sequence, + std::chrono::microseconds{ + static_cast( + std::llround(active_encode_tick.time_milliseconds * + 1'000.0)) + }); + static_cast(encode_ms.submit( + std::chrono::duration( + std::chrono::steady_clock::now() - started).count())); } void Gallery_Video_Stream::Private::publish_media_frame() { if (!active_video || !active_composition) return; @@ -418,7 +356,7 @@ void Gallery_Video_Stream::bind_plots() { }); compose.describe("owner", "gallery") .describe("stage", "latest completed Plot frames to atlas"); - auto encode = media->add("gallery.h264.encode", [weak] { + auto encode = media->add("FFmpeg.H264.encode", [weak] { if (const auto owner = weak.lock()) { auto& owner_data = static_cast(*owner->d); try { @@ -430,7 +368,10 @@ void Gallery_Video_Stream::bind_plots() { } }); encode.describe("owner", "gallery") - .describe("stage", "H.264 encode in Scene completion pipeline"); + .describe("stage", "FFmpeg H.264 encode in Scene completion pipeline") + .describe("backend", "FFmpeg") + .describe("codec", "H.264") + .describe("execution", "Taskflow worker"); auto publish = media->add("gallery.webrtc.publish", [weak] { if (const auto owner = weak.lock()) { auto& owner_data = static_cast(*owner->d); diff --git a/web_server/src/Gallery_Video_Stream.ipp b/web_server/src/Gallery_Video_Stream.ipp index 099ed04..782f691 100644 --- a/web_server/src/Gallery_Video_Stream.ipp +++ b/web_server/src/Gallery_Video_Stream.ipp @@ -4,9 +4,6 @@ #include #include namespace aethera::web { -namespace detail { -struct Video_Encode_Domain; -} struct Gallery_Video_Stream::Private : Prev_Private { using Object = Impl; struct Source { @@ -28,7 +25,6 @@ struct Gallery_Video_Stream::Private : Prev_Private { std::vector sources{}; /* 已按业务标识排序的稳定图集来源。 */ std::unique_ptr atlas{}; /* 最近完成帧与 RGBA 图集的唯一状态源。 */ H264_Encoder encoder; /* 页面唯一 H.264 编码器。 */ - std::unique_ptr encode_domain{}; /* FFmpeg 驱动调用域。 */ Plot_Render_Tick active_encode_tick{}; /* 当前媒体 DAG 的输入时钟。 */ std::optional active_composition{}; /* 当前合成结果所有权。 */ std::optional active_video{}; /* 当前编码结果所有权。 */ diff --git a/web_server/src/Plot.cpp b/web_server/src/Plot.cpp index ed8dc7e..874fc4f 100644 --- a/web_server/src/Plot.cpp +++ b/web_server/src/Plot.cpp @@ -243,6 +243,9 @@ nlohmann::json taskflow_trace_json(const Taskflow_Frame_Trace& trace) { {"completed_ms", task.completed_ms}, {"duration_ms", task.duration_ms}, {"cpu_duration_ms", task.cpu_duration_ms}, + {"cpu_cycles", task.cpu_cycles}, + {"cooperative_wait_ms", task.cooperative_wait_ms}, + {"cpu_time_coarse", task.cpu_time_coarse}, {"observer_entry_ms", task.observer_entry_ms}, {"observer_exit_ms", task.observer_exit_ms}, {"observer_entry_cpu_ms", task.observer_entry_cpu_ms}, diff --git a/webapp_gallery/src/app.tsx b/webapp_gallery/src/app.tsx index 46a2a9b..f5a53d2 100644 --- a/webapp_gallery/src/app.tsx +++ b/webapp_gallery/src/app.tsx @@ -4,7 +4,7 @@ import {Responsive, useContainerWidth, type LayoutItem, type ResponsiveLayouts} import {ResizableBox} from "react-resizable"; import ReconnectingWebSocket from "reconnecting-websocket"; import ELK from "elkjs/lib/elk.bundled.js"; -import {Background, Controls, MarkerType, MiniMap, ReactFlow, +import {Background, Controls, MarkerType, MiniMap, Position, ReactFlow, type Edge as Flow_Edge, type Node as Flow_Node} from "@xyflow/react"; import * as echarts from "echarts/core"; import {LineChart} from "echarts/charts"; @@ -79,7 +79,8 @@ type Taskflow_Graph_Trace = {stage: string; name: string; submitted_ms: number; completed: boolean; nodes: Taskflow_Node_Trace[]}; type Taskflow_Execution_Trace = {native_id: string; node_id: string; worker_id: number; worker_queue_size: number; worker_queue_capacity: number; ready_ms: number; entered_ms: number; started_ms: number; finished_ms: number; - completed_ms: number; duration_ms: number; cpu_duration_ms: number; observer_entry_ms: number; observer_exit_ms: number; + completed_ms: number; duration_ms: number; cpu_duration_ms: number; cpu_cycles: number; cooperative_wait_ms: number; + cpu_time_coarse: boolean; observer_entry_ms: number; observer_exit_ms: number; observer_entry_cpu_ms: number; observer_exit_cpu_ms: number; queue_wait_ms: number}; type Taskflow_Frame_Trace = {sequence: number; correlation_id: number; created_time_unix_ns: number; worker_count: number; markers?: Record; graphs: Taskflow_Graph_Trace[]; executions: Taskflow_Execution_Trace[]}; @@ -1137,20 +1138,160 @@ function nanoseconds(value: number) { return milliseconds(value / 1_000_000); } -const taskflow_operation_names: Record = { - condition: "执行条件", - data: "绘制", - graph: "子图", - extension: "扩展", - complete: "提交组件 State", - cache_composite: "缓存合成" -}; +function cpu_cycles(value: number) { + if (!Number.isFinite(value) || value <= 0) return "--"; + if (value >= 1_000_000_000) return `${(value / 1_000_000_000).toFixed(2)} Gcy`; + if (value >= 1_000_000) return `${(value / 1_000_000).toFixed(2)} Mcy`; + if (value >= 1_000) return `${(value / 1_000).toFixed(1)} Kcy`; + return `${Math.round(value)} cy`; +} + +function task_cpu_label(sample: Taskflow_Execution_Trace) { + if (sample.cpu_time_coarse && sample.cpu_duration_ms === 0 && sample.cpu_cycles > 0) + return "低于系统 CPU 计时分辨率"; + return milliseconds(sample.cpu_duration_ms); +} + +type Taskflow_Node_Phase = "prepare" | "paint" | "control" | "completion" | "other"; + +function taskflow_node_phase(name: string): Taskflow_Node_Phase { + if (name.includes(".prepare.")) return "prepare"; + if (name.includes(".paint.")) return "paint"; + if (name === "scene.paint" || name === "scene.paint.setup" || name === "scene.paint.complete") return "paint"; + if (name.includes(".completion") || name.includes(".publish")) return "completion"; + if (name.startsWith("scene.")) return "control"; + return "other"; +} function taskflow_node_name(name: string) { - const parts = name.split(".").filter(part => part && part !== "paint"); - if (parts.length === 0) return name; - const operation = taskflow_operation_names[parts.at(-1) ?? ""] ?? parts.at(-1); - return parts.length === 1 ? operation ?? name : `${parts[0]} · ${operation}`; + const parts = name.split(".").filter(Boolean); + if (!parts.length) return name; + + if (name === "scene.begin") return "Scene · 帧开始"; + if (name === "scene.prepare") return "Scene · 数据准备子图"; + if (name === "scene.paint.setup") return "Scene · 绘制目标准备"; + if (name === "scene.paint") return "Scene · 绘制子图"; + if (name === "scene.paint.complete") return "Scene · 像素绘制完成"; + if (name === "scene.completion") return "Scene · 帧完成处理"; + if (name === "plot.frame.publish") return "Plot · 发布完成帧"; + if (name === "gallery.atlas.compose") return "Gallery · 图集合成"; + if (name === "FFmpeg.H264.encode" || name === "gallery.h264.encode") return "FFmpeg H.264 · 编码"; + if (name === "gallery.webrtc.publish") return "WebRTC · 视频帧发布"; + + const owner = parts[0]; + const stage = parts[1]; + const operation = parts[2] ?? parts[1]; + if (stage === "prepare") { + const labels: Record = { + condition: "准备条件", data: "数据准备", graph: "准备子图", + extension: "准备扩展", complete: "准备完成" + }; + return `${owner} · ${labels[operation] ?? `准备 / ${operation}`}`; + } + if (stage === "paint") { + const labels: Record = { + condition: "绘制条件", data: "实际绘制", graph: "绘制子图", + extension: "绘制扩展", complete: "绘制完成", cache_composite: "缓存合成" + }; + return `${owner} · ${labels[operation] ?? `绘制 / ${operation}`}`; + } + + const labels: Record = { + condition: "执行条件", data: "执行数据", graph: "子图", extension: "扩展", + complete: "完成", cache_composite: "缓存合成" + }; + return parts.length === 1 ? (labels[parts[0]] ?? name) + : `${owner} · ${labels[parts.at(-1) ?? ""] ?? parts.at(-1)}`; +} + +function taskflow_phase_label(name: string) { + const phase = taskflow_node_phase(name); + if (name === "FFmpeg.H264.encode" || name === "gallery.h264.encode") return "FFMPEG"; + if (name.includes(".paint.data")) return "PAINT"; + if (name.includes(".prepare.data")) return "PREP"; + if (phase === "paint") return "绘制阶段"; + if (phase === "prepare") return "准备阶段"; + if (phase === "completion") return "完成阶段"; + return null; +} + +async function copy_text(text: string) { + if (navigator.clipboard?.writeText) { + await navigator.clipboard.writeText(text); + return; + } + const textarea = document.createElement("textarea"); + textarea.value = text; + textarea.style.position = "fixed"; + textarea.style.opacity = "0"; + document.body.appendChild(textarea); + textarea.focus(); + textarea.select(); + const copied = document.execCommand("copy"); + textarea.remove(); + if (!copied) throw new Error("copy failed"); +} + +function Taskflow_Node_Label({node, sample, summary, state, level}: { + node: Taskflow_Node_Trace; sample?: Taskflow_Execution_Trace; summary?: Taskflow_Node_Aggregate; + state: unknown; level: number; +}) { + const [copied, set_copied] = useState(false); + const wait = sample?.queue_wait_ms ?? 0; + const duration = sample?.duration_ms ?? 0; + const phase = taskflow_node_phase(node.name); + const phase_label = taskflow_phase_label(node.name); + const copy_node = async () => { + try { + await copy_text(JSON.stringify({ + node, execution: sample ?? null, + aggregate: summary ? { + present: summary.present, executed: summary.executed, frames: summary.frames, + duration: summary.duration, queue: summary.queue, cpu: summary.cpu, observer: summary.observer, + workers: summary.workers, stability: summary.stability + } : null, + state: state ?? null + }, null, 2)); + set_copied(true); + window.setTimeout(() => set_copied(false), 1000); + } catch { + set_copied(false); + } + }; + return
event.stopPropagation()} onWheel={event => event.stopPropagation()}> +
+
+ {taskflow_node_name(node.name)} + {phase_label ? {phase_label} : null} +
+
+ + 层 {level + 1} +
+
+ {node.name} + {summary ? <> + {node.type} · 样本 {summary.executed}/{summary.frames} · W {summary.workers.join(", ") || "--"} + 执行 {milliseconds(summary.duration.average)} ± {milliseconds(summary.duration.variability)} · P95 {milliseconds(summary.duration.p95)} + 排队 {milliseconds(summary.queue.average)} ± {milliseconds(summary.queue.variability)} · P99 {milliseconds(summary.queue.p99)} + 线程 CPU{summary.cpu_time_coarse ? "(低分辨率)" : ""} {milliseconds(summary.cpu.average)} · CPU 活动 {cpu_cycles(summary.cycles.average)} + 协作等待 {milliseconds(summary.cooperative_wait.average)} · Observer {milliseconds(summary.observer.average)} + : <>{node.type} · W{sample?.worker_id ?? "--"} + 任务体 {sample ? milliseconds(duration) : "未执行"} · 排队 {sample ? milliseconds(wait) : "--"} + 线程 CPU {sample ? task_cpu_label(sample) : "--"}{sample?.cpu_time_coarse ? "(低分辨率)" : ""} · CPU 活动 {sample ? cpu_cycles(sample.cpu_cycles) : "--"} + 协作等待 {sample ? milliseconds(sample.cooperative_wait_ms) : "--"} · 未归因墙时 {sample ? `${sample.cpu_time_coarse ? "≈ " : ""}${milliseconds(Math.max(0, duration - sample.cooperative_wait_ms - sample.cpu_duration_ms))}` : "--"} + Observer {sample ? milliseconds(sample.observer_entry_ms + sample.observer_exit_ms) : "--"}} + {Object.entries(node.attributes ?? {}).map(([key, value]) => {key}:{value})} + {state ?
event.stopPropagation()} onClick={event => event.stopPropagation()} + onWheel={event => event.stopPropagation()}>运行状态 JSON +
{JSON.stringify(state, null, 2)}
: null} +
; } function taskflow_graph_analysis(graph: Taskflow_Graph_Trace, executions: Taskflow_Execution_Trace[]) { @@ -1167,7 +1308,10 @@ function taskflow_graph_analysis(graph: Taskflow_Graph_Trace, executions: Taskfl const execution_time = leaf_rows.reduce((sum, row) => sum + row.duration_ms, 0); const module_envelope_time = module_rows.reduce((sum, row) => sum + row.duration_ms, 0); const cpu_execution_time = leaf_rows.reduce((sum, row) => sum + row.cpu_duration_ms, 0); - const descheduled_time = Math.max(0, execution_time - cpu_execution_time); + const cooperative_wait_time = leaf_rows.reduce((sum, row) => sum + row.cooperative_wait_ms, 0); + const cpu_cycles_total = leaf_rows.reduce((sum, row) => sum + row.cpu_cycles, 0); + const cpu_time_coarse = leaf_rows.some(row => row.cpu_time_coarse); + const unattributed_wall_time = Math.max(0, execution_time - cooperative_wait_time - cpu_execution_time); const observer_entry_time = rows.reduce((sum, row) => sum + row.observer_entry_ms, 0); const observer_exit_time = rows.reduce((sum, row) => sum + row.observer_exit_ms, 0); const observer_cpu_time = rows.reduce((sum, row) => sum + row.observer_entry_cpu_ms + row.observer_exit_cpu_ms, 0); @@ -1176,9 +1320,11 @@ function taskflow_graph_analysis(graph: Taskflow_Graph_Trace, executions: Taskfl const last_completed = rows.length ? Math.max(...rows.map(row => row.completed_ms)) : graph.finished_ms; const longest = rows.reduce( (result, row) => !result || row.duration_ms > result.duration_ms ? row : result, null); - const longest_non_cpu = rows.reduce((result, row) => { - const value = Math.max(0, row.duration_ms - row.cpu_duration_ms); - const previous = result ? Math.max(0, result.duration_ms - result.cpu_duration_ms) : -1; + const longest_unattributed = rows.reduce((result, row) => { + const value = Math.max(0, row.duration_ms - row.cooperative_wait_ms - row.cpu_duration_ms); + const previous = result + ? Math.max(0, result.duration_ms - result.cooperative_wait_ms - result.cpu_duration_ms) + : -1; return value > previous ? row : result; }, null); const events = rows.flatMap(row => [ @@ -1233,8 +1379,8 @@ function taskflow_graph_analysis(graph: Taskflow_Graph_Trace, executions: Taskfl for (const level of levels.values()) width_by_level.set(level, (width_by_level.get(level) ?? 0) + 1); return { rows, leaf_rows, module_rows, levels, wall_time, execution_time, - module_envelope_time, cpu_execution_time, - descheduled_time, observer_entry_time, observer_cpu_time, + module_envelope_time, cpu_execution_time, cooperative_wait_time, cpu_cycles_total, cpu_time_coarse, + unattributed_wall_time, observer_entry_time, observer_cpu_time, observer_exit_time, queue_time, body_wall_time, observer_wall_time, idle_wall_time, initial_wait, completion_tail, internal_idle_time: Math.max(0, idle_wall_time - initial_wait - completion_tail), @@ -1242,7 +1388,7 @@ function taskflow_graph_analysis(graph: Taskflow_Graph_Trace, executions: Taskfl maximum_parallelism, layer_count: width_by_level.size, parallel_layer_count: [...width_by_level.values()].filter(width => width > 1).length, - longest, longest_non_cpu + longest, longest_unattributed }; } @@ -1251,6 +1397,7 @@ type Taskflow_Node_Stability = "stable" | "variable" | "missing" | "long_tail"; type Taskflow_Node_Aggregate = { node: Taskflow_Node_Trace; present: number; executed: number; frames: number; duration: Distribution_Statistics; queue: Distribution_Statistics; cpu: Distribution_Statistics; + cycles: Distribution_Statistics; cooperative_wait: Distribution_Statistics; cpu_time_coarse: boolean; observer: Distribution_Statistics; workers: number[]; stability: Taskflow_Node_Stability; }; type Taskflow_Graph_Aggregate = { @@ -1283,7 +1430,8 @@ function aggregate_taskflow_graph(frames: Taskflow_Frame_Trace[], key: string): .map(graph => ({frame, graph}))); if (!samples.length) return null; type Mutable_Node = {template: Taskflow_Node_Trace; present: number; executed: number; duration: number[]; - queue: number[]; cpu: number[]; observer: number[]; workers: Set}; + queue: number[]; cpu: number[]; cycles: number[]; cooperative_wait: number[]; cpu_time_coarse: boolean; + observer: number[]; workers: Set}; const accumulated = new Map(); const edges = new Map(); const topology_signatures = new Set(); @@ -1295,7 +1443,8 @@ function aggregate_taskflow_graph(frames: Taskflow_Frame_Trace[], key: string): wall.push(Math.max(0, graph.finished_ms - graph.submitted_ms)); for (const node of graph.nodes) { const current = accumulated.get(node.id) ?? {template: node, present: 0, executed: 0, - duration: [], queue: [], cpu: [], observer: [], workers: new Set()}; + duration: [], queue: [], cpu: [], cycles: [], cooperative_wait: [], cpu_time_coarse: false, + observer: [], workers: new Set()}; ++current.present; const sample = execution.get(node.native_id); if (sample) { @@ -1303,6 +1452,9 @@ function aggregate_taskflow_graph(frames: Taskflow_Frame_Trace[], key: string): current.duration.push(sample.duration_ms); current.queue.push(sample.queue_wait_ms); current.cpu.push(sample.cpu_duration_ms); + current.cycles.push(sample.cpu_cycles); + current.cooperative_wait.push(sample.cooperative_wait_ms); + current.cpu_time_coarse = current.cpu_time_coarse || sample.cpu_time_coarse; current.observer.push(sample.observer_entry_ms + sample.observer_exit_ms); current.workers.add(sample.worker_id); } @@ -1340,7 +1492,9 @@ function aggregate_taskflow_graph(frames: Taskflow_Frame_Trace[], key: string): const node = {...value.template, native_id: id, id, predecessors: predecessors.get(id) ?? [], successors: successors.get(id) ?? []}; nodes.set(id, {node, present: value.present, executed: value.executed, frames: frames.length, - duration, queue, cpu: distribution_statistics(value.cpu), observer: distribution_statistics(value.observer), + duration, queue, cpu: distribution_statistics(value.cpu), cycles: distribution_statistics(value.cycles), + cooperative_wait: distribution_statistics(value.cooperative_wait), cpu_time_coarse: value.cpu_time_coarse, + observer: distribution_statistics(value.observer), workers: [...value.workers].sort((left, right) => left - right), stability}); return node; }); @@ -1390,26 +1544,39 @@ function taskflow_node_state(node: Taskflow_Node_Trace, components: Component[], return taskflow_render_domain_state(node, frame); } -function Taskflow_Dag({graph, executions, frame, aggregate, components, gallery_state, standalone = false}: {graph: Taskflow_Graph_Trace; executions: Taskflow_Execution_Trace[]; +function Taskflow_Dag({graph, executions, frame, aggregate, components, gallery_state}: {graph: Taskflow_Graph_Trace; executions: Taskflow_Execution_Trace[]; frame?: Taskflow_Frame_Trace; aggregate?: Taskflow_Graph_Aggregate | null; components: Component[]; - gallery_state?: Gallery_Pipeline_State | null; standalone?: boolean}) { + gallery_state?: Gallery_Pipeline_State | null}) { const [nodes, set_nodes] = useState([]); const [edges, set_edges] = useState([]); const [copy_state, set_copy_state] = useState("复制拓扑 JSON"); const [viewport_width, set_viewport_width] = useState(1000); const [viewport_height, set_viewport_height] = useState(720); + const [fullscreen, set_fullscreen] = useState(false); useEffect(() => { let cancelled = false; const node_id = new Map(graph.nodes.map(node => [node.native_id, node.id])); const execution = new Map(executions.map(value => [value.native_id, value])); const analysis = taskflow_graph_analysis(graph, executions); + const predecessor_count = new Map(); + for (const node of graph.nodes) predecessor_count.set(node.id, 0); + for (const node of graph.nodes) for (const successor of node.successors) { + const target = node_id.get(successor); + if (target) predecessor_count.set(target, (predecessor_count.get(target) ?? 0) + 1); + } const flow_edges: Flow_Edge[] = []; for (const node of graph.nodes) for (const successor of node.successors) { const target = node_id.get(successor); if (!target) continue; const id = `${node.id}->${target}`; const presence = aggregate?.edge_presence.get(id) ?? 1; - flow_edges.push({id, source: node.id, target, type: "smoothstep", + // Keep simple 1 -> 1 dependencies as direct vertical segments. + // Fan-out/fan-in and cross-column dependencies use React Flow's + // bezier edge instead of SmoothStep. SmoothStep can route unrelated + // edges through the same horizontal corridor and visually form a + // misleading rectangle/loop even though the graph is acyclic. + const serial_edge = node.successors.length === 1 && (predecessor_count.get(target) ?? 0) === 1; + flow_edges.push({id, source: node.id, target, type: serial_edge ? "straight" : "default", markerEnd: {type: MarkerType.ArrowClosed}, animated: false, style: aggregate && presence < aggregate.frames ? {strokeDasharray: "7 5", opacity: .5 + .5 * presence / aggregate.frames} : undefined}); } @@ -1421,7 +1588,11 @@ function Taskflow_Dag({graph, executions, frame, aggregate, components, gallery_ "elk.layered.spacing.nodeNodeBetweenLayers": "68", "elk.spacing.nodeNode": "42", "elk.layered.nodePlacement.strategy": "BRANDES_KOEPF" }, - children: graph.nodes.map(node => ({id: node.id, width: 300, height: taskflow_node_state(node, components, frame, gallery_state) ? 230 : aggregate ? 174 : 142})), + children: graph.nodes.map(node => { + const attributes = Object.keys(node.attributes ?? {}).length; + const base_height = taskflow_node_state(node, components, frame, gallery_state) ? 250 : aggregate ? 198 : 172; + return {id: node.id, width: 300, height: base_height + Math.min(attributes, 6) * 16}; + }), edges: flow_edges.map(edge => ({id: edge.id, sources: [edge.source], targets: [edge.target]})) }).then(layout => { if (cancelled) return; @@ -1436,29 +1607,15 @@ function Taskflow_Dag({graph, executions, frame, aggregate, components, gallery_ return { id: node.id, position: {x: position?.x ?? 0, y: position?.y ?? 0}, - data: {label:
-
{taskflow_node_name(node.name)}层 {level + 1}
- {node.name} - {summary ? <> - {node.type} · 样本 {summary.executed}/{summary.frames} · W {summary.workers.join(", ") || "--"} - 执行 {milliseconds(summary.duration.average)} ± {milliseconds(summary.duration.variability)} · P95 {milliseconds(summary.duration.p95)} - 排队 {milliseconds(summary.queue.average)} ± {milliseconds(summary.queue.variability)} · P99 {milliseconds(summary.queue.p99)} - CPU {milliseconds(summary.cpu.average)} · Observer {milliseconds(summary.observer.average)} - : <>{node.type} · W{sample?.worker_id ?? "--"} - 任务体 {sample ? milliseconds(duration) : "未执行"} · 排队 {sample ? milliseconds(wait) : "--"} - CPU {sample ? milliseconds(sample.cpu_duration_ms) : "--"} · 被抢占 {sample ? milliseconds(Math.max(0, duration - sample.cpu_duration_ms)) : "--"} - Observer {sample ? milliseconds(sample.observer_entry_ms + sample.observer_exit_ms) : "--"}} - {Object.entries(node.attributes ?? {}).map(([key, value]) => - {key}:{value})} - {state ?
event.stopPropagation()} - onClick={event => event.stopPropagation()} - onWheel={event => event.stopPropagation()}>运行状态 JSON -
{JSON.stringify(state, null, 2)}
: null} -
}, - className: summary ? `taskflowNode ${{stable: "taskflowNodeStable", variable: "taskflowNodeVariable", + sourcePosition: Position.Bottom, + targetPosition: Position.Top, + data: {label: }, + className: `${summary ? `taskflowNode ${{stable: "taskflowNodeStable", variable: "taskflowNodeVariable", missing: "taskflowNodeMissing", long_tail: "taskflowNodeLongTail"}[summary.stability]}` - : sample ? wait > duration && wait > .1 ? "taskflowNode taskflowNodeWaiting" : "taskflowNode taskflowNodeExecuted" : "taskflowNode" + : sample ? wait > duration && wait > .1 ? "taskflowNode taskflowNodeWaiting" : "taskflowNode taskflowNodeExecuted" : "taskflowNode"} + ${node.name.includes(".paint.data") ? "taskflowNodeActualPaint" : ""} + ${node.name.includes(".prepare.data") ? "taskflowNodePrepareData" : ""}`.trim() }; })); set_edges(flow_edges); @@ -1468,7 +1625,7 @@ function Taskflow_Dag({graph, executions, frame, aggregate, components, gallery_ const copy_topology = async () => { const native_ids = new Set(graph.nodes.map(node => node.native_id)); try { - await navigator.clipboard.writeText(JSON.stringify({ + await copy_text(JSON.stringify({ sequence: frame?.sequence, correlation_id: frame?.correlation_id, markers: frame?.markers ?? {}, @@ -1483,13 +1640,13 @@ function Taskflow_Dag({graph, executions, frame, aggregate, components, gallery_ } catch { set_copy_state("复制失败"); } }; - const flow = ; - return
+ return
{aggregate ? <>稳定节点 存在波动 @@ -1499,15 +1656,18 @@ function Taskflow_Dag({graph, executions, frame, aggregate, components, gallery_ 已执行,执行时间为主 真实排队时间大于执行时间}
- -
{standalone ?
{flow}
: + + + +
{fullscreen ?
{flow}
: { set_viewport_width(data.size.width); set_viewport_height(data.size.height); }}>
{flow}
}
; } -function Taskflow_Frame_Pane({plot, components, standalone = false}: {plot: Plot; components: Component[]; standalone?: boolean}) { +function Taskflow_Frame_Pane({plot, components}: {plot: Plot; components: Component[]}) { const [frame_count, set_frame_count] = useState(8); const [scene_response, set_scene_response] = useState(null); const [frame_index, set_frame_index] = useState(0); @@ -1594,12 +1754,11 @@ function Taskflow_Frame_Pane({plot, components, standalone = false}: {plot: Plot return {total, frame_target, background, cache_targets, taskflow, coordination: Math.max(0, total - frame_target - background - cache_targets - taskflow)}; }, [frame]); - return
void load()}/> + return
void load()}/>
- {response ? `${response.captured}/${response.requested} 帧${response.complete ? " · 已完成" : ` · 还需 ${response.remaining} 帧`}` : "按需捕获,未请求时 Observer 不写入逐帧数据"}
{error ?

{error}

: null} {frame ? <> @@ -1623,14 +1782,16 @@ function Taskflow_Frame_Pane({plot, components, standalone = false}: {plot: Plot
长尾节点
{[...aggregate.nodes.values()].filter(node => node.stability === "long_tail").length}
Topology 平均 ± 波动
{milliseconds(aggregate.wall.average)} ± {milliseconds(aggregate.wall.variability)}
Topology P95 / P99
{milliseconds(aggregate.wall.p95)} / {milliseconds(aggregate.wall.p99)}
- + : view_mode === "single" && graph && graph_analysis ? <>
业务阶段
{graph.stage}
DAG 节点
{graph.nodes.length}
Topology 墙钟
{milliseconds(graph_analysis.wall_time)}
叶子任务墙钟总和
{milliseconds(graph_analysis.execution_time)}
Module 包络总和
{milliseconds(graph_analysis.module_envelope_time)}
-
叶子任务实际 CPU
{milliseconds(graph_analysis.cpu_execution_time)}
-
非 CPU 计费墙钟(估算)
{milliseconds(graph_analysis.descheduled_time)}
+
叶子任务线程 CPU{graph_analysis.cpu_time_coarse ? "(低分辨率)" : ""}
{milliseconds(graph_analysis.cpu_execution_time)}
+
叶子任务 CPU 活动
{cpu_cycles(graph_analysis.cpu_cycles_total)}
+
累计协作等待
{milliseconds(graph_analysis.cooperative_wait_time)}
+
未归因墙时{graph_analysis.cpu_time_coarse ? "(估算)" : ""}
{milliseconds(graph_analysis.unattributed_wall_time)}
任务体墙钟并集
{milliseconds(graph_analysis.body_wall_time)}
Observer 独占墙钟
{milliseconds(graph_analysis.observer_wall_time)}
Observer entry 墙钟
{milliseconds(graph_analysis.observer_entry_time)}
@@ -1645,7 +1806,7 @@ function Taskflow_Frame_Pane({plot, components, standalone = false}: {plot: Plot
实际最大并行
{graph_analysis.maximum_parallelism}
依赖层 / 并行层
{graph_analysis.layer_count} / {graph_analysis.parallel_layer_count}
最长执行节点
{graph_analysis.longest ? milliseconds(graph_analysis.longest.duration_ms) : "--"}
-
最长非 CPU 节点(估算)
{graph_analysis.longest_non_cpu ? `${taskflow_node_name(graph_analysis.longest_non_cpu.node_id.split("/").at(-1) ?? graph_analysis.longest_non_cpu.node_id)} · ${milliseconds(Math.max(0, graph_analysis.longest_non_cpu.duration_ms - graph_analysis.longest_non_cpu.cpu_duration_ms))}` : "--"}
+
最长未归因墙时节点
{graph_analysis.longest_unattributed ? `${taskflow_node_name(graph_analysis.longest_unattributed.node_id.split("/").at(-1) ?? graph_analysis.longest_unattributed.node_id)} · ${graph_analysis.longest_unattributed.cpu_time_coarse ? "≈ " : ""}${milliseconds(Math.max(0, graph_analysis.longest_unattributed.duration_ms - graph_analysis.longest_unattributed.cooperative_wait_ms - graph_analysis.longest_unattributed.cpu_duration_ms))}` : "--"}
{graph.stage === "render_2d.paint" && paint_analysis ? <>
同帧 Paint 总墙钟
{milliseconds(paint_analysis.total)}
帧目标准备
{milliseconds(paint_analysis.frame_target)}
@@ -1655,7 +1816,7 @@ function Taskflow_Frame_Pane({plot, components, standalone = false}: {plot: Plot
Paint 阶段衔接
{milliseconds(paint_analysis.coordination)}
: null}
状态
{graph.completed ? "完成" : "未完成"}
-
:
该帧没有所选 Taskflow 阶段切换到“多帧聚合拓扑”仍可查看其他帧中的同一业务阶段。
} + :
该帧没有所选 Taskflow 阶段切换到“多帧聚合拓扑”仍可查看其他帧中的同一业务阶段。
} :
等待逐帧 Taskflow 样本输入 N 后捕获后续实际渲染帧;每帧独立绑定其 DAG 元信息和 Observer 结果。
}
; } @@ -1912,13 +2073,9 @@ function load_workspace_model() { } export function App() { - const taskflow_route = /^\/taskflow\/([^/]+)\/?$/.exec(window.location.pathname); - const taskflow_route_id = taskflow_route ? decodeURIComponent(taskflow_route[1]) : null; const [plots, set_plots] = useState([]); const [category, set_category] = useState("全部"); const [selected, set_selected] = useState(null); use_selected_plot_diagnostics(selected); - const gallery_videos = use_gallery_videos(taskflow_route_id - ? plots.filter(plot => plot.id === taskflow_route_id) - : plots); + const gallery_videos = use_gallery_videos(plots); const [execution_policies, set_execution_policies] = useState({}); const [schema, set_schema] = useState(null); const [frame_diagnostics, set_frame_diagnostics] = useState(null); @@ -1934,13 +2091,8 @@ export function App() { ]))); }); }, []); useEffect(() => { - if (taskflow_route_id) { - const target = plots.find(plot => plot.id === taskflow_route_id); - if (target && selected?.id !== target.id) set_selected(target); - return; - } if (!selected && plots[0]) set_selected(plots[0]); - }, [plots, selected?.id, taskflow_route_id]); + }, [plots, selected?.id]); useEffect(() => { set_frame_diagnostics(null); const receive = (event: Event) => { @@ -1986,14 +2138,6 @@ export function App() { if (category === "2D" || category === "3D") return plots.filter(plot => plot.dimension === category); return plots; }, [category, plots]); - if (taskflow_route_id) return
-
AETHERA TASKFLOW

独立拓扑分析

- -
- {selected ? - :
正在读取图形组件
} -
; const gallery =
AETHERA 渲染实验室

实时图形组件库

{selected ? 当前图形 {plot_labels[selected.id] ?? selected.title} : 点击任意图形后,属性和 DAG 节点状态会自动同步。}