diff --git a/src/App.tsx b/src/App.tsx
index 3634a6e..bb9e188 100644
--- a/src/App.tsx
+++ b/src/App.tsx
@@ -1,4 +1,4 @@
-import { useState, useEffect, useMemo } from 'react';
+import { useState, useEffect, useMemo, Fragment } from 'react';
import { invoke } from '@tauri-apps/api/core';
import {
AreaChart, Area, XAxis, YAxis, Tooltip, ResponsiveContainer,
@@ -45,15 +45,19 @@ interface ProcessHistoryPoint {
}
interface AggregatedProcess {
+ pid: number;
name: string;
avg_cpu: number;
peak_cpu: number;
avg_memory_mb: number;
peak_memory_mb: number;
+ inclusive_avg_cpu: number;
+ inclusive_avg_memory_mb: number;
instance_count: number;
warnings: string[];
history: ProcessHistoryPoint[];
is_syspulse: boolean;
+ children: AggregatedProcess[];
}
interface ProfilingReport {
@@ -148,7 +152,7 @@ function App() {
);
}
- if (!stats) return
Initializing SysPulse...
;
+ if (!stats) return Initializing SysPulse...
;
const avgCpu = stats.cpu_usage.reduce((a, b) => a + b, 0) / stats.cpu_usage.length;
const memoryPercent = (stats.used_memory / stats.total_memory) * 100;
@@ -261,7 +265,7 @@ function App() {
Processes
- Automatic aggregation is active. Child processes are merged for cleaner profiling.
+ Live system feed. Profiling session will provide a full hierarchical tree.
@@ -296,7 +300,7 @@ function App() {
)} />
{proc.name}
- {proc.pid}
+ {proc.pid}
{proc.cpu_usage.toFixed(1)}%
{(proc.memory / 1024 / 1024).toFixed(0)} MB
@@ -319,13 +323,21 @@ function App() {
);
}
-type SortField = 'name' | 'avg_cpu' | 'peak_cpu' | 'avg_memory_mb' | 'peak_memory_mb' | 'instance_count';
+type SortField = 'name' | 'pid' | 'inclusive_avg_cpu' | 'peak_cpu' | 'inclusive_avg_memory_mb' | 'peak_memory_mb';
function ReportView({ report, onBack }: { report: ProfilingReport, onBack: () => void }) {
- const [sortField, setSortField] = useState
('avg_cpu');
+ const [sortField, setSortField] = useState('inclusive_avg_cpu');
const [sortOrder, setSortOrder] = useState<'asc' | 'desc'>('desc');
const [selectedProcess, setSelectedProcess] = useState(null);
const [hideProfiler, setHideProfiler] = useState(true);
+ const [expandedNodes, setExpandedNodes] = useState>(new Set());
+
+ const toggleExpand = (pid: number) => {
+ const newExpanded = new Set(expandedNodes);
+ if (newExpanded.has(pid)) newExpanded.delete(pid);
+ else newExpanded.add(pid);
+ setExpandedNodes(newExpanded);
+ };
const handleSort = (field: SortField) => {
if (sortField === field) {
@@ -363,6 +375,73 @@ function ReportView({ report, onBack }: { report: ProfilingReport, onBack: () =>
}
};
+ const renderTreeRows = (nodes: AggregatedProcess[], depth = 0): React.ReactNode => {
+ return nodes.map((proc) => {
+ const isExpanded = expandedNodes.has(proc.pid);
+ const hasChildren = proc.children && proc.children.length > 0;
+
+ return (
+
+ {
+ e.stopPropagation();
+ setSelectedProcess(proc);
+ }}
+ >
+
+ {hasChildren ? (
+
{
+ e.stopPropagation();
+ toggleExpand(proc.pid);
+ }}
+ className="p-1 hover:bg-surface2 rounded transition-colors"
+ >
+
+
+ ) : (
+
+ )}
+
+
{proc.name}
+
+
+
{proc.pid}
+
+
+ {proc.inclusive_avg_cpu.toFixed(1)}%
+ {proc.children.length > 0 && ({proc.avg_cpu.toFixed(1)}% self) }
+
+
+
{proc.peak_cpu.toFixed(1)}%
+
+
+ {proc.inclusive_avg_memory_mb.toFixed(0)}MB
+ {proc.children.length > 0 && ({proc.avg_memory_mb.toFixed(0)}MB self) }
+
+
+
{proc.peak_memory_mb.toFixed(0)}MB
+
+
+ {proc.warnings.length > 0 ? proc.warnings.map((w, idx) => (
+
+ {w}
+
+ )) : (
+ proc.children.length > 0 ?
{proc.children.length} units : null
+ )}
+
+
+ {hasChildren && isExpanded && renderTreeRows(proc.children, depth + 1)}
+
+ );
+ });
+ };
+
return (
@@ -410,7 +489,7 @@ function ReportView({ report, onBack }: { report: ProfilingReport, onBack: () =>
{new Date(report.end_time).toLocaleTimeString()}
-
Unique Processes
+
Root Processes
{report.aggregated_processes.length}
@@ -453,50 +532,35 @@ function ReportView({ report, onBack }: { report: ProfilingReport, onBack: () =>
- Analysis Matrix
+ Hierarchical Matrix
-
Select Process to Inspect
+
+
+
Toggle Nodes to Expand
+
handleSort('name')}>
Process {sortField === 'name' && (sortOrder === 'asc' ? '↑' : '↓')}
-
handleSort('instance_count')}>Units
-
handleSort('avg_cpu')}>Avg CPU
-
handleSort('peak_cpu')}>Peak CPU
-
handleSort('avg_memory_mb')}>Avg Mem
-
handleSort('peak_memory_mb')}>Peak Mem
+
handleSort('pid')}>PID
+
handleSort('inclusive_avg_cpu')}>Total CPU
+
handleSort('peak_cpu')}>Peak
+
handleSort('inclusive_avg_memory_mb')}>Total Mem
+
handleSort('peak_memory_mb')}>Peak
Insights
- {sortedProcesses.map((proc, i) => (
-
setSelectedProcess(proc)}
- className="grid grid-cols-[1fr_80px_100px_100px_100px_100px_200px] gap-4 px-4 py-4 border-b border-surface1/20 hover:bg-surface1/20 cursor-pointer transition-all rounded-xl group"
- >
-
-
{proc.instance_count}
-
{proc.avg_cpu.toFixed(1)}%
-
{proc.peak_cpu.toFixed(1)}%
-
{proc.avg_memory_mb.toFixed(0)}MB
-
{proc.peak_memory_mb.toFixed(0)}MB
-
- {proc.warnings.length > 0 ? proc.warnings.map((w, idx) => (
-
- {w}
-
- )) : (
-
Healthy
- )}
-
-
- ))}
+ {renderTreeRows(sortedProcesses)}
+
+
+
+
+
Memory is Resident Set Size (RSS). Summed totals may exceed physical RAM due to shared segments.
@@ -506,7 +570,7 @@ function ReportView({ report, onBack }: { report: ProfilingReport, onBack: () =>
-
Process Inspector
+
Process Inspector (PID: {selectedProcess.pid})
{selectedProcess.name}
-
Instances Detected
-
{selectedProcess.instance_count}
-
Maximum concurrent processes seen with this name.
+
Total Avg CPU
+
{selectedProcess.inclusive_avg_cpu.toFixed(1)}%
+
Sum of this process and all its children.
-
Average Impact
-
{selectedProcess.avg_cpu.toFixed(1)}%
-
Mean CPU usage across the entire session.
+
Total Avg RAM
+
{selectedProcess.inclusive_avg_memory_mb.toFixed(0)}MB
+
Combined RSS memory of the subtree.
-
Resource History (Summed)
+
Self-Resource History (Exclusive)
@@ -552,13 +616,31 @@ function ReportView({ report, onBack }: { report: ProfilingReport, onBack: () =>
-
-
+
+
+
+
Process Details
+
+
+ Peak Self CPU
+ {selectedProcess.peak_cpu.toFixed(1)}%
+
+
+ Peak Self RAM
+ {selectedProcess.peak_memory_mb.toFixed(0)}MB
+
+
+ Child Process Count
+ {selectedProcess.children.length}
+
+
+
+