insert all files for the moment
This commit is contained in:
373
argondashboard.py
Executable file
373
argondashboard.py
Executable file
@@ -0,0 +1,373 @@
|
|||||||
|
#!/bin/python3
|
||||||
|
|
||||||
|
import time
|
||||||
|
import os
|
||||||
|
import sys
|
||||||
|
|
||||||
|
import signal
|
||||||
|
import curses
|
||||||
|
|
||||||
|
|
||||||
|
sys.path.append("/etc/argon/")
|
||||||
|
from argonsysinfo import *
|
||||||
|
from argonregister import *
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
############
|
||||||
|
# Constants
|
||||||
|
############
|
||||||
|
COLORPAIRID_DEFAULT=1
|
||||||
|
COLORPAIRID_LOGO=2
|
||||||
|
COLORPAIRID_DEFAULTINVERSE=3
|
||||||
|
COLORPAIRID_ALERT=4
|
||||||
|
COLORPAIRID_WARNING=5
|
||||||
|
COLORPAIRID_GOOD=6
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
INPUTREFRESHMS=100
|
||||||
|
DISPLAYREFRESHMS=5000
|
||||||
|
UPS_LOGFILE="/dev/shm/upslog.txt"
|
||||||
|
|
||||||
|
|
||||||
|
###################
|
||||||
|
# Display Elements
|
||||||
|
###################
|
||||||
|
|
||||||
|
def displaydatetime(stdscr):
|
||||||
|
try:
|
||||||
|
curtimenow = time.localtime()
|
||||||
|
|
||||||
|
stdscr.addstr(1, 1, time.strftime("%A", curtimenow), curses.color_pair(COLORPAIRID_DEFAULT))
|
||||||
|
stdscr.addstr(2, 1, time.strftime("%b %d,%Y", curtimenow), curses.color_pair(COLORPAIRID_DEFAULT))
|
||||||
|
stdscr.addstr(3, 1, time.strftime("%I:%M%p", curtimenow), curses.color_pair(COLORPAIRID_DEFAULT))
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
def displayipbattery(stdscr):
|
||||||
|
try:
|
||||||
|
displaytextright(stdscr,1, argonsysinfo_getip()+" ", COLORPAIRID_DEFAULT)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
status = ""
|
||||||
|
level = ""
|
||||||
|
outobj = {}
|
||||||
|
# Load status
|
||||||
|
fp = open(UPS_LOGFILE, "r")
|
||||||
|
logdata = fp.read()
|
||||||
|
alllines = logdata.split("\n")
|
||||||
|
ctr = 0
|
||||||
|
while ctr < len(alllines):
|
||||||
|
tmpval = alllines[ctr].strip()
|
||||||
|
curinfo = tmpval.split(":")
|
||||||
|
if len(curinfo) > 1:
|
||||||
|
tmpattrib = curinfo[0].lower().split(" ")
|
||||||
|
# The rest are assumed to be value
|
||||||
|
outobj[tmpattrib[0]] = tmpval[(len(curinfo[0])+1):].strip()
|
||||||
|
ctr = ctr + 1
|
||||||
|
|
||||||
|
# Map to data
|
||||||
|
try:
|
||||||
|
statuslist = outobj["power"].lower().split(" ")
|
||||||
|
if statuslist[0] == "battery":
|
||||||
|
tmp_charging = 0
|
||||||
|
else:
|
||||||
|
tmp_charging = 1
|
||||||
|
tmp_battery = int(statuslist[1].replace("%",""))
|
||||||
|
|
||||||
|
colorpairidx = COLORPAIRID_DEFAULT
|
||||||
|
if tmp_charging:
|
||||||
|
if tmp_battery > 99:
|
||||||
|
status="Plugged"
|
||||||
|
level=""
|
||||||
|
else:
|
||||||
|
status="Charging"
|
||||||
|
level=str(tmp_battery)+"%"
|
||||||
|
else:
|
||||||
|
status="Battery"
|
||||||
|
level=str(tmp_battery)+"%"
|
||||||
|
if tmp_battery <= 20:
|
||||||
|
colorpairidx = COLORPAIRID_ALERT
|
||||||
|
elif tmp_battery <= 50:
|
||||||
|
colorpairidx = COLORPAIRID_WARNING
|
||||||
|
else:
|
||||||
|
colorpairidx = COLORPAIRID_GOOD
|
||||||
|
|
||||||
|
displaytextright(stdscr,2, status+" ", colorpairidx)
|
||||||
|
displaytextright(stdscr,3, level+" ", colorpairidx)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
def displayramcpu(stdscr, refcpu, rowstart, colstart):
|
||||||
|
curusage_b = argonsysinfo_getcpuusagesnapshot()
|
||||||
|
try:
|
||||||
|
outputlist = []
|
||||||
|
tmpraminfo = argonsysinfo_getram()
|
||||||
|
outputlist.append({"title": "ram ", "value": tmpraminfo[1]+" "+tmpraminfo[0]+" Free"})
|
||||||
|
|
||||||
|
for cpuname in refcpu:
|
||||||
|
if cpuname == "cpu":
|
||||||
|
continue
|
||||||
|
if refcpu[cpuname]["total"] == curusage_b[cpuname]["total"]:
|
||||||
|
outputlist.append({"title": cpuname, "value": "Loading"})
|
||||||
|
else:
|
||||||
|
total = curusage_b[cpuname]["total"]-refcpu[cpuname]["total"]
|
||||||
|
idle = curusage_b[cpuname]["idle"]-refcpu[cpuname]["idle"]
|
||||||
|
outputlist.append({"title": cpuname, "value": str(int(100*(total-idle)/(total)))+"% Used"})
|
||||||
|
displaytitlevaluelist(stdscr, rowstart, colstart, outputlist)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return curusage_b
|
||||||
|
|
||||||
|
|
||||||
|
def displaytempfan(stdscr, rowstart, colstart):
|
||||||
|
print( f"displaytempfan( {stdscr}, {rowstart}, {colstart} )" )
|
||||||
|
try:
|
||||||
|
outputlist = []
|
||||||
|
try:
|
||||||
|
if bus is not None:
|
||||||
|
fanspeed = argonregister_getfanspeed(bus)
|
||||||
|
fanspeedstr = "Off"
|
||||||
|
if fanspeed > 0:
|
||||||
|
fanspeedstr = str(fanspeed)+"%"
|
||||||
|
outputlist.append({"title": "Fan ", "value": fanspeedstr})
|
||||||
|
except:
|
||||||
|
outputlist.append( {"title":"Fan ", "value": 'Error!'})
|
||||||
|
pass
|
||||||
|
# Todo load from config
|
||||||
|
temperature = "C"
|
||||||
|
hddtempctr = 0
|
||||||
|
maxcval = 0
|
||||||
|
mincval = 200
|
||||||
|
|
||||||
|
|
||||||
|
# Get min/max of hdd temp
|
||||||
|
hddtempobj = argonsysinfo_gethddtemp()
|
||||||
|
for curdev in hddtempobj:
|
||||||
|
if hddtempobj[curdev] < mincval:
|
||||||
|
mincval = hddtempobj[curdev]
|
||||||
|
if hddtempobj[curdev] > maxcval:
|
||||||
|
maxcval = hddtempobj[curdev]
|
||||||
|
hddtempctr = hddtempctr + 1
|
||||||
|
|
||||||
|
cpucval = argonsysinfo_getcputemp()
|
||||||
|
if hddtempctr > 0:
|
||||||
|
alltempobj = {"cpu": cpucval,"hdd min": mincval, "hdd max": maxcval}
|
||||||
|
# Update max C val to CPU Temp if necessary
|
||||||
|
if maxcval < cpucval:
|
||||||
|
maxcval = cpucval
|
||||||
|
|
||||||
|
displayrowht = 8
|
||||||
|
displayrow = 8
|
||||||
|
for curdev in alltempobj:
|
||||||
|
if temperature == "C":
|
||||||
|
# Celsius
|
||||||
|
tmpstr = str(alltempobj[curdev])
|
||||||
|
if len(tmpstr) > 4:
|
||||||
|
tmpstr = tmpstr[0:4]
|
||||||
|
else:
|
||||||
|
# Fahrenheit
|
||||||
|
tmpstr = str(32+9*(alltempobj[curdev])/5)
|
||||||
|
if len(tmpstr) > 5:
|
||||||
|
tmpstr = tmpstr[0:5]
|
||||||
|
if len(curdev) <= 3:
|
||||||
|
outputlist.append({"title": curdev.upper(), "value": tmpstr +temperature})
|
||||||
|
else:
|
||||||
|
outputlist.append({"title": curdev.upper(), "value": tmpstr +temperature})
|
||||||
|
else:
|
||||||
|
maxcval = cpucval
|
||||||
|
if temperature == "C":
|
||||||
|
# Celsius
|
||||||
|
tmpstr = str(cpucval)
|
||||||
|
if len(tmpstr) > 4:
|
||||||
|
tmpstr = tmpstr[0:4]
|
||||||
|
else:
|
||||||
|
# Fahrenheit
|
||||||
|
tmpstr = str(32+9*(cpucval)/5)
|
||||||
|
if len(tmpstr) > 5:
|
||||||
|
tmpstr = tmpstr[0:5]
|
||||||
|
|
||||||
|
outputlist.append({"title": "Temp", "value": tmpstr +temperature})
|
||||||
|
displaytitlevaluelist(stdscr, rowstart, colstart, outputlist)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
def displaystorage(stdscr, rowstart, colstart):
|
||||||
|
try:
|
||||||
|
outputlist = []
|
||||||
|
tmpobj = argonsysinfo_listhddusage()
|
||||||
|
for curdev in tmpobj:
|
||||||
|
outputlist.append({"title": curdev, "value": argonsysinfo_kbstr(tmpobj[curdev]['total'])+ " "+ str(int(100*tmpobj[curdev]['used']/tmpobj[curdev]['total']))+"% Used" })
|
||||||
|
displaytitlevaluelist(stdscr, rowstart, colstart, outputlist)
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
|
##################
|
||||||
|
# Helpers
|
||||||
|
##################
|
||||||
|
|
||||||
|
# Initialize I2C Bus
|
||||||
|
bus = argonregister_initializebusobj()
|
||||||
|
|
||||||
|
def handle_resize(signum, frame):
|
||||||
|
# TODO: Not working?
|
||||||
|
curses.update_lines_cols()
|
||||||
|
# Ideally redraw here
|
||||||
|
|
||||||
|
def displaytitlevaluelist(stdscr, rowstart, leftoffset, curlist):
|
||||||
|
rowidx = rowstart
|
||||||
|
while rowidx < curses.LINES and len(curlist) > 0:
|
||||||
|
curline = ""
|
||||||
|
tmpitem = curlist.pop(0)
|
||||||
|
curline = tmpitem["title"]+": "+str(tmpitem["value"])
|
||||||
|
|
||||||
|
stdscr.addstr(rowidx, leftoffset, curline)
|
||||||
|
rowidx = rowidx + 1
|
||||||
|
|
||||||
|
|
||||||
|
def displaytextcentered(stdscr, rownum, strval, colorpairidx = COLORPAIRID_DEFAULT):
|
||||||
|
leftoffset = 0
|
||||||
|
numchars = len(strval)
|
||||||
|
if numchars < 1:
|
||||||
|
return
|
||||||
|
elif (numchars > curses.COLS):
|
||||||
|
leftoffset = 0
|
||||||
|
strval = strval[0:curses.COLS]
|
||||||
|
else:
|
||||||
|
leftoffset = (curses.COLS - numchars)>>1
|
||||||
|
|
||||||
|
stdscr.addstr(rownum, leftoffset, strval, curses.color_pair(colorpairidx))
|
||||||
|
|
||||||
|
|
||||||
|
def displaytextright(stdscr, rownum, strval, colorpairidx = COLORPAIRID_DEFAULT):
|
||||||
|
leftoffset = 0
|
||||||
|
numchars = len(strval)
|
||||||
|
if numchars < 1:
|
||||||
|
return
|
||||||
|
elif (numchars > curses.COLS):
|
||||||
|
leftoffset = 0
|
||||||
|
strval = strval[0:curses.COLS]
|
||||||
|
else:
|
||||||
|
leftoffset = curses.COLS - numchars
|
||||||
|
|
||||||
|
stdscr.addstr(rownum, leftoffset, strval, curses.color_pair(colorpairidx))
|
||||||
|
|
||||||
|
|
||||||
|
def displaylinebreak(stdscr, rownum, colorpairidx = COLORPAIRID_DEFAULTINVERSE):
|
||||||
|
strval = " "
|
||||||
|
while len(strval) < curses.COLS:
|
||||||
|
strval = strval + " "
|
||||||
|
stdscr.addstr(rownum, 0, strval, curses.color_pair(colorpairidx))
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
##################
|
||||||
|
# Main Loop
|
||||||
|
##################
|
||||||
|
|
||||||
|
def mainloop(stdscr):
|
||||||
|
try:
|
||||||
|
# Set up signal handler
|
||||||
|
signal.signal(signal.SIGWINCH, handle_resize)
|
||||||
|
|
||||||
|
maxloopctr = int(DISPLAYREFRESHMS/INPUTREFRESHMS)
|
||||||
|
sleepsecs = INPUTREFRESHMS/1000
|
||||||
|
|
||||||
|
loopctr = maxloopctr
|
||||||
|
loopmode = True
|
||||||
|
|
||||||
|
stdscr = curses.initscr()
|
||||||
|
|
||||||
|
# Turn off echoing of keys, and enter cbreak mode,
|
||||||
|
# where no buffering is performed on keyboard input
|
||||||
|
curses.noecho()
|
||||||
|
curses.cbreak()
|
||||||
|
curses.curs_set(0)
|
||||||
|
curses.start_color()
|
||||||
|
|
||||||
|
#curses.COLOR_BLACK
|
||||||
|
#curses.COLOR_BLUE
|
||||||
|
#curses.COLOR_CYAN
|
||||||
|
#curses.COLOR_GREEN
|
||||||
|
#curses.COLOR_MAGENTA
|
||||||
|
#curses.COLOR_RED
|
||||||
|
#curses.COLOR_WHITE
|
||||||
|
#curses.COLOR_YELLOW
|
||||||
|
|
||||||
|
curses.init_pair(COLORPAIRID_DEFAULT, curses.COLOR_WHITE, curses.COLOR_BLACK)
|
||||||
|
curses.init_pair(COLORPAIRID_LOGO, curses.COLOR_WHITE, curses.COLOR_RED)
|
||||||
|
curses.init_pair(COLORPAIRID_DEFAULTINVERSE, curses.COLOR_BLACK, curses.COLOR_WHITE)
|
||||||
|
curses.init_pair(COLORPAIRID_ALERT, curses.COLOR_RED, curses.COLOR_BLACK)
|
||||||
|
curses.init_pair(COLORPAIRID_WARNING, curses.COLOR_YELLOW, curses.COLOR_BLACK)
|
||||||
|
curses.init_pair(COLORPAIRID_GOOD, curses.COLOR_GREEN, curses.COLOR_BLACK)
|
||||||
|
|
||||||
|
stdscr.nodelay(True)
|
||||||
|
|
||||||
|
refcpu = argonsysinfo_getcpuusagesnapshot()
|
||||||
|
while True:
|
||||||
|
try:
|
||||||
|
key = stdscr.getch()
|
||||||
|
# if key == ord('x') or key == ord('X'):
|
||||||
|
# Any key
|
||||||
|
if key > 0:
|
||||||
|
break
|
||||||
|
except curses.error:
|
||||||
|
# No key was pressed
|
||||||
|
pass
|
||||||
|
|
||||||
|
loopctr = loopctr + 1
|
||||||
|
if loopctr >= maxloopctr:
|
||||||
|
loopctr = 0
|
||||||
|
# Screen refresh loop
|
||||||
|
# Clear screen
|
||||||
|
stdscr.clear()
|
||||||
|
|
||||||
|
displaytextcentered(stdscr, 0, " ", COLORPAIRID_LOGO)
|
||||||
|
displaytextcentered(stdscr, 1, " Argon40 Dashboard ", COLORPAIRID_LOGO)
|
||||||
|
displaytextcentered(stdscr, 2, " ", COLORPAIRID_LOGO)
|
||||||
|
displaytextcentered(stdscr, 3, "Press any key to close")
|
||||||
|
displaylinebreak(stdscr, 5)
|
||||||
|
|
||||||
|
# Display Elements
|
||||||
|
displaydatetime(stdscr)
|
||||||
|
displayipbattery(stdscr)
|
||||||
|
|
||||||
|
# Data Columns
|
||||||
|
rowstart = 7
|
||||||
|
colstart = 20
|
||||||
|
refcpu = displayramcpu(stdscr, refcpu, rowstart, colstart)
|
||||||
|
displaystorage(stdscr, rowstart, colstart+30)
|
||||||
|
displaytempfan(stdscr, rowstart, colstart+60)
|
||||||
|
|
||||||
|
# Main refresh even
|
||||||
|
stdscr.refresh()
|
||||||
|
|
||||||
|
time.sleep(sleepsecs)
|
||||||
|
|
||||||
|
except Exception as initerr:
|
||||||
|
pass
|
||||||
|
|
||||||
|
##########
|
||||||
|
# Cleanup
|
||||||
|
##########
|
||||||
|
|
||||||
|
try:
|
||||||
|
curses.curs_set(1)
|
||||||
|
curses.echo()
|
||||||
|
curses.nocbreak()
|
||||||
|
curses.endwin()
|
||||||
|
except Exception as closeerr:
|
||||||
|
pass
|
||||||
|
|
||||||
|
curses.wrapper(mainloop)
|
||||||
43
fioscript/fio-ran-r4k.job
Normal file
43
fioscript/fio-ran-r4k.job
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
[global]
|
||||||
|
name=fio-rand-r
|
||||||
|
filename=readdatafile
|
||||||
|
rw=randread
|
||||||
|
bs=4K
|
||||||
|
direct=1
|
||||||
|
time_based=1
|
||||||
|
runtime=300
|
||||||
|
group_reporting=1
|
||||||
|
ioengine=libaio
|
||||||
|
|
||||||
|
[job1]
|
||||||
|
size=10g
|
||||||
|
iodepth=64
|
||||||
|
|
||||||
|
[job2]
|
||||||
|
size=10g
|
||||||
|
iodepth=64
|
||||||
|
|
||||||
|
[job3]
|
||||||
|
size=10g
|
||||||
|
iodepth=64
|
||||||
|
|
||||||
|
[job4]
|
||||||
|
size=10g
|
||||||
|
iodepth=64
|
||||||
|
|
||||||
|
[job5]
|
||||||
|
size=10g
|
||||||
|
iodepth=64
|
||||||
|
|
||||||
|
[job6]
|
||||||
|
size=10g
|
||||||
|
iodepth=64
|
||||||
|
|
||||||
|
[job7]
|
||||||
|
size=10g
|
||||||
|
iodepth=64
|
||||||
|
|
||||||
|
[job8]
|
||||||
|
size=10g
|
||||||
|
iodepth=64
|
||||||
|
|
||||||
42
fioscript/fio-ran-w4k.job
Normal file
42
fioscript/fio-ran-w4k.job
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
[global]
|
||||||
|
name=fio-rand-w
|
||||||
|
filename=writedatafile
|
||||||
|
rw=randwrite
|
||||||
|
bs=4K
|
||||||
|
direct=1
|
||||||
|
time_based=1
|
||||||
|
runtime=300
|
||||||
|
group_reporting=1
|
||||||
|
ioengine=libaio
|
||||||
|
|
||||||
|
[job1]
|
||||||
|
size=10g
|
||||||
|
iodepth=64
|
||||||
|
|
||||||
|
[job2]
|
||||||
|
size=10g
|
||||||
|
iodepth=64
|
||||||
|
|
||||||
|
[job3]
|
||||||
|
size=10g
|
||||||
|
iodepth=64
|
||||||
|
|
||||||
|
[job4]
|
||||||
|
size=10g
|
||||||
|
iodepth=64
|
||||||
|
|
||||||
|
[job5]
|
||||||
|
size=10g
|
||||||
|
iodepth=64
|
||||||
|
|
||||||
|
[job6]
|
||||||
|
size=10g
|
||||||
|
iodepth=64
|
||||||
|
|
||||||
|
[job7]
|
||||||
|
size=10g
|
||||||
|
iodepth=64
|
||||||
|
|
||||||
|
[job8]
|
||||||
|
size=10g
|
||||||
|
iodepth=64
|
||||||
43
fioscript/fio-seq-r4k.job
Normal file
43
fioscript/fio-seq-r4k.job
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
[global]
|
||||||
|
name=fio-seq-r
|
||||||
|
filename=readdatafile
|
||||||
|
rw=read
|
||||||
|
bs=4K
|
||||||
|
direct=1
|
||||||
|
time_based=1
|
||||||
|
runtime=300
|
||||||
|
group_reporting=1
|
||||||
|
ioengine=libaio
|
||||||
|
|
||||||
|
[job1]
|
||||||
|
size=10g
|
||||||
|
iodepth=64
|
||||||
|
|
||||||
|
[job2]
|
||||||
|
size=10g
|
||||||
|
iodepth=64
|
||||||
|
|
||||||
|
[job3]
|
||||||
|
size=10g
|
||||||
|
iodepth=64
|
||||||
|
|
||||||
|
[job4]
|
||||||
|
size=10g
|
||||||
|
iodepth=64
|
||||||
|
|
||||||
|
[job5]
|
||||||
|
size=10g
|
||||||
|
iodepth=64
|
||||||
|
|
||||||
|
[job6]
|
||||||
|
size=10g
|
||||||
|
iodepth=64
|
||||||
|
|
||||||
|
[job7]
|
||||||
|
size=10g
|
||||||
|
iodepth=64
|
||||||
|
|
||||||
|
[job8]
|
||||||
|
size=10g
|
||||||
|
iodepth=64
|
||||||
|
|
||||||
42
fioscript/fio-seq-w4k.job
Normal file
42
fioscript/fio-seq-w4k.job
Normal file
@@ -0,0 +1,42 @@
|
|||||||
|
[global]
|
||||||
|
name=fio-seq-w
|
||||||
|
filename=writedatafile
|
||||||
|
rw=write
|
||||||
|
bs=4K
|
||||||
|
direct=1
|
||||||
|
time_based=1
|
||||||
|
runtime=300
|
||||||
|
group_reporting=1
|
||||||
|
ioengine=libaio
|
||||||
|
|
||||||
|
[job1]
|
||||||
|
size=10g
|
||||||
|
iodepth=64
|
||||||
|
|
||||||
|
[job2]
|
||||||
|
size=10g
|
||||||
|
iodepth=64
|
||||||
|
|
||||||
|
[job3]
|
||||||
|
size=10g
|
||||||
|
iodepth=64
|
||||||
|
|
||||||
|
[job4]
|
||||||
|
size=10g
|
||||||
|
iodepth=64
|
||||||
|
|
||||||
|
[job5]
|
||||||
|
size=10g
|
||||||
|
iodepth=64
|
||||||
|
|
||||||
|
[job6]
|
||||||
|
size=10g
|
||||||
|
iodepth=64
|
||||||
|
|
||||||
|
[job7]
|
||||||
|
size=10g
|
||||||
|
iodepth=64
|
||||||
|
|
||||||
|
[job8]
|
||||||
|
size=10g
|
||||||
|
iodepth=64
|
||||||
184
monitor1up.py
Executable file
184
monitor1up.py
Executable file
@@ -0,0 +1,184 @@
|
|||||||
|
#!/usr/bin/python3
|
||||||
|
#
|
||||||
|
# Setup environment and pull in all of the items we need from gpiozero. The
|
||||||
|
# gpiozero library is the new one that supports Raspberry PI 5's (and I suspect
|
||||||
|
# will be new direction for all prior version the RPIi.)
|
||||||
|
#
|
||||||
|
from gpiozero import CPUTemperature
|
||||||
|
import time
|
||||||
|
import os
|
||||||
|
|
||||||
|
class DriveStat:
|
||||||
|
'''
|
||||||
|
DriveStat class -
|
||||||
|
|
||||||
|
This class gets the drive statistics from sysfs for the device passed
|
||||||
|
in. There are several statistics can can be obtained. Note that since
|
||||||
|
all of the data is pulled at the same time, it is upto the caller to
|
||||||
|
make sure all the stats needed are obtained at the same time.
|
||||||
|
|
||||||
|
See: https://www.kernel.org/doc/html/latest/block/stat.html
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
device - the name of the device to track
|
||||||
|
'''
|
||||||
|
|
||||||
|
READ_IOS = 0
|
||||||
|
READ_MERGES = 1
|
||||||
|
READ_SECTORS = 2
|
||||||
|
READ_TICKS = 3
|
||||||
|
WRITE_IOS = 4
|
||||||
|
WRITE_MERGES = 5
|
||||||
|
WRITE_SECTORS = 6
|
||||||
|
WRITE_TICKS = 7
|
||||||
|
IN_FLIGHT = 8
|
||||||
|
IO_TICKS = 9
|
||||||
|
TIME_IN_QUEUE = 10
|
||||||
|
DISCARD_IOS = 11
|
||||||
|
DISCARD_MERGES = 12
|
||||||
|
DISCARD_SECTORS = 13
|
||||||
|
DISCARD_TICS = 14
|
||||||
|
FLUSH_IOS = 15
|
||||||
|
FLUSH_TICKS = 16
|
||||||
|
|
||||||
|
def __init__( self, device:str ):
|
||||||
|
self.last = []
|
||||||
|
self.stats = []
|
||||||
|
self.device = device
|
||||||
|
self._readStats()
|
||||||
|
|
||||||
|
def _readStats( self ):
|
||||||
|
'''
|
||||||
|
Read the disk statistics. The stored statics in sysfs are stored as a single file
|
||||||
|
so that when the data is read, all of the stats correlate to the same time. The data
|
||||||
|
is from the time the device has come online.
|
||||||
|
|
||||||
|
last and set to the old version of the data, and the latest data is stored in stats
|
||||||
|
|
||||||
|
'''
|
||||||
|
try:
|
||||||
|
self.last = self.stats
|
||||||
|
with open( f"/sys/block/{self.device}/stat", "r") as f:
|
||||||
|
stats = f.readline().strip().split(" ")
|
||||||
|
self.stats = [int(l) for l in stats if l]
|
||||||
|
except Exception as e:
|
||||||
|
print( f"Failure reading disk statistics for {device} error {e}" )
|
||||||
|
|
||||||
|
def _getStats( self ) -> list[int]:
|
||||||
|
'''
|
||||||
|
Read the devices statistics from the device,and return it.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
An array containing all of the data colleected about the device.
|
||||||
|
'''
|
||||||
|
self._readStats()
|
||||||
|
if self.last == []:
|
||||||
|
data = self.stats[:]
|
||||||
|
else:
|
||||||
|
data = [ d-self.last[i] for i,d in enumerate( self.stats ) ]
|
||||||
|
return data
|
||||||
|
|
||||||
|
def readAllStats( self ) -> list[int]:
|
||||||
|
'''
|
||||||
|
read all of the drive statisics from sysfs for the device.
|
||||||
|
|
||||||
|
Returns
|
||||||
|
A list of all of the device stats
|
||||||
|
'''
|
||||||
|
return self._getStats()
|
||||||
|
|
||||||
|
def readSectors( self )-> int:
|
||||||
|
return self._getStats()[DriveStat.READ_SECTORS]
|
||||||
|
|
||||||
|
def writeSectors( self ) -> int:
|
||||||
|
return self._getStats()[DriveStat.WRITE_SECTORS]
|
||||||
|
|
||||||
|
def discardSectors( self ) -> int:
|
||||||
|
return self._getStats()[DriveStat.DISCARD_SECTORS]
|
||||||
|
|
||||||
|
def readWriteSectors( self ) -> (int,int):
|
||||||
|
data = self._getStats()
|
||||||
|
return (data[DriveStat.READ_SECTORS],data[DriveStat.WRITE_SECTORS])
|
||||||
|
|
||||||
|
|
||||||
|
def setupTemperatureObject():
|
||||||
|
'''
|
||||||
|
Get a cpu temperature object, and set the min and max range.
|
||||||
|
|
||||||
|
When the ranges are set to the non-default values, if the temperature is
|
||||||
|
less than min_temp we get 0, and when the temperature reaches the max we get
|
||||||
|
a value of 1. This value can be used directly as a duty cycle for the fan.
|
||||||
|
|
||||||
|
Return:
|
||||||
|
A CPU temperature object
|
||||||
|
'''
|
||||||
|
cpuTemp = None
|
||||||
|
try:
|
||||||
|
cpuTemp = CPUTemperature()
|
||||||
|
except Exception as error:
|
||||||
|
log.error( f"Error creating CPU temperature object, error is {error}" )
|
||||||
|
|
||||||
|
return cpuTemp
|
||||||
|
|
||||||
|
def getFanSpeed() -> int:
|
||||||
|
'''
|
||||||
|
Obtain the speed of the fan attached to the CPU. This is accomplished reading
|
||||||
|
the information from sysfs.
|
||||||
|
|
||||||
|
NOTE: There is an assumption that the fan s hanging off of /hwmon/hwmon3. This may
|
||||||
|
or may not be the case in all situations.
|
||||||
|
'''
|
||||||
|
fanSpeed = 0
|
||||||
|
try:
|
||||||
|
command = os.popen( 'cat /sys/devices/platform/cooling_fan/hwmon/*/fan1_input' )
|
||||||
|
fanSpeed = command.read().strip()
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
return int(fanSpeed)
|
||||||
|
|
||||||
|
def getNVMETemp(device : str) -> float:
|
||||||
|
'''
|
||||||
|
Obtain the temperature of the device passed in, using smartctl.
|
||||||
|
|
||||||
|
Parameters :
|
||||||
|
device - A string containing the device name.
|
||||||
|
|
||||||
|
Returns:
|
||||||
|
The temperature as a float
|
||||||
|
'''
|
||||||
|
smartOutRaw = ""
|
||||||
|
try:
|
||||||
|
command = os.popen( f'smartctl -A /dev/{device}' )
|
||||||
|
smartOutRaw = command.read()
|
||||||
|
except Exception as e:
|
||||||
|
return 0
|
||||||
|
finally:
|
||||||
|
command.close()
|
||||||
|
|
||||||
|
smartOut = [ l for l in smartOutRaw.split('\n') if l]
|
||||||
|
for smartAttr in ["Temperature:","194","190"]:
|
||||||
|
try:
|
||||||
|
line = [l for l in smartOut if l.startswith(smartAttr)][0]
|
||||||
|
parts = [p for p in line.replace('\t',' ').split(' ') if p]
|
||||||
|
if smartAttr == "Temperature:":
|
||||||
|
return float(parts[1])
|
||||||
|
else:
|
||||||
|
return float(parts[0])
|
||||||
|
except IndexError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
return float(0.0)
|
||||||
|
|
||||||
|
|
||||||
|
stats = DriveStat( 'nvme0n1' )
|
||||||
|
cpuTemp = setupTemperatureObject()
|
||||||
|
|
||||||
|
while True:
|
||||||
|
print( f"CPU : {cpuTemp.temperature}" )
|
||||||
|
print( f"Fan : {getFanSpeed()}" )
|
||||||
|
print( f"NVME : {getNVMETemp('nvme0n1')}" )
|
||||||
|
data = stats.readWriteSectors()
|
||||||
|
print( f"Read : {data[0]*512}" )
|
||||||
|
print( f"Write: {data[1] *512}" )
|
||||||
|
time.sleep( 1 )
|
||||||
|
|
||||||
Reference in New Issue
Block a user