Using a bash script for SystemStats instead of ZigStat
This commit is contained in:
parent
b68d5c9f4c
commit
d009b8d5c8
8 changed files with 234 additions and 160 deletions
|
|
@ -1,49 +0,0 @@
|
||||||
#!/bin/bash
|
|
||||||
|
|
||||||
# A script to display CPU temperature, CPU usage, memory usage, and disk usage without the 'sensors' package.
|
|
||||||
|
|
||||||
echo "--- System Metrics ---"
|
|
||||||
|
|
||||||
# Get CPU Temperature in Celsius from a kernel file.
|
|
||||||
# This method is more common on modern systems but may vary.
|
|
||||||
# It reads the temperature from the first available core.
|
|
||||||
# Function to get CPU temperature
|
|
||||||
|
|
||||||
# Check for the common thermal zone path
|
|
||||||
if [ -f "/sys/class/thermal/thermal_zone0/temp" ]; then
|
|
||||||
temp_file="/sys/class/thermal/thermal_zone0/temp"
|
|
||||||
# Check for a different thermal zone path (e.g., some older systems)
|
|
||||||
elif [ -f "/sys/class/hwmon/hwmon0/temp1_input" ]; then
|
|
||||||
temp_file="/sys/class/hwmon/hwmon0/temp1_input"
|
|
||||||
else
|
|
||||||
echo "Error: Could not find a CPU temperature file."
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
# Read the raw temperature value
|
|
||||||
raw_temp=$(cat "$temp_file")
|
|
||||||
|
|
||||||
# The value is usually in millidegrees Celsius, so we divide by 1000.
|
|
||||||
temp_celsius=$((raw_temp / 1000))
|
|
||||||
|
|
||||||
echo "CPU Temperature: ${temp_celsius}°C"
|
|
||||||
|
|
||||||
|
|
||||||
# Get CPU Usage
|
|
||||||
# 'top' is a standard utility for this. It gives a real-time view of system processes.
|
|
||||||
cpu_usage=$(top -bn1 | grep "Cpu(s)" | sed "s/.*, *\([0-9.]*\)%* id.*/\1/" | awk '{print 100 - $1}')
|
|
||||||
echo "CPU Usage: ${cpu_usage}%"
|
|
||||||
|
|
||||||
# Get Memory Usage
|
|
||||||
# 'free' provides information about memory usage.
|
|
||||||
mem_total=$(free | grep Mem | awk '{print $2}')
|
|
||||||
mem_used=$(free | grep Mem | awk '{print $3}')
|
|
||||||
mem_usage=$((100 * mem_used / mem_total))
|
|
||||||
echo "Memory Usage: ${mem_usage}%"
|
|
||||||
|
|
||||||
# Get Disk Usage
|
|
||||||
# 'df' reports file system disk space usage. We check the root directory.
|
|
||||||
disk_usage=$(df -h / | grep / | awk '{print $5}' | sed 's/%//g')
|
|
||||||
echo "Disk Usage: ${disk_usage}%"
|
|
||||||
|
|
||||||
echo "----------------------"
|
|
||||||
192
Bin/system-stats.sh
Executable file
192
Bin/system-stats.sh
Executable file
|
|
@ -0,0 +1,192 @@
|
||||||
|
#!/bin/bash
|
||||||
|
|
||||||
|
# A Bash script to monitor system stats and output them in JSON format.
|
||||||
|
# This script is a conversion of ZigStat
|
||||||
|
|
||||||
|
# --- Configuration ---
|
||||||
|
# Default sleep duration in seconds. Can be overridden by the first argument.
|
||||||
|
SLEEP_DURATION=3
|
||||||
|
|
||||||
|
# --- Argument Parsing ---
|
||||||
|
# Check if a command-line argument is provided for the sleep duration.
|
||||||
|
if [[ -n "$1" ]]; then
|
||||||
|
# Basic validation to ensure the argument is a number (integer or float).
|
||||||
|
if [[ "$1" =~ ^[0-9]+(\.[0-9]+)?$ ]]; then
|
||||||
|
SLEEP_DURATION=$1
|
||||||
|
else
|
||||||
|
# Output to stderr if the format is invalid.
|
||||||
|
echo "Warning: Invalid duration format '$1'. Using default of ${SLEEP_DURATION}s." >&2
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Global Cache Variables ---
|
||||||
|
# These variables will store the discovered CPU temperature sensor path and type
|
||||||
|
# to avoid searching for it on every loop iteration.
|
||||||
|
TEMP_SENSOR_PATH=""
|
||||||
|
TEMP_SENSOR_TYPE=""
|
||||||
|
|
||||||
|
# --- Data Collection Functions ---
|
||||||
|
|
||||||
|
#
|
||||||
|
# Gets memory usage in GB and as a percentage.
|
||||||
|
#
|
||||||
|
get_memory_info() {
|
||||||
|
awk '
|
||||||
|
/MemTotal/ {total=$2}
|
||||||
|
/MemAvailable/ {available=$2}
|
||||||
|
END {
|
||||||
|
if (total > 0) {
|
||||||
|
usage_kb = total - available
|
||||||
|
usage_gb = usage_kb / 1000000
|
||||||
|
usage_percent = (usage_kb / total) * 100
|
||||||
|
# MODIFIED: Round the memory percentage to the nearest integer.
|
||||||
|
printf "%.1f %.0f\n", usage_gb, usage_percent
|
||||||
|
} else {
|
||||||
|
# Fallback if /proc/meminfo is unreadable or empty.
|
||||||
|
print "0.0 0.0"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
' /proc/meminfo
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Gets the usage percentage of the root filesystem ("/").
|
||||||
|
#
|
||||||
|
get_disk_usage() {
|
||||||
|
# df gets disk usage. --output=pcent shows only the percentage for the root path.
|
||||||
|
# tail -1 gets the data line, and tr removes the '%' sign and whitespace.
|
||||||
|
df --output=pcent / | tail -1 | tr -d ' %'
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Calculates current CPU usage over a short interval.
|
||||||
|
#
|
||||||
|
get_cpu_usage() {
|
||||||
|
# Read all 10 CPU time fields to prevent errors on newer kernels.
|
||||||
|
read -r cpu prev_user prev_nice prev_system prev_idle prev_iowait prev_irq prev_softirq prev_steal prev_guest prev_guest_nice < /proc/stat
|
||||||
|
|
||||||
|
# Calculate previous total and idle times.
|
||||||
|
local prev_total_idle=$((prev_idle + prev_iowait))
|
||||||
|
local prev_total=$((prev_user + prev_nice + prev_system + prev_idle + prev_iowait + prev_irq + prev_softirq + prev_steal + prev_guest + prev_guest_nice))
|
||||||
|
|
||||||
|
# Wait for a short period.
|
||||||
|
sleep 0.05
|
||||||
|
|
||||||
|
# Read all 10 CPU time fields again for the second measurement.
|
||||||
|
read -r cpu user nice system idle iowait irq softirq steal guest guest_nice < /proc/stat
|
||||||
|
|
||||||
|
# Calculate new total and idle times.
|
||||||
|
local total_idle=$((idle + iowait))
|
||||||
|
local total=$((user + nice + system + idle + iowait + irq + softirq + steal + guest + guest_nice))
|
||||||
|
|
||||||
|
# Add a check to prevent division by zero if total hasn't changed.
|
||||||
|
if (( total <= prev_total )); then
|
||||||
|
echo "0.0"
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Calculate the difference over the interval.
|
||||||
|
local diff_total=$((total - prev_total))
|
||||||
|
local diff_idle=$((total_idle - prev_total_idle))
|
||||||
|
|
||||||
|
# Use awk for floating-point calculation and print the percentage.
|
||||||
|
awk -v total="$diff_total" -v idle="$diff_idle" '
|
||||||
|
BEGIN {
|
||||||
|
if (total > 0) {
|
||||||
|
# Formula: 100 * (Total - Idle) / Total
|
||||||
|
usage = 100 * (total - idle) / total
|
||||||
|
# MODIFIED: Changed format from "%.2f" back to "%.1f" for one decimal place.
|
||||||
|
printf "%.1f\n", usage
|
||||||
|
} else {
|
||||||
|
# MODIFIED: Changed output back to "0.0" to match the precision.
|
||||||
|
print "0.0"
|
||||||
|
}
|
||||||
|
}'
|
||||||
|
}
|
||||||
|
|
||||||
|
#
|
||||||
|
# Finds and returns the CPU temperature in degrees Celsius.
|
||||||
|
# Caches the sensor path for efficiency.
|
||||||
|
#
|
||||||
|
get_cpu_temp() {
|
||||||
|
# If the sensor path hasn't been found yet, search for it.
|
||||||
|
if [[ -z "$TEMP_SENSOR_PATH" ]]; then
|
||||||
|
for dir in /sys/class/hwmon/hwmon*; do
|
||||||
|
# Check if the 'name' file exists and read it.
|
||||||
|
if [[ -f "$dir/name" ]]; then
|
||||||
|
local name
|
||||||
|
name=$(<"$dir/name")
|
||||||
|
# Check for supported sensor types (matches Zig code).
|
||||||
|
if [[ "$name" == "coretemp" || "$name" == "k10temp" ]]; then
|
||||||
|
TEMP_SENSOR_PATH=$dir
|
||||||
|
TEMP_SENSOR_TYPE=$name
|
||||||
|
break # Found it, no need to keep searching.
|
||||||
|
fi
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
fi
|
||||||
|
|
||||||
|
# If after searching no sensor was found, return 0.
|
||||||
|
if [[ -z "$TEMP_SENSOR_PATH" ]]; then
|
||||||
|
echo 0
|
||||||
|
return
|
||||||
|
fi
|
||||||
|
|
||||||
|
# --- Get temp based on sensor type ---
|
||||||
|
if [[ "$TEMP_SENSOR_TYPE" == "coretemp" ]]; then
|
||||||
|
# For Intel 'coretemp', average all core temperatures.
|
||||||
|
# find gets all temp inputs, cat reads them, and awk calculates the average.
|
||||||
|
# The value is in millidegrees Celsius, so we divide by 1000.
|
||||||
|
find "$TEMP_SENSOR_PATH" -type f -name 'temp*_input' -print0 | xargs -0 cat | awk '
|
||||||
|
{ total += $1; count++ }
|
||||||
|
END {
|
||||||
|
if (count > 0) print int(total / count / 1000);
|
||||||
|
else print 0;
|
||||||
|
}'
|
||||||
|
|
||||||
|
elif [[ "$TEMP_SENSOR_TYPE" == "k10temp" ]]; then
|
||||||
|
# For AMD 'k10temp', find the 'Tctl' sensor, which is the control temperature.
|
||||||
|
local tctl_input=""
|
||||||
|
for label_file in "$TEMP_SENSOR_PATH"/temp*_label; do
|
||||||
|
if [[ -f "$label_file" ]] && [[ $(<"$label_file") == "Tctl" ]]; then
|
||||||
|
# The input file has the same name but with '_input' instead of '_label'.
|
||||||
|
tctl_input="${label_file%_label}_input"
|
||||||
|
break
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
if [[ -f "$tctl_input" ]]; then
|
||||||
|
# Read the temperature and convert from millidegrees to degrees.
|
||||||
|
echo "$(( $(<"$tctl_input") / 1000 ))"
|
||||||
|
else
|
||||||
|
echo 0 # Fallback
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo 0 # Should not happen if cache logic is correct.
|
||||||
|
fi
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
# --- Main Loop ---
|
||||||
|
# This loop runs indefinitely, gathering and printing stats.
|
||||||
|
while true; do
|
||||||
|
# Call the functions to gather all the data.
|
||||||
|
# 'read' is used to capture the two output values from get_memory_info.
|
||||||
|
read -r mem_gb mem_per <<< "$(get_memory_info)"
|
||||||
|
|
||||||
|
# Command substitution captures the single output from the other functions.
|
||||||
|
disk_per=$(get_disk_usage)
|
||||||
|
cpu_usage=$(get_cpu_usage)
|
||||||
|
cpu_temp=$(get_cpu_temp)
|
||||||
|
|
||||||
|
# Use printf to format the final JSON output string, matching the Zig program.
|
||||||
|
printf '{"mem":"%s", "cpu": "%s", "cputemp": "%s", "memper": "%s", "diskper": "%s"}\n' \
|
||||||
|
"$mem_gb" \
|
||||||
|
"$cpu_usage" \
|
||||||
|
"$cpu_temp" \
|
||||||
|
"$mem_per" \
|
||||||
|
"$disk_per"
|
||||||
|
|
||||||
|
# Wait for the specified duration before the next update.
|
||||||
|
sleep "$SLEEP_DURATION"
|
||||||
|
done
|
||||||
|
|
@ -28,13 +28,8 @@ NBox {
|
||||||
height: Style.marginTiny * scaling
|
height: Style.marginTiny * scaling
|
||||||
}
|
}
|
||||||
|
|
||||||
NSystemMonitor {
|
|
||||||
id: sysMon
|
|
||||||
intervalSeconds: 1
|
|
||||||
}
|
|
||||||
|
|
||||||
NCircleStat {
|
NCircleStat {
|
||||||
value: sysMon.cpuUsage || SysInfo.cpuUsage
|
value: SystemStats.cpuUsage
|
||||||
icon: "speed"
|
icon: "speed"
|
||||||
flat: true
|
flat: true
|
||||||
contentScale: 0.8
|
contentScale: 0.8
|
||||||
|
|
@ -42,7 +37,7 @@ NBox {
|
||||||
height: 68 * scaling
|
height: 68 * scaling
|
||||||
}
|
}
|
||||||
NCircleStat {
|
NCircleStat {
|
||||||
value: sysMon.cpuTemp || SysInfo.cpuTemp
|
value: SystemStats.cpuTemp
|
||||||
suffix: "°C"
|
suffix: "°C"
|
||||||
icon: "device_thermostat"
|
icon: "device_thermostat"
|
||||||
flat: true
|
flat: true
|
||||||
|
|
@ -51,7 +46,7 @@ NBox {
|
||||||
height: 68 * scaling
|
height: 68 * scaling
|
||||||
}
|
}
|
||||||
NCircleStat {
|
NCircleStat {
|
||||||
value: sysMon.memoryUsagePer || SysInfo.memoryUsagePer
|
value: SystemStats.memoryUsagePer
|
||||||
icon: "memory"
|
icon: "memory"
|
||||||
flat: true
|
flat: true
|
||||||
contentScale: 0.8
|
contentScale: 0.8
|
||||||
|
|
@ -59,7 +54,7 @@ NBox {
|
||||||
height: 68 * scaling
|
height: 68 * scaling
|
||||||
}
|
}
|
||||||
NCircleStat {
|
NCircleStat {
|
||||||
value: sysMon.diskUsage || SysInfo.diskUsage
|
value: SystemStats.diskUsage
|
||||||
icon: "data_usage"
|
icon: "data_usage"
|
||||||
flat: true
|
flat: true
|
||||||
contentScale: 0.8
|
contentScale: 0.8
|
||||||
|
|
|
||||||
|
|
@ -1,47 +0,0 @@
|
||||||
pragma Singleton
|
|
||||||
|
|
||||||
import QtQuick
|
|
||||||
import Qt.labs.folderlistmodel
|
|
||||||
import Quickshell
|
|
||||||
import Quickshell.Io
|
|
||||||
|
|
||||||
Singleton {
|
|
||||||
id: manager //TBC
|
|
||||||
|
|
||||||
property string updateInterval: "2s"
|
|
||||||
property string cpuUsageStr: ""
|
|
||||||
property string cpuTempStr: ""
|
|
||||||
property string memoryUsageStr: ""
|
|
||||||
property string memoryUsagePerStr: ""
|
|
||||||
property real cpuUsage: 0
|
|
||||||
property real memoryUsage: 0
|
|
||||||
property real cpuTemp: 0
|
|
||||||
property real diskUsage: 0
|
|
||||||
property real memoryUsagePer: 0
|
|
||||||
property string diskUsageStr: ""
|
|
||||||
|
|
||||||
Process {
|
|
||||||
id: zigstatProcess
|
|
||||||
running: true
|
|
||||||
command: [Quickshell.shellDir + "/Programs/zigstat", updateInterval]
|
|
||||||
stdout: SplitParser {
|
|
||||||
onRead: function (line) {
|
|
||||||
try {
|
|
||||||
const data = JSON.parse(line)
|
|
||||||
cpuUsage = +data.cpu
|
|
||||||
cpuTemp = +data.cputemp
|
|
||||||
memoryUsage = +data.mem
|
|
||||||
memoryUsagePer = +data.memper
|
|
||||||
diskUsage = +data.diskper
|
|
||||||
cpuUsageStr = data.cpu + "%"
|
|
||||||
cpuTempStr = data.cputemp + "°C"
|
|
||||||
memoryUsageStr = data.mem + "G"
|
|
||||||
memoryUsagePerStr = data.memper + "%"
|
|
||||||
diskUsageStr = data.diskper + "%"
|
|
||||||
} catch (e) {
|
|
||||||
console.error("Failed to parse zigstat output:", e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
37
Services/SystemStats.qml
Normal file
37
Services/SystemStats.qml
Normal file
|
|
@ -0,0 +1,37 @@
|
||||||
|
pragma Singleton
|
||||||
|
|
||||||
|
import QtQuick
|
||||||
|
import Qt.labs.folderlistmodel
|
||||||
|
import Quickshell
|
||||||
|
import Quickshell.Io
|
||||||
|
|
||||||
|
Singleton {
|
||||||
|
id: root
|
||||||
|
|
||||||
|
// Public values
|
||||||
|
property real cpuUsage: 0
|
||||||
|
property real cpuTemp: 0
|
||||||
|
property real memoryUsagePer: 0
|
||||||
|
property real diskUsage: 0
|
||||||
|
|
||||||
|
// Background process emitting one JSON line per sample
|
||||||
|
Process {
|
||||||
|
id: reader
|
||||||
|
running: true
|
||||||
|
command: ["sh", "-c", Quickshell.shellDir + "/Bin/system-stats.sh"]
|
||||||
|
stdout: SplitParser {
|
||||||
|
onRead: function (line) {
|
||||||
|
try {
|
||||||
|
const data = JSON.parse(line)
|
||||||
|
root.cpuUsage = data.cpu
|
||||||
|
root.cpuTemp = data.cputemp
|
||||||
|
root.memoryUsagePer = data.memper
|
||||||
|
root.diskUsage = data.diskper
|
||||||
|
} catch (e) {
|
||||||
|
|
||||||
|
// ignore malformed lines
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -76,7 +76,7 @@ Rectangle {
|
||||||
Text {
|
Text {
|
||||||
id: valueLabel
|
id: valueLabel
|
||||||
anchors.centerIn: parent
|
anchors.centerIn: parent
|
||||||
text: `${Math.round(root.value)}${root.suffix}`
|
text: `${root.value}${root.suffix}`
|
||||||
font.pointSize: Style.fontSizeMedium * scaling * contentScale
|
font.pointSize: Style.fontSizeMedium * scaling * contentScale
|
||||||
color: Colors.textPrimary
|
color: Colors.textPrimary
|
||||||
horizontalAlignment: Text.AlignHCenter
|
horizontalAlignment: Text.AlignHCenter
|
||||||
|
|
|
||||||
|
|
@ -1,54 +0,0 @@
|
||||||
import QtQuick
|
|
||||||
import Quickshell
|
|
||||||
import Quickshell.Io
|
|
||||||
|
|
||||||
// Lightweight system monitor using standard Linux interfaces.
|
|
||||||
// Provides cpu usage %, cpu temperature (°C), and memory usage %.
|
|
||||||
// No external helpers; uses /proc and /sys via a shell loop.
|
|
||||||
Item {
|
|
||||||
id: root
|
|
||||||
|
|
||||||
// Public values
|
|
||||||
property real cpuUsage: 0
|
|
||||||
property real cpuTemp: 0
|
|
||||||
property real memoryUsagePer: 0
|
|
||||||
property real diskUsage: 0
|
|
||||||
|
|
||||||
// Interval in seconds between updates
|
|
||||||
property int intervalSeconds: 1
|
|
||||||
|
|
||||||
// Background process emitting one JSON line per sample
|
|
||||||
Process {
|
|
||||||
id: reader
|
|
||||||
running: true
|
|
||||||
command: ["sh", "-c", // Outputs: {"cpu":<int>,"memper":<int>,"cputemp":<int>}
|
|
||||||
"interval=" + intervalSeconds + "; " + "while true; do " + // First /proc/stat snapshot
|
|
||||||
"read _ u1 n1 s1 id1 iw1 ir1 si1 st1 gs1 < /proc/stat; "
|
|
||||||
+ "t1=$((u1+n1+s1+id1+iw1+ir1+si1+st1)); i1=$((id1+iw1)); " + "sleep $interval; " + // Second /proc/stat snapshot
|
|
||||||
"read _ u2 n2 s2 id2 iw2 ir2 si2 st2 gs2 < /proc/stat; " + "t2=$((u2+n2+s2+id2+iw2+ir2+si2+st2)); i2=$((id2+iw2)); "
|
|
||||||
+ "dt=$((t2 - t1)); di=$((i2 - i1)); " + "cpu=$(( (100*(dt - di)) / (dt>0?dt:1) )); " + // Memory percent via /proc/meminfo (kB)
|
|
||||||
"mt=$(awk '/MemTotal/ {print $2}' /proc/meminfo); " + "ma=$(awk '/MemAvailable/ {print $2}' /proc/meminfo); "
|
|
||||||
+ "mm=$((mt - ma)); mp=$(( (100*mm) / (mt>0?mt:1) )); " + // Temperature: scan hwmon and thermal zones, choose max; convert m°C → °C
|
|
||||||
"ct=0; " + "for f in /sys/class/hwmon/hwmon*/temp*_input /sys/class/thermal/thermal_zone*/temp; do "
|
|
||||||
+ "[ -r \"$f\" ] || continue; v=$(cat \"$f\" 2>/dev/null); " + "[ -z \"$v\" ] && continue; "
|
|
||||||
+ "if [ \"$v\" -gt 1000 ] 2>/dev/null; then v=$((v/1000)); fi; " + "[ \"$v\" -gt \"$ct\" ] 2>/dev/null && ct=$v; "
|
|
||||||
+ "done; " + // Disk usage percent for root filesystem
|
|
||||||
"dp=$(df -P / 2>/dev/null | awk 'NR==2{gsub(/%/,\"\",$5); print $5}'); " + "[ -z \"$dp\" ] && dp=0; " + // Emit JSON line
|
|
||||||
"echo \"{\\\"cpu\\\":$cpu,\\\"memper\\\":$mp,\\\"cputemp\\\":$ct,\\\"diskper\\\":$dp}\"; " + "done"]
|
|
||||||
|
|
||||||
stdout: SplitParser {
|
|
||||||
onRead: function (line) {
|
|
||||||
try {
|
|
||||||
const data = JSON.parse(line)
|
|
||||||
root.cpuUsage = +data.cpu
|
|
||||||
root.cpuTemp = +data.cputemp
|
|
||||||
root.memoryUsagePer = +data.memper
|
|
||||||
root.diskUsage = +data.diskper
|
|
||||||
} catch (e) {
|
|
||||||
|
|
||||||
// ignore malformed lines
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue