sane-sysload: implement CPU measurement
This commit is contained in:
parent
91c2b04ab4
commit
b3c5e53156
|
@ -16,15 +16,19 @@ formatstr is a Python format string.
|
|||
variables available for formatting:
|
||||
- {bat_icon}
|
||||
- {bat_time}
|
||||
- {cpu_icon}
|
||||
- {cpu_pct}
|
||||
- {mem_icon}
|
||||
- {mem_pct}
|
||||
and some presets, encapsulating the above:
|
||||
- {bat}
|
||||
- {cpu}
|
||||
- {mem}
|
||||
"""
|
||||
|
||||
import argparse
|
||||
import logging
|
||||
import time
|
||||
|
||||
from dataclasses import dataclass
|
||||
from enum import Enum
|
||||
|
@ -34,6 +38,7 @@ logger = logging.getLogger(__name__)
|
|||
# these icons may only render in nerdfonts
|
||||
ICON_BAT_CHG = ["", "", "", ""]
|
||||
ICON_BAT_DIS = ["", "", "", ""]
|
||||
ICON_CPU=""
|
||||
ICON_MEM="☵"
|
||||
SUFFIX_ICON = " " # thin space
|
||||
SUFFIX_PERCENT = "%"
|
||||
|
@ -82,6 +87,9 @@ class Formatter:
|
|||
def render_charge_icon(self, direction: ChargeDirection, percentage: float) -> str:
|
||||
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:
|
||||
return f"{ICON_MEM}{self.suffix_icon}"
|
||||
|
||||
|
@ -147,6 +155,83 @@ class MemInfo:
|
|||
logger.debug(f"/proc/meminfo: {entry}={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:
|
||||
"""
|
||||
reads values from /sys/class/power_supply/$dev/ API
|
||||
|
@ -273,16 +358,39 @@ def try_all_batteries() -> BatteryInfo | None:
|
|||
@dataclass
|
||||
class AllInfo:
|
||||
_fmt: Formatter
|
||||
_mem: MemInfo | None
|
||||
_bat: BatteryInfo | None
|
||||
__bat: BatteryInfo | None = 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
|
||||
def bat(self) -> str:
|
||||
return f"{self.bat_icon}{self.bat_time}"
|
||||
@property
|
||||
def cpu(self) -> str:
|
||||
return f"{self.cpu_icon}{self.cpu_pct}"
|
||||
@property
|
||||
def mem(self) -> str:
|
||||
return f"{self.mem_icon}{self.mem_pct}"
|
||||
|
||||
# manual/low-level fields
|
||||
@property
|
||||
def mem_icon(self) -> str:
|
||||
if self._mem is None: return ""
|
||||
|
@ -324,6 +432,23 @@ class AllInfo:
|
|||
else:
|
||||
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:
|
||||
def __init__(self, obj: object, attr: str):
|
||||
self.obj = obj
|
||||
|
@ -357,15 +482,14 @@ def main() -> None:
|
|||
f.suffix_hr = args.hour_suffix
|
||||
f.suffix_min = args.minute_suffix
|
||||
|
||||
info = AllInfo(
|
||||
f,
|
||||
MemInfo(),
|
||||
try_all_batteries(),
|
||||
)
|
||||
info = AllInfo(f)
|
||||
print(args.formatstr.format(
|
||||
bat=LazyFormatter(info, "bat"),
|
||||
bat_icon=LazyFormatter(info, "bat_icon"),
|
||||
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_icon=LazyFormatter(info, "mem_icon"),
|
||||
mem_pct=LazyFormatter(info, "mem_pct"),
|
||||
|
|
Loading…
Reference in New Issue
Block a user