Profiling Laptop Power Dynamics: A Pure Linux sysfs Approach

Modern laptops present a complex power delivery layout. When running on battery, energy measurement is straightforward. However, when connected to AC power, internal battery gauges obscure system-level draw.

Over a nearly two-hour workload session, I profiled my laptop’s power draw using pure Linux Kernel interfaces—without external hardware tools like USB-C power meters—to separate battery charging overhead from active system consumption.


1. System Telemetry Architecture

The Linux Kernel exposes power telemetry through two primary interfaces under /sys:

  • sysfs ACPI Power Supply Subsystem (/sys/class/power_supply/BAT*/)
  • voltage_now & current_now: Instantaneous terminal parameters (\(\mu\text{V}\), \(\mu\text{A}\)).
  • status: Power state transition matrix (Discharging, Charging, Full).
  • power_now: Direct power draw (\(\mu\text{W}\)) provided by the Smart Battery System (SBS) Embedded Controller (EC).

  • Running Average Power Limit / RAPL (/sys/class/powercap/intel-rapl/)
  • energy_uj: Cumulative energy counter (\(\mu\text{J}\)) consumed by the CPU package.
flowchart TB
    adapter["<b>AC Adapter</b><br/>DC Input"]

    subgraph power_paths ["POWER DISTRIBUTION"]
        direction LR
        components["<b>System Components</b><br/>CPU, GPU, RAM"]
        charger["<b>Battery Controller</b><br/>Charger IC"]
    end

    integration["<b>Energy Integration</b><br/>sysfs BAT0 / Internal RAPL"]

    adapter --> components
    adapter --> charger

    components -- "Internal RAPL + Base Estimate" --> integration
    charger -- "sysfs BAT0" --> integration

2. Mathematical Modeling & Integration

Power varies dynamically across system states, requiring numerical integration over time intervals \(\Delta t\):

\[E_{\text{total}} = \sum_{i=1}^{N} \frac{P(t_i) \cdot \Delta t_i}{3600} \quad [\text{Wh}]\]

State Machine Logic

  1. Discharging Phase (\(P_{\text{sys}} = P_{\text{bat}}\)): The system power is directly measured from the battery terminal discharge rate:
\[P_{\text{bat}} = U_{\text{now}} \cdot I_{\text{now}}\]
  1. Charging Phase (\(P_{\text{bat}} = P_{\text{charge}}\)): The battery gauge only sees current entering the chemical cells. System component power \(P_{\text{sys}}\) is modeled by differentiating the RAPL energy counter over time plus baseline platform power \(P_{\text{base}}\) (~5 W for display, NVMe, and memory):
\[P_{\text{sys}} \approx \frac{\Delta E_{\text{RAPL}}}{\Delta t} + P_{\text{base}}\]

3. Data Collection Pipeline

A custom Bash daemon was executed to sample telemetry at \(1\text{ Hz}\) into a structured time-series CSV:

#!/usr/bin/env bash
# High-frequency power state sampler
BAT="/sys/class/power_supply/BAT0"
RAPL="/sys/class/powercap/intel-rapl/intel-rapl:0/energy_uj"

echo "timestamp,status,capacity_pct,voltage_v,current_a,battery_power_w,cpu_energy_uj"

while true; do
    TS=$(date -u +"%Y-%m-%dT%H:%M:%SZ")
    STATUS=$(cat "$BAT/status")
    CAP=$(cat "$BAT/capacity")
    V=$(awk "BEGIN {print $(cat $BAT/voltage_now) / 1e6}")
    I=$(awk "BEGIN {print $(cat $BAT/current_now) / 1e6}")
    P=$(awk "BEGIN {print $V * $I}")
    CPU=$(cat "$RAPL" 2>/dev/null || echo 0)
    
    echo "$TS,$STATUS,$CAP,$V,$I,$P,$CPU"
    sleep 1
done


4. Analytical Results

Processing the dataset via Python (pandas / numpy) over a 1.81-hour test run yielded the following energy distribution:

Metric / Phase Duration Energy Transferred Avg Power Draw
Discharging Mode 0.81 h (48.6 min) \(12.54\text{ Wh}\) ~15.5 W
Active Charging Mode 1.00 h (60.0 min) \(24.60\text{ Wh}\) (To Battery) ~24.6 W
System Active Overhead 1.81 h (108.6 min) \(27.99\text{ Wh}\) (Components) ~15.4 W
Total Internal Consumption 1.81 h 52.59 Wh ~29.0 W

5. Thermal & Efficiency Losses (AC-to-DC Conversion)

The internal telemetry accounts for energy delivered after onboard regulation. To estimate total power pulled from the AC mains grid, we must factor in hardware conversion efficiencies:

\[\text{Efficiency Coefficients:} \quad \eta_{\text{adapter}} \approx 0.89, \quad \eta_{\text{charger\_ic}} \approx 0.92\] \[E_{\text{wall}} = \frac{E_{\text{sys}}}{\eta_{\text{adapter}}} + \frac{E_{\text{charge}}}{\eta_{\text{adapter}} \cdot \eta_{\text{charger\_ic}}}\]

Applying these coefficients to our recorded 52.59 Wh internal load yields an estimated ~59.8 Wh (~0.06 kWh) total draw at the mains wall outlet.


This site uses Just the Docs, a documentation theme for Jekyll.