/ services / SystemUsage.qml
SystemUsage.qml
  1  pragma Singleton
  2  
  3  import Quickshell
  4  import Quickshell.Io
  5  import QtQuick
  6  
  7  Singleton {
  8      id: root
  9  
 10      property real cpuPerc
 11      property real cpuTemp
 12      property real gpuPerc
 13      property real gpuTemp
 14      property int memUsed
 15      property int memTotal
 16      readonly property real memPerc: memTotal > 0 ? memUsed / memTotal : 0
 17      property int storageUsed
 18      property int storageTotal
 19      property real storagePerc: storageTotal > 0 ? storageUsed / storageTotal : 0
 20  
 21      property int lastCpuIdle
 22      property int lastCpuTotal
 23  
 24      function formatKib(kib: int): var {
 25          const mib = 1024;
 26          const gib = 1024 ** 2;
 27          const tib = 1024 ** 3;
 28  
 29          if (kib >= tib)
 30              return {
 31                  value: kib / tib,
 32                  unit: "TiB"
 33              };
 34          if (kib >= gib)
 35              return {
 36                  value: kib / gib,
 37                  unit: "GiB"
 38              };
 39          if (kib >= mib)
 40              return {
 41                  value: kib / mib,
 42                  unit: "MiB"
 43              };
 44          return {
 45              value: kib,
 46              unit: "KiB"
 47          };
 48      }
 49  
 50      Timer {
 51          running: true
 52          interval: 3000
 53          repeat: true
 54          onTriggered: {
 55              stat.reload();
 56              meminfo.reload();
 57              storage.running = true;
 58              cpuTemp.running = true;
 59              gpuUsage.running = true;
 60              gpuTemp.running = true;
 61          }
 62      }
 63  
 64      FileView {
 65          id: stat
 66  
 67          path: "/proc/stat"
 68          onLoaded: {
 69              const data = text().match(/^cpu\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)\s+(\d+)/);
 70              if (data) {
 71                  const stats = data.slice(1).map(n => parseInt(n, 10));
 72                  const total = stats.reduce((a, b) => a + b, 0);
 73                  const idle = stats[3];
 74  
 75                  const totalDiff = total - root.lastCpuTotal;
 76                  const idleDiff = idle - root.lastCpuIdle;
 77                  root.cpuPerc = totalDiff > 0 ? (1 - idleDiff / totalDiff) : 0;
 78  
 79                  root.lastCpuTotal = total;
 80                  root.lastCpuIdle = idle;
 81              }
 82          }
 83      }
 84  
 85      FileView {
 86          id: meminfo
 87  
 88          path: "/proc/meminfo"
 89          onLoaded: {
 90              const data = text();
 91              root.memTotal = parseInt(data.match(/MemTotal: *(\d+)/)[1], 10) || 1;
 92              root.memUsed = (root.memTotal - parseInt(data.match(/MemAvailable: *(\d+)/)[1], 10)) || 0;
 93          }
 94      }
 95  
 96      Process {
 97          id: storage
 98  
 99          running: true
100          command: ["sh", "-c", "df | grep '^/dev/' | awk '{print $3, $4}'"]
101          stdout: SplitParser {
102              splitMarker: ""
103              onRead: data => {
104                  let used = 0;
105                  let avail = 0;
106                  for (const line of data.trim().split("\n")) {
107                      const [u, a] = line.split(" ");
108                      used += parseInt(u, 10);
109                      avail += parseInt(a, 10);
110                  }
111                  root.storageUsed = used;
112                  root.storageTotal = used + avail;
113              }
114          }
115      }
116  
117      Process {
118          id: cpuTemp
119  
120          running: true
121          command: ["fish", "-c", "cat /sys/class/thermal/thermal_zone*/temp | string join ' '"]
122          stdout: SplitParser {
123              onRead: data => {
124                  const temps = data.trim().split(" ");
125                  const sum = temps.reduce((acc, d) => acc + parseInt(d, 10), 0);
126                  root.cpuTemp = sum / temps.length / 1000;
127              }
128          }
129      }
130  
131      Process {
132          id: gpuUsage
133  
134          running: true
135          command: ["sh", "-c", "cat /sys/class/drm/card*/device/gpu_busy_percent"]
136          stdout: SplitParser {
137              splitMarker: ""
138              onRead: data => {
139                  const percs = data.trim().split("\n");
140                  const sum = percs.reduce((acc, d) => acc + parseInt(d, 10), 0);
141                  root.gpuPerc = sum / percs.length / 100;
142              }
143          }
144      }
145  
146      Process {
147          id: gpuTemp
148  
149          running: true
150          command: ["sh", "-c", "sensors | jq -nRc '[inputs]'"]
151          stdout: SplitParser {
152              onRead: data => {
153                  let eligible = false;
154                  let sum = 0;
155                  let count = 0;
156                  for (const line of JSON.parse(data)) {
157                      if (line === "Adapter: PCI adapter")
158                          eligible = true;
159                      else if (line === "")
160                          eligible = false;
161                      else if (eligible) {
162                          const match = line.match(/^(temp[0-9]+|GPU core|edge)+:\s+\+([0-9]+\.[0-9]+)°C/);
163                          if (match) {
164                              sum += parseFloat(match[2]);
165                              count++;
166                          }
167                      }
168                  }
169                  root.gpuTemp = count > 0 ? sum / count : 0;
170              }
171          }
172      }
173  }