D⍠𝔽: Monitoren von Systemwerten wie FHEM-Speichernutzung, CPU Last in %, Akku, DOIF

Begonnen von Torxgewinde, 02 November 2023, 22:08:32

Vorheriges Thema - Nächstes Thema

Torxgewinde

Hallo,
Da ich den Speicherverbrauch von FHEM allein, also nicht vom ganzem Betriebsystem, im Auge behalten wollte, habe ich ein DOIF erstellt. Das Besondere daran ist, dass die Werte direkt aus /sys und /proc ausgelesen werden. Die erste Version mit mehreren qx() Aufrufen war im Vergleich deutlich langsamer. Auch konnte ich anhand des Devices mal den DOIF-Perl-Modus austesten nachdem ich ein wenig Hilfe und Tipps erhalten hatte (Danke Damian!). Um es nützlicher zu machen, werden auch die Werte für eine Netztwerkkarte, einen Akku, der CPU Temperatur und der Prozessoren einzeln wie auch in Summe dargestellt. Die CPU Last habe ich in Prozent anzeigen wollen, die Berechnung dazu habe ich bei Stackoverflow gefunden und nach Perl & FHEM portiert.

Hier das Device:
defmod Hardwaremonitor.doif DOIF # Definitions of locally used functions\
subs {\
    # read a file and return its content\
    sub my_read_file($) {\
        my ($filename) = @_;;\
        my $fh;;\
        return "Error with $filename: $!" unless open($fh, '<', $filename);;\
        my $data = do { local $/;; <$fh> };;\
        close($fh);;\
        return $data;;\
    }\
}\
\
# Block to be executed at startup and every N seconds\
init {[+10];;\
    use Time::HiRes;;\
    my $start_time = gettimeofday();;\
    set_Reading_Begin();;\
    \
    ## retrieve values from /proc and /sys:\
    my $BATT_STATE = my_read_file('/sys/class/power_supply/'.[$SELF:BatteryName,"BAT0"].'/status');;\
    my $BATT_CAPACITY_DESIGN = my_read_file('/sys/class/power_supply/'.[$SELF:BatteryName,"BAT0"].'/charge_full_design');;\
    my $BATT_CAPACITY_FULL = my_read_file('/sys/class/power_supply/'.[$SELF:BatteryName,"BAT0"].'/charge_full');;\
    my $BATT_CHARGE_NOW = my_read_file('/sys/class/power_supply/'.[$SELF:BatteryName,"BAT0"].'/charge_now');;\
    my $BATT_CYCLES = my_read_file('/sys/class/power_supply/'.[$SELF:BatteryName,"BAT0"].'/cycle_count');;\
    my $BATT_CURRENT = my_read_file('/sys/class/power_supply/'.[$SELF:BatteryName,"BAT0"].'/current_now');;\
    my $BATT_CHARGE_SYSTEM = my_read_file('/sys/class/power_supply/'.[$SELF:BatteryName,"BAT0"].'/capacity');;\
    my $MEM_INFO = my_read_file('/proc/meminfo');;\
    my $MY_STATUS = my_read_file("/proc/$$/status");;\
    my $net_rx_bytes = my_read_file('/sys/class/net/'.[$SELF:NetworkcardName,"eth0"].'/statistics/rx_bytes');;\
    my $net_tx_bytes = my_read_file('/sys/class/net/'.[$SELF:NetworkcardName,"eth0"].'/statistics/tx_bytes');;\
    my $cpu_temp = my_read_file('/sys/class/thermal/thermal_zone0/temp') / 1000;;\
    my $currentStats = my_read_file('/proc/stat');;\
    my $previousStats = [$SELF:.cpustat,undef];;\
    \
    ## remove linebreaks at the end of these variables\
    foreach($BATT_STATE, $BATT_CAPACITY_DESIGN, $BATT_CAPACITY_FULL, $BATT_CHARGE_NOW, $BATT_CYCLES, $BATT_CURRENT, $BATT_CHARGE_SYSTEM, $net_rx_bytes, $net_tx_bytes) {\
        s/[\r\n]+$//;;\
    }\
    ## process values and perform calculations:\
    my $BATT_CHARGE_CALCULATED = int(100 * $BATT_CHARGE_NOW / $BATT_CAPACITY_FULL);;\
    my ($MemTotal) = $MEM_INFO =~ /^MemTotal:\s+(\d+)\s+kB/m;;\
    my ($MemFree) = $MEM_INFO =~ /^MemFree:\s+(\d+)\s+kB/m;;\
    my ($MemAvailable) = $MEM_INFO =~ /^MemAvailable:\s+(\d+)\s+kB/m;;\
    my ($VmSize) = $MY_STATUS =~ /^VmSize:\s+(\d+)\s+kB/m;;\
    \
    ## publish readings to this DOIF device:\
    set_Reading_Update("BATT_STATE", $BATT_STATE);;\
    set_Reading_Update("BATT_CAPACITY_DESIGN", $BATT_CAPACITY_DESIGN);;\
    set_Reading_Update("BATT_CAPACITY_FULL", $BATT_CAPACITY_FULL);;\
    set_Reading_Update("BATT_CHARGE_NOW", $BATT_CHARGE_NOW);;\
    set_Reading_Update("BATT_CYCLES", $BATT_CYCLES);;\
    set_Reading_Update("BATT_CURRENT", $BATT_CURRENT);;\
    set_Reading_Update("BATT_CHARGE_CALCULATED", $BATT_CHARGE_CALCULATED);;\
    set_Reading_Update("BATT_CHARGE_SYSTEM", $BATT_CHARGE_SYSTEM);;\
    set_Reading_Update("MemTotal", $MemTotal);;\
    set_Reading_Update("MemFree", $MemFree);;\
    set_Reading_Update("MemAvailable", $MemAvailable);;\
    set_Reading_Update("net_rx_bytes", $net_rx_bytes);;\
    set_Reading_Update("net_tx_bytes", $net_tx_bytes);;\
    set_Reading_Update("cpu_temp", $cpu_temp);;\
    set_Reading_Update("VmSize", $VmSize);;\
    set_Reading_Update(".cpustat", $currentStats);;\
    \
    ##just extract cpu, cpu0, cpu1 .. cpuN as array\
    my @cpus = map { (split " ", $_)[0] } grep { /cpu/ } split "\n", $currentStats;;\
    \
    ##iterate over all cpus in /proc/stat\
    foreach my $cpu (@cpus) {\
        ##skip if no previous stats to compare to\
        last unless ($previousStats);;\
        \
        ## algorithm from https://stackoverflow.com/questions/23367857/accurate-calculation-of-cpu-usage-given-in-percentage-in-linux\
        ## converted to perl & FHEM\
        ## verified with: "taskset -c 0 stress-ng --cpu 1 --cpu-load 100" for each CPU\
        my $currentLine = (grep { /$cpu / } split("\n", $currentStats))[0];;\
        my ($cpul, $user, $nice, $system, $idle, $iowait, $irq, $softirq, $steal, $guest, $guest_nice) = split(" ", $currentLine);;\
        my $previousLine = (grep { /$cpu / } split("\n", $previousStats))[0];;\
        my ($prevcpul, $prevuser, $prevnice, $prevsystem, $previdle, $previowait, $previrq, $prevsoftirq, $prevsteal, $prevguest, $prevguest_nice) = split(" ", $previousLine);;\
        \
        my $PrevIdle = $previdle + $previowait;;\
        my $Idle = $idle + $iowait;;\
        my $PrevNonIdle = $prevuser + $prevnice + $prevsystem + $previrq + $prevsoftirq + $prevsteal;;\
        my $NonIdle = $user + $nice + $system + $irq + $softirq + $steal;;\
        my $PrevTotal = $PrevIdle + $PrevNonIdle;;\
        my $Total = $Idle + $NonIdle;;\
        my $totald = $Total - $PrevTotal;;\
        my $idled = $Idle - $PrevIdle;;\
        \
        my $CPU_Percentage = ($totald - $idled) / $totald * 100;;\
        \
        set_Reading_Update($cpu, $CPU_Percentage);;\
    }\
    \
    ## report the runtime of this block as reading, this is useful to assess performance of this block\
    my $runtime = (gettimeofday() - $start_time) * 1000;;\
    set_Reading_Update("runtime", "$runtime ms");;\
    set_Reading_End(1);;\
}
attr Hardwaremonitor.doif DOIF_Readings NetworkcardName:"eth0",\
BatteryName:"BAT0"
attr Hardwaremonitor.doif alias Hardwaremonitor
attr Hardwaremonitor.doif event_Readings VmSize_MB:[$SELF:VmSize]/1024,\
MemTotal_MB:[$SELF:MemTotal]/1024,\
mem_available_percent:int(100*[$SELF:MemAvailable]/[$SELF:MemTotal]),\
net_rx_bytes_per_s:[$SELF:net_rx_bytes:diffpsec],\
net_tx_bytes_per_s:[$SELF:net_tx_bytes:diffpsec],\
net_rx_Mbps:8*[$SELF:net_rx_bytes:diffpsec]/(1024*1024),\
net_tx_Mbps:8*[$SELF:net_tx_bytes:diffpsec]/(1024*1024)
attr Hardwaremonitor.doif group Hardwaremonitor
attr Hardwaremonitor.doif icon icoSYSTEM
attr Hardwaremonitor.doif room Server
attr Hardwaremonitor.doif stateFormat Battery: BATT_CHARGE_SYSTEM %, Memory available: mem_available_percent %
attr Hardwaremonitor.doif uiTable {\
  package ui_Table;;\
}\
\
## card ($collect,$header,$icon,$min,$max,$minColor,$maxColor,$unit,$func,$decfont,$size,$model,$lightness,\
##       $collect2,$min2,$max2,$minColor2,$maxColor2,$unit2,$func2,$decfont2)\
card([[$SELF:cpu:col7d],[$SELF:cpu0:col7d],[$SELF:cpu1:col7d]],"CPU Last","it_cpu",0,100,120,0,["cpu,yellow","cpu0,green","cpu1,red"],undef,"0,,,%",",1,,,,1,600",undef,undef)\
\
## card ($collect,$header,$icon,$min,$max,$minColor,$maxColor,$unit,$func,$decfont,$size,$model,$lightness,\
##       $collect2,$min2,$max2,$minColor2,$maxColor2,$unit2,$func2,$decfont2)\
card([$SELF:VmSize_MB:col7d],"FHEM-Speichernutzung (VmSize=".[$SELF:VmSize]."kB)","it_memory",0,[$SELF:MemTotal_MB],120,0,"MB",undef,"0,,,",",1,,,,,600",undef,undef)\
\
## card ($collect,$header,$icon,$min,$max,$minColor,$maxColor,$unit,$func,$decfont,$size,$model,$lightness,\
##       $collect2,$min2,$max2,$minColor2,$maxColor2,$unit2,$func2,$decfont2)\
## max is assumed to be 10Mbps for this FHEM server, larger values are charted correctly and the y-scale adapts to larger figures\
## figures are rounded to two decimals by the charts component\
card([[$SELF:net_tx_Mbps:col7d],[$SELF:net_rx_Mbps:col7d]],"Netzwerk","it_network",0,10,120,0,["🔺 Mbps,yellow","🔻 Mbps,green"],undef,"2,,,",",1,,,,,600",undef,undef)\
\
## card ($collect,$header,$icon,$min,$max,$minColor,$maxColor,$unit,$func,$decfont,$size,$model,$lightness,\
##       $collect2,$min2,$max2,$minColor2,$maxColor2,$unit2,$func2,$decfont2)\
card([$SELF:BATT_CHARGE_CALCULATED:col7d],"Batterieladezustand","measure_battery_75",0,100,0,120,"%",undef,"0,,,",",1,,,,,600",undef,undef)\
\
## card ($collect,$header,$icon,$min,$max,$minColor,$maxColor,$unit,$func,$decfont,$size,$model,$lightness,\
##       $collect2,$min2,$max2,$minColor2,$maxColor2,$unit2,$func2,$decfont2)\
card([$SELF:cpu_temp:col7d],"CPU Temperatur","temp_temperature",20,100,120,0,"°C",undef,"0,,,",",1,,,,,600",undef,undef)

Sie sieht es dann aus:
Du darfst diesen Dateianhang nicht ansehen. 

Erwähnenswert ist natürlich auch SYSMON, der schon lange Teil von FHEM ist.

Edit #1: Netzwerk-Card-Plot hinzugefügt