feat: implement profiling run import and remove overhead toggle

This commit is contained in:
2026-02-22 21:50:20 +01:00
parent 3ec80a5fcb
commit 54d551fa8b

View File

@@ -6,7 +6,7 @@ import {
} from 'recharts'; } from 'recharts';
import { import {
Activity, Cpu, Server, Database, Play, Square, Activity, Cpu, Server, Database, Play, Square,
AlertTriangle, ArrowLeft, Shield, CheckSquare, Square as SquareIcon, Save, X AlertTriangle, ArrowLeft, Shield, Save, X, Download
} from 'lucide-react'; } from 'lucide-react';
import { clsx } from 'clsx'; import { clsx } from 'clsx';
import { twMerge } from 'tailwind-merge'; import { twMerge } from 'tailwind-merge';
@@ -70,7 +70,6 @@ function App() {
const [view, setView] = useState<'dashboard' | 'report'>('dashboard'); const [view, setView] = useState<'dashboard' | 'report'>('dashboard');
const [stats, setStats] = useState<SystemStats | null>(null); const [stats, setStats] = useState<SystemStats | null>(null);
const [history, setHistory] = useState<{ time: string; cpu: number }[]>([]); const [history, setHistory] = useState<{ time: string; cpu: number }[]>([]);
const [excludeSelf, setExcludeSelf] = useState(true);
const [report, setReport] = useState<ProfilingReport | null>(null); const [report, setReport] = useState<ProfilingReport | null>(null);
useEffect(() => { useEffect(() => {
@@ -78,7 +77,7 @@ function App() {
try { try {
const isRecording = stats?.is_recording ?? false; const isRecording = stats?.is_recording ?? false;
const data = await invoke<SystemStats>('get_system_stats', { const data = await invoke<SystemStats>('get_system_stats', {
excludeSelf, excludeSelf: true,
minimal: isRecording || view === 'report' minimal: isRecording || view === 'report'
}); });
setStats(data); setStats(data);
@@ -98,7 +97,7 @@ function App() {
fetchStats(); fetchStats();
const interval = setInterval(fetchStats, 1000); const interval = setInterval(fetchStats, 1000);
return () => clearInterval(interval); return () => clearInterval(interval);
}, [excludeSelf, view, stats?.is_recording]); }, [view, stats?.is_recording]);
const toggleRecording = async () => { const toggleRecording = async () => {
if (stats?.is_recording) { if (stats?.is_recording) {
@@ -110,6 +109,27 @@ function App() {
} }
}; };
const handleImport = (e: React.ChangeEvent<HTMLInputElement>) => {
const file = e.target.files?.[0];
if (!file) return;
const reader = new FileReader();
reader.onload = (event) => {
try {
const data = JSON.parse(event.target?.result as string) as ProfilingReport;
if (data.aggregated_processes && data.timeline) {
setReport(data);
setView('report');
} else {
alert('Invalid report format');
}
} catch (err) {
alert('Failed to parse JSON');
}
};
reader.readAsText(file);
};
const killProcess = async (pid: number) => { const killProcess = async (pid: number) => {
try { try {
await invoke('run_as_admin', { command: `kill -9 ${pid}` }); await invoke('run_as_admin', { command: `kill -9 ${pid}` });
@@ -127,7 +147,7 @@ function App() {
); );
} }
if (!stats) return <div className="h-screen w-screen flex items-center justify-center text-text bg-base">Loading System Data...</div>; if (!stats) return <div className="h-screen w-screen flex items-center justify-center text-text bg-base font-black italic tracking-tighter uppercase text-2xl">Initializing SysPulse...</div>;
const avgCpu = stats.cpu_usage.reduce((a, b) => a + b, 0) / stats.cpu_usage.length; const avgCpu = stats.cpu_usage.reduce((a, b) => a + b, 0) / stats.cpu_usage.length;
const memoryPercent = (stats.used_memory / stats.total_memory) * 100; const memoryPercent = (stats.used_memory / stats.total_memory) * 100;
@@ -142,13 +162,12 @@ function App() {
<span className="font-black tracking-tighter uppercase italic text-xl">SysPulse</span> <span className="font-black tracking-tighter uppercase italic text-xl">SysPulse</span>
</div> </div>
<div className="flex items-center gap-4 px-4"> <div className="flex items-center gap-4 px-4">
<button <label className="flex items-center gap-2 text-subtext0 hover:text-text transition-colors text-[10px] font-black uppercase tracking-widest cursor-pointer group">
className="flex items-center gap-2 text-subtext0 hover:text-text transition-colors text-xs font-bold" <Download size={14} className="text-blue group-hover:scale-110 transition-transform" />
onClick={() => setExcludeSelf(!excludeSelf)} <span>Import Run</span>
> <input type="file" accept=".json" onChange={handleImport} className="hidden" />
{excludeSelf ? <CheckSquare size={14} className="text-blue" /> : <SquareIcon size={14} />} </label>
<span>HIDE OVERHEAD</span> <div className="h-4 w-px bg-surface1 mx-1" />
</button>
<button <button
className={cn( className={cn(
"flex items-center gap-2 px-6 py-2 rounded-xl text-xs font-black transition-all shadow-xl", "flex items-center gap-2 px-6 py-2 rounded-xl text-xs font-black transition-all shadow-xl",