-
-
Save samv/7d85bce7c6b16945759b6acf3f481d54 to your computer and use it in GitHub Desktop.
CPU info script
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/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}") |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
$ 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