Skip to content

Instantly share code, notes, and snippets.

@samv
Last active August 28, 2024 11:15
Show Gist options
  • Save samv/7d85bce7c6b16945759b6acf3f481d54 to your computer and use it in GitHub Desktop.
Save samv/7d85bce7c6b16945759b6acf3f481d54 to your computer and use it in GitHub Desktop.
CPU info script
#!/usr/bin/env python3
#
# 'cpu' is a Linux–only (for now) utility to extract and query useful info out of /proc/cpuinfo
# This is mainly useful when scaling operations to make most use of a system, but also it can show
# some useful summary information.
#
# Running 'cpu' on its own shows most fields, except aliases and certain generated fields.
#
# $ cpu
# I: system summary:
# I: packages : 2
# I: cores : 40
# I: threads : 80
#
# Running it with one argument, eg 'cpu cores' will give you just that reading and nothing else:
#
# $ cpu cores
# 40
#
# Running it with two or more arguments, eg 'cpu sockets cores cache' will print one line per
# value as a shell variable script:
#
# $ cpu sockets cores cache
# cpu_sockets=2
# cpu_cores=40
# cpu_cache=100
#
# You can use the converted shell variable name, too—and it takes care of quoting
#
# $ cpu makes cores cache_per_core model_name
# cpu_makes=50
# cpu_cores=40
# cpu_cache_per_core=2.5
# cpu_model_name='Intel(R) Xeon(R) CPU E5-2673 v4 @ 2.30GHz'
#
import os
import re
import shlex
import sys
from typing import Any
proc_info = []
with open("/proc/cpuinfo") as cpuinfo:
proc = None
for line in cpuinfo.readlines():
if ":" not in line:
proc = None
continue
elif proc is None:
proc = {}
proc_info.append(proc)
key, value = (x.strip() for x in line.split(":", 1))
try:
value = int(value)
except (TypeError, ValueError):
pass
proc[key] = value
phys_info = []
known_variant_keys = frozenset(("processor", "core id", "apicid", "initial apicid", "cpu MHz"))
unknown_variant_keys = set()
for proc in proc_info:
phys_id = proc["physical id"] or 0
if phys_id >= len(phys_info):
phys_info.extend(None for _ in range(phys_id + 1 - len(phys_info)))
if phys_info[phys_id] is None:
phys_info[phys_id] = proc
continue
phys_proc = phys_info[phys_id]
for key in proc:
if key in known_variant_keys or key in unknown_variant_keys:
continue
if proc[key] != phys_proc[key]:
print(f"W: package {phys_id}: key '{key}' is {proc[key]!r} on processor {proc['processor']} but {phys_proc[key]!r} on processor {phys_proc['processor']}", file=sys.stderr)
unknow1GyGn_variant_keys.add(key)
continue
cap_re = re.compile(r'(?P<scalar>\d+(?:\.\d+)?)\s*(?P<unit>[kKmMgG]?)(?:[bB](?:ytes?)?)?\s*$')
unit_sizes = dict(k=1<<10, m=1<<20, g=1<<30)
def to_MiB(capacity_text: str) -> float:
cap_m = cap_re.match(capacity_text)
if not cap_m:
raise RuntimeException(f"can't convert value {capacity_string} to a number of bytes")
cap = cap_m.group("scalar")
cap_val = float(cap)
unit = cap_m.group("unit")
cap_bytes = unit_sizes[unit.lower()] * cap_val if unit else cap_val
return cap_bytes / unit_sizes["m"]
info = dict(
packages=len(phys_info),
cores=sum(proc['cpu cores'] for proc in phys_info),
threads=sum(proc['siblings'] for proc in phys_info),
cache=sum(to_MiB(proc['cache size']) for proc in phys_info),
bogomips=sum(float(proc['bogomips']) * float(proc['siblings']) for proc in phys_info),
)
info["affinity"] = int(len(os.sched_getaffinity(0)) * info["cores"] / info["threads"])
info["cache/core"] = info["cache"] / info["cores"]
# one 'make' per 2MiB of L2 cache
info["makes"] = max(int(info["affinity"] * info["cache/core"] / 2), 1)
if sys.stdout.isatty() and len(sys.argv) == 1:
print(f"I: system summary:", file=sys.stderr)
for key, value in info.items():
print(f"I: {key:10s}: {value}")
info["sockets"] = info["packages"]
bogoxips = info['bogomips']
bogoxips_name = "bogomips"
for scale in "gtpezyrq":
new_name = f"bogo{scale}ips"
bogoxips /= 1000
# just zero it out when it gets miniscule
if bogoxips < 0.01:
bogoxips = 0
info[new_name] = bogoxips
bogoxips_name = new_name
epsilon = 2**-20
def key_to_shell(key: str) -> str:
var_name = key.replace(" ", "_").replace("/", "_per_")
if not var_name.startswith("cpu_"):
var_name = f"cpu_{var_name}"
return var_name
rev_info = dict((key_to_shell(key), key) for key in set(info).union(set(phys_info[0])))
show_var_names = len(sys.argv) > 2
def maybe_quote(value: Any) -> str:
if show_var_names:
return(shlex.quote(str(value)))
else:
return(str(value))
for arg in sys.argv[1:]:
shell_arg = key_to_shell(arg)
if arg not in info:
if arg.lower() in info:
arg = arg.lower()
elif shell_arg in rev_info:
arg = rev_info[shell_arg]
if arg not in info and arg not in phys_info[0]:
arg_with_cpu = f"cpu {arg}"
if arg_with_cpu in phys_info[0]:
arg = arg_with_cpu
elif arg.lower() in phys_info[0]:
arg = arg.lower()
elif arg_with_cpu.lower() in phys_info[0]:
arg = arg_with_cpu.lower()
else:
raise KeyError(f"CPU property {arg} not recognized")
if show_var_names:
var_name = arg.replace(" ", "_").replace("/", "_per_")
if var_name.startswith("cpu_"):
var_name = var_name[4:]
print(f"cpu_{var_name}=", end="")
if arg in info:
info_val = info[arg]
if isinstance(info_val, float) and abs(info_val - int(info_val)) <= info_val * epsilon:
info_val = int(info_val)
if arg.startswith("bogo") and info_val == 0:
print(f"W: sorry, this machine ain't got no {arg}", file=sys.stderr)
print(maybe_quote(info_val))
elif arg in phys_info[0]:
print(maybe_quote(phys_info[0][arg]))
else:
raise RuntimeException(f"not found: {arg}")
$ cpu
I: system summary:
I: packages : 2
I: cores : 40
I: threads : 80
I: cache : 100.0
I: bogomips : 367147.2
I: affinity : 40
I: cache/core: 2.5
I: makes : 50
$ cpu bogotips
0.3671472
$ cpu bogopips
W: sorry, this machine ain't got no bogopips
0
$ cpu sockets
2
$ cpu model_name cores
cpu_model_name='Intel(R) Xeon(R) CPU E5-2673 v4 @ 2.30GHz'
cpu_cores=40
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment