sane-sysinfo: add a way to render memory use
This commit is contained in:
parent
f4d806c0c2
commit
0f6c9f3cde
|
@ -24,6 +24,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_MEM="☵"
|
||||||
SUFFIX_ICON = " " # thin space
|
SUFFIX_ICON = " " # thin space
|
||||||
SUFFIX_PERCENT = "%"
|
SUFFIX_PERCENT = "%"
|
||||||
# SUFFIX_ICON=" "
|
# SUFFIX_ICON=" "
|
||||||
|
@ -68,9 +69,12 @@ class Formatter:
|
||||||
suffix_hr: str = SUFFIX_HR
|
suffix_hr: str = SUFFIX_HR
|
||||||
suffix_min: str = SUFFIX_MIN
|
suffix_min: str = SUFFIX_MIN
|
||||||
|
|
||||||
def render_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_mem_icon(self) -> str:
|
||||||
|
return f"{ICON_MEM}{self.suffix_icon}"
|
||||||
|
|
||||||
def render_hours_minutes(self, minutes: int) -> str:
|
def render_hours_minutes(self, minutes: int) -> str:
|
||||||
hr = minutes // 60
|
hr = minutes // 60
|
||||||
min = minutes % 60
|
min = minutes % 60
|
||||||
|
@ -84,7 +88,7 @@ class Formatter:
|
||||||
level = max(0, min(3, level))
|
level = max(0, min(3, level))
|
||||||
level = int(round(level))
|
level = int(round(level))
|
||||||
|
|
||||||
logger.debug(f"render_icon: direction={direction} level={level}")
|
logger.debug(f"render_charge_icon: direction={direction} level={level}")
|
||||||
|
|
||||||
if direction == ChargeDirection.Charging:
|
if direction == ChargeDirection.Charging:
|
||||||
return ICON_BAT_CHG[level]
|
return ICON_BAT_CHG[level]
|
||||||
|
@ -93,6 +97,46 @@ class Formatter:
|
||||||
|
|
||||||
raise RuntimeError(f"invalid ChargeDirection {direction}")
|
raise RuntimeError(f"invalid ChargeDirection {direction}")
|
||||||
|
|
||||||
|
class MemInfo:
|
||||||
|
"""
|
||||||
|
reads values from /proc/meminfo
|
||||||
|
"""
|
||||||
|
def __init__(self):
|
||||||
|
try:
|
||||||
|
lines = open("/proc/meminfo").readlines()
|
||||||
|
except Exception as e:
|
||||||
|
logger.info(f"failed to open /proc/meminfo: {e}")
|
||||||
|
lines = []
|
||||||
|
|
||||||
|
# lines are like:
|
||||||
|
# MemTotal: 16262708 kB
|
||||||
|
# HugePages_Total: 0
|
||||||
|
self.entries = {}
|
||||||
|
for l in lines:
|
||||||
|
if ":" not in l: continue
|
||||||
|
key_len = l.index(":")
|
||||||
|
key, value_str = l[:key_len].strip(), l[key_len+1:].strip()
|
||||||
|
|
||||||
|
unit_str = ""
|
||||||
|
if " " in value_str:
|
||||||
|
value_len = value_str.index(" ")
|
||||||
|
value_str, unit_str = value_str[:value_len].strip(), value_str[value_len+1:].strip()
|
||||||
|
|
||||||
|
try:
|
||||||
|
value = int(value_str)
|
||||||
|
except:
|
||||||
|
logger.info(f"unexpected /proc/meminfo line: {l}")
|
||||||
|
continue
|
||||||
|
|
||||||
|
if unit_str == "kB":
|
||||||
|
value = value * 1024
|
||||||
|
self.entries[key] = value
|
||||||
|
|
||||||
|
def get(self, entry):
|
||||||
|
v = self.entries.get(entry)
|
||||||
|
logger.debug(f"/proc/meminfo: {entry}={v}")
|
||||||
|
return v
|
||||||
|
|
||||||
class PowerSupply:
|
class PowerSupply:
|
||||||
"""
|
"""
|
||||||
reads values from /sys/class/power_supply/$dev/ API
|
reads values from /sys/class/power_supply/$dev/ API
|
||||||
|
@ -186,7 +230,7 @@ class BatteryInfo:
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
def try_path(p: str) -> BatteryInfo | None:
|
def try_battery_path(p: str) -> BatteryInfo | None:
|
||||||
"""
|
"""
|
||||||
try to read battery information from some p = "/sys/class/power_supply/$node" path
|
try to read battery information from some p = "/sys/class/power_supply/$node" path
|
||||||
"""
|
"""
|
||||||
|
@ -205,10 +249,10 @@ def try_path(p: str) -> BatteryInfo | None:
|
||||||
|
|
||||||
return BatteryInfo(ps.capacity, charges_per_hour, ps.status)
|
return BatteryInfo(ps.capacity, charges_per_hour, ps.status)
|
||||||
|
|
||||||
def try_all_paths() -> BatteryInfo | None:
|
def try_all_batteries() -> BatteryInfo | None:
|
||||||
p = try_path("/sys/class/power_supply/axp20x-battery") # Pinephone
|
p = try_battery_path("/sys/class/power_supply/axp20x-battery") # Pinephone
|
||||||
if p is None:
|
if p is None:
|
||||||
p = try_path("/sys/class/power_supply/BAT0") # Thinkpad
|
p = try_battery_path("/sys/class/power_supply/BAT0") # Thinkpad
|
||||||
|
|
||||||
logger.debug(f"perc: {p.percent_charged if p else None}")
|
logger.debug(f"perc: {p.percent_charged if p else None}")
|
||||||
logger.debug(f"charge: {p.percent_charged if p else None}")
|
logger.debug(f"charge: {p.percent_charged if p else None}")
|
||||||
|
@ -216,22 +260,52 @@ def try_all_paths() -> BatteryInfo | None:
|
||||||
|
|
||||||
return p
|
return p
|
||||||
|
|
||||||
def pretty_output(f: Formatter, p: BatteryInfo) -> str:
|
@dataclass
|
||||||
if p.minutes_to_charged != None:
|
class AllInfo:
|
||||||
logger.debug("charging")
|
_fmt: Formatter
|
||||||
icon = f.render_icon(ChargeDirection.Charging, p.percent_charged)
|
_mem: MemInfo | None
|
||||||
duration = p.minutes_to_charged
|
_bat: BatteryInfo | None
|
||||||
else:
|
|
||||||
logger.debug("discharging")
|
|
||||||
icon = f.render_icon(ChargeDirection.Discharging, p.percent_charged)
|
|
||||||
duration = p.minutes_to_discharged
|
|
||||||
|
|
||||||
if duration is not None and duration < 1440:
|
@property
|
||||||
details = f.render_hours_minutes(duration)
|
def mem_icon(self) -> str:
|
||||||
else:
|
if self._mem is None: return ""
|
||||||
details = f.render_percent(p.percent_charged)
|
return self._fmt.render_mem_icon()
|
||||||
|
|
||||||
return f"{icon}{details}"
|
@property
|
||||||
|
def mem_pct(self) -> str:
|
||||||
|
if self._mem is None: return ""
|
||||||
|
|
||||||
|
total = self._mem.get("MemTotal")
|
||||||
|
free = self._mem.get("MemAvailable")
|
||||||
|
|
||||||
|
if total is None or free is None or free > total:
|
||||||
|
return ""
|
||||||
|
|
||||||
|
mem_use_pct = int((total - free) / total * 100)
|
||||||
|
return self._fmt.render_percent(mem_use_pct)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def bat_icon(self) -> str:
|
||||||
|
if self._bat is None: return ""
|
||||||
|
elif self._bat.minutes_to_charged != None:
|
||||||
|
logger.debug("bat_icon: charging")
|
||||||
|
return self._fmt.render_charge_icon(ChargeDirection.Charging, self._bat.percent_charged)
|
||||||
|
else:
|
||||||
|
logger.debug("bat_icon: discharging")
|
||||||
|
return self._fmt.render_charge_icon(ChargeDirection.Discharging, self._bat.percent_charged)
|
||||||
|
|
||||||
|
@property
|
||||||
|
def bat_time(self) -> str:
|
||||||
|
if self._bat is None: return ""
|
||||||
|
elif self._bat.minutes_to_charged != None:
|
||||||
|
duration = self._bat.minutes_to_charged
|
||||||
|
else:
|
||||||
|
duration = self._bat.minutes_to_discharged
|
||||||
|
|
||||||
|
if duration is not None and duration < 1440:
|
||||||
|
return self._fmt.render_hours_minutes(duration)
|
||||||
|
else:
|
||||||
|
return self._fmt.render_percent(self._bat.percent_charged)
|
||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
logging.basicConfig()
|
logging.basicConfig()
|
||||||
|
@ -243,6 +317,8 @@ def main() -> None:
|
||||||
parser.add_argument("--hour-suffix", default=SUFFIX_HR)
|
parser.add_argument("--hour-suffix", default=SUFFIX_HR)
|
||||||
parser.add_argument("--minute-suffix", default=SUFFIX_MIN)
|
parser.add_argument("--minute-suffix", default=SUFFIX_MIN)
|
||||||
parser.add_argument("--percent-suffix", default=SUFFIX_PERCENT)
|
parser.add_argument("--percent-suffix", default=SUFFIX_PERCENT)
|
||||||
|
parser.add_argument("--template", default="{_.bat_icon}{_.bat_time}")
|
||||||
|
# parser.add_argument("--template", default="{_.mem_icon}{_.mem_pct}")
|
||||||
args = parser.parse_args()
|
args = parser.parse_args()
|
||||||
|
|
||||||
if args.debug:
|
if args.debug:
|
||||||
|
@ -254,9 +330,12 @@ 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
|
||||||
|
|
||||||
p = try_all_paths()
|
info = AllInfo(
|
||||||
if p is not None:
|
f,
|
||||||
print(pretty_output(f, p))
|
MemInfo(),
|
||||||
|
try_all_batteries(),
|
||||||
|
)
|
||||||
|
print(args.template.format(_=info))
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main()
|
main()
|
||||||
|
|
Loading…
Reference in New Issue
Block a user