DEV Community

milton rojas
milton rojas

Posted on

I built a Python hardware monitor that shows real battery degradation, CPU temps per core, and SSD wear -- here's how

I was troubleshooting a performance issue on my laptop when I noticed something that stopped me cold.

My CPU was sitting at 20% load. Nothing crazy. Just a browser, a terminal, and a couple of background tasks. But the temperature readout I was watching — thrown together in an afternoon — showed 83°C on the hottest core.

Then I checked the battery. 74.4% health after 8 years. The designed capacity was 45,000 mWh. The battery now maxes out at 33,480 mWh. Windows just says "100% charged" like nothing is wrong.

Task Manager told me none of this. Neither did any monitoring software I trusted — they all wanted to install tray icons, telemetry services, or drivers I didn't ask for.

So I built my own.


The Problem with Windows Monitoring

Windows exposes a lot of hardware data. The problem is it's scattered across three different APIs, one of them requires a kernel driver you probably don't have, and none of it is surfaced in the default UI.

  • Task Manager: CPU load %, memory GB. That's about it.
  • Resource Monitor: Slightly more, still no temps.
  • Performance Monitor: Overwhelming. Not useful for at-a-glance health.
  • Third-party tools: HWiNFO64 is great but it's a full application. GPU-Z, CPU-Z — each does one thing. Nothing unified and scriptable.

The goal: one Python script, no installer, reads what Windows actually knows about your hardware, and prints it cleanly.


Part 1 — GPU Stats via nvidia-smi

The easiest piece. NVIDIA ships nvidia-smi with every driver install. It has a machine-readable --query-gpu mode that outputs CSV:

import subprocess

r = subprocess.run(
    [
        "nvidia-smi",
        "--query-gpu=temperature.gpu,utilization.gpu,memory.used,memory.free",
        "--format=csv,noheader"
    ],
    capture_output=True,
    text=True
)
temp, util, used, free = r.stdout.strip().split(", ")
total_mib = int(used.split()[0]) + int(free.split()[0])
print(f"GPU: {temp}C, {util} load, {used}/{total_mib} MiB VRAM")
Enter fullscreen mode Exit fullscreen mode

The --format=csv,noheader flag gives you clean values with units attached (62 C, 45 %, 3273 MiB). Split on ", ", strip the units when you need integers, done.

For the VRAM bar graph I used a simple ratio:

def bar(used, total, width=30):
    filled = int((used / total) * width)
    return "X" * filled + "." * (width - filled)
Enter fullscreen mode Exit fullscreen mode

Part 2 — Battery Wear via WMI (the interesting one)

Most people don't know this WMI class exists. BatteryFullChargedCapacity and BatteryStaticData live in the root/wmi namespace — not the default root/cimv2 that most WMI examples use.

import wmi

w = wmi.WMI(namespace="root/wmi")
full = list(w.BatteryFullChargedCapacity())[0].FullChargedCapacity
design = list(w.BatteryStaticData())[0].DesignedCapacity
health_pct = round(full / design * 100, 1)
print(f"Battery health: {health_pct}%  ({full} / {design} mWh)")
Enter fullscreen mode Exit fullscreen mode

DesignedCapacity is what the manufacturer rated the battery at when it left the factory. FullChargedCapacity is what it can actually hold right now. The ratio is your wear percentage.

This is the same data that powercfg /batteryreport uses internally — you are just reading it directly instead of parsing an HTML report.

My battery: 33,480 / 45,000 = 74.4%. After 8 years that is actually respectable. But I would not have known without digging into this WMI namespace.


Part 3 — SSD SMART via PowerShell StorageReliabilityCounter

Windows 8+ ships with Get-StorageReliabilityCounter, a PowerShell cmdlet that wraps SMART data without needing smartmontools or admin elevation for basic stats:

import subprocess, json

ps = (
    "Get-PhysicalDisk | "
    "Get-StorageReliabilityCounter | "
    "Select-Object PowerOnHours,Temperature,Wear | "
    "ConvertTo-Json"
)
r = subprocess.run(
    ["powershell", "-Command", ps],
    capture_output=True,
    text=True
)
data = json.loads(r.stdout)
print(
    f"SSD: {data['PowerOnHours']} hours on, "
    f"{data['Temperature']}C, wear: {data['Wear']}%"
)
Enter fullscreen mode Exit fullscreen mode

PowerOnHours is cumulative lifetime hours. Wear is the SMART wear indicator (0 = new, 100 = end of life). Temperature is the drive's current thermal reading.

My T-FORCE VULCAN Z: 9,101 hours on, 33C, 0% wear. Still healthy.

One caveat: if you have multiple drives, ConvertTo-Json returns an array. Wrap data in a list check (if isinstance(data, list)) and iterate.


Part 4 — CPU Temps via LibreHardwareMonitor WMI

This one is the trickiest. Windows intentionally blocks direct MSR (Model Specific Register) reads from userspace — that is where the actual thermal sensor data lives. To read CPU core temps without a kernel driver, you need something that already has ring-0 access to expose the data through a friendlier API.

LibreHardwareMonitor solves this. When it is running, it exposes all sensor data through its own WMI namespace: root/LibreHardwareMonitor.

import wmi

lhm = wmi.WMI(namespace="root/LibreHardwareMonitor")
sensors = lhm.Sensor()

cpu_temps = [
    s for s in sensors
    if s.SensorType == "Temperature" and "CPU Core" in s.Name
]
for sensor in sorted(cpu_temps, key=lambda s: s.Name):
    temp = float(sensor.Value)
    flag = "  [HOT]" if temp >= 80 else ""
    print(f"  {sensor.Name:<20} {temp}C{flag}")
Enter fullscreen mode Exit fullscreen mode

The dependency chain: run LibreHardwareMonitor in the background, then query its WMI namespace from Python. The tool handles this check at startup and warns if LHM is not running.


What the Output Looks Like

============================================================
  DESKTOP-V0J6K6M -- Hardware Monitor   2026-05-27 19:00
============================================================

[CPU]  Intel Core i7-8750H
  Load: 20%   Clock: 2208 MHz
  CPU Core #1    83.0C  [HOT]
  CPU Core #2    70.0C
  CPU Core #3    72.0C
  CPU Core #4    68.0C
  CPU Core #5    71.0C
  CPU Core #6    69.0C
  CPU Package    83.0C   Power: 25.2W

[GPU]  NVIDIA GeForce GTX 1050 Ti
  Temp:  62C
  VRAM:  [############################..] 3273/4005 MiB

[Storage]  T-FORCE VULCAN Z 512GB
  Temp: 33C   On: 9101 hrs   Wear: 0%

[Battery]  L17M3PG1
  Charge: 100%
  Health: [######################........] 74.4%

============================================================
Enter fullscreen mode Exit fullscreen mode

The [HOT] flag triggers at 80C for cores, 85C for package. Thresholds are constants you can tune.


Building It Out

The script grew from "print some temps" into something with a few more features I kept reaching for:

  • --watch mode that refreshes every N seconds (clears the terminal each cycle)
  • --json flag that dumps all readings as structured JSON for piping into other tools
  • Graceful degradation: if nvidia-smi is not found, the GPU section is skipped cleanly instead of crashing
  • Battery section only appears on laptops (checks if BatteryStaticData returns any results)

The watch mode is where it became genuinely useful. Seeing the temps respond in real time when you open a heavy application tells you a lot more than a one-shot reading.


If You Don't Want to Build It Yourself

I packaged the full version — --watch mode, --json export, the LHM detection logic, and thresholds already tuned — and put it on Gumroad for $5: AI Hardware Monitor for Windows

It is a single .py file. No installer, no tray icon, no telemetry. Dependencies are wmi (pip) and LibreHardwareMonitor for CPU temps. Everything else is stdlib.

If you just want the battery and SSD pieces, the snippets above are complete as-is. The WMI namespaces are the non-obvious part — once you have those, the rest is straightforward Python.


Summary

Data API Needs
GPU temp, VRAM, utilization nvidia-smi subprocess NVIDIA GPU + driver
Battery health % root/wmi via BatteryFullChargedCapacity Laptop, Python wmi package
SSD hours, temp, wear PowerShell Get-StorageReliabilityCounter Windows 8+, no elevation needed
CPU per-core temps root/LibreHardwareMonitor WMI LibreHardwareMonitor running

The battery one is the sleeper. Most people have no idea root/wmi exposes this. Check yours — you might be surprised what your laptop is hiding from you.

Top comments (0)