diff --git a/README.md b/README.md index 56455b2..67cc798 100644 --- a/README.md +++ b/README.md @@ -47,3 +47,29 @@ If for some reason you don't like the changes, run argon-config and uninstall. ``` curl http://download.argon40.com/argoneon.sh | bash ``` + +## argon-status + +``` +usage: argon-status [-h] [-v] [-a] [-c] [-d] [-f] [-i] [-m] [-r] [-s] [-t] [-u] [--hddtemp] + +optional arguments: + -h, --help show this help message and exit + -v, --version Display the version of the argon scripts. + -a, --all Display full status of the Argon EON. + -c, --cpu Display the current CPU utilization. + -d, --devices Display informaton about devices in the EON. + -f, --fan Get current fan speed. + -i, --ip Display currently configured IP addresses. + -m, --memory Display memory utilization on the EON. + -r, --raid Display current state of the raid Array if it exists. + -s, --storage Display information about the storage system. + -t, --temp Display information about the current temperature. + -u, --hdduse Display disk utilization. + --hddtemp Display the temperature of the storage devices. +``` + +When used with no arguments, argon-status will display as if argon-status --devices --ip was used. If you do not wish to have this as a default, set the ARGON_STATUS_DEFAULT to what you wish the default to be, such as +``` +export ARGON_STATUS_DEFAULT="-t --hddtemp -f" +``` diff --git a/argon-status b/argon-status new file mode 100755 index 0000000..1c054cb --- /dev/null +++ b/argon-status @@ -0,0 +1,255 @@ +#!/usr/bin/python3 + +import sys +import os +import time +import math +sys.path.append( "/etc/argon/" ) +from argonsysinfo import * + +import argparse + +# +def printTable(myDict, colList=None, title : str = None ): + """ Pretty print a list of dictionarys (myDict) as a dynamically sized table. + If column names (colList) aren't specified, they will show in random order. + Author: Therry Husson - Use it as you want but don't blame me. + """ + if isinstance(myDict, dict): + myDict = [myDict] + + if title: + print( f"\n{title}") + + if not colList: colList = list(myDict[0].keys() if myDict else []) + myList = [colList] # 1st row = header + for item in myDict: myList.append([str(item[col] if item[col] is not None else '') for col in colList]) + colSize = [max(map(len,col)) for col in zip(*myList)] + colSep = ' | ' + fullWidth = sum(colSize)+((len(colSize)-1)*len(colSep)) + formatStr = colSep.join(["{{:<{}}}".format(i) for i in colSize]) + myList.insert(1,['-' * i for i in colSize]) # Separating line + print('-'*fullWidth) + for item in myList: print( formatStr.format(*item)) + + def centre( string ): + strLen = len(string) + if strLen < fullWidth: + padLen = int((fullWidth-strLen)/2) + if padLen > 0: + string = string.rjust(strLen+padLen) + print( string ) + + if len(myDict) == 0: + centre("* No data to display *") + print( '-'*fullWidth) + +# +def show_storage(): + """ Display the storage devices in the system. These not, devices involved + in a RAID array are NOT displayed, however the RAID device is. + """ + devices = argonsysinfo_listhddusage() + lst = [] + for dev in devices: + lst.append( {"Device": dev + ,"Total": argonsysinfo_kbstr(devices[dev]['total']) + ,"Used": argonsysinfo_kbstr(devices[dev]['used']) + ,"Pct": f"{devices[dev]['percent']}%" + } + ) + printTable( lst, ["Device","Total","Used","Pct"], title="Storage Usage:") + +# +def show_raid(): + """ + If software RAID is setup, report on the status of the RAID sets. If there is + no RAID setup, inform the user. + """ + raidList = argonsysinfo_listraid()['raidlist'] + lst = [] + rebuildExists = False + keys = ['Device', 'Type', 'Size', 'State' ] + for item in raidList: + stateArray = item['info']['state'].split(", ") + if len(stateArray) == 1: + state = stateArray[0] + elif len(stateArray) == 2: + state = stateArray[1] + elif len(stateArray) >= 3: + state = stateArray[2] + else: + state = None + raidDict = {'Device' : item['title'] + ,'Type' : item['info']['raidtype'].upper() + ,'Size' : argonsysinfo_kbstr(item['info']['size']) + ,'State' : state.capitalize() + ,'Rebuild': None + } + if len(item['info']['resync']) > 0: + rebuildExists = True + raidDict['Rebuild'] = item['info']['resync'] + lst.append( raidDict ) + + if rebuildExists: + keys.append("Rebuild") + + if len(lst) > 0: + printTable(lst,keys,title="RAID Arrays:" ) + else: + print( "No RAID Arrays configured!" ) + +# +def show_cpuUtilization(): + """ + Display the current CPU utilization. Not all that helpful as it is simply a + snapshot, and tools such as htop etc work much better. + """ + lst = [{'CPU': d['title'], "%": d["value"]} for d in argonsysinfo_listcpuusage()] + printTable( lst, ['CPU','%'], title = 'CPU Utilization') + +# +def show_cpuTemperature(): + """ + Display the current CPU temperature + """ + rawTemp = argonsysinfo_getcputemp() + ctemp = argonsysinfo_truncateFloat(rawTemp,2) + ftemp = argonsysinfo_convertCtoF(rawTemp,2) + printTable({"C":ctemp,"F":ftemp},title="CPU Temperature:") + +# +def show_ipaddresses(): + """ + Display a list of all Network interfaces configured with IP addresses, with the + exception of any bridge types setup for containers + """ + lst = [{"Interface":item[0],'IP':item[1]} for item in argonsysinfo_getipList()] + printTable(lst,title="IP Addresses:") + +# +def show_hddTemperature(): + """ + Display the current temperatures of any disk devices in the system, note that + this includes the temperature for any NVME device, so you may need to modify your + fan triggers + """ + hddTemp = argonsysinfo_gethddtemp() + lst = [] + for item in hddTemp: + rawTemp = hddTemp[item] + ctemp = argonsysinfo_truncateFloat(rawTemp,1) + ftemp = argonsysinfo_convertCtoF(rawTemp,1) + lst.append( {'Device':item, "C":ctemp, "F":ftemp}) + printTable( lst, title="Storage Temperature:") + +# +def show_fanspeed(): + """ + Display the current fan speed percentage. + """ + printTable({"Speed %" : argonsysinfo_getCurrentFanSpeed()},['Speed %'],title='Fan Speed') + +def show_hddutilization(): + """ + Display the current disk device utilization, this is basically useless, use dstat. + """ + start = time.clock_gettime_ns(time.CLOCK_MONOTONIC) + usage1 = argonsysinfo_diskusage() + time.sleep(1) + usage2 = argonsysinfo_diskusage() + stop = time.clock_gettime_ns(time.CLOCK_MONOTONIC) + + for istop in usage2: + for istart in usage1: + if istop['disk'] == istart['disk']: + istart['readsector'] = istop['readsector'] - istart['readsector'] + istart['writesector'] = istop['writesector'] - istart['writesector'] + span = ((stop - start)/1000000000) + lst = [] + for item in usage1: + readbw = (item['readsector']/2)/span + writebw = (item['writesector']/2)/span + lst.append({'Device':item['disk'], "Read/Sec":argonsysinfo_kbstr(int(readbw)),"Write/Sec":argonsysinfo_kbstr(int(writebw))}) + printTable(lst, title = 'Storage Utilization:' ) +# +def show_all(): + show_storage() + show_raid() + show_hddTemperature() + show_cpuUtilization() + show_cpuTemperature() + show_ipaddresses() + show_fanspeed() + show_memory() + +def show_memory(): + memory = argonsysinfo_getram() + printTable({"Total":memory[1],"Free":memory[0]},title="Memory:") + +# +def print_version(): + print( 'Currently running version 2023.01.15-01') + +# +def setup_arguments(): + parser = argparse.ArgumentParser() + parser.add_argument( '-v', '--version', action='store_true', help='Display the version of the argon scripts.') + parser.add_argument( '-a', '--all', action='store_true', help='Display full status of the Argon EON.') + parser.add_argument( '-c', '--cpu', action='store_true', help='Display the current CPU utilization.') + parser.add_argument( '-d', '--devices', action='store_true', help='Display informaton about devices in the EON.') + parser.add_argument( '-f', '--fan', action='store_true', help='Get current fan speed.') + parser.add_argument( '-i', '--ip', action='store_true', help='Display currently configured IP addresses.') + parser.add_argument( '-m', '--memory', action='store_true', help='Display memory utilization on the EON.') + parser.add_argument( '-r', '--raid', action='store_true', help='Display current state of the raid Array if it exists.') + parser.add_argument( '-s', '--storage', action='store_true', help='Display information about the storage system.') + parser.add_argument( '-t', '--temp', action='store_true', help='Display information about the current temperature.') + parser.add_argument( '-u', '--hdduse', action='store_true', help='Display disk utilization.') + parser.add_argument( '--hddtemp', action='store_true', help='Display the temperature of the storage devices.') + return parser + +# + +start = time.clock_gettime_ns(time.CLOCK_MONOTONIC) +usage1= argonsysinfo_diskusage() + +def main(): + parser = setup_arguments() + if len(sys.argv) > 1: + args = parser.parse_args() + elif 'ARGON_STATUS_DEFAULT' in os.environ: + commands = os.environ['ARGON_STATUS_DEFAULT'].split(" ") + args = parser.parse_args(commands) + else: + args = parser.parse_args(['--devices','--ip']) + + if args.version : + print_version() + if args.cpu: + show_cpuUtilization() + if args.devices: + show_hddTemperature() + show_storage() + show_raid() + if args.fan: + show_fanspeed() + if args.raid : + show_raid() + if args.storage : + show_storage() + if args.temp : + show_cpuTemperature() + if args.hddtemp: + show_hddTemperature() + if args.ip: + show_ipaddresses() + if args.memory: + show_memory() + if args.hdduse: + show_hddutilization() + if args.all: + show_all() + +if __name__ == "__main__": + setup_arguments() + main() diff --git a/argononed.py b/argononed.py old mode 100644 new mode 100755 index 554d849..48efcf6 --- a/argononed.py +++ b/argononed.py @@ -190,14 +190,6 @@ def load_unitconfig(fname): # Location of config file varies based on OS # -speed = Path('/tmp/fanspeed.txt') - -def getCurrentFanSpeed(): - try: - return int(float(speed.read_text())) - except FileNotFoundError: - return None - def setFanOff (): setFanSpeed (overrideSpeed = 0) @@ -208,17 +200,10 @@ def setFanSpeed (overrideSpeed : int = None, instantaneous : bool = True): CPUFanConfig = {65.0:100, 60.0:55, 55.0: 30} HDDFanConfig = {50.0:100, 40.0:55, 30.0: 30} - - def writeSpeed(theSpeed): - try: - speed.write_text(str(theSpeed)) - except: - ... - - prevspeed = getCurrentFanSpeed() + prevspeed = argonsysinfo_getCurrentFanSpeed() if not prevspeed: prevspeed = 0 - writeSpeed (prevspeed) + argonsysinfo_recordCurrentFanSpeed( prevspeed ) if overrideSpeed is not None: newspeed = overrideSpeed @@ -241,11 +226,10 @@ def writeSpeed(theSpeed): # Spin up to prevent issues on older units bus.write_byte(ADDR_FAN,100) time.sleep(1) - #print( "Setting fanspeed to " + str(newspeed) ) bus.write_byte(ADDR_FAN,int(newspeed)) except IOError: return prevspeed - writeSpeed (newspeed) + argonsysinfo_recordCurrentFanSpeed( newspeed ) return newspeed def temp_check(): @@ -662,7 +646,7 @@ def display_defaultimg(): elif cmd == "FANOFF": # Turn off fan - bus.write_byte(ADDR_FAN,0) + setFanOff() if OLED_ENABLED == True: display_defaultimg() @@ -683,3 +667,7 @@ def display_defaultimg(): ipcq.join() except: GPIO.cleanup() + + elif cmd == "VERSION": + print( "Version: 2023.01.15") + diff --git a/argonsysinfo.py b/argonsysinfo.py index f4b4274..3b180b6 100644 --- a/argonsysinfo.py +++ b/argonsysinfo.py @@ -8,6 +8,31 @@ import time import socket import psutil +from pathlib import Path + +fanspeed = Path('/tmp/fanspeed.txt') + +# +def argonsysinfo_getCurrentFanSpeed(): + """ Get the current fanspeed of the system, by reading a file we have stored the speed in. + This allows other applications for determine what the current fan speed is, as we cannot read + (apparently) from the device when we set the speed. + """ + try: + return int(float(fanspeed.read_text())) + except FileNotFoundError: + return None + except ValueError: + return None + +# +def argonsysinfo_recordCurrentFanSpeed( theSpeed ): + """ Record the current fanspeed for external applications to use. + """ + try: + fanspeed.write_text(str(theSpeed)) + except: + ... def argonsysinfo_listcpuusage(sleepsec = 1): outputlist = [] @@ -198,11 +223,11 @@ def getSmart(smartCmd): ## Smart attrbute not found ... return None - theTemp = getSmart(f"{hddtempcmd} -d sat -n standby,0 -A /dev/{curdev}") + theTemp = getSmart(f"sudo {hddtempcmd} -d sat -n standby,0 -A /dev/{curdev}") if theTemp: outputobj[curdev] = theTemp else: - theTemp = getSmart(f"{hddtempcmd} -n standby,0 -A /dev/{curdev}") + theTemp = getSmart(f"sudo {hddtempcmd} -n standby,0 -A /dev/{curdev}") if theTemp: outputobj[curdev] = theTemp return outputobj @@ -391,7 +416,7 @@ def argonsysinfo_getraiddetail(devname): spare = 0 resync = "" hddlist =[] - command = os.popen('mdadm -D /dev/'+devname) + command = os.popen('sudo mdadm -D /dev/'+devname) tmp = command.read() command.close() alllines = tmp.split("\n") @@ -471,3 +496,21 @@ def argonsysinfo_diskusage(): usage.append( temp ) return usage + +def argonsysinfo_truncateFloat( value, dp ): + """ make sure the value passed in has no more decimal places than the + passed in (dp) number of places. + """ + value *= pow( 10, dp ) + value = round( value ) + value /= pow( 10, dp ) + return value + +def argonsysinfo_convertCtoF( rawTemp, dp ): + """ Convert a raw temperature in degrees C to degrees F, and make sure the + value is truncated to the specified number of decimal places + """ + rawTemp = (32 + (rawTemp * 9)/5) + rawTemp = argonsysinfo_truncateFloat( rawTemp, dp ) + return rawTemp; +