From 5437b1b267c359d7703b173c7a23316a302a817d Mon Sep 17 00:00:00 2001 From: wyc <1104749580@qq.com> Date: Tue, 25 Aug 2026 15:34:31 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=E4=BA=86=20=E8=BF=98?= =?UTF-8?q?=E6=98=AF=E5=8D=A1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- kernel/src/kernel/render_common.cpp | 23 +++++++++++++++++++++++ kernel/src/kernel/render_common.hpp | 5 +++++ web_server/src/Web_Server.cpp | 7 ++++++- webapp_gallery/src/app.tsx | 28 +++++++++++++++++++--------- webapp_gallery/src/styles.css | 8 +++++--- 5 files changed, 58 insertions(+), 13 deletions(-) diff --git a/kernel/src/kernel/render_common.cpp b/kernel/src/kernel/render_common.cpp index a5a5569..46803d6 100644 --- a/kernel/src/kernel/render_common.cpp +++ b/kernel/src/kernel/render_common.cpp @@ -66,6 +66,7 @@ private: std::atomic active_task_type{tf::TaskType::UNDEFINED}; std::atomic_uint64_t task_time_ns{}; std::atomic_uint64_t busy_time_ns{}; + std::atomic_uint64_t cpu_time_ns{}; std::atomic_uint64_t min_task_time_ns{std::numeric_limits::max()}; std::atomic_uint64_t max_task_time_ns{}; }; @@ -82,6 +83,7 @@ private: }; std::vector> starts; std::vector worker_busy_starts; + std::vector worker_cpu_starts; std::unique_ptr worker_statistics; std::size_t worker_statistics_count{}; std::array task_types; @@ -99,6 +101,7 @@ private: std::atomic_size_t max_weak_dependencies{}; std::atomic_uint64_t total_execution_time_ns{}; std::atomic_uint64_t worker_busy_time_ns{}; + std::atomic_uint64_t worker_cpu_time_ns{}; std::atomic_uint64_t first_task_time_ns{}; std::atomic_uint64_t last_task_time_ns{}; std::atomic_uint64_t longest_task_time_ns{}; @@ -130,6 +133,7 @@ public: void set_up(std::size_t workers) override { starts.resize(workers); worker_busy_starts.resize(workers); + worker_cpu_starts.resize(workers); worker_statistics = std::make_unique(workers); worker_statistics_count = workers; for (auto& worker : starts) worker.reserve(8); @@ -139,6 +143,7 @@ public: auto& worker_starts = starts[worker.id()]; if (worker_starts.empty()) { worker_busy_starts[worker.id()] = now; + worker_cpu_starts[worker.id()] = current_thread_cpu_ns(); auto active = active_workers.fetch_add(1, std::memory_order_relaxed) + 1; update_max(peak_active_workers, active); } @@ -248,6 +253,11 @@ public: completed - worker_busy_starts[worker.id()]).count()); worker_busy_time_ns.fetch_add(busy, std::memory_order_relaxed); worker_state.busy_time_ns.fetch_add(busy, std::memory_order_relaxed); + const auto cpu_started = worker_cpu_starts[worker.id()]; + const auto cpu = cpu_completed_ns >= cpu_started + ? cpu_completed_ns - cpu_started : 0; + worker_cpu_time_ns.fetch_add(cpu, std::memory_order_relaxed); + worker_state.cpu_time_ns.fetch_add(cpu, std::memory_order_relaxed); active_workers.fetch_sub(1, std::memory_order_relaxed); worker_state.active_task_hash.store(0, std::memory_order_relaxed); worker_state.active_task_started_ns.store(0, std::memory_order_relaxed); @@ -300,10 +310,16 @@ public: state.max_weak_dependencies = max_weak_dependencies.load(std::memory_order_relaxed); state.total_task_time_ns = total_execution_time_ns.load(std::memory_order_relaxed); state.worker_busy_time_ns = worker_busy_time_ns.load(std::memory_order_relaxed); + state.worker_cpu_time_ns = worker_cpu_time_ns.load(std::memory_order_relaxed); auto first = first_task_time_ns.load(std::memory_order_relaxed); auto last = last_task_time_ns.load(std::memory_order_relaxed); state.observed_wall_time_ns = first && last >= first ? last - first : 0; state.worker_utilization = workers && state.observed_wall_time_ns ? static_cast(state.worker_busy_time_ns) * 100.0 / static_cast(state.observed_wall_time_ns) / static_cast(workers) : 0.0; + state.worker_cpu_utilization = workers && state.observed_wall_time_ns + ? static_cast(state.worker_cpu_time_ns) * 100.0 / + static_cast(state.observed_wall_time_ns) / + static_cast(workers) + : 0.0; state.task_types.resize(task_types.size()); for (std::size_t i = 0; i < task_types.size(); ++i) { const auto& source = task_types[i]; @@ -335,11 +351,18 @@ public: ? std::string(tf::to_string(active_type)) : std::string{}; target.task_time_ns = source.task_time_ns.load(std::memory_order_relaxed); target.busy_time_ns = source.busy_time_ns.load(std::memory_order_relaxed); + target.cpu_time_ns = source.cpu_time_ns.load(std::memory_order_relaxed); + target.non_cpu_time_ns = target.busy_time_ns > target.cpu_time_ns + ? target.busy_time_ns - target.cpu_time_ns : 0; target.idle_time_ns = state.observed_wall_time_ns > target.busy_time_ns ? state.observed_wall_time_ns - target.busy_time_ns : 0; auto min = source.min_task_time_ns.load(std::memory_order_relaxed); target.min_task_time_ns = target.task_count ? min : 0; target.max_task_time_ns = source.max_task_time_ns.load(std::memory_order_relaxed); target.utilization = state.observed_wall_time_ns ? static_cast(target.busy_time_ns) * 100.0 / static_cast(state.observed_wall_time_ns) : 0.0; + target.cpu_utilization = state.observed_wall_time_ns + ? static_cast(target.cpu_time_ns) * 100.0 / + static_cast(state.observed_wall_time_ns) + : 0.0; } state.longest_task_time_ns = longest_task_time_ns.load(std::memory_order_relaxed); state.longest_task_hash = longest_task_hash.load(std::memory_order_relaxed); diff --git a/kernel/src/kernel/render_common.hpp b/kernel/src/kernel/render_common.hpp index 7206799..c60458c 100644 --- a/kernel/src/kernel/render_common.hpp +++ b/kernel/src/kernel/render_common.hpp @@ -52,10 +52,13 @@ struct Task_Worker_State { std::string active_task_type{}; /* 当前任务的 Taskflow 原生 TaskType;空闲时为空。 */ std::uint64_t task_time_ns{}; std::uint64_t busy_time_ns{}; + std::uint64_t cpu_time_ns{}; /* Worker最外层任务活跃区间内累计的线程 CPU 时间。 */ + std::uint64_t non_cpu_time_ns{}; /* busy_time_ns 减去 cpu_time_ns;只表示未计费墙钟,不推断锁或抢占。 */ std::uint64_t idle_time_ns{}; std::uint64_t min_task_time_ns{}; std::uint64_t max_task_time_ns{}; double utilization{}; + double cpu_utilization{}; bool operator==(const Task_Worker_State&) const = default; }; /* @@ -87,8 +90,10 @@ struct Task_Runtime_State : State_Type { std::uint64_t longest_task_time_ns{}; std::uint64_t total_task_time_ns{}; std::uint64_t worker_busy_time_ns{}; + std::uint64_t worker_cpu_time_ns{}; std::uint64_t observed_wall_time_ns{}; double worker_utilization{}; + double worker_cpu_utilization{}; std::vector task_types; std::vector workers; bool operator==(const Task_Runtime_State&) const = default; diff --git a/web_server/src/Web_Server.cpp b/web_server/src/Web_Server.cpp index 032a533..1737728 100644 --- a/web_server/src/Web_Server.cpp +++ b/web_server/src/Web_Server.cpp @@ -54,10 +54,13 @@ nlohmann::json taskflow_runtime_json() { {"time_ns", worker.active_task_time_ns}}}, {"task_time_ns", worker.task_time_ns}, {"busy_time_ns", worker.busy_time_ns}, + {"cpu_time_ns", worker.cpu_time_ns}, + {"non_cpu_time_ns", worker.non_cpu_time_ns}, {"idle_time_ns", worker.idle_time_ns}, {"min_task_time_ns", worker.min_task_time_ns}, {"max_task_time_ns", worker.max_task_time_ns}, - {"utilization", worker.utilization}}); + {"utilization", worker.utilization}, + {"cpu_utilization", worker.cpu_utilization}}); nlohmann::json task_types = nlohmann::json::array(); for (const auto& type : state.task_types) task_types.push_back({ @@ -91,8 +94,10 @@ nlohmann::json taskflow_runtime_json() { {"time_ns", state.longest_task_time_ns}}}, {"total_task_time_ns", state.total_task_time_ns}, {"worker_busy_time_ns", state.worker_busy_time_ns}, + {"worker_cpu_time_ns", state.worker_cpu_time_ns}, {"observed_wall_time_ns", state.observed_wall_time_ns}, {"worker_utilization", state.worker_utilization}, + {"worker_cpu_utilization", state.worker_cpu_utilization}, {"task_types", std::move(task_types)}, {"workers", std::move(workers)}}; } diff --git a/webapp_gallery/src/app.tsx b/webapp_gallery/src/app.tsx index 5c9ad63..34a4aa5 100644 --- a/webapp_gallery/src/app.tsx +++ b/webapp_gallery/src/app.tsx @@ -87,7 +87,8 @@ type Taskflow_Frame_Response = {protocol: "aethera.taskflow.frames"; version: 1; captured: number; complete: boolean; frames: Taskflow_Frame_Trace[]}; type Taskflow_Worker_State = {id: number; task_count: number; current_queue_size: number; current_queue_capacity: number; peak_queue_size: number; max_queue_capacity: number; active_task: {native_id: string; type: string; time_ns: number}; - task_time_ns: number; busy_time_ns: number; idle_time_ns: number; min_task_time_ns: number; max_task_time_ns: number; utilization: number}; + task_time_ns: number; busy_time_ns: number; cpu_time_ns: number; non_cpu_time_ns: number; idle_time_ns: number; + min_task_time_ns: number; max_task_time_ns: number; utilization: number; cpu_utilization: number}; type Taskflow_Type_State = {name: string; count: number; total_time_ns: number; min_time_ns: number; max_time_ns: number}; type Taskflow_Runtime_State = {protocol: "aethera.taskflow.runtime"; version: 1; worker_count: number; active_topologies: number; active_taskflows: number; peak_active_taskflows: number; completed_taskflows: number; failed_taskflows: number; @@ -95,7 +96,8 @@ type Taskflow_Runtime_State = {protocol: "aethera.taskflow.runtime"; version: 1; named_tasks: number; peak_worker_queue_size: number; max_worker_queue_capacity: number; max_predecessors: number; max_successors: number; max_strong_dependencies: number; max_weak_dependencies: number; longest_task: {native_id: string; name: string; type: string; time_ns: number}; total_task_time_ns: number; - worker_busy_time_ns: number; observed_wall_time_ns: number; worker_utilization: number; + worker_busy_time_ns: number; worker_cpu_time_ns: number; observed_wall_time_ns: number; + worker_utilization: number; worker_cpu_utilization: number; task_types: Taskflow_Type_State[]; workers: Taskflow_Worker_State[]}; const default_plot_execution_policy = (): Plot_Execution_Policy => ({visible: true}); @@ -1221,6 +1223,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; + return value > previous ? row : result; + }, null); const events = rows.flatMap(row => [ {time: row.started_ms, delta: 1}, {time: row.finished_ms, delta: -1} @@ -1281,7 +1288,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, longest_non_cpu }; } @@ -1459,7 +1466,7 @@ function Taskflow_Frame_Pane({plot}: {plot: Plot}) {
Topology 墙钟
{milliseconds(graph_analysis.wall_time)}
任务体墙钟总和
{milliseconds(graph_analysis.execution_time)}
任务体实际 CPU
{milliseconds(graph_analysis.cpu_execution_time)}
-
Worker 被抢占/阻塞
{milliseconds(graph_analysis.descheduled_time)}
+
非 CPU 计费墙钟(估算)
{milliseconds(graph_analysis.descheduled_time)}
任务体墙钟并集
{milliseconds(graph_analysis.body_wall_time)}
Observer 独占墙钟
{milliseconds(graph_analysis.observer_wall_time)}
Observer entry 墙钟
{milliseconds(graph_analysis.observer_entry_time)}
@@ -1474,6 +1481,7 @@ function Taskflow_Frame_Pane({plot}: {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.stage === "render_2d.paint" && paint_analysis ? <>
同帧 Paint 总墙钟
{milliseconds(paint_analysis.total)}
帧目标准备
{milliseconds(paint_analysis.frame_target)}
@@ -1505,18 +1513,20 @@ function Taskflow_Runtime_Pane() { return
全局执行域

Taskflow 总体观测

每秒读取一次累计 Observer 状态;逐帧拓扑捕获在各图的“Taskflow 帧分析”页按需开启。

{error ?

{error}

: null}{state ? <>
-
Worker
{state.worker_count}
总体占用率
{state.worker_utilization.toFixed(1)}%
+
Worker
{state.worker_count}
任务体墙钟占用
{state.worker_utilization.toFixed(1)}%
+
线程 CPU 占用
{state.worker_cpu_utilization.toFixed(1)}%
活跃 Worker
{state.active_workers}/{state.worker_count}
活跃任务
{state.active_tasks}
活跃 Topology
{state.active_topologies}
活跃 Taskflow
{state.active_taskflows}
累计任务
{state.observed_tasks.toLocaleString("zh-CN")}
失败 Taskflow
{state.failed_taskflows}
队列峰值
{state.peak_worker_queue_size}
最长任务
{nanoseconds(state.longest_task.time_ns)}
-

Observer 的任务持续时间包含任务体内部的锁等待或阻塞;队列峰值与就绪等待能定位 Executor 拥塞,但 Taskflow 原生 Observer 不提供具体互斥量名称。具体帧请在逐帧 DAG 中核对并行关系、Worker 与长尾。

+

任务体墙钟占用表示 Worker 正位于任务调用栈中,不等同于 CPU 使用率。线程 CPU 占用按最外层活跃区间累计;两者差值包含 OS 未计费、内核等待和任务体阻塞,但不能仅凭差值断定某一把锁。Executor 拥塞应结合队列峰值和逐帧就绪等待判断。

Worker 占用与队列累计值,不在 GET 时重新计算任务样本。
{state.workers.map(worker =>
-
Worker {worker.id}{worker.utilization.toFixed(1)}%
-
+
Worker {worker.id}墙 {worker.utilization.toFixed(1)}% · CPU {worker.cpu_utilization.toFixed(1)}%
+
任务
{worker.task_count}
队列 当前/峰值
{worker.current_queue_size}/{worker.peak_queue_size}
-
最长
{nanoseconds(worker.max_task_time_ns)}
活跃持续
{worker.active_task.time_ns ? nanoseconds(worker.active_task.time_ns) : "空闲"}
+
最长
{nanoseconds(worker.max_task_time_ns)}
活跃持续
{worker.active_task.time_ns ? nanoseconds(worker.active_task.time_ns) : "空闲"}
+
累计 CPU
{nanoseconds(worker.cpu_time_ns)}
非 CPU 墙钟
{nanoseconds(worker.non_cpu_time_ns)}
{worker.active_task.time_ns ? {worker.active_task.type} · {worker.active_task.native_id} : null}
)}
Taskflow 原生任务类型按 Observer TaskType 累计执行次数与耗时。
diff --git a/webapp_gallery/src/styles.css b/webapp_gallery/src/styles.css index fd5330b..4169866 100644 --- a/webapp_gallery/src/styles.css +++ b/webapp_gallery/src/styles.css @@ -232,12 +232,14 @@ canvas { display: block; width: 100%; height: 100%; background: #070d18; } .taskflowRuntimeHeader { display: flex; align-items: center; justify-content: space-between; gap: 14px; padding: 17px 16px 14px; border-bottom: 1px solid #20314b; background: #0b1524; } .taskflowRuntimeHeader h2 { margin: 4px 0 0; font-size: 21px; } .taskflowRuntimeHeader p { margin: 5px 0 0; color: #71839e; font-size: 11px; } -.taskflowWorkerGrid { display: grid; grid-template-columns: repeat(auto-fit, minmax(180px, 1fr)); gap: 9px; padding: 11px; } +.taskflowWorkerGrid { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); align-content: start; gap: 9px; max-height: min(58vh, 720px); overflow-y: auto; padding: 11px; scrollbar-gutter: stable; } .taskflowWorkerGrid article { padding: 10px; border: 1px solid #1f334e; border-radius: 8px; background: #091321; } .taskflowWorkerGrid article > header { display: flex; justify-content: space-between; color: #dce8f8; font-size: 11px; } .taskflowWorkerGrid article > header span { color: #5ce4c2; font: 11px/1 ui-monospace, monospace; } -.taskflowUtilization { height: 6px; overflow: hidden; margin: 9px 0; border-radius: 99px; background: #18273b; } -.taskflowUtilization i { display: block; height: 100%; border-radius: inherit; background: linear-gradient(90deg, #3aa58d, #5ce4c2); } +.taskflowUtilization { position: relative; height: 9px; overflow: hidden; margin: 9px 0; border-radius: 99px; background: #18273b; } +.taskflowUtilization i, .taskflowUtilization b { position: absolute; left: 0; display: block; border-radius: inherit; } +.taskflowUtilization i { top: 0; height: 100%; background: #2b887c; } +.taskflowUtilization b { bottom: 0; height: 4px; background: #5ce4c2; } .taskflowWorkerGrid dl { display: grid; grid-template-columns: 1fr 1fr; gap: 5px; margin: 0; } .taskflowWorkerGrid dl > div { display: flex; justify-content: space-between; gap: 5px; color: #71839e; font-size: 9px; } .taskflowWorkerGrid dd { margin: 0; color: #aec0d8; font-family: ui-monospace, monospace; }