The Problem
Users want to know how much memory is in use — is the browser eating 8GB again? A RAM widget must distinguish between "used" (actively allocated) and "available" (free + cached + buffer memory that can be reclaimed). Showing just "used" vs "total" is misleading because Linux aggressively uses free memory for caching.
The Naive Approach
Read free -h via shell command and parse the output. This spawns a subprocess every time, which is slow and wasteful. Alternatively, parse /proc/meminfo manually with string splitting.
Timer {
interval: 5000
onTriggered: {
// "free -h | grep Mem" and parse the line
// Or read /proc/meminfo line by line
}
}Think of RAM like a workbench. Used memory is the project you're actively working on. Cached memory is spare parts stacked in the corner — they take up bench space but can be swept away instantly if you need room. "Available" is the space you'd have after clearing the clutter.
The Idea
Read /proc/meminfo and extract MemTotal, MemAvailable, and MemFree. Compute used as MemTotal - MemAvailable (not MemTotal - MemFree). This gives the true usable memory that can't be reclaimed.
Let's Build It
A RAM usage widget with a progress bar:
import Quickshell
import QtQuick
Row {
spacing: 8
property real memTotal: 0
property real memAvailable: 0
property real usedPercent: 0
function updateMem() {
var data = readFile("/proc/meminfo")
var total = parseMemValue(data, "MemTotal:")
var available = parseMemValue(data, "MemAvailable:")
if (total > 0) {
memTotal = total
memAvailable = available
usedPercent = Math.round((total - available) / total * 100)
}
}
function parseMemValue(text, key) {
var idx = text.indexOf(key)
if (idx < 0) return 0
var line = text.substring(idx).split("\n")[0]
return parseInt(line.replace(/[^0-9]/g, ""))
}
Timer {
interval: 5000
running: true
repeat: true
triggeredOnStart: true
onTriggered: updateMem()
}
Text {
text: ""
font.pixelSize: 14
color: usedPercent > 85 ? "#f38ba8" : "#89b4fa"
}
Rectangle {
width: 60; height: 8
radius: 4
color: "#313244"
anchors.verticalCenter: parent.verticalCenter
Rectangle {
width: parent.width * usedPercent / 100
height: parent.height
radius: 4
color: usedPercent > 85 ? "#f38ba8" : usedPercent > 70 ? "#fab387" : "#a6e3a1"
Behavior on width { SmoothedAnimation { duration: 200 } }
}
}
Text {
text: Math.round((memTotal - memAvailable) / 1024) + "M / " + Math.round(memTotal / 1024) + "M"
font.pixelSize: 11
color: "#a6adc8"
}
}Let's Improve It
Add a swap indicator and a memory-hungry process popup:
// Extend with swap info
property real swapTotal: 0
property real swapUsed: 0
property real swapPercent: 0
function updateMem() {
var data = readFile("/proc/meminfo")
memTotal = parseMemValue(data, "MemTotal:")
memAvailable = parseMemValue(data, "MemAvailable:")
swapTotal = parseMemValue(data, "SwapTotal:")
var swapFree = parseMemValue(data, "SwapFree:")
swapUsed = swapTotal - swapFree
swapPercent = swapTotal > 0 ? Math.round(swapUsed / swapTotal * 100) : 0
usedPercent = Math.round((memTotal - memAvailable) / memTotal * 100)
}Using MemFree instead of MemAvailable. MemFree is just completely unused pages. MemAvailable includes reclaimable cache and buffers. The former makes memory look nearly full when it's actually healthy.
Formatting units inconsistently. /proc/meminfo reports in kilobytes. Divide by 1024 for MB, or 1048576 for GB. Always store values in KB and convert only for display.
Updating too fast. RAM usage changes slowly. Every 5 seconds is more than enough. Updating every second wastes CPU for no visible benefit.
Under the Hood
/proc/meminfo is generated by the kernel's memory manager. MemAvailable was added in Linux 3.14 and estimates reclaimable memory by walking the LRU lists. Reading the file is cheap — the kernel serializes the numbers into a buffer in microseconds. Quickshell's readFile() slurps the whole file into a string.
The SmoothedAnimation on the progress bar width ensures smooth visual updates even though the data updates only every 5 seconds. The Behavior on width interpolates between the old and new values over 200ms, preventing jarring jumps.
Exercises
- Use MemAvailable, not MemFree, for accurate usable memory
- Store values in KB, convert for display only
- 5-second update interval is sufficient for RAM
- SmoothedAnimation makes progress bars look fluid
- Kernel provides MemAvailable since Linux 3.14