sane-sysload: implement CPU measurement
This commit is contained in:
@@ -16,15 +16,19 @@ formatstr is a Python format string.
|
|||||||
variables available for formatting:
|
variables available for formatting:
|
||||||
- {bat_icon}
|
- {bat_icon}
|
||||||
- {bat_time}
|
- {bat_time}
|
||||||
|
- {cpu_icon}
|
||||||
|
- {cpu_pct}
|
||||||
- {mem_icon}
|
- {mem_icon}
|
||||||
- {mem_pct}
|
- {mem_pct}
|
||||||
and some presets, encapsulating the above:
|
and some presets, encapsulating the above:
|
||||||
- {bat}
|
- {bat}
|
||||||
|
- {cpu}
|
||||||
- {mem}
|
- {mem}
|
||||||
"""
|
"""
|
||||||
|
|
||||||
import argparse
|
import argparse
|
||||||
import logging
|
import logging
|
||||||
|
import time
|
||||||
|
|
||||||
from dataclasses import dataclass
|
from dataclasses import dataclass
|
||||||
from enum import Enum
|
from enum import Enum
|
||||||
@@ -34,6 +38,7 @@ logger = logging.getLogger(__name__)
|
|||||||
# these icons may only render in nerdfonts
|
# these icons may only render in nerdfonts
|
||||||
ICON_BAT_CHG = ["", "", "", ""]
|
ICON_BAT_CHG = ["", "", "", ""]
|
||||||
ICON_BAT_DIS = ["", "", "", ""]
|
ICON_BAT_DIS = ["", "", "", ""]
|
||||||
|
ICON_CPU=""
|
||||||
ICON_MEM="☵"
|
ICON_MEM="☵"
|
||||||
SUFFIX_ICON = " " # thin space
|
SUFFIX_ICON = " " # thin space
|
||||||
SUFFIX_PERCENT = "%"
|
SUFFIX_PERCENT = "%"
|
||||||
@@ -82,6 +87,9 @@ class Formatter:
|
|||||||
def render_charge_icon(self, direction: ChargeDirection, percentage: float) -> str:
|
def render_charge_icon(self, direction: ChargeDirection, percentage: float) -> str:
|
||||||
return f"{self._choose_icon(direction, percentage)}{self.suffix_icon}"
|
return f"{self._choose_icon(direction, percentage)}{self.suffix_icon}"
|
||||||
|
|
||||||
|
def render_cpu_icon(self) -> str:
|
||||||
|
return f"{ICON_CPU}{self.suffix_icon}"
|
||||||
|
|
||||||
def render_mem_icon(self) -> str:
|
def render_mem_icon(self) -> str:
|
||||||
return f"{ICON_MEM}{self.suffix_icon}"
|
return f"{ICON_MEM}{self.suffix_icon}"
|
||||||
|
|
||||||
@@ -147,6 +155,83 @@ class MemInfo:
|
|||||||
logger.debug(f"/proc/meminfo: {entry}={v}")
|
logger.debug(f"/proc/meminfo: {entry}={v}")
|
||||||
return v
|
return v
|
||||||
|
|
||||||
|
class ProcStat:
|
||||||
|
"""
|
||||||
|
reads vaues from /proc/stat, mostly CPU-related.
|
||||||
|
"""
|
||||||
|
# /proc/stat format is documented here: <https://www.linuxhowtos.org/System/procstat.htm>
|
||||||
|
# these are AGGREGATES SINCE SYSTEM BOOT
|
||||||
|
# to measure current CPU usage, need to take multiple samples
|
||||||
|
# cpu <user> <system> <nice> <idle> <iowait> <irg> <softirq> 0 0 0
|
||||||
|
# (what are the last three fields?)
|
||||||
|
# where:
|
||||||
|
# measurements are in units of jiffies or USER_HZ
|
||||||
|
# user: normal processes executing in user mode
|
||||||
|
# nice: niced processes executing in user mode
|
||||||
|
# system: processes executing in kernel mode
|
||||||
|
# idle: twiddling thumbs
|
||||||
|
# iowait: waiting for I/O to complete
|
||||||
|
# irq: servicing interrupts
|
||||||
|
# softirq: servicing softirqs
|
||||||
|
def __init__(self, entries=None):
|
||||||
|
if entries is not None:
|
||||||
|
self.entries = entries
|
||||||
|
return
|
||||||
|
|
||||||
|
# else, read from procfs...
|
||||||
|
try:
|
||||||
|
lines = open("/proc/stat").readlines()
|
||||||
|
except Exception as e:
|
||||||
|
logger.info(f"failed to open /proc/stat: {e}")
|
||||||
|
lines = []
|
||||||
|
|
||||||
|
self.entries = {}
|
||||||
|
for l in lines:
|
||||||
|
pieces = l.strip().split(" ")
|
||||||
|
name, values = pieces[0], [p for p in pieces[1:] if p]
|
||||||
|
if name:
|
||||||
|
self.entries[name] = [int(v) for v in values]
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def sample(seconds: float = 1.0):
|
||||||
|
sample1 = ProcStat()
|
||||||
|
time.sleep(seconds)
|
||||||
|
sample2 = ProcStat()
|
||||||
|
return sample2 - sample1
|
||||||
|
|
||||||
|
def __sub__(self, other: 'ProcStat') -> 'ProcStat':
|
||||||
|
entries = {}
|
||||||
|
for k in self.entries:
|
||||||
|
entries[k] = [i - j for i, j in zip(self.entries[k], other.entries[k])]
|
||||||
|
return ProcStat(entries)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cpu_user(self) -> int:
|
||||||
|
return self.entries["cpu"][0]
|
||||||
|
@property
|
||||||
|
def cpu_system(self) -> int:
|
||||||
|
return self.entries["cpu"][1]
|
||||||
|
@property
|
||||||
|
def cpu_nice(self) -> int:
|
||||||
|
return self.entries["cpu"][2]
|
||||||
|
@property
|
||||||
|
def cpu_idle(self) -> int:
|
||||||
|
return self.entries["cpu"][3]
|
||||||
|
@property
|
||||||
|
def cpu_iowait(self) -> int:
|
||||||
|
return self.entries["cpu"][4]
|
||||||
|
@property
|
||||||
|
def cpu_irq(self) -> int:
|
||||||
|
return self.entries["cpu"][5]
|
||||||
|
@property
|
||||||
|
def cpu_softirq(self) -> int:
|
||||||
|
return self.entries["cpu"][6]
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cpu_total(self) -> int:
|
||||||
|
# TODO: not sure if i'm supposed to include irq stuff here?
|
||||||
|
return self.cpu_user + self.cpu_system + self.cpu_nice + self.cpu_idle + self.cpu_iowait + self.cpu_irq + self.cpu_softirq
|
||||||
|
|
||||||
class PowerSupply:
|
class PowerSupply:
|
||||||
"""
|
"""
|
||||||
reads values from /sys/class/power_supply/$dev/ API
|
reads values from /sys/class/power_supply/$dev/ API
|
||||||
@@ -273,16 +358,39 @@ def try_all_batteries() -> BatteryInfo | None:
|
|||||||
@dataclass
|
@dataclass
|
||||||
class AllInfo:
|
class AllInfo:
|
||||||
_fmt: Formatter
|
_fmt: Formatter
|
||||||
_mem: MemInfo | None
|
__bat: BatteryInfo | None = None
|
||||||
_bat: BatteryInfo | None
|
__cpu: ProcStat | None = None
|
||||||
|
__mem: MemInfo | None = None
|
||||||
|
|
||||||
|
# lazy-loading
|
||||||
|
@property
|
||||||
|
def _bat(self):
|
||||||
|
if self.__bat is None:
|
||||||
|
self.__bat = try_all_batteries()
|
||||||
|
return self.__bat
|
||||||
|
@property
|
||||||
|
def _cpu(self):
|
||||||
|
if self.__cpu is None:
|
||||||
|
self.__cpu = ProcStat.sample()
|
||||||
|
return self.__cpu
|
||||||
|
@property
|
||||||
|
def _mem(self):
|
||||||
|
if self.__mem is None:
|
||||||
|
self.__mem = MemInfo()
|
||||||
|
return self.__mem
|
||||||
|
|
||||||
|
# user-facing format shorthands
|
||||||
@property
|
@property
|
||||||
def bat(self) -> str:
|
def bat(self) -> str:
|
||||||
return f"{self.bat_icon}{self.bat_time}"
|
return f"{self.bat_icon}{self.bat_time}"
|
||||||
@property
|
@property
|
||||||
|
def cpu(self) -> str:
|
||||||
|
return f"{self.cpu_icon}{self.cpu_pct}"
|
||||||
|
@property
|
||||||
def mem(self) -> str:
|
def mem(self) -> str:
|
||||||
return f"{self.mem_icon}{self.mem_pct}"
|
return f"{self.mem_icon}{self.mem_pct}"
|
||||||
|
|
||||||
|
# manual/low-level fields
|
||||||
@property
|
@property
|
||||||
def mem_icon(self) -> str:
|
def mem_icon(self) -> str:
|
||||||
if self._mem is None: return ""
|
if self._mem is None: return ""
|
||||||
@@ -324,6 +432,23 @@ class AllInfo:
|
|||||||
else:
|
else:
|
||||||
return self._fmt.render_percent(self._bat.percent_charged)
|
return self._fmt.render_percent(self._bat.percent_charged)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cpu_icon(self) -> str:
|
||||||
|
if self._cpu is None: return ""
|
||||||
|
return self._fmt.render_cpu_icon()
|
||||||
|
|
||||||
|
@property
|
||||||
|
def cpu_pct(self) -> str:
|
||||||
|
if self._cpu is None:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
idle = self._cpu.cpu_idle + self._cpu.cpu_iowait
|
||||||
|
total = self._cpu.cpu_total
|
||||||
|
|
||||||
|
cpu_use_pct = int((total - idle) / total * 100)
|
||||||
|
return self._fmt.render_percent(cpu_use_pct)
|
||||||
|
|
||||||
|
|
||||||
class LazyFormatter:
|
class LazyFormatter:
|
||||||
def __init__(self, obj: object, attr: str):
|
def __init__(self, obj: object, attr: str):
|
||||||
self.obj = obj
|
self.obj = obj
|
||||||
@@ -357,15 +482,14 @@ def main() -> None:
|
|||||||
f.suffix_hr = args.hour_suffix
|
f.suffix_hr = args.hour_suffix
|
||||||
f.suffix_min = args.minute_suffix
|
f.suffix_min = args.minute_suffix
|
||||||
|
|
||||||
info = AllInfo(
|
info = AllInfo(f)
|
||||||
f,
|
|
||||||
MemInfo(),
|
|
||||||
try_all_batteries(),
|
|
||||||
)
|
|
||||||
print(args.formatstr.format(
|
print(args.formatstr.format(
|
||||||
bat=LazyFormatter(info, "bat"),
|
bat=LazyFormatter(info, "bat"),
|
||||||
bat_icon=LazyFormatter(info, "bat_icon"),
|
bat_icon=LazyFormatter(info, "bat_icon"),
|
||||||
bat_time=LazyFormatter(info, "bat_time"),
|
bat_time=LazyFormatter(info, "bat_time"),
|
||||||
|
cpu=LazyFormatter(info, "cpu"),
|
||||||
|
cpu_icon=LazyFormatter(info, "cpu_icon"),
|
||||||
|
cpu_pct=LazyFormatter(info, "cpu_pct"),
|
||||||
mem=LazyFormatter(info, "mem"),
|
mem=LazyFormatter(info, "mem"),
|
||||||
mem_icon=LazyFormatter(info, "mem_icon"),
|
mem_icon=LazyFormatter(info, "mem_icon"),
|
||||||
mem_pct=LazyFormatter(info, "mem_pct"),
|
mem_pct=LazyFormatter(info, "mem_pct"),
|
||||||
|
Reference in New Issue
Block a user