#!/usr/bin/env python # -*- coding: utf-8 -*- ################################################################################ # # Copyright (C) 2014 Neil MacLeod (bcmstat.sh@nmacleod.com) # # This Program is free software; you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation; either version 2, or (at your option) # any later version. # # This Program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. # # Simple utility to monitor Raspberry Pi BCM283X SoC, network, CPU and memory statistics # # Usage: # # ./bcmstat.py xcd10 # # Help available with -h. # # Default is to run at lowest possible priority (maximum niceness, +19), # but this can mean slow responses. To ensure more timely responses, use N # to run at default/normal priority (ie. don't re-nice), or M to run at # maximum priority (and minimum niceness, -20). # ################################################################################ from __future__ import print_function import os, sys, datetime, time, errno, subprocess, re, getpass import platform, socket, hashlib if sys.version_info >= (3, 0): import urllib.request as urllib2 else: import urllib2 GITHUB = "https://raw.github.com/MilhouseVH/bcmstat/master" VERSION = "0.5.5" VCGENCMD = None VCDBGCMD = None GPU_ALLOCATED_R = None GPU_ALLOCATED_M = None SUDO = "" TCMAX = 0.0 TPMAX = 0.0 LIMIT_TEMP = True COLOUR = False SYSINFO = {} DEFAULT_COLS_FILTER = ["UFT", "Vcore", "ARM", "Core", "H264", "TempCore", "TempPMIC", "IRQ", "RX", "TX", "CPU", "CPUuser", "CPUnice", "CPUsys", "CPUidle", "CPUiowt", "CPUirq", "CPUs/irq", "CPUtotal", "GPUfree", "MEMfree", "MEMdelta", "MEMaccum"] EXTRA_COLS_FILTER = ["V3D", "ISP"] # [USER:8][NEW:1][MEMSIZE:3][MANUFACTURER:4][PROCESSOR:4][TYPE:8][REV:4] # NEW 23: will be 1 for the new scheme, 0 for the old scheme # MEMSIZE 20: 0=256M 1=512M 2=1G # MANUFACTURER 16: 0=SONY 1=EGOMAN 2=EMBEST 3=SONY JAPAN 4=EMBEST # PROCESSOR 12: 0=2835 1=2836 2=2837, 3=2838 # TYPE 04: 0=MODELA 1=MODELB 2=MODELA+ 3=MODELB+ 4=Pi2 MODELB 5=ALPHA 6=CM 8=Pi3 9=Pi0 10=CM3 12=Pi0W # REV 00: 0=REV0 1=REV1 2=REV2 3=REV3 #0 Unknown #1 pi3rev1.0 = 1<<23 | 2<<20 | 2<<12 | 8<<4 | 0<<0 = 0xa02080 #2 pi3rev1.2 = 1<<23 | 2<<20 | 2<<12 | 8<<4 | 2<<0 = 0xa02082 #3 2837 pi2 rev1.1 = 1<<23 | 2<<20 | 2<<12 | 4<<4 | 1<<0 = 0xa02041 #4 2836 pi2 = 1<<23 | 2<<20 | 1<<12 | 4<<4 | 1<<0 = 0xa01041 #5 rev1.1 B+ = 1<<23 | 1<<20 | 0<<12 | 3<<4 | 0xf<<0 = 0x90003f #6 pi0 = 1<<23 | 1<<20 | 0<<12 | 9<<4 | 0<<0 = 0x900090 #Extras: #7 pi1rev2.0 = 1<<23 | 1<<20 | 0<<12 | 1<<4 | 2<<0 = 0x900012 #8 2837 pi2rev1.0 = 1<<23 | 2<<20 | 2<<12 | 4<<4 | 0<<0 = 0xa01040 #9 pi0 W = 1<<23 | 1<<20 | 0<<12 | 9<<4 | 0<<0 = 0x9000c0 #10 2837 pi2rev1.2 = 1<<23 | 2<<20 | 2<<12 | 4<<4 | 0<<0 = 0xa02042 class RPIHardware(): def __init__(self, rev_code = None): self.hardware_raw = {"rev_code": 0, "bits": "", "pcb": 0, "user": 0, "new": 0, "memsize": 0, "manufacturer": 0, "processor": 0, "type": 0, "rev": 0} self.hardware_fmt = {"bits": "", "pcb": "", "new": "", "memsize": "", "manufacturer": "", "processor": "", "type": "", "rev": ""} # Note: Some of these memory sizes and processors are fictional and relate to unannounced products - logic would # dictate such products may exist at some point in the future, but it's only guesswork. self.memsizes = ["256MB", "512MB", "1GB", "2GB", "4GB", "8GB"] self.manufacturers = ["Sony UK", "Egoman", "Embest", "Sony Japan", "Embest", "Stadium"] self.processors = ["2835", "2836", "2837", "2838", "2839", "2840"] self.models = ["Model A", "Model B", "Model A+", "Model B+", "Pi2 Model B", "Alpha", "CM1", "Unknown", "Pi3", "Pi0", "CM3", "Unknown", "Pi0 W", "Pi3 Model B+", "Pi3 Model A+", "Unknown", "CM3+", "Pi4 Model B"] self.pcbs = ["Unknown", "Pi3 Rev1.0", "Pi3 Rev1.2", "Pi2 2837 Rev1.1", "Pi2 2836", "Pi1 B+ Rev 1.1", "Pi0", "Pi1 B Rev2.0", "Pi2 (2837) Rev1.0", "Pi0 W", "Pi2 (2837) Rev1.2", "Pi3 B+"] self.set_rev_code(rev_code) # self.dump() # Output a pretty format... based on some guesswork, not exhaustively tested def __str__(self): #[Pi#|CM] Rev #.# (SoC #### with ###MB RAM) manufactured by XXXXXXXXXX pretty = [] if self.hardware_fmt["type"].startswith("CM"): pretty.append(self.hardware_fmt["type"]) elif self.hardware_fmt["type"].startswith("Pi"): pretty.append(self.hardware_fmt["type"]) else: pretty.append("Pi1") pretty.append(self.hardware_fmt["type"]) if self.hardware_raw["pcb"] == 7: #Pi1 B r2.0 rev = "2.0" else: rev = "1.%d" % self.hardware_raw["rev"] pretty.append("rev %s," % rev) pretty.append("BCM%s SoC with %s RAM" % (self.hardware_fmt["processor"], self.hardware_fmt["memsize"])) pretty.append("by %s" % self.hardware_fmt["manufacturer"]) return " ".join(pretty) def dump(self): print("%s\n%s" % (self.hardware_raw, self.hardware_fmt)) def bin(self, s, len = 32): return ("%*s" % (len, self._bin(s))).replace(" ", "0") def _bin(self, s): return str(s) if s<=1 else self._bin(s>>1) + str(s&1) def getbits(self, bits, lsb, len=1): return (bits & (((2 ** len) - 1) << lsb)) >> lsb def readfile(self, infile, isbinary=False): if not isbinary: if os.path.exists(infile): with open(infile, 'r') as stream: return stream.read()[:-1].split("\n") else: return "" else: if os.path.exists(infile): with open(infile, 'rb') as stream: return stream.read() else: return [-1] def read_rev_code(self): for line in self.readfile("/proc/cpuinfo", ""): if line.startswith("Revision\t:"): return "0x%s" % line.split(" ")[1] else: revision = self.readfile("/proc/device-tree/system/linux,revision", isbinary=True) if len(revision) == 4: v = (ord(revision[0]) << 24) + (ord(revision[1]) << 16) + (ord(revision[2]) << 8) + (ord(revision[3])) else: v = 0 return '{:08x}'.format(v) def set_rev_code(self, rev_code): if rev_code is None: rev_code = int(self.read_rev_code(), 16) b = self.bin(rev_code) self.hardware_raw["rev_code"] = rev_code self.hardware_raw["bits"] = "%s %s %s %s %s %s %s" % (b[0:8], b[8:9], b[9:12], b[12:16], b[16:20], b[20:28], b[28:32]) self.hardware_raw["user"] = self.getbits(rev_code, 24, 8) self.hardware_raw["new"] = self.getbits(rev_code, 23, 1) self.hardware_raw["memsize"] = self.getbits(rev_code, 20, 3) self.hardware_raw["manufacturer"] = self.getbits(rev_code, 16, 4) self.hardware_raw["processor"] = self.getbits(rev_code, 12, 4) self.hardware_raw["type"] = self.getbits(rev_code, 4, 8) self.hardware_raw["rev"] = self.getbits(rev_code, 0, 4) #http://elinux.org/RPi_HardwareHistory#Board_Revision_History if self.hardware_raw["type"] == 0: if self.hardware_raw["rev"] in [0, 1, 2, 3]: self.hardware_raw["new"] = 0 self.hardware_raw["memsize"] = 0 self.hardware_raw["processor"] = 0 self.hardware_raw["type"] = 1 self.hardware_raw["rev"] = 1 elif self.hardware_raw["rev"] in [4, 5, 6]: self.hardware_raw["new"] = 0 self.hardware_raw["memsize"] = 0 self.hardware_raw["processor"] = 0 self.hardware_raw["type"] = 1 self.hardware_raw["rev"] = 2 elif self.hardware_raw["rev"] in [0xd, 0xe, 0xf]: self.hardware_raw["new"] = 1 self.hardware_raw["memsize"] = 1 self.hardware_raw["processor"] = 0 self.hardware_raw["type"] = 1 self.hardware_raw["rev"] = 2 pcb_base = self.hardware_raw["new"] << 23 | self.hardware_raw["memsize"] << 20 | self.hardware_raw["processor"] << 12 | self.hardware_raw["type"] << 4 | self.hardware_raw["rev"] << 0 if pcb_base == 0xa02080: pcb = 1 elif pcb_base == 0xa02082: pcb = 2 elif pcb_base == 0xa02041: pcb = 3 elif pcb_base == 0xa01041: pcb = 4 elif pcb_base == 0xa01040: pcb = 8 elif pcb_base == 0x90003f: pcb = 5 elif (pcb_base & 0xfffff0) == 0x900090: pcb = 6 elif pcb_base == 0x900012: pcb = 7 elif (pcb_base & 0xfffff0) == 0x9000c0: pcb = 9 elif (pcb_base & 0xfffff0) in [0xa22080, 0xa32082]: pcb = 3 elif pcb_base == 0xa02042: pcb = 10 elif pcb_base == 0xa020d0: pcb = 11 else: pcb = 0 self.hardware_raw["pcb"] = pcb self.hardware_fmt = {"pcb": "Unknown", "bits": "", "new": "", "memsize": "", "manufacturer": "Unknown", "processor": "Unknown", "type": "Unknown", "rev": ""} self.hardware_fmt["bits"] = self.hardware_raw["bits"] self.hardware_fmt["new"] = ["No", "Yes"][self.hardware_raw["new"]] self.hardware_fmt["memsize"] = self.memsizes[self.hardware_raw["memsize"]] if 0 <= self.hardware_raw["manufacturer"] < len(self.manufacturers): self.hardware_fmt["manufacturer"] = self.manufacturers[self.hardware_raw["manufacturer"]] self.hardware_fmt["processor"] = self.processors[self.hardware_raw["processor"]] if 0 <= self.hardware_raw["type"] < len(self.models): self.hardware_fmt["type"] = self.models[self.hardware_raw["type"]] self.hardware_fmt["rev"] = "Rev%d" % self.hardware_raw["rev"] if 0 <= self.hardware_raw["pcb"] < len(self.pcbs): self.hardware_fmt["pcb"] = self.pcbs[self.hardware_raw["pcb"]] def GetPiModel(self): if self.hardware_raw["processor"] == 0: return "RPi1" elif self.hardware_raw["processor"] == 1: return "RPi2" elif self.hardware_raw["processor"] == 2: return "RPi3" elif self.hardware_raw["processor"] == 3: return "RPi4" elif self.hardware_raw["processor"] == 4: return "RPi5" def GetBoardPCB(self): return self.hardware_fmt["pcb"] def GetMemSize(self): return self.hardware_fmt["memsize"] def GetManufacturer(self): return self.hardware_fmt["manufacturer"] def GetProcessor(self): return self.hardware_fmt["processor"] def GetType(self): return self.hardware_fmt["type"] def GetRev(self): return self.hardware_fmt["rev"] # https://www.raspberrypi.org/forums/viewtopic.php?f=63&t=147781&start=50#p972790 def GetThresholdValues(self, storage, filter, withclear): keys = ["under-voltage", "arm-capped", "throttled"] # If withclear is supported, clear persistent bits after querying (value is "since last query") # The alternative value is "since boot". Always use "since boot" on first query. if withclear and storage[1][0] != 0: value = int(vcgencmd("get_throttled 0x7"), 16) else: value = int(vcgencmd("get_throttled"), 16) storage[2] = storage[1] storage[1] = (time.time(), {keys[0]: (self.getbits(value, 0, 1), self.getbits(value, 16, 1)), keys[1]: (self.getbits(value, 1, 1), self.getbits(value, 17, 1)), keys[2]: (self.getbits(value, 2, 1), self.getbits(value, 18, 1))}) if storage[2][0] != 0: s0 = storage[0] s1 = storage[1] s2 = storage[2] dTime = s1[0] - s2[0] dTime = 1 if dTime <= 0 else dTime threshold = {} for k in keys: now = s1[1][k][0] occ = s1[1][k][1] prev = s0[1][k][1] if s0[0] != 0 else 0 prev |= s2[1][k][1] # If an event isn't currently active but an event has occurred since the last query # then report it as active since the previous query if withclear and now == 0 and occ == 1: now = 1 # Persist occurred status across queries threshold[k] = (now, occ | prev) storage[0] = (dTime, threshold) # Primitives def printn(text): print(text, file=sys.stdout, end="") sys.stdout.flush() def printout(msg, newLine=True): sys.stdout.write(msg) if newLine: sys.stdout.write("\n") sys.stdout.flush() def printerr(msg, newLine=True): sys.stderr.write(msg) if newLine: sys.stderr.write("\n") sys.stderr.flush() def runcommand(command, ignore_error=False): try: if sys.version_info >= (3, 0): return subprocess.check_output(command.split(" "), stderr=subprocess.STDOUT).decode("utf-8")[:-1] else: return subprocess.check_output(command.split(" "), stderr=subprocess.STDOUT)[:-1] except: if ignore_error: return None else: raise def find_vcgencmd_vcdbg(): global VCGENCMD, VCDBGCMD, VCGENCMD_GET_MEM for file in [runcommand("which vcgencmd", ignore_error=True), "/usr/bin/vcgencmd", "/opt/vc/bin/vcgencmd"]: if file and os.path.exists(file) and os.path.isfile(file) and os.access(file, os.X_OK): VCGENCMD = file break for file in [runcommand("which vcdbg", ignore_error=True), "/usr/bin/vcgdbg", "/opt/vc/bin/vcdbg"]: if file and os.path.exists(file) and os.path.isfile(file) and os.access(file, os.X_OK): VCDBGCMD = file break # Determine if we have reloc/malloc get_mem capability VCGENCMD_GET_MEM = False if VCGENCMD: if vcgencmd("get_mem reloc_total") != "0M" or vcgencmd("get_mem reloc") != "0M": VCGENCMD_GET_MEM = True def vcgencmd(args, split=True): global VCGENCMD if split: return grep("", runcommand("%s %s" % (VCGENCMD, args)), 1, split_char="=") else: return runcommand("%s %s" % (VCGENCMD, args)) def vcgencmd_items(args, isInt=False): d = {} for l in [x.split("=") for x in vcgencmd(args, split=False).split("\n")]: if not isInt: d[l[0]] = l[1] elif l[1][:2] == "0x": d[l[0]] = int(l[1], 16) else: d[l[0]] = int(l[1]) return d def vcdbg(args): global VCDBGCMD, SUDO return runcommand("%s%s %s" % (SUDO, VCDBGCMD, args)) def readfile(infile, defaultval=""): if os.path.exists(infile): with open(infile, 'r') as stream: return stream.read()[:-1] else: return defaultval def grep(match_string, input_string, field=None, head=None, tail=None, split_char=" ", case_sensitive=True, defaultvalue=None): re_flags = 0 if case_sensitive else re.IGNORECASE lines = [] for line in [x for x in input_string.split("\n") if re.search(match_string, x, flags=re_flags)]: aline = re.sub("%s+" % split_char, split_char, line.strip()).split(split_char) if field is not None: if len(aline) > field: lines.append(aline[field]) else: lines.append(split_char.join(aline)) # Don't process any more lines than we absolutely have to if head and not tail and len(lines) >= head: break if head: lines = lines[:head] if tail: lines = lines[-tail:] if defaultvalue and lines == []: return defaultvalue else: return "\n".join(lines) # grep -v - return everything but the match string def grepv(match_string, input_string, field=None, head=None, tail=None, split_char=" ", case_sensitive=False): return grep(r"^((?!%s).)*$" % match_string, input_string, field, head, tail, split_char, case_sensitive) def tobytes(value): if value[-1:] == "M": return int(float(value[:-1]) * 1048576) # 1024*1024 elif value[-1:] == "K": return int(float(value[:-1]) * 1024) else: return int(value) def colourise(display, nformat, green, yellow, red, withcomma, compare=None, addSign=False): global COLOUR cnum = format(display, ",d") if withcomma else display if addSign and display > 0: cnum = "+%s" % cnum number = compare if compare is not None else display if COLOUR: if red > green: if number >= red: return "%s%s%s" % ("\033[0;31m", nformat % cnum, "\033[0m") elif yellow is not None and number >= yellow: return "%s%s%s" % ("\033[0;33m", nformat % cnum, "\033[0m") elif number >= green: return "%s%s%s" % ("\033[0;32m", nformat % cnum, "\033[0m") else: if number <= red: return "%s%s%s" % ("\033[0;31m", nformat % cnum, "\033[0m") elif yellow is not None and number <= yellow: return "%s%s%s" % ("\033[0;33m", nformat % cnum, "\033[0m") elif number <= green: return "%s%s%s" % ("\033[0;32m", nformat % cnum, "\033[0m") return nformat % cnum def getIRQ(storage, filter, sysinfo): storage[2] = storage[1] nproc = sysinfo["nproc"] irq = 0 for line in grep(":", readfile("/proc/interrupts")).split("\n"): fields = line.split(" ") if fields[0] == "FIQ:": continue if fields[0] == "Err:": break for n in range(1, nproc + 1): irq += int(fields[n]) storage[1] = (time.time(), irq) if storage[2][0] != 0: s1 = storage[1] s2 = storage[2] dTime = s1[0] - s2[0] dTime = 1 if dTime <= 0 else dTime storage[0] = (dTime, [int((int(s1[1]) - int(s2[1]))/dTime)]) def minmax(min, max, value): if value < min: return min elif value > max: return max else: return value def getDefaultInterface(): interfaces = grep("^[ ]*.*:", readfile("/proc/net/dev")) for interface in [i for i in interfaces.split("\n")]: name = interface.split(":")[0] if name.startswith("eth") or name.startswith("enxb827eb"): return name return "wlan0" # Collect processor stats once per loop, so that consistent stats are # used when calculating total system load and individual core loads def getProcStats(storage, filter): storage[2] = storage[1] cores = {} for core in grep("^cpu[0-9]*", readfile("/proc/stat")).split("\n"): items = core.split(" ") jiffies = [] for jiffy in items[1:]: jiffies.append(int(jiffy)) cores[items[0]] = jiffies storage[1] = (time.time(), cores) if storage[2][0] != 0: s1 = storage[1] s2 = storage[2] dTime = s1[0] - s2[0] dTime = 1 if dTime <= 0 else dTime cores = {} for core in s1[1]: if core in s2[1]: jiffies = [] for i in range(0, 10): jiffies.append((int(s1[1][core][i]) - int(s2[1][core][i])) / dTime) cores[core] = jiffies storage[0] = (dTime, cores) # Total system load def getCPULoad(storage, filter, proc, sysinfo): if proc[2][0] != 0: dTime = proc[0][0] core = proc[0][1]["cpu"] nproc = sysinfo["nproc"] c = [] for i in range(0, 10): c.append(minmax(0, 100, (core[i] / nproc))) storage[0] = (dTime, [c[0], c[1], c[2], c[3], c[4], c[5], c[6], 100 - c[3]]) # Simple View of Total system load (combines hardware interrupts, software interrupts, and I/O waits into sys; removes idle) def getSimpleCPULoad(storage, filter, proc, sysinfo): if proc[2][0] != 0: dTime = proc[0][0] core = proc[0][1]["cpu"] nproc = sysinfo["nproc"] c = [] for i in range(0, 10): c.append(minmax(0, 100, (core[i] / nproc))) storage[0] = (dTime, [c[0], c[1], c[2] + c[4] + c[5] + c[6], 100 - c[3]]) # Individual core loads def getCoreStats(storage, filter, proc): if "CPU" in filter: if proc[2][0] != 0: dTime = proc[0][0] load = [] for core in sorted(proc[0][1]): if core == "cpu": continue load.append((core, 100 - minmax(0, 100, proc[0][1][core][3]))) storage[0] = (dTime, load) def getNetwork(storage, filter, interface): storage[2] = storage[1] storage[1] = (time.time(), grep("^[ ]*%s:" % interface, readfile("/proc/net/dev"))) if storage[2][0] != 0: s1 = storage[1] s2 = storage[2] dTime = s1[0] - s2[0] dTime = 1 if dTime <= 0 else dTime n1 = s1[1].split(" ") n2 = s2[1].split(" ") pRX = int(n2[1]) cRX = int(n1[1]) pTX = int(n2[9]) cTX = int(n1[9]) cRX = cRX + 4294967295 if cRX < pRX else cRX cTX = cTX + 4294967295 if cTX < pTX else cTX dRX = cRX - pRX dTX = cTX - pTX storage[0] = (dTime, [int(dRX/dTime), int(dTX/dTime), dRX, dTX]) def getBCM283X(storage, filter, STATS_WITH_VOLTS, STATS_WITH_PMIC_TEMP): global TCMAX, LIMIT_TEMP, TPMAX #Grab temp - ignore temps of 85C as this seems to be an occasional aberration in the reading if "TempCore" in filter: tCore = float(readfile("/sys/class/thermal/thermal_zone0/temp")) tCore = 0 if tCore < 0 else tCore if LIMIT_TEMP: TCMAX = tCore if (tCore > TCMAX and tCore < 85000) else TCMAX else: TCMAX = tCore if tCore > TCMAX else TCMAX else: tCore = None TCMAX = None if STATS_WITH_PMIC_TEMP and "TempPMIC" in filter: tPMIC = vcgencmd("measure_temp pmic", split=False) if tPMIC.find("error") != -1: tPMIC = None tPMIC_MAX = None else: tPMIC = float(tPMIC.split("=")[1].replace("'C","")) TPMAX = tPMIC if tPMIC > TPMAX else TPMAX else: tPMIC = None tPMIC_MAX = None if STATS_WITH_VOLTS and "Vcore" in filter: volts = vcgencmd("measure_volts core") if volts and (len(volts) - volts.find(".")) < 5: volts = "%s00" % volts[:-1] else: volts = volts[:-1] else: volts = "" farm = int(vcgencmd("measure_clock arm")) + 500000 if "ARM" in filter else 0 fcore = int(vcgencmd("measure_clock core")) + 500000 if "Core" in filter else 0 fh264 = int(vcgencmd("measure_clock h264")) + 500000 if "H264" in filter else 0 fv3d = int(vcgencmd("measure_clock v3d")) + 500000 if "V3D" in filter else 0 fisp = int(vcgencmd("measure_clock isp")) + 500000 if "ISP" in filter else 0 storage[2] = storage[1] storage[1] = (time.time(), [farm, fcore, fh264, fv3d, fisp, tCore, TCMAX, tPMIC, TPMAX, volts]) if storage[2][0] != 0: s1 = storage[1] s2 = storage[2] dTime = s1[0] - s2[0] dTime = 1 if dTime <= 0 else dTime storage[0] = (dTime, s1[1]) def getMemory(storage, filter, include_swap): MEMTOTAL = 0 MEMFREE = 0 MEMUSED = 0 MEMDIFF = 0 SWAPTOTAL = 0 SWAPFREE = 0 SWAPCACHED= 0 for line in readfile("/proc/meminfo").split("\n"): field_groups = re.search("^(.*):[ ]*([0-9]*) .*$", line) if field_groups.group(1) in ["MemFree", "Buffers", "Cached", "SReclaimable"]: MEMFREE += int(field_groups.group(2)) elif field_groups.group(1) == "MemTotal": MEMTOTAL = int(field_groups.group(2)) elif include_swap and field_groups.group(1) == "SwapTotal": SWAPTOTAL += int(field_groups.group(2)) elif include_swap and field_groups.group(1) == "SwapFree": SWAPFREE += int(field_groups.group(2)) elif include_swap and field_groups.group(1) == "SwapCached": SWAPCACHED += int(field_groups.group(2)) MEMTOTAL += SWAPTOTAL MEMFREE += SWAPFREE MEMUSED = (1-(float(MEMFREE)/float(MEMTOTAL)))*100 if SWAPTOTAL != 0: SWAPUSED = (1-(float(SWAPFREE)/float(SWAPTOTAL)))*100 else: SWAPUSED = None storage[2] = storage[1] storage[1] = (time.time(), [MEMTOTAL, MEMFREE, MEMUSED, SWAPUSED]) if storage[2][0] != 0: s1 = storage[1] s2 = storage[2] dTime = s1[0] - s2[0] dTime = 1 if dTime <= 0 else dTime storage[0] = (dTime, [s1[1][0], s1[1][1], s1[1][2], s1[1][2] - s2[1][2], s1[1][3]]) def getGPUMem(storage, filter, STATS_GPU_R, STATS_GPU_M): global GPU_ALLOCATED_R, GPU_ALLOCATED_M, VCGENCMD_GET_MEM if VCGENCMD_GET_MEM: if not GPU_ALLOCATED_R: GPU_ALLOCATED_R = tobytes(vcgencmd("get_mem reloc_total")) GPU_ALLOCATED_M = tobytes(vcgencmd("get_mem malloc_total")) freemem_r = vcgencmd("get_mem reloc") if STATS_GPU_R else "" freemem_m = vcgencmd("get_mem malloc") if STATS_GPU_M else "" else: vcgencmd("cache_flush") # Get gpu memory data. We only need to process a few lines near the top so # ignore individual memory block details by truncating data to 512 bytes. gpudata = vcdbg("reloc")[:512] if not GPU_ALLOCATED_R: GPU_ALLOCATED_R = tobytes(grep("total space allocated", gpudata, 4, head=1)[:-1]) GPU_ALLOCATED_M = 0 freemem_r = grep("free memory in", gpudata, 0, head=1) freemem_m = "" data = {} if STATS_GPU_R: if freemem_r == "": freemem_r = "???" bfreemem_r = 0 percent_free_r = 0 else: bfreemem_r = tobytes(freemem_r) percent_free_r = (float(bfreemem_r)/float(GPU_ALLOCATED_R))*100 data["reloc"] = [freemem_r, bfreemem_r, int(percent_free_r), GPU_ALLOCATED_R] if STATS_GPU_M: if freemem_m == "": freemem_m = "???" bfreemem_m = 0 percent_free_m = 0 else: bfreemem_m = tobytes(freemem_m) percent_free_m = (float(bfreemem_m)/float(GPU_ALLOCATED_M))*100 data["malloc"] = [freemem_m, bfreemem_m, int(percent_free_m), GPU_ALLOCATED_M] storage[2] = storage[1] storage[1] = (time.time(), data) if storage[2][0] != 0: storage[0] = (storage[1][0] - storage[2][0], data) def getMemDeltas(storage, filter, MEM, GPU): storage[2] = storage[1] storage[1] = (time.time(), MEM[1], GPU[1]) if storage[2][0] == 0: storage[0] = (0, (0, 0, 0, 0)) else: s1 = storage[1] s2 = storage[2] dTime = s1[0] - s2[0] dTime = 1 if dTime <= 0 else dTime dMem = s1[1][1][1] - s2[1][1][1] dGPU = s1[2][1]["reloc"][1] - s2[2][1]["reloc"][1] storage[0] = (dTime, (dMem, dGPU, storage[0][1][2] + dMem, storage[0][1][3] + dGPU)) def ceildiv(a, b): return -(-a // b) def MHz(value, fwidth, cwidth): return ("%*dMHz" % (fwidth, value)).center(cwidth) def MaxSDRAMVolts(): vRAM = "1.2000V" for item in ["sdram_p", "sdram_c", "sdram_i"]: item_v = vcgencmd("measure_volts %s" % item) if item_v and (len(item_v) - item_v.find(".")) < 5: item_v = "%s00V" % item_v[:-1] vRAM = item_v if item_v and item_v > vRAM else vRAM return vRAM # Calculate offset from voltage, allowing for 50mV of variance def MaxSDRAMOffset(): return (int(MaxSDRAMVolts()[:-1].replace(".", "")) - 12000 + 50) / 250 def getsysinfo(HARDWARE): sysinfo = {} RPI_MODEL = HARDWARE.GetPiModel() # RPi1, RPi2, RPi3 etc. VCG_INT = vcgencmd_items("get_config int", isInt=True) CORE_DEFAULT_IDLE = CORE_DEFAULT_BUSY = 250 H264_DEFAULT_IDLE = H264_DEFAULT_BUSY = 250 V3D_DEFAULT_IDLE = V3D_DEFAULT_BUSY = 250 ISP_DEFAULT_IDLE = ISP_DEFAULT_BUSY = 250 if VCG_INT.get("disable_auto_turbo", 0) == 0: CORE_DEFAULT_BUSY += 50 H264_DEFAULT_BUSY += 50 if RPI_MODEL == "RPi1": ARM_DEFAULT_IDLE = 700 SDRAM_DEFAULT = 400 elif RPI_MODEL == "RPi2": ARM_DEFAULT_IDLE = 600 SDRAM_DEFAULT = 450 elif RPI_MODEL == "RPi3": ARM_DEFAULT_IDLE = 600 SDRAM_DEFAULT = 450 elif RPI_MODEL == "RPi4": ARM_DEFAULT_IDLE = 600 SDRAM_DEFAULT = 3200 else: ARM_DEFAULT_IDLE = 600 SDRAM_DEFAULT = 450 sysinfo["hardware"] = HARDWARE sysinfo["model"] = RPI_MODEL sysinfo["nproc"] = len(grep("^processor", readfile("/proc/cpuinfo")).split("\n")) # Kernel 4.8+ doesn't create cpufreq sysfs when force_turbo=1, in which case # min/max frequencies will both be the same as current if os.path.exists("/sys/devices/system/cpu/cpu0/cpufreq"): sysinfo["arm_min"] = int(int(readfile("/sys/devices/system/cpu/cpu0/cpufreq/scaling_min_freq"))/1000) sysinfo["arm_max"] = int(int(readfile("/sys/devices/system/cpu/cpu0/cpufreq/scaling_max_freq"))/1000) else: sysinfo["arm_min"] = int((int(vcgencmd("measure_clock arm")) + 500000) / 1e6) sysinfo["arm_max"] = sysinfo["arm_min"] if "sdram_freq" not in VCG_INT: VCG_INT["sdram_freq"] = int(int(vcgencmd("measure_clock pllh"))/1e6) sysinfo["core_max"] = VCG_INT.get("core_freq", VCG_INT.get("gpu_freq", CORE_DEFAULT_BUSY)) sysinfo["h264_max"] = VCG_INT.get("h264_freq", VCG_INT.get("gpu_freq", H264_DEFAULT_BUSY)) sysinfo["v3d_max"] = VCG_INT.get("v3d_freq", VCG_INT.get("gpu_freq", V3D_DEFAULT_BUSY)) sysinfo["isp_max"] = VCG_INT.get("isp_freq", VCG_INT.get("gpu_freq", ISP_DEFAULT_BUSY)) sysinfo["sdram_max"] = VCG_INT.get("sdram_freq", SDRAM_DEFAULT) sysinfo["arm_volt"] = VCG_INT.get("over_voltage", 0) sysinfo["sdram_volt"] = MaxSDRAMOffset() sysinfo["temp_limit"] = VCG_INT.get("temp_limit", 85) sysinfo["force_turbo"]= (VCG_INT.get("force_turbo", 0) != 0) if sysinfo["force_turbo"]: core_min = sysinfo["core_max"] h264_min = sysinfo["h264_max"] v3d_min = sysinfo["v3d_max"] isp_min = sysinfo["isp_max"] else: core_min = CORE_DEFAULT_IDLE h264_min = H264_DEFAULT_IDLE v3d_min = V3D_DEFAULT_IDLE isp_min = ISP_DEFAULT_IDLE core_min = sysinfo["core_max"] if sysinfo["core_max"] < core_min else core_min h264_min = sysinfo["h264_max"] if sysinfo["h264_max"] < h264_min else h264_min v3d_min = sysinfo["v3d_max"] if sysinfo["v3d_max"] < v3d_min else v3d_min isp_min = sysinfo["isp_max"] if sysinfo["isp_max"] < isp_min else isp_min sysinfo["core_min"] = core_min sysinfo["h264_min"] = h264_min sysinfo["v3d_min"] = v3d_min sysinfo["isp_min"] = isp_min # Calculate thresholds for green/yellow/red colour arm_min = sysinfo["arm_min"] - 10 arm_max = sysinfo["arm_max"] - 5 if sysinfo["arm_max"] > ARM_DEFAULT_IDLE else 1e6 core_min = sysinfo["core_min"] - 10 core_max = sysinfo["core_max"] - 5 if sysinfo["core_max"] > CORE_DEFAULT_IDLE else 1e6 h264_min = sysinfo["h264_min"] - 10 h264_max = sysinfo["h264_max"] - 5 if sysinfo["h264_max"] > H264_DEFAULT_IDLE else 1e6 v3d_min = sysinfo["v3d_min"] - 10 v3d_max = sysinfo["v3d_max"] - 5 if sysinfo["v3d_max"] > V3D_DEFAULT_IDLE else 1e6 isp_min = sysinfo["isp_min"] - 10 isp_max = sysinfo["isp_max"] - 5 if sysinfo["isp_max"] > ISP_DEFAULT_IDLE else 1e6 limits = {} limits["arm"] = (arm_min, arm_max) limits["core"] = (core_min, core_max) limits["h264"] = (h264_min, h264_max) limits["v3d"] = (v3d_min, v3d_max) limits["isp"] = (isp_min, isp_max) sysinfo["limits"] = limits return sysinfo def ShowConfig(nice_value, priority_desc, sysinfo, args): global VCGENCMD, VERSION BOOTED = datetime.datetime.fromtimestamp(int(grep("btime", readfile("/proc/stat"), 1))).strftime('%c') MEM_ARM = int(vcgencmd("get_mem arm")[:-1]) MEM_GPU = int(vcgencmd("get_mem gpu")[:-1]) MEM_MAX = MEM_ARM + MEM_GPU SWAP_TOTAL = int(grep("SwapTotal", readfile("/proc/meminfo"), field=1, defaultvalue="0")) VCG_INT = vcgencmd_items("get_config int", isInt=False) NPROC = sysinfo["nproc"] ARM_MIN = sysinfo["arm_min"] ARM_MAX = sysinfo["arm_max"] CORE_MIN = sysinfo["core_min"] CORE_MAX = sysinfo["core_max"] H264_MAX = sysinfo["h264_max"] SDRAM_MAX = sysinfo["sdram_max"] ARM_VOLT = sysinfo["arm_volt"] SDRAM_VOLT = sysinfo["sdram_volt"] TEMP_LIMIT = sysinfo["temp_limit"] FORCE_TURBO= sysinfo["force_turbo"] vCore = vcgencmd("measure_volts core") vRAM = MaxSDRAMVolts() GOV = readfile("/sys/devices/system/cpu/cpu0/cpufreq/scaling_governor", "undefined") FIRMWARE = ", ".join(grepv("Copyright", vcgencmd("version", split=False)).replace(", ","").split("\n")).replace(" ,",",") OTHER_VARS = ["temp_limit=%d" % TEMP_LIMIT] for item in ["force_turbo", "initial_turbo", "disable_auto_turbo", "avoid_pwm_pll", "hdmi_force_hotplug", "hdmi_force_edid_audio", "no_hdmi_resample", "disable_pvt", "sdram_schmoo"]: if item in VCG_INT: OTHER_VARS.append("%s=%s" % (item, VCG_INT[item])) CODECS = [] for codec in ["H264", "H263", "WVC1", "MPG4", "MPG2", "VP8", "VP6", "VORB", "THRA", "MJPG", "FLAC", "PCM"]: if vcgencmd("codec_enabled %s" % codec) == "enabled": CODECS.append(codec) CODECS = CODECS if CODECS else ["none"] nv = "%s%d" % ("+" if nice_value > 0 else "", nice_value) SWAP_MEM = "" if SWAP_TOTAL == 0 else " plus %dMB Swap" % int(ceildiv(SWAP_TOTAL, 1024)) ARM_ARCH = grep("^model name", readfile("/proc/cpuinfo"), field=2, head=1)[0:5] print(" Config: v%s, args \"%s\", priority %s (%s)" % (VERSION, " ".join(args), priority_desc, nv)) print(" Board: %d x %s core%s available, %s governor (%s)" % (NPROC, ARM_ARCH, "s"[NPROC==1:], GOV, sysinfo["hardware"])) print(" Memory: %sMB (split %sMB ARM, %sMB GPU)%s" % (MEM_MAX, MEM_ARM, MEM_GPU, SWAP_MEM)) print("HW Block: | %s | %s | %s | %s |" % ("ARM".center(7), "Core".center(6), "H264".center(6), "SDRAM".center(11))) print("Min Freq: | %s | %s | %s | %s |" % (MHz(ARM_MIN,4,7), MHz(CORE_MIN,3,6), MHz(0,3,6), MHz(SDRAM_MAX,3,11))) print("Max Freq: | %s | %s | %s | %s |" % (MHz(ARM_MAX,4,7), MHz(CORE_MAX,3,6), MHz(H264_MAX,3,6), MHz(SDRAM_MAX,3,11))) if vCore and (len(vCore) - vCore.find(".")) < 5: vCore = "%s00V" % vCore[:-1] v1 = "%d, %s" % (ARM_VOLT, vCore) v2 = "%d, %s" % (SDRAM_VOLT, vRAM) v1 = "+%s" % v1 if ARM_VOLT > 0 else v1 v2 = "+%s" % v2 if SDRAM_VOLT > 0 else v2 print("Voltages: | %s | %s |" % (v1.center(25), v2.center(11))) # Chop "Other" properties up into multiple lines of limited length strings line = "" lines = [] for item in OTHER_VARS: if (len(line) + len(item)) <= 80: line = item if line == "" else "%s, %s" % (line, item) else: lines.append(line) line = "" if line: lines.append(line) c=0 for l in lines: c += 1 if c == 1: print(" Other: %s" % l) else: print(" %s" % l) print("Firmware: %s" % FIRMWARE) print(" Codecs: %s" % " ".join(CODECS)) printn(" Booted: %s" % BOOTED) def addHeadingValue(filter, name, value, var1, var2, padding=' '): if name in filter: return ("%s%s%s" % (var1, padding, value), "%s%s%s" % (var2, padding, "="*len(value))) else: return (var1, var2) def addDetailValue(filter, name, value, var1, padding=' ', prefix='', suffix=''): if name in filter: return "%s%s%s%s%s" % (var1, padding, prefix, value, suffix) else: return var1 def ShowHeadings(filter, display_flags, sysinfo): HDR1 = "Time " HDR2 = "========" if display_flags["threshold"]: (HDR1, HDR2) = addHeadingValue(filter, "UFT", "UFT", HDR1, HDR2) if display_flags["core_volts"]: (HDR1, HDR2) = addHeadingValue(filter, "Vcore", "Vcore ", HDR1, HDR2) (HDR1, HDR2) = addHeadingValue(filter, "ARM", " ARM", HDR1, HDR2) (HDR1, HDR2) = addHeadingValue(filter, "Core", " Core", HDR1, HDR2) (HDR1, HDR2) = addHeadingValue(filter, "H264", " H264", HDR1, HDR2) (HDR1, HDR2) = addHeadingValue(filter, "V3D", " V3D", HDR1, HDR2) (HDR1, HDR2) = addHeadingValue(filter, "ISP", " ISP", HDR1, HDR2) (HDR1, HDR2) = addHeadingValue(filter, "TempCore", "Core Temp (Max)", HDR1, HDR2) if display_flags["temp_pmic"]: (HDR1, HDR2) = addHeadingValue(filter, "TempPMIC", "PMIC Temp (Max)", HDR1, HDR2) (HDR1, HDR2) = addHeadingValue(filter, "IRQ", " IRQ/s", HDR1, HDR2) if display_flags["network"]: if display_flags["human_readable"]: (HDR1, HDR2) = addHeadingValue(filter, "RX", "RX kB/s", HDR1, HDR2) (HDR1, HDR2) = addHeadingValue(filter, "TX", "TX kB/s", HDR1, HDR2) else: (HDR1, HDR2) = addHeadingValue(filter, "RX", " RX B/s", HDR1, HDR2) (HDR1, HDR2) = addHeadingValue(filter, "TX", " TX B/s", HDR1, HDR2) if display_flags["utilisation"]: (HDR1, HDR2) = addHeadingValue(filter, "CPUuser", " %user", HDR1, HDR2) (HDR1, HDR2) = addHeadingValue(filter, "CPUnice", " %nice", HDR1, HDR2) (HDR1, HDR2) = addHeadingValue(filter, "CPUsys", " %sys", HDR1, HDR2) (HDR1, HDR2) = addHeadingValue(filter, "CPUidle", " %idle", HDR1, HDR2) (HDR1, HDR2) = addHeadingValue(filter, "CPUiowt", " %iowt", HDR1, HDR2) (HDR1, HDR2) = addHeadingValue(filter, "CPUirq", " %irq", HDR1, HDR2) (HDR1, HDR2) = addHeadingValue(filter, "CPUs/irq", "%s/irq", HDR1, HDR2) (HDR1, HDR2) = addHeadingValue(filter, "CPUtotal", "%total", HDR1, HDR2) if display_flags["sutilisation"]: (HDR1, HDR2) = addHeadingValue(filter, "CPUuser", " %user", HDR1, HDR2) (HDR1, HDR2) = addHeadingValue(filter, "CPUnice", " %nice", HDR1, HDR2) (HDR1, HDR2) = addHeadingValue(filter, "CPUsys", " %sys+", HDR1, HDR2) (HDR1, HDR2) = addHeadingValue(filter, "CPUtotal", "%total", HDR1, HDR2) if display_flags["cpu_cores"]: for i in range(0, sysinfo["nproc"]): (HDR1, HDR2) = addHeadingValue(filter, "CPU", " cpu%d" % i, HDR1, HDR2) if display_flags["gpu_malloc"]: (HDR1, HDR2) = addHeadingValue(filter, "GPUfree", "Malloc Free", HDR1, HDR2) if display_flags["gpu_reloc"]: if display_flags["gpu_malloc"]: (HDR1, HDR2) = addHeadingValue(filter, "GPUfree", "Reloc Free", HDR1, HDR2) else: (HDR1, HDR2) = addHeadingValue(filter, "GPUfree", "GPUMem Free", HDR1, HDR2) if display_flags["cpu_mem"]: if display_flags["human_readable"]: (HDR1, HDR2) = addHeadingValue(filter, "MEMfree", "MemFreeMB / %used", HDR1, HDR2) else: (HDR1, HDR2) = addHeadingValue(filter, "MEMfree", "MemFreeKB / %used", HDR1, HDR2) if display_flags["swap"]: (HDR1, HDR2) = addHeadingValue(filter, "MEMfree", "(SwUse)", HDR1, HDR2, padding='') if display_flags["deltas"]: (HDR1, HDR2) = addHeadingValue(filter, "MEMdelta", "Delta GPU B Mem kB", HDR1, HDR2) if display_flags["accumulated"]: (HDR1, HDR2) = addHeadingValue(filter, "MEMaccum", "Accum GPU B Mem kB", HDR1, HDR2) printn("%s\n%s" % (HDR1, HDR2)) def ShowStats(filter, display_flags, sysinfo, threshold, bcm2385, irq, network, cpuload, memory, gpumem, cores, deltas): global ARM_MIN, ARM_MAX now = datetime.datetime.now() TIME = "%02d:%02d:%02d" % (now.hour, now.minute, now.second) LINE = "%s" % TIME if display_flags["threshold"] and "UFT" in filter: (volts_now, volts_hist) = threshold["under-voltage"] (freq_now, freq_hist) = threshold["arm-capped"] (throt_now, throt_hist) = threshold["throttled"] dVolts = dFreq = dThrottled = " " nVolts = nFreq = nThrottled = 0 if volts_now == 1: dVolts = "U" nVolts = 3 elif volts_hist == 1: dVolts = "u" nVolts = 2 if freq_now == 1: dFreq = "F" nFreq = 3 elif freq_hist == 1: dFreq = "f" nFreq = 2 if throt_now == 1: dThrottled = "T" nThrottled = 3 elif throt_hist == 1: dThrottled = "t" nThrottled = 2 uft = "%s%s%s" % (colourise(dVolts, "%s", 1, 2, 3, False, compare=nVolts), colourise(dFreq, "%s", 1, 2, 3, False, compare=nFreq), colourise(dThrottled, "%s", 1, 2, 3, False, compare=nThrottled)) LINE = addDetailValue(filter, "UFT", uft, LINE) limits = sysinfo["limits"] (arm_min, arm_max) = limits["arm"] (core_min, core_max) = limits["core"] (h264_min, h264_max) = limits["h264"] (v3d_min, v3d_max) = limits["v3d"] (isp_min, isp_max) = limits["isp"] fTC = "%5.2fC" if bcm2385[3] < 100000 else "%5.1fC" fTM = "%5.2fC" if bcm2385[4] < 100000 else "%5.1fC" if display_flags["core_volts"]: LINE = addDetailValue(filter, "Vcore", bcm2385[9], LINE) LINE = addDetailValue(filter, "ARM", colourise(bcm2385[0]/1000000, "%4dMhz", arm_min, None, arm_max, False), LINE) LINE = addDetailValue(filter, "Core", colourise(bcm2385[1]/1000000, "%4dMhz",core_min, None, core_max, False), LINE) LINE = addDetailValue(filter, "H264", colourise(bcm2385[2]/1000000, "%4dMhz", 0, h264_min, h264_max, False), LINE) LINE = addDetailValue(filter, "V3D", colourise(bcm2385[3]/1000000, "%4dMhz", 0, v3d_min, v3d_max, False), LINE) LINE = addDetailValue(filter, "ISP", colourise(bcm2385[4]/1000000, "%4dMhz", 0, isp_min, isp_max, False), LINE) if "TempCore" in filter: LINE = addDetailValue(filter, "TempCore", colourise(bcm2385[5]/1000, fTC, 50.0, 70.0, 80.0, False), LINE) LINE = addDetailValue(filter, "TempCore", colourise(bcm2385[6]/1000, fTM, 50.0, 70.0, 80.0, False), LINE, prefix='(', suffix=')') if display_flags["temp_pmic"] and "TempPMIC" in filter: fTC = "%5.2fC" if bcm2385[7] < 100000 else "%5.1fC" fTM = "%5.2fC" if bcm2385[8] < 100000 else "%5.1fC" LINE = addDetailValue(filter, "TempPMIC", colourise(bcm2385[7], fTC, 50.0, 70.0, 80.0, False), LINE) LINE = addDetailValue(filter, "TempPMIC", colourise(bcm2385[8], fTM, 50.0, 70.0, 80.0, False), LINE, prefix='(', suffix=')') LINE = addDetailValue(filter, "IRQ", colourise(irq[0], "%6s", 500, 2500, 5000, True), LINE) if display_flags["network"]: if display_flags["human_readable"]: LINE = addDetailValue(filter, "RX", colourise(int(network[0]/1024), "%7s", 0.5e3, 2.5e3, 5.0e3, True), LINE) LINE = addDetailValue(filter, "TX", colourise(int(network[1]/1024), "%7s", 0.5e3, 2.5e3, 5.0e3, True), LINE) else: LINE = addDetailValue(filter, "RX", colourise(network[0], "%11s", 0.5e6, 2.5e6, 5.0e6, True), LINE) LINE = addDetailValue(filter, "TX", colourise(network[1], "%11s", 0.5e6, 2.5e6, 5.0e6, True), LINE) if display_flags["utilisation"]: LINE = addDetailValue(filter, "CPUuser", colourise(cpuload[0], "%6.2f", 30, 50, 70, False), LINE) LINE = addDetailValue(filter, "CPUnice", colourise(cpuload[1], "%6.2f", 10, 20, 30, False), LINE) LINE = addDetailValue(filter, "CPUsys", colourise(cpuload[2], "%6.2f", 30, 50, 70, False), LINE) LINE = addDetailValue(filter, "CPUidle", colourise(cpuload[3], "%6.2f", 70, 50, 30, False), LINE) LINE = addDetailValue(filter, "CPUiowt", colourise(cpuload[4], "%6.2f", 2, 5, 10, False), LINE) LINE = addDetailValue(filter, "CPUirq", colourise(cpuload[5], "%6.2f", 2, 5, 10, False), LINE) LINE = addDetailValue(filter, "CPUs/irq", colourise(cpuload[6], "%6.2f", 7.5, 15, 20, False), LINE) LINE = addDetailValue(filter, "CPUtotal", colourise(cpuload[7], "%6.2f", 30, 50, 70, False), LINE) if display_flags["sutilisation"]: LINE = addDetailValue(filter, "CPUuser", colourise(cpuload[0], "%6.2f", 30, 50, 70, False), LINE) LINE = addDetailValue(filter, "CPUnice", colourise(cpuload[1], "%6.2f", 10, 20, 30, False), LINE) LINE = addDetailValue(filter, "CPUsys", colourise(cpuload[2], "%6.2f", 30, 50, 70, False), LINE) LINE = addDetailValue(filter, "CPUtotal", colourise(cpuload[3], "%6.2f", 30, 50, 70, False), LINE) if display_flags["cpu_cores"] and "CPU" in filter: for core in cores: LINE = addDetailValue(filter, "CPU", colourise(core[1], "%6.2f", 30, 50, 70, False), LINE) if display_flags["gpu_malloc"]: data = gpumem["malloc"] LINE = addDetailValue(filter, "GPUfree", colourise(data[0], "%4s", 70, 50, 30, False, compare=data[2]), LINE) LINE = addDetailValue(filter, "GPUfree", colourise(data[2], "%3d%%", 70, 50, 30, False, compare=data[2]), LINE, prefix='(', suffix=')') if display_flags["gpu_reloc"]: data = gpumem["reloc"] LINE = addDetailValue(filter, "GPUfree", colourise(data[0], "%4s", 70, 50, 30, False, compare=data[2]), LINE) LINE = addDetailValue(filter, "GPUfree", colourise(data[2], "%3d%%", 70, 50, 30, False, compare=data[2]), LINE, prefix='(', suffix=')') if display_flags["cpu_mem"]: if display_flags["human_readable"]: data = "%s / %s" % (colourise(int(memory[1]/1024), "%9s", 60, 75, 85, True, compare=memory[2]), colourise(memory[2], "%4.1f%%", 60, 75, 85, False, compare=memory[2])) else : data = "%s / %s" % (colourise(memory[1], "%9s", 60, 75, 85, True, compare=memory[2]), colourise(memory[2], "%4.1f%%", 60, 75, 85, False, compare=memory[2])) LINE = addDetailValue(filter, "MEMfree", data, LINE) # Swap memory if display_flags["swap"] and memory[4] is not None: LINE = addDetailValue(filter, "MEMfree", colourise(memory[4], "%4.1f%%", 1, 5, 15, False, compare=memory[4]), LINE, padding='', prefix='(', suffix=')') if display_flags["deltas"] and "MEMdelta" in filter: dmem = deltas[0] dgpu = deltas[1] if dmem < 0: cmem = 2 elif dmem > 0: cmem = 1 else: cmem = 0 if dgpu < 0: cgpu = 2 elif dgpu > 0: cgpu = 1 else: cgpu = 0 LINE = addDetailValue(filter, "MEMdelta", colourise(dgpu, "%12s", 1, None, 2, True, compare=cgpu, addSign=True), LINE) LINE = addDetailValue(filter, "MEMdelta", colourise(dmem, "%10s", 1, None, 2, True, compare=cmem, addSign=True), LINE) if display_flags["accumulated"] and "MEMaccum" in filter: dmem = deltas[2] dgpu = deltas[3] if dmem < 0: cmem = 2 elif dmem > 0: cmem = 1 else: cmem = 0 if dgpu < 0: cgpu = 2 elif dgpu > 0: cgpu = 1 else: cgpu = 0 LINE = addDetailValue(filter, "MEMaccum", colourise(dgpu, "%12s", 1, None, 2, True, compare=cgpu, addSign=True), LINE) LINE = addDetailValue(filter, "MEMaccum", colourise(dmem, "%10s", 1, None, 2, True, compare=cmem, addSign=True), LINE) printn("\n%s" % LINE) def ShowHelp(): print("Usage: %s [c|m] [d#] [H#] [i ] [k] [L|N|M] [o[-+]col,...] [y|Y] [x|X|r|R] [p|P] [T] [t] [g|G] [f|F] [D][A] [s|S] [q|Q] [V|U|W|C] [Z] [h]" % os.path.basename(__file__)) print() print("c Colourise output (white: minimal load or usage, then ascending through green, amber and red).") print("m Monochrome output (no colourise)") print("d # Specify interval (in seconds) between each iteration - default is 2") print("H # Header every n iterations (0 = no header, default is 30)") print("J # Exit after n iterations (0 = no auto exit (default))") print("i iface Monitor network interface other than the default eth0/enx or wlan0, eg. br1") print("k Show RX/TX and Memory stats in human-friendly units (kB and MB respectively)") print("L Run at lowest priority (nice +20) - default") print("N Run at normal priority (nice 0)") print("M Run at maximum priority (nice -20)") print("o cols Comma delimited list of columns to display. Prefix column name with - to hide a column, and + to add a column. Use no prefix to replace all default columns. Column names are case-sensitive.") print(" eg. \"-o-RX,-TX\" to hide both RX and TX, while continuing to show all other default columns.") print(" eg. \"-o+V3D,+ISP,-H264\" to show V3D and ISP columns, hide H264, and continue to show all other default columns.") print(" eg. \"-oRX,TX\" to show only RX and TX (ignore other -col/+col definitions, and disable default columns).") print(" Available columns: %s" % ", ".join(DEFAULT_COLS_FILTER + EXTRA_COLS_FILTER)) print("y/Y Do (y)/don't (Y) show threshold event flags (U=under-voltage, F=ARM freq capped, T=currently throttled, lowercase if event has occurred in the past") print("r/R Do (r)/don't (R) monitor simple CPU load and memory usage stats (not compatible with x/X)") print("x/X Do (x)/don't (X) monitor detailed CPU load and memory usage stats (not compatible with r/R)") print("p/P Do (p)/don't (P) monitor individual core load stats (when core count > 1)") print("g/G Do (g)/don't (G) monitor additional GPU memory stats (reloc memory)") print("f/F Do (f)/don't (F) monitor additional GPU memory stats (malloc memory)") print("s/S Do (s)/don't (S) include any available swap memory when calculating memory statistics") print("q/Q Do (q)/don't (Q) suppress configuraton information") print("e/E Do (e)/don't (E) show core voltage") print("D Show delta memory - negative: memory allocated, positive: memory freed") print("A Show accumulated delta memory - negative: memory allocated, positive: memory freed") print("T Maximum temperature is normally capped at 85C - use this option to disable temperature cap") print("t Show PMIC temperature (if available, ignore if not)") print() print("V Check version") print("U Update to latest version if an update is available") print("W Force update to latest version") print("C Disable auto-update") print() print("Z Ignore any default configuration") print() print("h Print this help") print() print("Set default properties in ~/.bcmstat.conf or ~/.config/bcmstat.conf") print() print("Note: Default behaviour is to run at lowest possible priority (L) unless N or M specified.") #=================== def checkVersion(show_version=False): global GITHUB, VERSION (remoteVersion, remoteHash) = get_latest_version() if show_version: printout("Current Version: v%s" % VERSION) printout("Latest Version: %s" % ("v" + remoteVersion if remoteVersion else "Unknown")) printout("") if remoteVersion and remoteVersion > VERSION: printout("A new version of this script is available - use the \"U\" option to automatically apply update.") printout("") if show_version: url = GITHUB.replace("//raw.","//").replace("/master","/blob/master") printout("Full changelog: %s/CHANGELOG.md" % url) def downloadLatestVersion(args, autoupdate=False, forceupdate=False): global GITHUB, VERSION (remoteVersion, remoteHash) = get_latest_version() if autoupdate and (not remoteVersion or remoteVersion <= VERSION): return False if not remoteVersion: printerr("FATAL: Unable to determine version of the latest file, check internet and github.com are available.") return if not forceupdate and remoteVersion <= VERSION: printerr("Current version is already up to date - no update required.") return try: response = urllib2.urlopen("%s/%s" % (GITHUB, "bcmstat.sh")) data = response.read() except Exception as e: if autoupdate: return False printerr("Exception in downloadLatestVersion(): %s" % e) printerr("FATAL: Unable to download latest version, check internet and github.com are available.") return digest = hashlib.md5() digest.update(data) if (digest.hexdigest() != remoteHash): if autoupdate: return False printerr("FATAL: Checksum of new version is incorrect, possibly corrupt download - abandoning update.") return path = os.path.realpath(__file__) dir = os.path.dirname(path) if os.path.exists("%s%s.git" % (dir, os.sep)): printerr("FATAL: Might be updating version in git repository... Abandoning update!") return try: THISFILE = open(path, "wb") THISFILE.write(data) THISFILE.close() except: if autoupdate: printout("NOTICE - A new version (v%s) of this script is available." % remoteVersion) printout("NOTICE - Use the \"U\" option to apply update.") else: printerr("FATAL: Unable to update current file, check you have write access") return False printout("Successfully updated from v%s to v%s" % (VERSION, remoteVersion)) return True def get_latest_version(): global GITHUB return get_latest_version_ex("%s/%s" % (GITHUB, "VERSION")) def get_latest_version_ex(url, headers=None, checkerror=True): GLOBAL_TIMEOUT = socket.getdefaulttimeout() ITEMS = (None, None) try: socket.setdefaulttimeout(5.0) if headers: opener = urllib2.build_opener() opener.addheaders = headers response = opener.open(url) else: response = urllib2.urlopen(url) if sys.version_info >= (3, 0): data = response.read().decode("utf-8") else: data = response.read() items = data.replace("\n","").split(" ") if len(items) == 2: ITEMS = items else: if checkerror: printerr("Bogus data in get_latest_version_ex(): url [%s], data [%s]" % (url, data)) except Exception as e: if checkerror: printerr("Exception in get_latest_version_ex(): url [%s], text [%s]" % (url, e)) socket.setdefaulttimeout(GLOBAL_TIMEOUT) return ITEMS # # Download new version if available, then replace current # process - os.execl() doesn't return. # # Do nothing if newer version not available. # def autoUpdate(args): if downloadLatestVersion(args, autoupdate=True): argv = sys.argv argv.append("C") os.execl(sys.executable, sys.executable, *argv) def main(args): global COLOUR, SUDO, LIMIT_TEMP global PEAKVALUES global VCGENCMD_GET_MEM HARDWARE = RPIHardware() INTERFACE = getDefaultInterface() DELAY = 2 HDREVERY = 30 QEVERY = 0 COLOUR = True QUIET = False NICE_ADJUST = +20 INCLUDE_SWAP = True COLUMN_FILTER = list(DEFAULT_COLS_FILTER) STATS_THRESHOLD = False STATS_THRESHOLD_CLEAR = False STATS_WITH_VOLTS = False STATS_WITH_PMIC_TEMP = False STATS_CPU_MEM = False STATS_UTILISATION = False SIMPLE_UTILISATION = False STATS_CPU_CORE= False STATS_GPU_R = False STATS_GPU_M = False STATS_DELTAS = False STATS_ACCUMULATED = False HUMAN_READABLE = False CHECK_UPDATE = True IGNORE_DEFAULTS = False # Pre-process command line args to determine if we should # ignored the stored defaults VALUE = False for x in " ".join(args): if x == " ": VALUE = False continue if not (VALUE or (x >= "0" and x <= "9")): if x == "Z": IGNORE_DEFAULTS = True break VALUE = x in ["i", "d", "h"] oargs = args # Read default settings from config file # Can be overidden by command line. if IGNORE_DEFAULTS == False: config1 = os.path.expanduser("~/.bcmstat.conf") config2 = os.path.expanduser("~/.config/bcmstat.conf") if os.path.exists(config1): args.insert(0, readfile(config1)) elif os.path.exists(config2): args.insert(0, readfile(config2)) # Crude attempt at argument parsing as I don't want to use argparse # but instead try and keep it vaguely more shell-like, ie. -xcd10 # rather than -x -c -d 10 etc. argp = [("", "")] i = 0 VALUE = False for x in " ".join(args): if x == " ": VALUE = False continue if VALUE or (x >= "0" and x <= "9"): t = (argp[i][0], "%s%s" % (argp[i][1], x)) argp[i] = t else: argp.append((x,"")) VALUE = x in ["i", "o", "d", "h"] i += 1 del argp[0] for arg in argp: a1 = arg[0] a2 = arg[1] if a1 == "c": COLOUR = True elif a1 == "m": COLOUR = False elif a1 == "d": DELAY = int(a2) elif a1 == "H": HDREVERY = int(a2) elif a1 == "J": QEVERY = int(a2) elif a1 == "i": INTERFACE = a2 elif a1 == "o": newCols = [] invalidCols = [] ALL_COLS = DEFAULT_COLS_FILTER + EXTRA_COLS_FILTER for column in a2.split(","): if column: if column.startswith("-"): colname = column[1:] if colname in COLUMN_FILTER: COLUMN_FILTER.remove(colname) elif column.startswith("+"): colname = column[1:] if colname not in COLUMN_FILTER: COLUMN_FILTER.append(colname) else: colname = column newCols.append(column) if colname and colname not in ALL_COLS: invalidCols.append(colname) if invalidCols: print("Unknown column(s) specified: %s" % ", ".join(sorted(set(invalidCols)))) sys.exit(2) if newCols: COLUMN_FILTER = newCols elif a1 == "L": NICE_ADJUST = +20 elif a1 == "N": NICE_ADJUST = 0 elif a1 == "M": NICE_ADJUST = -20 elif a1 == "e": STATS_WITH_VOLTS = True elif a1 == "E": STATS_WITH_VOLTS = False elif a1 == "g": STATS_GPU_R = True elif a1 == "G": STATS_GPU_R = False elif a1 == "f": STATS_GPU_M = True elif a1 == "F": STATS_GPU_M = False elif a1 == "y": STATS_THRESHOLD = True elif a1 == "Y": STATS_THRESHOLD = False elif a1 == "r": STATS_CPU_MEM = True SIMPLE_UTILISATION = True STATS_UTILISATION = False elif a1 == "R": STATS_CPU_MEM = False SIMPLE_UTILISATION = False elif a1 == "x": STATS_CPU_MEM = True SIMPLE_UTILISATION = False STATS_UTILISATION = True elif a1 == "X": STATS_CPU_MEM = False STATS_UTILISATION = False elif a1 == "p": STATS_CPU_CORE = True elif a1 == "P": STATS_CPU_CORE = False elif a1 == "T": LIMIT_TEMP = False elif a1 == "t": STATS_WITH_PMIC_TEMP = True elif a1 == "D": STATS_DELTAS = True elif a1 == "A": STATS_ACCUMULATED = True elif a1 == "k": HUMAN_READABLE = True elif a1 == "s": INCLUDE_SWAP = True elif a1 == "S": INCLUDE_SWAP = False elif a1 == "q": QUIET = True elif a1 == "Q": QUIET = False elif a1 == "V": checkVersion(True) return elif a1 == "U": downloadLatestVersion(oargs, forceupdate=False) return elif a1 == "W": downloadLatestVersion(oargs, forceupdate=True) return elif a1 == "C": CHECK_UPDATE = False elif a1 == "h": ShowHelp() return elif a1 in ["-", "Z"]: pass else: printn("Sorry, don't understand option [%s] - exiting" % a1) sys.exit(2) if CHECK_UPDATE: path = os.path.realpath(__file__) dir = os.path.dirname(path) if os.access(dir, os.W_OK): autoUpdate(oargs) # Do we need sudo to raise process priority or run vcdbg? if getpass.getuser() != "root": SUDO = "sudo " # Find out where vcgencmd/vcdbg binaries are... find_vcgencmd_vcdbg() SWAP_ENABLED = (int(grep("SwapTotal", readfile("/proc/meminfo"), field=1, defaultvalue="0")) != 0) # Renice self if NICE_ADJUST < 0: PRIO_D = "maximum" elif NICE_ADJUST == 0: PRIO_D = "normal" else: PRIO_D = "lowest" try: NICE_V = os.nice(NICE_ADJUST) except OSError: runcommand("%srenice -n %d -p %d" % (SUDO, NICE_ADJUST, os.getpid())) NICE_V = os.nice(0) commands = vcgencmd("commands")[1:-1].split(", ") if STATS_THRESHOLD: if "get_throttled" in commands: if vcgencmd("get_throttled 0x0").find("error_msg") == -1: STATS_THRESHOLD_CLEAR = True else: print("WARNING: Threshold query not supported by current firmware - option will be disabled") STATS_THRESHOLD = False # Collect basic system configuration sysinfo = getsysinfo(HARDWARE) STATS_CPU_CORE = False if sysinfo["nproc"] < 2 else STATS_CPU_CORE if not QUIET: ShowConfig(NICE_V, PRIO_D, sysinfo, args) if STATS_GPU_M and not VCGENCMD_GET_MEM: msg="WARNING: malloc gpu memory stats (f) require firmware with a build date of 18 Jun 2014 (or later) - disabling" if QUIET: printerr("%s" % msg) else: printerr("\n\n%s" % msg, newLine=False) STATS_GPU_M = False # -Delta- -Current- -Previous- IRQ = [(0, None), (0, None), (0, None)] NET = [(0, None), (0, None), (0, None)] PROC= [(0, None), (0, None), (0, None)] CPU = [(0, None), (0, None), (0, None)] BCM = [(0, None), (0, None), (0, None)] MEM = [(0, None), (0, None), (0, None)] GPU = [(0, None), (0, None), (0, None)] CORE= [(0, None), (0, None), (0, None)] UFT = [(0, None), (0, None), (0, None)] DELTAS=[(0, None), (0, None), (0, None)] if STATS_THRESHOLD: HARDWARE.GetThresholdValues(UFT, COLUMN_FILTER, STATS_THRESHOLD_CLEAR) getBCM283X(BCM, COLUMN_FILTER, STATS_WITH_VOLTS, STATS_WITH_PMIC_TEMP) if BCM[1][1][7] == None: STATS_WITH_PMIC_TEMP = False getIRQ(IRQ, COLUMN_FILTER, sysinfo) getNetwork(NET, COLUMN_FILTER, INTERFACE) STATS_NETWORK = (NET[1][1] != "") if STATS_DELTAS or STATS_ACCUMULATED: STATS_CPU_MEM = True STATS_GPU_R = True if STATS_CPU_CORE or STATS_UTILISATION or SIMPLE_UTILISATION: getProcStats(PROC, COLUMN_FILTER) if STATS_CPU_MEM: getMemory(MEM, COLUMN_FILTER, (SWAP_ENABLED and INCLUDE_SWAP)) if STATS_GPU_R or STATS_GPU_M: getGPUMem(GPU, COLUMN_FILTER, STATS_GPU_R, STATS_GPU_M) if STATS_DELTAS or STATS_ACCUMULATED: getMemDeltas(DELTAS, COLUMN_FILTER, MEM, GPU) count = HDREVERY tcount = 0 firsthdr = True display_flags = {"threshold": STATS_THRESHOLD, "network": STATS_NETWORK, "cpu_mem": STATS_CPU_MEM, "core_volts": STATS_WITH_VOLTS, "human_readable": HUMAN_READABLE, "utilisation": STATS_UTILISATION, "sutilisation": SIMPLE_UTILISATION, "cpu_cores": STATS_CPU_CORE, "gpu_reloc": STATS_GPU_R, "gpu_malloc": STATS_GPU_M, "swap": (SWAP_ENABLED and INCLUDE_SWAP), "deltas": STATS_DELTAS, "accumulated": STATS_ACCUMULATED, "temp_pmic": STATS_WITH_PMIC_TEMP} #Store peak values PEAKVALUES = {"01#IRQ":0, "02#RX":0, "03#TX":0} if STATS_THRESHOLD: PEAKVALUES.update({"04#UVOLT":0, "05#FCAPPED":0, "06#THROTTLE":0}) while [ True ]: if HDREVERY != 0 and count >= HDREVERY: if not QUIET or not firsthdr: printn("\n\n") ShowHeadings(COLUMN_FILTER, display_flags, sysinfo) firsthdr = False count = 0 count += 1 tcount += 1 if STATS_THRESHOLD: HARDWARE.GetThresholdValues(UFT, COLUMN_FILTER, STATS_THRESHOLD_CLEAR) getBCM283X(BCM, COLUMN_FILTER, STATS_WITH_VOLTS, STATS_WITH_PMIC_TEMP) getIRQ(IRQ, COLUMN_FILTER, sysinfo) if STATS_NETWORK: getNetwork(NET, COLUMN_FILTER, INTERFACE) if STATS_CPU_CORE or STATS_UTILISATION or SIMPLE_UTILISATION: getProcStats(PROC, COLUMN_FILTER) if STATS_CPU_CORE: getCoreStats(CORE, COLUMN_FILTER, PROC) if STATS_UTILISATION: getCPULoad(CPU, COLUMN_FILTER, PROC, sysinfo) if SIMPLE_UTILISATION: getSimpleCPULoad(CPU, COLUMN_FILTER, PROC, sysinfo) if STATS_CPU_MEM: getMemory(MEM, COLUMN_FILTER, (SWAP_ENABLED and INCLUDE_SWAP)) if STATS_GPU_R or STATS_GPU_M: getGPUMem(GPU, COLUMN_FILTER, STATS_GPU_R, STATS_GPU_M) if STATS_DELTAS or STATS_ACCUMULATED: getMemDeltas(DELTAS, COLUMN_FILTER, MEM, GPU) ShowStats(COLUMN_FILTER, display_flags, sysinfo, UFT[0][1], BCM[0][1], IRQ[0][1], NET[0][1], CPU[0][1], MEM[0][1], GPU[0][1], CORE[0][1], DELTAS[0][1]) n = {} n["01#IRQ"] = IRQ[0][1][0] if IRQ[0][1][0] > PEAKVALUES["01#IRQ"] else PEAKVALUES["01#IRQ"] if STATS_NETWORK: n["02#RX"] = NET[0][1][0] if NET[0][1][0] > PEAKVALUES["02#RX"] else PEAKVALUES["02#RX"] n["03#TX"] = NET[0][1][1] if NET[0][1][1] > PEAKVALUES["03#TX"] else PEAKVALUES["03#TX"] if STATS_THRESHOLD: n["04#UVOLT"] = PEAKVALUES["04#UVOLT"] + UFT[0][1]["under-voltage"][0] n["05#FCAPPED"] = PEAKVALUES["05#FCAPPED"] + UFT[0][1]["arm-capped"][0] n["06#THROTTLE"] = PEAKVALUES["06#THROTTLE"] + UFT[0][1]["throttled"][0] PEAKVALUES = n if QEVERY > 0 and tcount >= QEVERY: raise KeyboardInterrupt time.sleep(DELAY) if __name__ == "__main__": try: PEAKVALUES = None main(sys.argv[1:]) except (KeyboardInterrupt, SystemExit) as e: print() if PEAKVALUES: line = "" for item in sorted(PEAKVALUES): line = "%s%s%s: %s" % (line, (", " if line else ""), item[3:], PEAKVALUES[item]) print("Peak Values: %s" % line) if type(e) == SystemExit: sys.exit(int(str(e)))