1800 lines
62 KiB
Bash
Executable file
1800 lines
62 KiB
Bash
Executable file
#!/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)))
|