Просмотр исходного кода

perf: decouple CPU sampling and redesign performance monitor UI

Backend:
- Add usageinfo/monitor.go with StartBackgroundMonitor() — a goroutine
  that computes CPU usage from /proc/stat deltas once per second and
  caches both CPU and RAM results behind a RWMutex.
- InfoHandleTaskInfo now reads from the cache via GetCachedStats(),
  responding in microseconds instead of blocking for ~1 s per request.

Frontend (taskManager.html):
- Redesign to match system_setting/main.css design language: CSS
  variables for all colours, card-based sections (border-radius 10px),
  Segoe UI / system-ui font, clean stats row with divider.
- Charts use tension 0.35 (bezier curves) instead of ~0 (straight lines)
  for a fluid, smooth appearance.
- Fix double-render flicker: addAndShiftChartDate called chart.update()
  twice per tick; replaced with shiftChart()/shiftNetChart() that modify
  all data arrays first, then call chart.update('none') once.
- Pre-fill chart datasets with 60 zeros at init (no 60-update loop).
- Poll interval changed from 100 ms to 1 s (matches backend cache TTL).
- Network and usage requests fire in parallel; next tick is scheduled
  only after both complete.
- Dark-mode colours are set at chart creation time (no post-init patch),
  eliminating the brief flash of wrong colours on load.

https://claude.ai/code/session_01Us3U3YHtUEzoGAq3iVr1iz
Claude 2 недель назад
Родитель
Сommit
9d3fc49de3
3 измененных файлов с 524 добавлено и 571 удалено
  1. 75 0
      src/mod/info/usageinfo/monitor.go
  2. 9 6
      src/system.info.go
  3. 440 565
      src/web/SystemAO/info/taskManager.html

+ 75 - 0
src/mod/info/usageinfo/monitor.go

@@ -0,0 +1,75 @@
+package usageinfo
+
+import (
+	"sync"
+	"time"
+)
+
+// monitorCache holds the most recently sampled CPU and RAM data.
+type monitorCache struct {
+	mu       sync.RWMutex
+	cpuUsage float64
+	usedRAM  string
+	totalRAM string
+	ramUsage float64
+	ready    bool
+}
+
+var globalCache = &monitorCache{}
+
+// StartBackgroundMonitor launches a goroutine that samples CPU and RAM once
+// per second and stores results in a shared cache. Call this once at startup.
+// The HTTP handler can then return cached data without blocking.
+func StartBackgroundMonitor() {
+	go func() {
+		prevStats, err := getCPUStats()
+		if err != nil {
+			// /proc/stat not available (Windows or unsupported platform):
+			// fall back to the blocking platform-specific method.
+			for {
+				cpu := GetCPUUsage()
+				usedRAM, totalRAM, ramPct := GetRAMUsage()
+				globalCache.mu.Lock()
+				globalCache.cpuUsage = cpu
+				globalCache.usedRAM = usedRAM
+				globalCache.totalRAM = totalRAM
+				globalCache.ramUsage = ramPct
+				globalCache.ready = true
+				globalCache.mu.Unlock()
+				time.Sleep(time.Second)
+			}
+		}
+
+		// Linux / macOS path: compare two /proc/stat snapshots 1 s apart.
+		for {
+			time.Sleep(time.Second)
+
+			currentStats, err := getCPUStats()
+			if err != nil {
+				continue
+			}
+
+			cpu := calculateCPUUsage(prevStats, currentStats)
+			prevStats = currentStats
+
+			usedRAM, totalRAM, ramPct := GetRAMUsage()
+
+			globalCache.mu.Lock()
+			globalCache.cpuUsage = cpu
+			globalCache.usedRAM = usedRAM
+			globalCache.totalRAM = totalRAM
+			globalCache.ramUsage = ramPct
+			globalCache.ready = true
+			globalCache.mu.Unlock()
+		}
+	}()
+}
+
+// GetCachedStats returns the most recent sampled stats without blocking.
+// ready is false for the first ~1 s after startup; callers may return zero
+// values until then.
+func GetCachedStats() (cpuUsage float64, usedRAM, totalRAM string, ramUsage float64, ready bool) {
+	globalCache.mu.RLock()
+	defer globalCache.mu.RUnlock()
+	return globalCache.cpuUsage, globalCache.usedRAM, globalCache.totalRAM, globalCache.ramUsage, globalCache.ready
+}

+ 9 - 6
src/system.info.go

@@ -102,6 +102,9 @@ func SystemInfoInit() {
 
 		router.HandleFunc("/system/info/getUsageInfo", InfoHandleTaskInfo)
 
+		// Sample CPU and RAM in the background so the endpoint is non-blocking.
+		usage.StartBackgroundMonitor()
+
 	} else {
 		//Remve hardware information from the infoServer
 		infoServer = info.NewInfoServer(info.ArOZInfo{
@@ -218,14 +221,14 @@ func InfoHandleTaskInfo(w http.ResponseWriter, r *http.Request) {
 		TotalRam string
 		RamUsage float64
 	}
-	cpuUsage := usage.GetCPUUsage()
-	usedRam, totalRam, usagePercentage := usage.GetRAMUsage()
+
+	cpuUsage, usedRam, totalRam, usagePercentage, _ := usage.GetCachedStats()
 
 	info := UsageInfo{
-		cpuUsage,
-		usedRam,
-		totalRam,
-		usagePercentage,
+		CPU:      cpuUsage,
+		UsedRAM:  usedRam,
+		TotalRam: totalRam,
+		RamUsage: usagePercentage,
 	}
 
 	js, _ := json.Marshal(info)

+ 440 - 565
src/web/SystemAO/info/taskManager.html

@@ -1,662 +1,537 @@
 <!DOCTYPE html>
 <head>
-    <title>Task Manager</title>
+    <title>Performance</title>
     <meta charset="UTF-8">
-    <meta name="viewport" content="width=device-width, initial-scale=1.0 user-scalable=no">
-    <link rel="stylesheet" href="../../script/semantic/semantic.min.css">
+    <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=no">
     <script type="text/javascript" src="../../script/jquery.min.js"></script>
-    <script type="text/javascript" src="../../script/semantic/semantic.min.js"></script>
     <script type="text/javascript" src="../../script/ao_module.js"></script>
     <script type="text/javascript" src="../info/js/chart/Chart.min.js"></script>
     <script type="text/javascript" src="../../script/applocale.js"></script>
     <style>
-        body{
-            background-color: #f3f3f3;
+        *, *::before, *::after { box-sizing: border-box; }
+
+        /* ── Light theme (matches system_setting/main.css palette) ── */
+        :root {
+            --bg:          #f3f3f3;
+            --card-bg:     #ffffff;
+            --card-border: #e5e5e5;
+            --divider:     #eeeeee;
+            --text:        #202020;
+            --text-dim:    #555;
+            --text-muted:  #888;
+            --text-desc:   #666;
+            --scrollbar:   #c8c8c8;
+
+            --cpu-line: #4c9dcb;
+            --cpu-fill: rgba(76, 157, 203, 0.12);
+            --cpu-grid: rgba(76, 157, 203, 0.18);
+
+            --ram-line: #9528b4;
+            --ram-fill: rgba(149, 40, 180, 0.12);
+            --ram-grid: rgba(149, 40, 180, 0.18);
+
+            --net-line: #a74f01;
+            --net-fill: rgba(167, 79, 1, 0.10);
+            --net-grid: rgba(167, 79, 1, 0.18);
         }
-        /* ── Task Manager dark theme ── */
+
+        /* ── Dark theme overrides ── */
         body.dark {
-            background: #1f1f1f !important;
-            color: #e3e3e3 !important;
+            --bg:          #1f1f1f;
+            --card-bg:     #2d2d2d;
+            --card-border: #3a3a3a;
+            --divider:     #363636;
+            --text:        #e3e3e3;
+            --text-dim:    #999;
+            --text-muted:  #666;
+            --text-desc:   #aaa;
+            --scrollbar:   #555555;
+
+            --cpu-line: #5db3e8;
+            --cpu-fill: rgba(93, 179, 232, 0.10);
+            --cpu-grid: rgba(255, 255, 255, 0.07);
+
+            --ram-line: #b94dd4;
+            --ram-fill: rgba(185, 77, 212, 0.10);
+            --ram-grid: rgba(255, 255, 255, 0.07);
+
+            --net-line: #d4722a;
+            --net-fill: rgba(212, 114, 42, 0.10);
+            --net-grid: rgba(255, 255, 255, 0.07);
+        }
+
+        html, body {
+            margin: 0;
+            background: var(--bg);
+            color: var(--text);
+            font-family: 'Segoe UI', system-ui, -apple-system, BlinkMacSystemFont, sans-serif;
+            font-size: 14px;
+            transition: background 0.15s, color 0.15s;
+        }
+
+        body::-webkit-scrollbar { width: 6px; }
+        body::-webkit-scrollbar-thumb { background: var(--scrollbar); border-radius: 3px; }
+
+        .perf-page {
+            padding: 18px 22px 32px;
         }
 
-        body.dark .ui.container {
-            color: #e3e3e3 !important;
+        /* ── Card ── */
+        .perf-section {
+            background: var(--card-bg);
+            border: 1px solid var(--card-border);
+            border-radius: 10px;
+            padding: 18px 20px 16px;
+            margin-bottom: 12px;
+            transition: background 0.15s, border-color 0.15s;
         }
 
-        body.dark .ui.header {
-            color: #e3e3e3 !important;
+        /* ── Section header: title + hardware label ── */
+        .section-header {
+            display: flex;
+            align-items: flex-start;
+            justify-content: space-between;
+            gap: 12px;
+            margin-bottom: 12px;
         }
 
-        body.dark .ui.header .sub.header {
-            color: #aaa !important;
+        .section-title {
+            font-size: 13.5px;
+            font-weight: 600;
+            color: var(--text);
+            margin: 0 0 3px;
+            line-height: 1.3;
         }
 
-        body.dark .ui.grid .column,
-        body.dark .ui.stackable.grid .column {
-            color: #e3e3e3 !important;
+        .section-sub {
+            font-size: 11.5px;
+            color: var(--text-muted);
+            margin: 0;
+            line-height: 1.4;
         }
 
-        body.dark span,
-        body.dark p {
-            color: #e3e3e3 !important;
+        .hw-label {
+            font-size: 12px;
+            color: var(--text-dim);
+            white-space: nowrap;
+            flex-shrink: 0;
+            padding-top: 2px;
+            max-width: 55%;
+            overflow: hidden;
+            text-overflow: ellipsis;
+            text-align: right;
         }
 
-        body.dark canvas {
-            background: #2a2a2a;
+        /* ── Chart container: fixed height, canvas fills it ── */
+        .chart-wrap {
+            position: relative;
+            height: 160px;
+        }
+
+        .chart-wrap canvas {
+            display: block;
             border-radius: 4px;
         }
+
+        /* ── Stats row below the chart ── */
+        .stats-row {
+            display: flex;
+            flex-wrap: wrap;
+            gap: 8px 28px;
+            margin-top: 12px;
+            padding-top: 12px;
+            border-top: 1px solid var(--divider);
+        }
+
+        .stat { min-width: 60px; }
+
+        .stat-val {
+            font-size: 17px;
+            font-weight: 600;
+            color: var(--text);
+            line-height: 1.25;
+        }
+
+        .stat-key {
+            font-size: 11px;
+            color: var(--text-desc);
+            margin-top: 2px;
+        }
+
+        /* Network RX / TX accent bars inherit the CSS variable so they
+           automatically match the chart line colour in both themes. */
+        .net-rx { border-left: 2px solid var(--net-line); padding-left: 10px; }
+        .net-tx { border-left: 2px dashed var(--net-line); padding-left: 10px; }
     </style>
 </head>
-
 <body>
-    <br>
-    <div class="ui narrow container">
-        <div id="cpuChartContainer" style="position: relative;">
-            <h3 class="ui header">
-                <span locale="taskManager/cpu">CPU</span>
-                <div class="sub header" locale="taskManager/cpu_description">CPU Usage (%) in the previous 60 seconds</div>
-            </h3>
-            <p id="CPUname" style="position: absolute; top: 1em; right: 0.3em; font-size: 16px;" locale="taskManager/cpu_info">Generic Processor</p>
-            <canvas id="cpuChart" width="1200" height="300"></canvas>
-            <div class="ui stackable grid" style="margin-top:0;">
-                <div class="four wide column">
-                    <div class="ui header">
-                        <span id="cpuUsage">0%</span>
-                        <div class="sub header" locale="taskManager/cpu_usage_description">Usage</div>
-                    </div>
+    <div class="perf-page">
+
+        <!-- ── CPU ── -->
+        <section class="perf-section">
+            <div class="section-header">
+                <div>
+                    <p class="section-title" locale="taskManager/cpu">CPU</p>
+                    <p class="section-sub" locale="taskManager/cpu_description">CPU Usage (%) in the previous 60 seconds</p>
+                </div>
+                <span id="CPUname" class="hw-label">—</span>
+            </div>
+            <div class="chart-wrap"><canvas id="cpuChart"></canvas></div>
+            <div class="stats-row">
+                <div class="stat">
+                    <div class="stat-val" id="cpuUsage">0%</div>
+                    <div class="stat-key" locale="taskManager/cpu_usage_description">Usage</div>
                 </div>
-                <div class="four wide column">
-                    <div class="ui header">
-                        <span id="cpufreq">Loading</span>
-                        <div class="sub header" locale="taskManager/cpu_frequency_description">Frequency</div>
-                    </div>
+                <div class="stat">
+                    <div class="stat-val" id="cpufreq">—</div>
+                    <div class="stat-key" locale="taskManager/cpu_frequency_description">Frequency</div>
+                </div>
+            </div>
+        </section>
+
+        <!-- ── Memory ── -->
+        <section class="perf-section">
+            <div class="section-header">
+                <div>
+                    <p class="section-title" locale="taskManager/memory">Memory</p>
+                    <p class="section-sub" locale="taskManager/memory_description">RAM Usage</p>
                 </div>
+                <span id="RAMInfo" class="hw-label">—</span>
             </div>
-        </div>
-        <br>
-        <div class="ui divider"></div>
-
-        <div id="ramChartContainer" style="position: relative;">
-            <h3 class="ui header">
-                <span locale="taskManager/memory">Memory</span>
-                <div class="sub header" locale="taskManager/memory_description">RAM Usage</div>
-            </h3>
-            <p id="RAMInfo" style="position: absolute; top: 1em; right: 0.3em; font-size: 16px;" locale="taskManager/memory_info"></p>
-            <canvas id="ramChart" width="1200" height="300"></canvas>
-            <br>
-            <div class="ui stackable grid" style="margin-top:-1em !important;">
-                <div class="four wide column">
-                    <div class="ui header">
-                        <span id="ramUsed">Loading</span>
-                        <div class="sub header" locale="taskManager/ram_used_description">Used</div>
-                    </div>
+            <div class="chart-wrap"><canvas id="ramChart"></canvas></div>
+            <div class="stats-row">
+                <div class="stat">
+                    <div class="stat-val" id="ramUsed">—</div>
+                    <div class="stat-key" locale="taskManager/ram_used_description">Used</div>
                 </div>
-                <div class="four wide column">
-                    <div class="ui header">
-                        <span id="ramUsedPercentage">Loading</span>
-                        <div class="sub header" locale="taskManager/ram_used_percentage_description">Used (%)</div>
-                    </div>
+                <div class="stat">
+                    <div class="stat-val" id="ramUsedPercentage">—</div>
+                    <div class="stat-key" locale="taskManager/ram_used_percentage_description">Used (%)</div>
                 </div>
-                <div class="four wide column">
-                    <div class="ui header">
-                        <span id="ramTotal">Loading</span>
-                        <div class="sub header" locale="taskManager/ram_total_description">Total</div>
-                    </div>
+                <div class="stat">
+                    <div class="stat-val" id="ramTotal">—</div>
+                    <div class="stat-key" locale="taskManager/ram_total_description">Total</div>
                 </div>
             </div>
-        </div>
-        <br>
-        <div class="ui divider"></div>
-        <div id="netChartContainer" style="position: relative; margin-top: 1.2em;">
-            <h3 class="ui header">
-                <span locale="taskManager/network">Network</span>
-                <div class="sub header" locale="taskManager/network_description">Network usage in the previous 60 seconds</div>
-            </h3>
-            <p id="netGraphScale" style="position: absolute; top: 1em; right: 0.3em; font-size: 16px;" locale="taskManager/network_scale">100 kbps</p>
-            <canvas id="netChart" width="1200" height="300"></canvas>
-            <div class="ui stackable grid" style="margin-top:0;">
-                <div class="four wide column">
-                    <div class="ui header" style="border-left: 2px solid #bc793f; padding-left: 1em;">
-                        <span id="rx">Loading</span>
-                        <div class="sub header" locale="taskManager/network_received_description">Received</div>
-                    </div>
+        </section>
+
+        <!-- ── Network ── -->
+        <section class="perf-section">
+            <div class="section-header">
+                <div>
+                    <p class="section-title" locale="taskManager/network">Network</p>
+                    <p class="section-sub" locale="taskManager/network_description">Network usage in the previous 60 seconds</p>
                 </div>
-                <div class="four wide column">
-                    <div class="ui header" style="border-left: 2px dotted #bc793f; padding-left: 1em;">
-                        <span id="tx">Loading</span>
-                        <div class="sub header" locale="taskManager/network_transmitted_description">Transmitted</div>
-                    </div>
+                <span id="netGraphScale" class="hw-label">—</span>
+            </div>
+            <div class="chart-wrap"><canvas id="netChart"></canvas></div>
+            <div class="stats-row">
+                <div class="stat net-rx">
+                    <div class="stat-val" id="rx">—</div>
+                    <div class="stat-key" locale="taskManager/network_received_description">Received</div>
+                </div>
+                <div class="stat net-tx">
+                    <div class="stat-val" id="tx">—</div>
+                    <div class="stat-key" locale="taskManager/network_transmitted_description">Transmitted</div>
                 </div>
             </div>
-        </div>
+        </section>
+
     </div>
-    <div style="height: 84px;"></div>
     <script>
-        var cpuChart;
-        var ramChart;
-        var netChart;
-        var previousNetData = [0, 0];
+        var cpuChart, ramChart, netChart;
+        var prevNetData = [0, 0];
         var isDarkMode = false;
 
-        // Detect theme before charts are initialised
-        ao_module_getSystemThemeColor(function(color) {
-            isDarkMode = (color !== 'whiteTheme');
-            document.body.classList.toggle('dark', isDarkMode);
-            if (isDarkMode) {
-                applyDarkChartColors();
-            }
-        });
-
-        var performanceLocale = NewAppLocale();
-        performanceLocale.init("../locale/system_settings/spec.json", function(){
-            performanceLocale.translate();
-            initInfo();
-        });
-
-
-        //Override Chart.js v3 poor API designs
-        Chart.defaults.plugins.tooltip.enabled = false;
-        Chart.defaults.plugins.legend.display = false;
-
-        var options = {
-            maintainAspectRatio: true,
-            responsive: true,
-			spanGaps: false,
-			elements: {
-				line: {
-					tension: 0.000001
-				}
-			},
-			plugins: {
-				filler: {
-					propagate: false
-				},
-			},
-			scales: {
-				x: {
-                    grid: {
-                        color:  "rgba(83, 160, 205, 0.2)"
-                    }
-                },
-                y: {
-                    min: 0,
-                    max: 100,
-                    grid: {
-                        color:  "rgba(83, 160, 205, 0.2)"
-                    },
-                    ticks: {
-                        display: false,
-                    }
-                }
-            },
-            legend: {
-                display: false,
+        // Color sets for light / dark themes
+        var COLORS = {
+            light: {
+                cpu:  { line: '#4c9dcb', fill: 'rgba(76,157,203,0.12)',  grid: 'rgba(76,157,203,0.18)'  },
+                ram:  { line: '#9528b4', fill: 'rgba(149,40,180,0.12)', grid: 'rgba(149,40,180,0.18)' },
+                net:  { line: '#a74f01', fill: 'rgba(167,79,1,0.10)',   grid: 'rgba(167,79,1,0.18)'   },
+                net2: { line: '#a74f01', fill: 'rgba(167,79,1,0.05)' }
             },
-            tooltips: {
-                callbacks: {
-                    label: function(tooltipItem) {
-                            return tooltipItem.yLabel;
-                    }
-                }
+            dark: {
+                cpu:  { line: '#5db3e8', fill: 'rgba(93,179,232,0.10)',  grid: 'rgba(255,255,255,0.07)' },
+                ram:  { line: '#b94dd4', fill: 'rgba(185,77,212,0.10)', grid: 'rgba(255,255,255,0.07)' },
+                net:  { line: '#d4722a', fill: 'rgba(212,114,42,0.10)', grid: 'rgba(255,255,255,0.07)' },
+                net2: { line: '#d4722a', fill: 'rgba(212,114,42,0.05)' }
             }
         };
 
-        var ramOptions = {
-            maintainAspectRatio: true,
-            responsive: true,
-			spanGaps: false,
-			elements: {
-				line: {
-					tension: 0.000001
-				}
-			},
-			plugins: {
-				filler: {
-					propagate: false
-				},
-			},
-			scales: {
-				x: {
-                    grid: {
-                        color:  "rgba(156, 55, 185, 0.2)"
-                    }
-                },
-                y: {
-                    min: 0,
-                    max: 100,
-                    grid: {
-                        color:  "rgba(156, 55, 185, 0.2)"
-                    },
-                    ticks: {
-                        display: false,
-                    }
-                }
-            },
-            legend: {
-                display: false,
-            },
-            tooltips: {
-                callbacks: {
-                    label: function(tooltipItem) {
-                            return tooltipItem.yLabel;
-                    }
-                }
-            }
-        };
+        // Suppress built-in tooltip and legend for all charts
+        Chart.defaults.plugins.tooltip.enabled = false;
+        Chart.defaults.plugins.legend.display = false;
 
-        var netOptions = {
-            maintainAspectRatio: true,
-            responsive: true,
-			spanGaps: false,
-			elements: {
-				line: {
-					tension: 0.000001
-				}
-			},
-			plugins: {
-				filler: {
-					propagate: false
-				},
-			},
-			scales: {
-				x: {
-                    grid: {
-                        color:  "rgba(167, 79, 1, 0.2)"
-                    }
-                },
-                y: {
-                    min: Math.min.apply(this, getMergedRxTxDataset()),
-                    max: Math.max.apply(this, getMergedRxTxDataset()) + 5,
-                    grid: {
-                        color:  "rgba(167, 79, 1, 0.2)"
-                    },
-                    ticks: {
-                        display: false,
-                    }
+        // Build options shared by CPU and RAM charts
+        function makeOptions(gridColor) {
+            return {
+                animation: false,
+                maintainAspectRatio: false,
+                responsive: true,
+                spanGaps: false,
+                elements: { line: { tension: 0.35 } },
+                plugins: { filler: { propagate: false } },
+                scales: {
+                    x: { grid: { color: gridColor }, ticks: { display: false } },
+                    y: { min: 0, max: 100, grid: { color: gridColor }, ticks: { display: false } }
                 }
-            },
-            legend: {
-                display: false,
-            },
-            tooltips: {
-                callbacks: {
-                    label: function(tooltipItem) {
-                            return tooltipItem.yLabel;
-                    }
-                }
-            }
-        };
-        
-        chartInit();
-
-        //Special code to handle embedding into the System Setting embedded windows
-        var insideIframe = false;
-        if (parent.managerInIframe !== undefined && parent.managerInIframe == true){
-            $(window).on("click", function(e){
-                parent.ao_module_focus();
-            });
+            };
         }
 
-        function initInfo(){
-            $.get("../../system/info/getCPUinfo", function(data){
-                var data = JSON.parse(data);
-                console.log(data);
-                $("#CPUname").text(data.Model);
-                if (parseFloat(data.Freq) > 1000){
-                    $("#cpufreq").text((data.Freq/1000).toFixed(2) + " Ghz");
-                }else{
-                    $("#cpufreq").text(data.Freq + " Mhz");
+        // Network chart options (dynamic y-axis range)
+        function makeNetOptions(gridColor) {
+            return {
+                animation: false,
+                maintainAspectRatio: false,
+                responsive: true,
+                spanGaps: false,
+                elements: { line: { tension: 0.35 } },
+                plugins: { filler: { propagate: false } },
+                scales: {
+                    x: { grid: { color: gridColor }, ticks: { display: false } },
+                    y: { min: 0, max: 100, grid: { color: gridColor }, ticks: { display: false } }
                 }
-                
-            });
-
-            $.get("../../system/info/getRAMinfo", function(data){
-                //Return ram in byte
-                var ramsize = bytesToSize(data);
-                $("#RAMInfo").text(ramsize);
-            })
+            };
         }
 
-        function bytesToSize(bytes) {
-            var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
-            if (bytes == 0) return '0 Byte';
-            var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1024)));
-            return (bytes / Math.pow(1024, i)).toFixed(1) + ' ' + sizes[i];
-        }
+        // ── Locale (can run in parallel with theme detection) ──
+        var performanceLocale = NewAppLocale();
+        performanceLocale.init("../locale/system_settings/spec.json", function() {
+            performanceLocale.translate();
+        });
 
-        function bitToSize(bytes) {
-            var sizes = ['b', 'Kb', 'Mb', 'Gb', 'Tb'];
-            if (bytes == 0) return '0 b';
-            var i = parseInt(Math.floor(Math.log(bytes) / Math.log(1000)));
-            return (bytes / Math.pow(1024, i)).toFixed(1) + ' ' + sizes[i];
-        }
+        // ── Theme detection → initialise everything with the right colours ──
+        ao_module_getSystemThemeColor(function(color) {
+            isDarkMode = (color !== 'whiteTheme');
+            document.body.classList.toggle('dark', isDarkMode);
+            chartInit();
+            initInfo();
+            updateData();
+        });
 
+        // ── Chart initialisation ──
+        function chartInit() {
+            var empty = new Array(60).fill('');
+            var zeros = new Array(60).fill(0);
+            var c = isDarkMode ? COLORS.dark : COLORS.light;
 
-        function chartInit(){
             cpuChart = new Chart('cpuChart', {
-				type: 'line',
-				data: {
-					labels: [],
-					datasets: [{
-						backgroundColor: "rgba(241,246,250,0.4)",
-						borderColor: "#4c9dcb",
-						data: [],
-                        radius: 0,
-                        borderWidth: 2,
-						fill: 'start'
+                type: 'line',
+                data: {
+                    labels: empty.slice(),
+                    datasets: [{
+                        data: zeros.slice(),
+                        borderColor: c.cpu.line,
+                        backgroundColor: c.cpu.fill,
+                        borderWidth: 1.5,
+                        pointRadius: 0,
+                        fill: 'start'
                     }]
-				},
-				options: options
+                },
+                options: makeOptions(c.cpu.grid)
             });
-            
-            //Push 60 empty data into the chart
-            for (var i =0; i < 60; i++){
-                addData(cpuChart, "",0)
-            }
 
-            //Create RAM Chart
             ramChart = new Chart('ramChart', {
-				type: 'line',
-				data: {
-					labels: [],
-					datasets: [{
-						backgroundColor: "rgba(244,242,244,0.4)",
-						borderColor: "#9528b4",
-						data: [],
-                        radius: 0,
-                        borderWidth: 2,
-						fill: 'start'
+                type: 'line',
+                data: {
+                    labels: empty.slice(),
+                    datasets: [{
+                        data: zeros.slice(),
+                        borderColor: c.ram.line,
+                        backgroundColor: c.ram.fill,
+                        borderWidth: 1.5,
+                        pointRadius: 0,
+                        fill: 'start'
                     }]
-				},
-				options: ramOptions
+                },
+                options: makeOptions(c.ram.grid)
             });
 
-            for (var i =0; i < 60; i++){
-                addData(ramChart, "",0)
-            }
-
-            //Create Network Chart
             netChart = new Chart('netChart', {
-				type: 'line',
-				data: {
-					labels: [],
-					datasets: [{
-						backgroundColor: "rgba(252,243,235,0.4)",
-						borderColor: "#a74f01",
-						data: [],
-                        radius: 0,
-                        borderWidth: 2,
-						fill: 'start'
-                    },
-                    {
-						backgroundColor: "rgba(252,243,235,0.2)",
-						borderColor: "#a74f01",
-                        borderDash: [3, 3],
-						data: [],
-                        radius: 0,
-                        borderWidth: 2,
-						fill: 'start'
-                        
-                    }]
-				},
-				options: netOptions
+                type: 'line',
+                data: {
+                    labels: empty.slice(),
+                    datasets: [
+                        {
+                            data: zeros.slice(),
+                            borderColor: c.net.line,
+                            backgroundColor: c.net.fill,
+                            borderWidth: 1.5,
+                            pointRadius: 0,
+                            fill: 'start'
+                        },
+                        {
+                            data: zeros.slice(),
+                            borderColor: c.net2.line,
+                            backgroundColor: c.net2.fill,
+                            borderDash: [4, 3],
+                            borderWidth: 1.5,
+                            pointRadius: 0,
+                            fill: 'start'
+                        }
+                    ]
+                },
+                options: makeNetOptions(c.net.grid)
             });
-
-            for (var i =0; i < 60; i++){
-                addNetData(netChart, "", 0, 0)
-            }
-            
         }
 
-        resizeCharts();
-        $(window).on("resize", function(){
-            resizeCharts();
-        })
+        // ── Static info (CPU model, total RAM) ──
+        function initInfo() {
+            $.get("../../system/info/getCPUinfo", function(data) {
+                var d = JSON.parse(data);
+                $("#CPUname").text(d.Model || '—');
+                if (parseFloat(d.Freq) > 1000) {
+                    $("#cpufreq").text((d.Freq / 1000).toFixed(2) + " GHz");
+                } else {
+                    $("#cpufreq").text(d.Freq + " MHz");
+                }
+            });
 
-        function resizeCharts(){
-            $("#cpuChart").width($("#cpuChartContainer").width());
-            $("#ramChart").width($("#ramChartContainer").width());
+            $.get("../../system/info/getRAMinfo", function(data) {
+                $("#RAMInfo").text(bytesToSize(data));
+            });
         }
 
-        // Apply dark colour palette to all three charts
-        function applyDarkChartColors() {
-            var darkGridColor = "rgba(255, 255, 255, 0.08)";
-            var darkTickColor = "#888";
-
-            // Update chart option objects so newly created charts use dark colors
-            [options, ramOptions, netOptions].forEach(function(opt) {
-                if (opt.scales && opt.scales.x) {
-                    opt.scales.x.grid = opt.scales.x.grid || {};
-                    opt.scales.x.grid.color = darkGridColor;
-                    opt.scales.x.ticks = opt.scales.x.ticks || {};
-                    opt.scales.x.ticks.color = darkTickColor;
-                }
-                if (opt.scales && opt.scales.y) {
-                    opt.scales.y.grid = opt.scales.y.grid || {};
-                    opt.scales.y.grid.color = darkGridColor;
-                    opt.scales.y.ticks = opt.scales.y.ticks || {};
-                    opt.scales.y.ticks.color = darkTickColor;
+        // ── Polling loop: both requests fire in parallel, next tick after both finish ──
+        function updateData() {
+            var completed = 0;
+            function scheduleNext() {
+                completed++;
+                if (completed >= 2) setTimeout(updateData, 1000);
+            }
+
+            // Network stats (non-blocking read from /sys/class/net on Linux)
+            $.get("../../system/network/getNICUsage")
+                .done(function(data) {
+                    if (data.error !== undefined) {
+                        $("#netGraphScale").text(data.error);
+                    } else if (prevNetData[0] === 0 && prevNetData[1] === 0) {
+                        // First reading: seed baseline, skip delta
+                        prevNetData = [data.RX, data.TX];
+                    } else {
+                        var rxd = data.RX - prevNetData[0];
+                        var txd = data.TX - prevNetData[1];
+                        prevNetData = [data.RX, data.TX];
+                        shiftNetChart(rxd, txd);
+                        $("#rx").text(bitToSize(rxd) + "/s");
+                        $("#tx").text(bitToSize(txd) + "/s");
+                    }
+                })
+                .always(scheduleNext);
+
+            // CPU + RAM usage (now served from in-memory cache — responds in < 1 ms)
+            $.ajax({
+                url: "../../system/info/getUsageInfo",
+                method: "GET",
+                timeout: 2000,
+                success: function(data) {
+                    shiftChart(cpuChart, data.CPU || 0);
+                    shiftChart(ramChart, data.RamUsage || 0);
+                    $("#cpuUsage").text((data.CPU || 0).toFixed(1) + "%");
+                    $("#ramUsedPercentage").text((data.RamUsage || 0).toFixed(1) + "%");
+                    $("#ramUsed").text(data.UsedRAM || '—');
+                    $("#ramTotal").text(data.TotalRam || '—');
                 }
-            });
+            }).always(scheduleNext);
+        }
 
-            // If charts are already created, patch them live
-            if (cpuChart) {
-                cpuChart.data.datasets[0].backgroundColor = "rgba(76, 157, 203, 0.15)";
-                cpuChart.options.scales.x.grid.color = darkGridColor;
-                cpuChart.options.scales.y.grid.color = darkGridColor;
-                cpuChart.update();
-            }
-            if (ramChart) {
-                ramChart.data.datasets[0].backgroundColor = "rgba(149, 40, 180, 0.15)";
-                ramChart.options.scales.x.grid.color = darkGridColor;
-                ramChart.options.scales.y.grid.color = darkGridColor;
-                ramChart.update();
-            }
-            if (netChart) {
-                netChart.data.datasets[0].backgroundColor = "rgba(167, 79, 1, 0.15)";
-                netChart.data.datasets[1].backgroundColor = "rgba(167, 79, 1, 0.08)";
-                netChart.options.scales.x.grid.color = darkGridColor;
-                netChart.options.scales.y.grid.color = darkGridColor;
-                netChart.update();
-            }
+        // Shift one data point into a single-dataset chart (one update, no flicker)
+        function shiftChart(chart, newVal) {
+            chart.data.labels.shift();
+            chart.data.labels.push('');
+            chart.data.datasets[0].data.shift();
+            chart.data.datasets[0].data.push(newVal);
+            chart.update('none');
         }
 
-        // Restore light-mode chart colours
-        function applyLightChartColors() {
-            var cpuGrid = "rgba(83, 160, 205, 0.2)";
-            var ramGrid = "rgba(156, 55, 185, 0.2)";
-            var netGrid = "rgba(167, 79, 1, 0.2)";
+        // Shift one data point into the dual-dataset network chart
+        function shiftNetChart(rxd, txd) {
+            netChart.data.labels.shift();
+            netChart.data.labels.push('');
+            netChart.data.datasets[0].data.shift();
+            netChart.data.datasets[0].data.push(rxd);
+            netChart.data.datasets[1].data.shift();
+            netChart.data.datasets[1].data.push(txd);
+
+            var allVals = netChart.data.datasets[0].data.concat(netChart.data.datasets[1].data);
+            var maxVal = Math.max.apply(null, allVals);
+            netChart.options.scales.y.min = 0;
+            netChart.options.scales.y.max = maxVal > 0 ? maxVal * 1.2 : 100;
+
+            updateNetScaleLabel(maxVal);
+            netChart.update('none');
+        }
 
-            [options].forEach(function(opt) {
-                if (opt.scales && opt.scales.x) opt.scales.x.grid.color = cpuGrid;
-                if (opt.scales && opt.scales.y) opt.scales.y.grid.color = cpuGrid;
-            });
-            [ramOptions].forEach(function(opt) {
-                if (opt.scales && opt.scales.x) opt.scales.x.grid.color = ramGrid;
-                if (opt.scales && opt.scales.y) opt.scales.y.grid.color = ramGrid;
-            });
-            [netOptions].forEach(function(opt) {
-                if (opt.scales && opt.scales.x) opt.scales.x.grid.color = netGrid;
-                if (opt.scales && opt.scales.y) opt.scales.y.grid.color = netGrid;
-            });
+        function updateNetScaleLabel(maxBits) {
+            maxBits = maxBits || 0;
+            var baseValue = parseInt(maxBits * 1.2);
+            var sizes = ['b', 'Kb', 'Mb', 'Gb', 'Tb'];
+            var scale = "0 bps";
+            if (baseValue > 0) {
+                var i = Math.min(Math.floor(Math.log(baseValue) / Math.log(1000)), sizes.length - 1);
+                var val = Math.ceil((baseValue / Math.pow(1024, i)) / 10) * 10;
+                scale = val + ' ' + sizes[i] + "ps";
+            }
+            $("#netGraphScale").text(scale);
+        }
 
+        // ── Theme switching ──
+        function applyThemeColors(dark) {
+            var c = dark ? COLORS.dark : COLORS.light;
             if (cpuChart) {
-                cpuChart.data.datasets[0].backgroundColor = "rgba(241,246,250,0.4)";
-                cpuChart.options.scales.x.grid.color = cpuGrid;
-                cpuChart.options.scales.y.grid.color = cpuGrid;
-                cpuChart.update();
+                cpuChart.data.datasets[0].borderColor = c.cpu.line;
+                cpuChart.data.datasets[0].backgroundColor = c.cpu.fill;
+                cpuChart.options.scales.x.grid.color = c.cpu.grid;
+                cpuChart.options.scales.y.grid.color = c.cpu.grid;
+                cpuChart.update('none');
             }
             if (ramChart) {
-                ramChart.data.datasets[0].backgroundColor = "rgba(244,242,244,0.4)";
-                ramChart.options.scales.x.grid.color = ramGrid;
-                ramChart.options.scales.y.grid.color = ramGrid;
-                ramChart.update();
+                ramChart.data.datasets[0].borderColor = c.ram.line;
+                ramChart.data.datasets[0].backgroundColor = c.ram.fill;
+                ramChart.options.scales.x.grid.color = c.ram.grid;
+                ramChart.options.scales.y.grid.color = c.ram.grid;
+                ramChart.update('none');
             }
             if (netChart) {
-                netChart.data.datasets[0].backgroundColor = "rgba(252,243,235,0.4)";
-                netChart.data.datasets[1].backgroundColor = "rgba(252,243,235,0.2)";
-                netChart.options.scales.x.grid.color = netGrid;
-                netChart.options.scales.y.grid.color = netGrid;
-                netChart.update();
+                netChart.data.datasets[0].borderColor = c.net.line;
+                netChart.data.datasets[0].backgroundColor = c.net.fill;
+                netChart.data.datasets[1].borderColor = c.net2.line;
+                netChart.data.datasets[1].backgroundColor = c.net2.fill;
+                netChart.options.scales.x.grid.color = c.net.grid;
+                netChart.options.scales.y.grid.color = c.net.grid;
+                netChart.update('none');
             }
         }
 
+        // Called by the parent frame when the desktop theme changes
         window.desktopThemeChanged = function(theme) {
             isDarkMode = (theme === 'dark');
             document.body.classList.toggle('dark', isDarkMode);
-            if (isDarkMode) {
-                applyDarkChartColors();
-            } else {
-                applyLightChartColors();
-            }
+            applyThemeColors(isDarkMode);
         };
 
-        updateData();
-        function updateData(){
-            //Testing use 
-            /*
-            setInterval(function(){
-                addAndShiftChartDate(cpuChart, "", Math.floor(Math.random() * 50) + 20)
-                addAndShiftChartDate(ramChart, "", Math.floor(Math.random() * 10) + 30)
-            }, 1000)
-            */
-
-             //Calculate the bandwidth diff
-            $.get("../../system/network/getNICUsage", function(data){
-                if (data.error !== undefined){
-                    //Error
-                    console.log(data.error);
-                    $("#netGraphScale").text(data.error);
-                    return;
-                }
-                if (previousNetData[0] == 0 && previousNetData[1] == 0){
-                    //Not initiated. Set base and wait for next iteration
-                    previousNetData = [data.RX, data.TX];
-                }else{
-                    var rxd = data.RX - previousNetData[0];
-                    var txd = data.TX - previousNetData[1];
-                    previousNetData = [data.RX, data.TX];
-                    addAndShiftNetworkData(netChart, "", rxd, txd);
-
-                    $("#rx").text(bitToSize(rxd)+"/s");
-                    $("#tx").text(bitToSize(txd)+"/s");
-
-                    //Get the max value of the diagram, round it to the cloest 10x
-                    var chartMaxValue = Math.max.apply(this, getMergedRxTxDataset()) * 1.2;
-
-                    //Special Rounding for calculating graph scale
-                    baseValue = parseInt(chartMaxValue);
-                    var scale = "0 bps"
-                    var sizes = ['b', 'Kb', 'Mb', 'Gb', 'Tb', 'Pb'];
-                    function roundUpNearest(num) {
-                        return Math.ceil(num / 10) * 10;
-                    }
-
-                    if (baseValue == 0){
-
-                    }else{
-                        var i = parseInt(Math.floor(Math.log(baseValue) / Math.log(1000)));
-                        scale = roundUpNearest((baseValue / Math.pow(1024, i)).toFixed(0))
-                        scale += ' ' + sizes[i] + "ps";
-                    }
-                    
-                    //console.log(baseValue, chartMaxValue, scale);
-                    $("#netGraphScale").text(scale);
-                }
-            })
-
-            //Get UsageInfo with timeout
-            $.ajax({
-                url: "../../system/info/getUsageInfo",
-                method: "GET",
-                success:function(data){
-                    //Update graph
-                    addAndShiftChartDate(cpuChart, "", data.CPU);
-                    addAndShiftChartDate(ramChart, "", data.RamUsage);
-
-                    //Update values
-                    $("#cpuUsage").text(data.CPU.toFixed(1) + "%");
-                    $("#ramUsedPercentage").text(data.RamUsage.toFixed(1) + "%")
-                    $("#ramUsed").text(data.UsedRAM);
-                    $("#ramTotal").text(data.TotalRam);
-
-                    setTimeout(function(){
-                        updateData();
-                    }, 100);
-                },
-                error: function(){
-                    //Error, retry in 5 seconds
-                    setTimeout(function(){
-                        updateData();
-                    }, 5000);
-                },
-                timeout: 2000
-            });
-        }
-
-        function addNetData(chart, label, rx, tx) {
-            chart.data.labels.push(label);
-            chart.data.datasets[0].data.push(rx);
-            chart.data.datasets[1].data.push(tx);
-            chart.update();
-        }
-
-        function addData(chart, label, data) {
-            chart.data.labels.push(label);
-            chart.data.datasets.forEach((dataset) => {
-                dataset.data.push(data);
-            });
-            chart.update();
-        }
-
-        function addAndShiftChartDate(chart, label, newdata) {
-            chart.data.labels.splice(0, 1); // remove first label
-            chart.data.datasets.forEach(function(dataset) {
-                dataset.data.splice(0, 1); // remove first data point
-            });
-
-            chart.update();
-
-            // Add new data
-            chart.data.labels.push(label); // add new label at end
-            chart.data.datasets.forEach(function(dataset, index) {
-                dataset.data.push(newdata); // add new data at end
+        // Forward clicks to the parent window when embedded in an iframe
+        if (parent.managerInIframe !== undefined && parent.managerInIframe == true) {
+            $(window).on("click", function() {
+                parent.ao_module_focus();
             });
-
-            chart.update();
         }
 
-        function addAndShiftNetworkData(chart, label, rxd, txd) {
-            chart.data.labels.splice(0, 1); // remove first label
-            chart.data.datasets.forEach(function(dataset) {
-                dataset.data.splice(0, 1); // remove first data point
-            });
-
-            chart.update();
-
-            // Add new data
-            chart.data.labels.push(label); // add new label at end
-            chart.data.datasets[0].data.push(rxd);
-            chart.data.datasets[1].data.push(txd);
-            
-            
-
-            //Update the sacle as well
-            netChart.options.scales.y.min = Math.min.apply(this, getMergedRxTxDataset());
-            netChart.options.scales.y.max = Math.max.apply(this, getMergedRxTxDataset()) *1.2;
-
-            chart.update();
+        // ── Utilities ──
+        function bytesToSize(bytes) {
+            var sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];
+            if (bytes == 0) return '0 Byte';
+            var i = Math.floor(Math.log(bytes) / Math.log(1024));
+            return (bytes / Math.pow(1024, i)).toFixed(1) + ' ' + sizes[i];
         }
 
-        function getMergedRxTxDataset(){
-            if (netChart == undefined){
-                return [0, 100];
-            }
-            var newArr = [];
-            newArr = newArr.concat(netChart.data.datasets[0].data,netChart.data.datasets[1].data);
-            return newArr;
+        function bitToSize(bits) {
+            var sizes = ['b', 'Kb', 'Mb', 'Gb', 'Tb'];
+            if (bits == 0) return '0 b';
+            var i = Math.min(Math.floor(Math.log(bits) / Math.log(1000)), sizes.length - 1);
+            return (bits / Math.pow(1024, i)).toFixed(1) + ' ' + sizes[i];
         }
-
     </script>
 </body>
-</html>
+</html>