Krolyxon
Commit af47ed6

Branches

Refs →

aurora.configquickshellauroraSysStats.qml

92 lines · 3.7 KB · QML

1pragma Singleton
2import QtQuick
3import Quickshell
4import Quickshell.Io
5
6// cpu / memory / disk / uptime. Shared by the cat, the bar and the System tab.
7Singleton {
8 id: root
9 property real cpu: 0 // 0..1
10 property real memUsed: 0 // GiB
11 property real memTotal: 0
12 property var disks: [] // [{source, target, label, used, size}] in GiB, "/" first
13 property real uptime: 0 // seconds
14 property var history: [] // last 40 cpu samples
15 property real prevIdle: -1
16 property real prevTotal: 0
17
18 FileView { id: statFile; path: "/proc/stat"; blockLoading: true }
19 FileView { id: memFile; path: "/proc/meminfo"; blockLoading: true }
20 FileView { id: upFile; path: "/proc/uptime"; blockLoading: true }
21
22 function sample() {
23 statFile.reload()
24 var m = statFile.text().match(/^cpu\s+(.*)$/m)
25 if (m) {
26 var f = m[1].trim().split(/\s+/).map(Number)
27 var idle = f[3] + (f[4] || 0)
28 var total = 0
29 for (var i = 0; i < Math.min(8, f.length); i++) total += f[i]
30 if (prevIdle >= 0 && total > prevTotal)
31 cpu = Math.max(0, Math.min(1, 1 - (idle - prevIdle) / (total - prevTotal)))
32 prevIdle = idle
33 prevTotal = total
34 var h = history.slice()
35 h.push(cpu)
36 if (h.length > 40) h.shift()
37 history = h
38 }
39
40 memFile.reload()
41 var mt = memFile.text()
42 var t = mt.match(/MemTotal:\s+(\d+)/), a = mt.match(/MemAvailable:\s+(\d+)/)
43 if (t && a) {
44 memTotal = parseInt(t[1]) / 1048576
45 memUsed = (parseInt(t[1]) - parseInt(a[1])) / 1048576
46 }
47
48 upFile.reload()
49 uptime = parseFloat(upFile.text().split(" ")[0]) || 0
50 }
51
52 Timer { interval: 1000; running: true; repeat: true; triggeredOnStart: true; onTriggered: root.sample() }
53
54 // every mounted *local* block device (internal nvme, external drives, usb sticks...).
55 // pseudo filesystems and network mounts are skipped; one entry per physical source
56 // (so btrfs subvolumes / bind mounts of the same partition don't repeat).
57 function refreshDisks() { dfProc.running = true }
58
59 function parseDf(text) {
60 var lines = text.split("\n"), seen = {}, out = []
61 for (var i = 1; i < lines.length; i++) {
62 var m = lines[i].match(/^(\S+)\s+(\d+)\s+(\d+)\s+(.*)$/)
63 if (!m) continue
64 var src = m[1], target = m[4].trim()
65 if (src.indexOf("/dev/") !== 0 || src.indexOf("/dev/loop") === 0) continue
66 if (target.indexOf("/boot") === 0 || target.indexOf("/efi") === 0 || target.indexOf("/var/lib") === 0) continue
67 if (seen[src]) continue
68 seen[src] = true
69 var size = parseFloat(m[3])
70 if (size <= 0) continue
71 var parts = target.split("/").filter(function (x) { return x !== "" })
72 out.push({
73 source: src, target: target,
74 label: parts.length ? parts[parts.length - 1] : "/",
75 used: parseFloat(m[2]) / 1073741824, size: size / 1073741824
76 })
77 }
78 out.sort(function (a, b) {
79 if (a.target === "/") return -1
80 if (b.target === "/") return 1
81 return a.target.localeCompare(b.target)
82 })
83 return out
84 }
85
86 Process {
87 id: dfProc
88 command: ["df", "-l", "-B1", "--output=source,used,size,target"]
89 stdout: StdioCollector { onStreamFinished: root.disks = root.parseDf(text) }
90 }
91 Timer { interval: 30000; running: true; repeat: true; triggeredOnStart: true; onTriggered: root.refreshDisks() }
92}