librpitx/app/bcmstat.sh

1801 lines
62 KiB
Bash
Raw Permalink Normal View History

2020-11-02 21:11:01 +08:00
#!/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] <Model X> 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 <iface>] [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<MAC> 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)))