# $Id$ package main; use strict; use warnings; use Data::Dumper; use JSON; use Blocking; my $SYSSTAT_hasSNMP = 1; my %SYSSTAT_diskTypes = ( ".1.3.6.1.2.1.25.2.1.1" => 'Other', ".1.3.6.1.2.1.25.2.1.2" => 'Ram', ".1.3.6.1.2.1.25.2.1.3" => 'VirtualMemory', ".1.3.6.1.2.1.25.2.1.4" => 'FixedDisk', ".1.3.6.1.2.1.25.2.1.5" => 'RemovableDisk', ".1.3.6.1.2.1.25.2.1.6" => 'FloppyDisk', ".1.3.6.1.2.1.25.2.1.7" => 'CompactDisk', ".1.3.6.1.2.1.25.2.1.8" => 'RamDisk', ".1.3.6.1.2.1.25.2.1.9" => 'FlashMemory', ".1.3.6.1.2.1.25.2.1.10" => 'NetworkDisk', ); sub SYSSTAT_Initialize($) { my ($hash) = @_; eval "use Net::SNMP"; $SYSSTAT_hasSNMP = 0 if($@); $hash->{ReadFn} = "SYSSTAT_Read"; $hash->{DefFn} = "SYSSTAT_Define"; $hash->{UndefFn} = "SYSSTAT_Undefine"; $hash->{ShutdownFn} = "SYSSTAT_Shutdown"; $hash->{NotifyFn} = "SYSSTAT_Notify"; $hash->{SetFn} = "SYSSTAT_Set"; $hash->{GetFn} = "SYSSTAT_Get"; $hash->{AttrFn} = "SYSSTAT_Attr"; $hash->{AttrList} = "disable:1 disabledForIntervals raspberrycpufreq:1 raspberrytemperature:0,1,2 synologytemperature:0,1,2 stat:1 uptime:1,2 load:0 noSSH:1,0 ssh_user"; $hash->{AttrList} .= " snmp:1,0 mibs:textField-long snmpVersion:1,2 snmpCommunity" if( $SYSSTAT_hasSNMP ); $hash->{AttrList} .= " filesystems showpercent readings:textField-long readingsFormat:textField-long"; $hash->{AttrList} .= " useregex:1"; $hash->{AttrList} .= " $readingFnAttributes"; } ##################################### sub SYSSTAT_Define($$) { my ($hash, $def) = @_; my @a = split("[ \t][ \t]*", $def); return "Usage: define SYSSTAT [interval [interval_fs [host]]]" if(@a < 2); my $interval = 60; if(int(@a)>=3) { $interval = $a[2]; } if( $interval < 60 ) { $interval = 60; } my $interval_fs = $interval * 60; if(int(@a)>=4) { $interval_fs = $a[3]; } if( $interval_fs < $interval ) { $interval_fs = $interval; } if( $interval_fs == $interval ) { $interval_fs = undef; } my $host = $a[4] if(int(@a)>=5);; delete( $hash->{INTERVAL_FS} ); delete( $hash->{HOST} ); $hash->{"HAS_Net::SNMP"} = $SYSSTAT_hasSNMP; $hash->{STATE} = "Initialized"; $hash->{INTERVAL} = $interval; $hash->{INTERVAL_FS} = $interval_fs if( defined( $interval_fs ) ); $hash->{HOST} = $host if( defined( $host ) ); $hash->{interval_fs} = $interval_fs; SYSSTAT_InitSNMP( $hash ) if( $init_done ); SYSSTAT_Connect($hash) if( $init_done ); if( !$hash->{HOST} ) { $hash->{helper}{has_proc_stat} = ( -r '/proc/stat' ); $hash->{helper}{has_proc_uptime} = ( -r '/proc/uptime' ); $hash->{helper}{has_proc_loadavg} = ( -r '/proc/loadavg' ); my $name = $hash->{NAME}; Log3 $name, 4, "$name: has_proc_stat: $hash->{helper}{has_proc_stat}"; Log3 $name, 4, "$name: has_proc_uptime: $hash->{helper}{has_proc_uptime}"; Log3 $name, 4, "$name: has_proc_loadavg: $hash->{helper}{has_proc_loadavg}"; } return undef; } sub SYSSTAT_Disconnect($) { my ($hash) = @_; my $name = $hash->{NAME}; RemoveInternalTimer($hash); readingsSingleUpdate($hash, 'connection', 'disconnected', 1) if( $hash->{STATE} eq 'Started' ) ; return if( !$hash->{FD} ); if( $hash->{PID} ) { kill( 9, $hash->{PID} ); return; } close($hash->{FH}) if($hash->{FH}); delete($hash->{FH}); delete($hash->{FD}); delete($selectlist{$name}); $hash->{PARTIAL} =''; $hash->{STATE} = "Disconnected"; Log3 $name, 3, "$name: Disconnected"; $hash->{LAST_DISCONNECT} = FmtDateTime( gettimeofday() ); } sub SYSSTAT_Connect($) { my ($hash) = @_; my $name = $hash->{NAME}; SYSSTAT_Disconnect($hash); if( !$hash->{HOST} ) { RemoveInternalTimer($hash); InternalTimer(gettimeofday()+5, "SYSSTAT_GetUpdate", $hash, 0); return; } elsif( AttrVal($name, "noSSH", undef ) ) { RemoveInternalTimer($hash); InternalTimer(gettimeofday()+5, "SYSSTAT_GetUpdate", $hash, 0); return; } return undef if( AttrVal($name, "disable", undef ) ); my @queue = (); $hash->{QUEUE} = \@queue; $hash->{SENT} = 0; $hash->{PARSED} = 0; $hash->{PARTIAL} =''; $hash->{STARTED} = 0; my ($child, $parent); if( socketpair($child, $parent, AF_UNIX, SOCK_STREAM, PF_UNSPEC) ) { $child->autoflush(1); $parent->autoflush(1); my $pid = fhemFork(); if(!defined($pid)) { close $parent; close $child; my $msg = "$name: Cannot fork: $!"; Log 1, $msg; return $msg; } if( $pid ) { close $parent; $child->blocking(0); $hash->{STATE} = "Started"; $hash->{CONNECTS}++; $hash->{FH} = $child; $hash->{FD} = fileno($child); $hash->{PID} = $pid; $selectlist{$name} = $hash; SYSSTAT_Write( $hash, 'uname -a' ); RemoveInternalTimer($hash); InternalTimer(gettimeofday()+5, "SYSSTAT_GetUpdate", $hash, 0); } else { close $child; close STDIN; close STDOUT; close STDERR; my $fn = $parent->fileno(); open(STDIN, "<&$fn") or die "can't redirect STDIN $!"; open(STDOUT, ">&$fn") or die "can't redirect STDOUT $!"; open(STDERR, ">&$fn") or die "can't redirect STDOUT $!"; #select STDIN; $| = 1; #select STDOUT; $| = 1; #STDIN->autoflush(1); STDOUT->autoflush(1); close $parent; $ENV{PYTHONUNBUFFERED} = 1; if( my $home = AttrVal($name, "home", undef ) ) { $home = $ENV{'PWD'} if( $home eq 'PWD' ); $ENV{'HOME'} = $home; Log3 $name, 2, "$name: setting \$HOME to $home"; } my $cmd = qx(which ssh); chomp( $cmd ); my $user = AttrVal($hash->{NAME}, "ssh_user", undef ); $cmd .= ' -q '; $cmd .= $user."\@" if( defined($user) ); $cmd .= $hash->{HOST}; Log3 $name, 2, "$name: starting: $cmd"; exec split( ' ', $cmd ) or Log3 $name, 1, "exec failed"; POSIX::_exit(0);; } } else { $hash->{STATE} = "Stopped"; Log3 $name, 3, "$name: socketpair failed"; InternalTimer(gettimeofday()+20, "SYSSTAT_Connect", $hash, 0); } } sub SYSSTAT_Write($$;$) { my ($hash,$cmd,$key) = @_; my $name = $hash->{NAME}; return undef if( !$hash->{FH} ); #FIXME: reconnect if QUEUE > xxx? push @{$hash->{QUEUE}}, {cmd => $cmd, key => $key, }; return if( scalar @{$hash->{QUEUE}} > 1 ); Log3 $name, 4, "$name: sending: $cmd"; syswrite $hash->{FH}, "echo \">>>cmd start $hash->{SENT}<<<\"\n"; syswrite $hash->{FH}, "$cmd\n"; syswrite $hash->{FH}, "echo \">>>cmd end $hash->{SENT}<<<\"\n"; ++$hash->{SENT}; return undef; } sub SYSSTAT_Parse($$$); sub SYSSTAT_Read($) { my ($hash) = @_; my $name = $hash->{NAME}; my $buf; my $ret = sysread($hash->{FH}, $buf, 65536 ); my $err = int($!); if( my $phash = $hash->{phash} ) { if( $ret <= 0 ) { $hash->{cleanup} = 1; SYSSTAT_killChild( $hash ); return; } readingsBeginUpdate($phash); my $key = $hash->{key}; my $cmd = $hash->{cmd}; $buf =~ s/\n$// if( $buf ); SYSSTAT_Parse($phash,$key?$key:$cmd,$buf); readingsEndUpdate($phash, 1); return; } if(!defined($ret) && $err == EWOULDBLOCK) { return; } if(!defined($ret) || $ret <= 0) { SYSSTAT_Disconnect( $hash ); delete $hash->{PID}; Log3 $name, 3, "$name: read: error during sysread: $!" if(!defined($ret)); Log3 $name, 3, "$name: read: end of file reached while sysread" if(defined($ret) && $ret <= 0); InternalTimer(gettimeofday()+10, "SYSSTAT_Connect", $hash, 0); return undef; } if( $buf =~ m/^(.*)?>>>cmd start (\d+)<<<\n(.*)\n>>>cmd end (\d+)<<<\n(.*)?/ ) { $buf = $3; $hash->{PARTIAL} = $5; $hash->{STARTED} = 0; } elsif( $buf =~ m/^>>>cmd start (\d+)<<<(.*)?/ ) { $hash->{PARTIAL} = $2; $hash->{STARTED} = 1; return; } elsif( $buf =~ m/(.*)>>>cmd end (\d+)<<<\n?$/ms ) { $buf = $hash->{PARTIAL} . $1; $hash->{STARTED} = 0; } elsif( !$hash->{STARTED} ) { return; } else { $hash->{PARTIAL} .= $buf; return; } my $entry = shift @{$hash->{QUEUE}}; if( scalar @{$hash->{QUEUE}} ) { my $cmd = $hash->{QUEUE}[0]->{cmd}; Log3 $name, 4, "$name: sending: $cmd"; syswrite $hash->{FH}, "echo \">>>cmd start $hash->{SENT}<<<\"\n"; syswrite $hash->{FH}, "$cmd\n"; syswrite $hash->{FH}, "echo \">>>cmd end $hash->{SENT}<<<\"\n"; ++$hash->{SENT}; } readingsBeginUpdate($hash); my $key = $entry->{key}; my $cmd = $entry->{cmd}; $buf =~ s/\n$// if( $buf ); SYSSTAT_Parse($hash,$key?$key:$cmd,$buf); readingsEndUpdate($hash, 1); ++$hash->{PARSED}; return undef; } sub SYSSTAT_killChild($) { my ($chash) = @_; my $name = $chash->{NAME}; kill( 9, $chash->{PID} ); if( !$chash->{cleanup} ) { my $pname = $chash->{phash}->{NAME}; Log3 $pname, 2, "$pname: timeout reached, killing pid $chash->{PID} for cmd $chash->{cmd}"; } RemoveInternalTimer($chash); delete($defs{$name}); delete($selectlist{$name}); } sub SYSSTAT_Parse($$$) { my ($hash,$key,$data) = @_; my $name = $hash->{NAME}; return undef if( !$key ); #FIXME: reconnect ? Log3 $name, 5, "$name: parsing: $key <- $data"; if( $key eq 'uname -a' && $data ) { readingsSingleUpdate($hash, 'connection', 'connected', 1); $hash->{STATE} = "Connected"; $hash->{uname} = $data; SYSSTAT_Write( $hash, 'ls /proc/stat' ); SYSSTAT_Write( $hash, 'ls /proc/uptime' ); SYSSTAT_Write( $hash, 'ls /proc/loadavg' ); } elsif( $key eq 'ls /proc/stat' && $data ) { $hash->{helper}{has_proc_stat} = $data =~ m'^/proc/stat' ? 1 : 0; Log3 $name, 4, "$name: has_proc_stat: $hash->{helper}{has_proc_stat}"; } elsif( $key eq 'ls /proc/uptime' && $data ) { $hash->{helper}{has_proc_uptime} = $data =~ m'^/proc/uptime' ? 1 : 0; Log3 $name, 4, "$name: has_proc_uptime: $hash->{helper}{has_proc_uptime}"; } elsif( $key eq 'ls /proc/loadavg' && $data ) { $hash->{helper}{has_proc_loadavg} = $data =~ m'^/proc/loadavg' ? 1 : 0; Log3 $name, 4, "$name: has_proc_loadavg: $hash->{helper}{has_proc_loadavg}"; } elsif( $key =~ m/#reading:(.*)/ && $data ) { my $reading = $1; my $VALUE = $data; if( my $value_format = $hash->{helper}{readingsFormat} ) { if( ref($value_format) eq 'HASH' ) { my $vf = ""; $vf = $value_format->{$reading} if( defined($reading) && exists($value_format->{$reading}) ); $vf = $value_format->{$reading.'.'.$VALUE} if( defined($reading) && exists($value_format->{$reading.'.'.$VALUE}) ); if( !ref($vf) && $vf =~ m/^{.*}$/s) { eval $vf; $VALUE = $data if( $@ ); Log3 $name, 2, "$name: $@" if( $@ ); } } else { Log3 $name, 2, "$name: readingsFormat is not a hash"; } } if( ref($VALUE) eq 'ARRAY' ) { my $i = 1; foreach my $value (@{$VALUE}) { readingsBulkUpdate($hash, $reading.$i, $value); ++$i } } else { readingsBulkUpdate($hash, $reading, $VALUE); } } elsif( $key eq 'cat /proc/loadavg' && $data ) { my ($avg_1, $avg_5, $avg_15) = split( ' ', $data, 4 ); readingsBulkUpdate($hash, 'state', "$avg_1 $avg_5 $avg_15"); readingsBulkUpdate($hash, 'load', $avg_1); } elsif( $key eq 'cat /proc/stat' && $data ) { my(undef,@values) = split( ' ', $data, 12 ); pop @values; if( !defined($hash->{helper}{proc_stat_old}) ) { $hash->{helper}{proc_stat_old} = \@values; return undef; } else { my @diff = map { $values[$_] - $hash->{helper}{proc_stat_old}->[$_] } 0 .. 4; $hash->{helper}{proc_stat_old} = \@values; my $sum = 0; $sum += $_ for @diff; my @percent = map { int($diff[$_]*1000 / $sum)/10 } 0 .. 4; if( @percent ) { #my($user,$nice,$system,$idle,$iowait,$irq,$softirq,$steal,$guest,$guest_nice) = @percent; readingsBulkUpdate($hash,"user", $percent[0]); readingsBulkUpdate($hash,"system", $percent[2]); readingsBulkUpdate($hash,"idle", $percent[3]); readingsBulkUpdate($hash,"iowait", $percent[4]); } } } elsif( $key eq 'cat /proc/uptime' && $data ) { my ($uptime) = split(' ', $data, 2 ); if( AttrVal($name, "uptime", 0) != 2 ) { # cut off partial seconds $uptime = int( $uptime ); my $seconds = $uptime % 60; $uptime = int($uptime / 60); my $minutes = $uptime % 60; $uptime = int($uptime / 60); my $hours = $uptime % 24; my $days = int($uptime / 24); $uptime = sprintf( "%d days, %d:%.2d:%.2d", $days, $hours, $minutes, $seconds); } if( $hash->{BlockingResult} ) { $hash->{BlockingResult}{uptime} = $uptime; } else { readingsBulkUpdate($hash,"uptime",$uptime); } } elsif( $key eq 'uptime' && $data ) { if( $data =~ m/(([.,\d]+)\s([.,\d]+)\s([.,\d]+))$/ ) { my $loadavg = $1; $loadavg =~ s/, / /g; $loadavg =~ s/,/./g; SYSSTAT_Parse($hash, 'cat /proc/loadavg', $loadavg); } if( AttrVal($name, "uptime", 0) > 0 ) { ############# match uptime time statement with the different formats seen on linux # examples # 18:52:21 up 26 days, 21:08, 2 users, load average: 0.04, 0.03, 0.05 # 18:52:21 up 26 days, 55 min, 1 user, load average: 0.05, 0.05, 0.05 # 18:52:21 up 55 min, 1 user, load average: 0.05, 0.05, 0.05 # 18:52:21 up 21:08, 1 user, load average: 0.05, 0.05, 0.05 # # complex expression to match only the time parts of the uptime result # $1 is complete up time information of uptime result # $2 is # days part of the uptime # $3 just the # from the "# days"" part or nothing if no days are given # $4 is complete hour/minutes or # min information # $5 is hours part if hours:min are given # $6 is minutes part if hours:min are given # $7 is minutes if # min is given if( $data =~ m/[[:alpha:]]{2}\s*(((\d*)\s*[[:alnum:]]*,?)?\s+((\d+):(\d+)|(\d+)\s+[[:alpha:]]+in[[:alpha:]]*)),?/ ) { my $days = $3?$3:0; my $hours = $5?$5:0; my $minutes = $6?$6:$7; my $uptime = $days * 24; $uptime += $hours; $uptime *= 60; $uptime += $minutes; $uptime *= 60; SYSSTAT_Parse($hash, 'cat /proc/uptime', $uptime); return; } } } elsif( $key eq '#freq1000' && $data ) { readingsBulkUpdate($hash,"cpufreq",$data/1000); } elsif( $key eq '#temp' && $data ) { if( $data > 0 && $data < 200 ) { if( AttrVal($name, "raspberrytemperature", 0) eq 2 ) { $data = sprintf( "%.1f", (3 * ReadingsVal($name,"temperature",$data) + $data ) / 4 ); } elsif( AttrVal($name, "synologytemperature", 0) eq 2 ) { $data = sprintf( "%.1f", (3 * ReadingsVal($name,"temperature",$data) + $data ) / 4 ); } readingsBulkUpdate($hash, 'temperature', $data); } } elsif( $key eq '#temp1000' && $data ) { SYSSTAT_Parse($hash, '#temp', $data/1000); } elsif( $data && $key =~ m/#filesystems(:(.*))?/ ) { my $cl = $2; my %filesystems = (); foreach my $line (split(/\n/, $data)) { next unless $line =~ /^(.+?)\s+(\d+\s+\d+\s+\d+\s.*)$/; @{$filesystems{$1}}{qw( total used free usageper mountpoint )} = (split /\s+/, $2)[0..4]; $filesystems{$1}{usageper} =~ s/%//; } $hash->{helper}{filesystems} = \%filesystems; if( $hash->{filesystems} ) { my $usage = $hash->{helper}{filesystems}; my $type = 'free'; if( AttrVal($name, "showpercent", "") ne "" ) { $type = 'usageper'; } if( AttrVal($name, "useregex", "") eq "" ) { for my $filesystem (@{$hash->{filesystems}}) { my $fs = $usage->{$filesystem}; readingsBulkUpdate($hash,$fs->{mountpoint},$fs->{$type}); } } else { for my $filesystem (@{$hash->{filesystems}}) { foreach my $key (keys %{$usage}) { if( $key =~ /$filesystem/ ) { my $fs = $usage->{$key}; readingsBulkUpdate($hash,$fs->{mountpoint},$fs->{$type}); } } } } } if( $cl && $defs{$cl} ) { my $ret; foreach my $filesystem (sort { $a cmp $b } keys %{$hash->{helper}{filesystems}} ) { $ret .= sprintf( "%30s %s\n", $filesystem, $hash->{helper}{filesystems}->{$filesystem}->{mountpoint} ); } $ret = sprintf( "%30s %s", "", "\n" ) .$ret if( $ret ); asyncOutput( $defs{$cl}, $ret ) if( $ret ); } } else { Log3 $name, 3, "$name: $key: $data"; } } sub SYSSTAT_InitSNMP($) { my ($hash) = @_; my $name = $hash->{NAME}; delete( $hash->{session} ); return if( !$SYSSTAT_hasSNMP ); return if( !$hash->{USE_SNMP} ); my $host = "localhost"; my $community = "public"; $host = $hash->{HOST} if( defined($hash->{HOST} ) ); my ( $session, $error ) = Net::SNMP->session( -hostname => $host, -community => AttrVal($name,"snmpCommunity","public"), -port => 161, -version => AttrVal($name,"snmpVersion",1), -translate => [ -timeticks => 0x0 ], ); if( $error ) { Log3 $name, 2, "$name: $error"; } elsif ( !defined($session) ) { Log3 $name, 2, "$name: can't connect to host $host"; } else { $session->timeout(3); $hash->{session} = $session; my @snmpoids = ( '.1.3.6.1.2.1.1.1.0', '.1.3.6.1.2.1.1.5.0' ); my $response = SYSSTAT_readOIDs($hash,\@snmpoids); $hash->{SystemDescription} = $response->{".1.3.6.1.2.1.1.1.0"}; $hash->{SystemName} = $response->{".1.3.6.1.2.1.1.5.0"}; } } sub SYSSTAT_Undefine($$) { my ($hash, $arg) = @_; RemoveInternalTimer($hash); BlockingKill($hash->{helper}{RUNNING_PID}) if(defined($hash->{helper}{RUNNING_PID})); SYSSTAT_Disconnect($hash); return undef; } sub SYSSTAT_Shutdown($) { my ($hash) = @_; RemoveInternalTimer($hash); SYSSTAT_Disconnect($hash); return undef; } sub SYSSTAT_Notify($$) { my ($hash,$dev) = @_; return if($dev->{NAME} ne "global"); return if(!grep(m/^INITIALIZED|REREADCFG$/, @{$dev->{CHANGED}})); SYSSTAT_InitSNMP( $hash ); SYSSTAT_Connect($hash); } sub SYSSTAT_Set($@) { my ($hash, $name, $cmd, $arg, @params) = @_; return "$name: set needs at least one parameter" if( !$cmd ); if( $cmd eq 'reconnect' ) { SYSSTAT_Connect( $hash ); return } elsif( $cmd eq "raw" ) { return "usage: raw " if( !$arg ); SYSSTAT_Write( $hash, $arg. ' '. join(' ', @params ) ); return; } elsif( $cmd eq "snmpDebug" ) { if( defined($hash->{session}) ) { $hash->{session}->debug($arg eq 'on' ? 0xff : 0x00 ); } else { return 'no snmp session'; } return; } my $list = ''; $list .= 'raw:noArg reconnect:noArg' if( $hash->{HOST} ); $list .= ' snmpDebug:on,off,' if( $SYSSTAT_hasSNMP ); return "Unknown argument $cmd, choose one of $list"; } sub SYSSTAT_Get($@) { my ($hash, $name, $cmd, $arg, @params) = @_; return "$name: get needs at least one parameter" if( !$cmd ); if($cmd eq "filesystems") { my $ret; if( !$hash->{HOST} || $hash->{CONNECTS} ) { SYSSTAT_getFilesystems($hash); return undef; } elsif( $hash->{USE_SNMP} && defined($hash->{session}) ) { my $types = SYSSTAT_readOIDs($hash,".1.3.6.1.2.1.25.2.3.1.2"); my $response = SYSSTAT_readOIDs($hash,".1.3.6.1.2.1.25.2.3.1.3"); foreach my $oid ( sort { ($a =~/\.(\d+)$/)[0] <=> ($b =~/\.(\d+)$/)[0]} keys %$response ) { $ret .= "\n" if( $ret ); my $id = ($oid =~/\.(\d+)$/)[0]; $ret .= sprintf( "%15s %s (%s)", $id, $response->{$oid}, $SYSSTAT_diskTypes{$types->{".1.3.6.1.2.1.25.2.3.1.2.$id"}} ); } $ret = sprintf( "%15s %s", "", "\n" ) .$ret if( $ret ); } return $ret; } elsif( $cmd eq "update" ) { $hash->{LOCAL} = 1; SYSSTAT_GetUpdate( $hash ); delete $hash->{LOCAL}; return; } return "Unknown argument $cmd, choose one of update:noArg filesystems:noArg"; } sub SYSSTAT_Attr($$$) { my ($cmd, $name, $attrName, $attrVal) = @_; $attrVal= "" unless defined($attrVal); my $orig = $attrVal; $attrVal = "1" if($attrName eq "snmp"); $attrVal = "1" if($attrName eq "useregex"); $attrVal = "1" if($attrName eq "showpercent"); $attrVal = "1" if($attrName eq "raspberrycpufreq"); my $hash = $defs{$name}; if( $attrName eq 'disable') { if( $cmd eq 'set' && $attrVal ne '0' ) { $attr{$name}{$attrName} = $attrVal; } else { $hash->{STATE} = "Disabled"; RemoveInternalTimer($hash); delete $attr{$name}{$attrName}; } $hash->{$attrName} = $attrVal; SYSSTAT_Connect($hash) } elsif( $attrName eq 'noSSH') { if( $cmd eq 'set' && $attrVal ne '0' ) { $attr{$name}{$attrName} = $attrVal; if( $hash->{HOST} ) { SYSSTAT_Disconnect($hash); delete $hash->{CONNECTS}; delete $hash->{helper}{has_proc_loadavg}; delete $hash->{helper}{has_proc_stat}; delete $hash->{helper}{has_proc_uptime}; } } else { SYSSTAT_Connect($hash); } } elsif( $attrName eq 'filesystems') { my @filesystems = split(',',$attrVal); @{$hash->{filesystems}} = @filesystems; } elsif( $attrName eq 'ssh_user') { $attr{$name}{$attrName} = $attrVal; SYSSTAT_Connect( $hash ) if( $init_done ); } elsif( $attrName eq 'snmpVersion' && $SYSSTAT_hasSNMP ) { $hash->{$attrName} = $attrVal; SYSSTAT_InitSNMP( $hash ); } elsif( $attrName eq 'snmpCommunity' && $SYSSTAT_hasSNMP ) { $hash->{$attrName} = $attrVal; SYSSTAT_InitSNMP( $hash ); } elsif ($attrName eq 'snmp' && $SYSSTAT_hasSNMP ) { if( $cmd eq 'set' && $attrVal ne '0' ) { $hash->{USE_SNMP} = $attrVal; SYSSTAT_InitSNMP( $hash ); } else { delete $hash->{USE_SNMP}; } } elsif ($attrName eq 'readingsFormat' ) { if( $cmd eq "set" ) { my $attrVal = $attrVal; my %specials= ( "%VALUE" => "1", ); my $err = perlSyntaxCheck($attrVal, %specials); return $err if($err); if( $attrVal =~ m/^{.*}$/s && $attrVal =~ m/=>/ ) { my $av = eval $attrVal; if( $@ ) { Log3 $hash->{NAME}, 2, "$hash->{NAME}: $@"; } else { $attrVal = $av if( ref($av) eq "HASH" ); } } $hash->{helper}{$attrName} = $attrVal; } else { delete $hash->{helper}{$attrName}; } } if( $cmd eq 'set' ) { if( $orig ne $attrVal ) { $attr{$name}{$attrName} = $attrVal; return "$attrName set to $attrVal"; } } return; } sub SYSSTAT_getFilesystems($) { my ($hash) = @_; my $name = $hash->{NAME}; Log3 $name, 5, "$name: trying /proc/loadavg"; my $cl = ''; $cl = ":$hash->{CL}{NAME}" if( $hash->{CONNECTS} && $hash->{CL} ); if( !$hash->{HOST} || $hash->{CONNECTS} ) { if( my $df = SYSSTAT_readCmd($hash, 'df -kP',"#filesystems$cl") ) { my $interactive = !defined($hash->{'.updateTimestamp'}); readingsBeginUpdate($hash) if( $interactive ); SYSSTAT_Parse($hash, '#filesystems', $df) if( $df ); readingsEndUpdate($hash, 1) if( $interactive ); return undef; } } #Log3 $name, 2, "$name: filesystems error"; } sub SYSSTAT_getFilesystemsSNMP($) { my ($hash) = @_; my $name = $hash->{NAME}; if( $hash->{USE_SNMP} && defined($hash->{session}) ) { my %filesystems = (); my $showpercent = AttrVal($name, 'showpercent', '') ne ''; my @snmpoids = (); for my $id (@{$hash->{filesystems}}) { push @snmpoids, ".1.3.6.1.2.1.25.2.3.1.3.$id"; push @snmpoids, ".1.3.6.1.2.1.25.2.3.1.4.$id" if( !$showpercent ); push @snmpoids, ".1.3.6.1.2.1.25.2.3.1.5.$id"; push @snmpoids, ".1.3.6.1.2.1.25.2.3.1.6.$id"; } my $response = SYSSTAT_readOIDs($hash,\@snmpoids); if( $response ) { for my $id (@{$hash->{filesystems}}) { my $unit = $response->{".1.3.6.1.2.1.25.2.3.1.4.$id"}; my $free = $response->{".1.3.6.1.2.1.25.2.3.1.5.$id"} - $response->{".1.3.6.1.2.1.25.2.3.1.6.$id"}; if( $showpercent ) { $free = 100 * $response->{".1.3.6.1.2.1.25.2.3.1.6.$id"} / $response->{".1.3.6.1.2.1.25.2.3.1.5.$id"}; $free = sprintf( '%.1f', $free ); } else { $free *= $unit; } my $name = $response->{".1.3.6.1.2.1.25.2.3.1.3.$id"}; if( $name =~ m/^([[:alpha:]]:\\)/ ) { $name = $1; $name =~ s.\\./.g; } else { $name =~ s/ //g; } $hash->{BlockingResult}{$name} = $free; } return undef; } } Log3 $name, 2, "$name: snmp filesystems error"; } sub SYSSTAT_getLoadAVG($); sub SYSSTAT_getPiTemp($); sub SYSSTAT_getUptime($); sub SYSSTAT_GetUpdate($) { my ($hash) = @_; my $name = $hash->{NAME}; if( AttrVal($name, "noSSH", undef) ) { my @queue = (); $hash->{QUEUE} = \@queue; } elsif( $hash->{QUEUE} && scalar @{$hash->{QUEUE}} ) { Log3 $name, 2, "$name: unanswered query in queue, reconnecting"; SYSSTAT_Connect($hash); return; } if(!$hash->{LOCAL}) { RemoveInternalTimer($hash); InternalTimer(gettimeofday()+$hash->{INTERVAL}, 'SYSSTAT_GetUpdate', $hash, 0); return if( IsDisabled($name) > 0 ); } my $do_diskusage = 1; if( defined($hash->{INTERVAL_FS} ) ) { $do_diskusage = 0; $hash->{interval_fs} -= $hash->{INTERVAL}; if( $hash->{interval_fs} <= 0 ) { $do_diskusage = 1; $hash->{interval_fs} += $hash->{INTERVAL_FS}; } if( $hash->{LOCAL} ) { $do_diskusage = 1; } } if( !$hash->{HOST} || $hash->{CONNECTS} ) { readingsBeginUpdate($hash); SYSSTAT_getLoadAVG( $hash ); SYSSTAT_getFilesystems($hash) if( $do_diskusage && $#{$hash->{filesystems}} >= 0 ); SYSSTAT_getPiTemp($hash) if( AttrVal($name, 'raspberrytemperature', 0) > 0 ); SYSSTAT_getPiFreq($hash) if( AttrVal($name, 'raspberrycpufreq', 0) > 0 ); SYSSTAT_getStat($hash) if( AttrVal($name, 'stat', 0) > 0 ); SYSSTAT_getUptime($hash) if( AttrVal($name, 'uptime', 0) > 0 ); if( my $readings = AttrVal($name, 'readings', undef) ) { foreach my $entry (split(/[\n]/, $readings)) { next if( !$entry ); my($reading,$cmd) = split(':', $entry ); if( my $value = SYSSTAT_readCmd($hash, $cmd, "#reading:$reading") ) { SYSSTAT_Parse($hash, "#reading:$reading", $value) if( $value ); } } } readingsEndUpdate($hash, defined($hash->{LOCAL} ? 0 : 1)); } if( $hash->{USE_SNMP} && defined($hash->{session}) ) { $hash->{do_diskusage} = $do_diskusage; $hash->{helper}{RUNNING_PID} = BlockingCall("SYSSTAT_BlockingCall", $hash, "SYSSTAT_BlockingDone", 300, "SYSSTAT_BlockingAborted", $hash) unless(exists($hash->{helper}{RUNNING_PID})); delete $hash->{do_diskusage}; } } sub SYSSTAT_GetUpdateSNMP($) { my ($hash) = @_; my $name = $hash->{NAME}; if( !$hash->{USE_SNMP} || !defined($hash->{session}) ) { return undef; } if(!$hash->{LOCAL}) { return undef if( IsDisabled($name) > 0 ); } SYSSTAT_getLoadAVGSNMP($hash) if( AttrVal($name, 'load', 1) > 0 ); SYSSTAT_getFilesystemsSNMP($hash) if( $hash->{do_diskusage} && $#{$hash->{filesystems}} >= 0 ); SYSSTAT_getSynoTempSNMP($hash) if( AttrVal($name, 'synologytemperature', 0) > 0 ); SYSSTAT_getStatSNMP($hash) if( AttrVal($name, 'stat', 0) > 0 ); SYSSTAT_getUptimeSNMP($hash) if( AttrVal($name, 'uptime', 0) > 0 ); SYSSTAT_getMIBS($hash); } sub SYSSTAT_getMIBS($) { my ($hash) = @_; my $name = $hash->{NAME}; my $mibs = AttrVal($name, 'mibs', undef); return undef if( !$mibs ); my @snmpoids; foreach my $entry (split(/[ ,\n]/, $mibs)) { next if( !$entry ); my($mib,undef) = split(':', $entry ); next if( !$mib ); push @snmpoids, $mib; } return undef if( !@snmpoids ); my $response = SYSSTAT_readOIDs($hash,\@snmpoids); foreach my $entry (split(/[ ,\n]/, $mibs)) { next if( !$entry ); my($mib,$reading) = split(':', $entry ); next if( !$mib ); next if( !$reading ); my $result = $response->{$mib}; $hash->{BlockingResult}{$reading} = $result; } } sub SYSSTAT_BlockingCall($) { my ($hash) = @_; my $name = $hash->{NAME}; $hash->{BlockingResult} = {}; SYSSTAT_GetUpdateSNMP($hash); return "$name:".encode_json( $hash->{BlockingResult} ); } sub SYSSTAT_BlockingDone($) { my ($string) = @_; my ($name,$json) = split(":", $string, 2); my $hash = $defs{$name}; Log3 $name, 4, "$name: BlockingCall finished: $hash->{helper}{RUNNING_PID}{fn}"; delete($hash->{helper}{RUNNING_PID}); #Log 1, $json; my $decoded = decode_json( $json ); my $in_update = !defined($hash->{'.updateTimestamp'}); readingsBeginUpdate($hash) if( $in_update ); foreach my $key (keys %{$decoded}) { my $reading = $key; my $VALUE = $decoded->{$key}; if( my $value_format = $hash->{helper}{readingsFormat} ) { if( ref($value_format) eq 'HASH' ) { my $vf = ""; $vf = $value_format->{$reading} if( defined($reading) && exists($value_format->{$reading}) ); $vf = $value_format->{$reading.'.'.$VALUE} if( defined($reading) && exists($value_format->{$reading.'.'.$VALUE}) ); if( !ref($vf) && $vf =~ m/^{.*}$/s) { eval $vf; $VALUE = $decoded->{$key} if( $@ ); Log3 $name, 2, "$name: $@" if( $@ ); } } else { Log3 $name, 2, "$name: readingsFormat is not a hash"; } } readingsBulkUpdate($hash, $key, $VALUE); } readingsEndUpdate($hash, 1) if( $in_update ); } sub SYSSTAT_BlockingAborted($) { my ($hash) = @_; my $name = $hash->{NAME}; Log3 $name, 2, "$name: BlockingCall aborted: $hash->{helper}{RUNNING_PID}{fn}"; delete($hash->{helper}{RUNNING_PID}); } sub SYSSTAT_readFile($$;$); sub SYSSTAT_readCmd($$;$); sub SYSSTAT_getLoadAVG($ ) { my ($hash) = @_; my $name = $hash->{NAME}; if( $hash->{helper}{has_proc_loadavg} ) { Log3 $name, 5, "$name: trying /proc/loadavg"; my $loadavg = SYSSTAT_readFile($hash, '/proc/loadavg'); SYSSTAT_Parse($hash, 'cat /proc/loadavg', $loadavg) if( $loadavg ); return; } return if( $hash->{USE_SNMP} && defined($hash->{session}) ); Log3 $name, 5, "$name: trying uptime"; my $uptime = SYSSTAT_readCmd($hash, 'uptime'); SYSSTAT_Parse($hash, 'uptime', $uptime ) if( $uptime ); } sub SYSSTAT_getLoadAVGSNMP($ ) { my ($hash) = @_; my $name = $hash->{NAME}; return if( $hash->{helper}{has_proc_loadavg} ); if( $hash->{USE_SNMP} && defined($hash->{session}) ) { Log3 $name, 5, "$name: trying snmp load avg"; my @snmpoids = ( '.1.3.6.1.4.1.2021.10.1.3.1', '.1.3.6.1.4.1.2021.10.1.3.2', '.1.3.6.1.4.1.2021.10.1.3.3' ); my $response = SYSSTAT_readOIDs($hash,\@snmpoids); if( !$response ) { my $response = SYSSTAT_readOIDs($hash,'.1.3.6.1.2.1.25.3.3.1.2'); my $avg; my %lavg = (); my $load; foreach my $key (keys %{$response}) { $avg .= ',' if( $avg ); $avg .= $response->{$key}; $load = 0 if( !$load ); $load += $response->{$key} / 100; } $hash->{BlockingResult}{state} = $avg if( defined($avg) ); $hash->{BlockingResult}{load} = $load if( defined($load) ); #readingsBulkUpdate($hash, 'state', $avg) if( $avg ); #readingsBulkUpdate($hash, 'load', $load) if( $load ); return undef; } my $avg_1 = $response->{'.1.3.6.1.4.1.2021.10.1.3.1'}; my $avg_5 = $response->{'.1.3.6.1.4.1.2021.10.1.3.2'}; my $avg_15 = $response->{'.1.3.6.1.4.1.2021.10.1.3.3'}; $hash->{BlockingResult}{state} = "$avg_1 $avg_5 $avg_15"; $hash->{BlockingResult}{load} = $avg_1; #readingsBulkUpdate($hash, 'state', "$avg_1 $avg_5 $avg_15"); #readingsBulkUpdate($hash, 'load', $avg_1); return undef; } Log3 $name, 2, "$name: snmp loadavg error"; } sub SYSSTAT_readOIDs($$) { my ($hash,$snmpoids) = @_; my $name = $hash->{NAME}; return undef if( !defined($hash->{session}) ); my $response; if( ref($snmpoids) eq 'ARRAY' ) { $response = $hash->{session}->get_request( @{$snmpoids} ); Log3 $name, 4, "$name: got empty result from snmp query ".$hash->{session}->error() if( !$response ); } else { $response = $hash->{session}->get_next_request($snmpoids); my @snmpoids = (); my @nextid = keys %$response; while ( @nextid && $nextid[0] && $nextid[0] =~ m/^$snmpoids/ ) { push( @snmpoids, $nextid[0] ); $response = $hash->{session}->get_next_request( $nextid[0] ); @nextid = keys %$response; } $response = $hash->{session}->get_request( @snmpoids ); #Log3 $name, 4, "$name: got empty result from snmp query ".$hash->{session}->error() if( !$response ); } return $response; } sub SYSSTAT_readCmd($$;$) { my ($hash,$command,$key) = @_; my $name = $hash->{NAME}; if( defined($hash->{HOST}) ) { SYSSTAT_Write( $hash, $command, $key ); return undef; } else { if( my $pid = open( my $fh, '-|', $command ) ) { if( 1 ) { #non-blocking my %chash = (); $chash{NR} = $devcount++; $chash{STATE} = $command; $chash{TYPE} = $hash->{TYPE}; $chash{NAME} = "$name:cmd:$chash{NR}"; $chash{key} = $key; $chash{cmd} = $command; $chash{phash} = $hash; $chash{TEMPORARY} = 1; $attr{$chash{NAME}}{room} = 'hidden'; $chash{FH} = $fh; $chash{FD} = fileno($fh); $chash{PID} = $pid; $defs{$chash{NAME}} = \%chash; $selectlist{$chash{NAME}} = \%chash; InternalTimer(gettimeofday()+5, 'SYSSTAT_killChild', \%chash, 0); return undef; } else { #blocking FIXME: does not work if ssh is used somewhere else my $value = `$command`; return $value; } } } return undef; } sub SYSSTAT_readFile($$;$) { my ($hash,$filename,$key) = @_; my $value; if( defined($hash->{HOST}) ) { SYSSTAT_Write( $hash, "cat $filename", $key ); return undef; } else { if( open( my $fh, '<', $filename ) ) { $value = <$fh>; close($fh); } } return $value; } sub SYSSTAT_getPiTemp($) { my ($hash) = @_; my $temp = SYSSTAT_readFile($hash, '/sys/class/thermal/thermal_zone0/temp', '#temp1000'); SYSSTAT_Parse($hash, '#temp1000', $temp) if( $temp ); } sub SYSSTAT_getSynoTempSNMP($) { my ($hash) = @_; if( $hash->{USE_SNMP} && defined($hash->{session}) ) { my @snmpoids = ( '.1.3.6.1.4.1.6574.1.2.0' ); my $response = SYSSTAT_readOIDs($hash,\@snmpoids); $hash->{BlockingResult}{temperature} = $response->{'.1.3.6.1.4.1.6574.1.2.0'}; } } sub SYSSTAT_getPiFreq($) { my ($hash) = @_; my $freq = SYSSTAT_readFile($hash, '/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq', '#freq1000'); SYSSTAT_Parse($hash, '#freq1000', $freq) if( $freq ); } sub SYSSTAT_getUptime($) { my ($hash) = @_; my $name = $hash->{NAME}; if( $hash->{helper}{has_proc_uptime} ) { Log3 $name, 5, "$name: trying /proc/uptime"; my $uptime = SYSSTAT_readFile($hash, '/proc/uptime'); SYSSTAT_Parse($hash, 'cat /proc/uptime', $uptime) if( $uptime ); return; } } sub SYSSTAT_getUptimeSNMP($) { my ($hash) = @_; my $name = $hash->{NAME}; return if( $hash->{helper}{has_proc_uptime} ); if( $hash->{USE_SNMP} && defined($hash->{session}) ) { Log3 $name, 5, "$name: trying snmp uptime"; my @snmpoids = ( '.1.3.6.1.2.1.1.3.0' ); my $response = SYSSTAT_readOIDs($hash,\@snmpoids); my $uptime = $response->{'.1.3.6.1.2.1.1.3.0'}; if( defined($uptime) ) { SYSSTAT_Parse($hash, 'cat /proc/uptime', $uptime/100); return; } @snmpoids = ( '.1.3.6.1.2.1.25.1.1.0' ); $response = SYSSTAT_readOIDs($hash,\@snmpoids); $uptime = $response->{'.1.3.6.1.2.1.25.1.1.0'}; if( defined($uptime) ) { SYSSTAT_Parse($hash, 'cat /proc/uptime', $uptime/100); return; } } Log3 $name, 2, "$name: snmp uptime error"; } sub SYSSTAT_getStat($) { my ($hash) = @_; my $name = $hash->{NAME}; if( $hash->{helper}{has_proc_stat} ) { Log3 $name, 5, "$name: trying /proc/stat"; my $line = SYSSTAT_readFile($hash, '/proc/stat'); SYSSTAT_Parse($hash, 'cat /proc/stat', $line) if( $line ); return; } return if( $hash->{USE_SNMP} && defined($hash->{session}) ); Log3 $name, 2, "$name: stat error"; } sub SYSSTAT_getStatSNMP($) { my ($hash) = @_; my $name = $hash->{NAME}; return if( $hash->{helper}{has_proc_stat} ); if( $hash->{USE_SNMP} && defined($hash->{session}) ) { Log3 $name, 5, "$name: trying snmp stat"; my @snmpoids = ( '.1.3.6.1.4.1.2021.11.9.0', '.1.3.6.1.4.1.2021.11.10.0', '.1.3.6.1.4.1.2021.11.11.0' ); my $response = SYSSTAT_readOIDs($hash,\@snmpoids); $hash->{BlockingResult}{user} = $response->{'.1.3.6.1.4.1.2021.11.9.0'}; $hash->{BlockingResult}{system} = $response->{'.1.3.6.1.4.1.2021.11.10.0'}; $hash->{BlockingResult}{idle} = $response->{'.1.3.6.1.4.1.2021.11.11.0'}; return; } Log3 $name, 2, "$name: snmp stat error"; } 1; =pod =item device =item summary system statistics for local and remote linux (and windows) systems =item summary_DE Systemstatistiken für lokale und entfernte Linux (und Windows) Rechner =begin html

SYSSTAT

    Provides system statistics for the host FHEM runs on or a remote Linux system that is reachable by preconfigured passwordless ssh access.

    Notes:
    • To monitor a target by snmp Net::SNMP hast to be installed.
    • To plot the load values the following code can be used:
        define sysstatlog FileLog /usr/local/FHEM/var/log/sysstat-%Y-%m.log sysstat
        attr sysstatlog nrarchive 1
        define svg_sysstat SVG sysstatlog:sysstat:CURRENT
        attr wl_sysstat label "Load Min: $data{min1}, Max: $data{max1}, Aktuell: $data{currval1}"
        attr wl_sysstat room System
        
    • to match the root filesystem (mount point '/') in diskusage plots use '#FileLog 4:/\x3a:0:' or '#FileLog 4:\s..\s:0:' and not '#FileLog 4:/:0:' as the later will match all mount points
    • .
    Define
      define <name> SYSSTAT [<interval> [<interval_fs>] [<host>]]

      Defines a SYSSTAT device.

      The load is updated every <interval> seconds. The default and minimum is 60.

      The diskusage is updated every <interval_fs> seconds. The default is <interval>*60 and the minimum is 60. <interval_fs> is only aproximated and works best if <interval_fs> is an integral multiple of <interval>.

      If <host> is given it has to be accessible by ssh without the need for a password. Examples:
        define sysstat SYSSTAT
        define sysstat SYSSTAT 300
        define sysstat SYSSTAT 60 600

    Readings
    • load
      the 1 minute load average (for windows targets monitored by snmp aproximated value
    • state
      the 1, 5 and 15 minute load averages (or windows targets monitored by snmp the per cpu utilization)
    • user,system,idle,iowait
      respective percentage of systemutilization (linux targets only)
    • <mountpoint>
      free bytes for <mountpoint>

    Set
      set <name> <value>

      where value is one of

    • raw <command>
      Sends <command> to the remote system by ssh.
      set <name> raw shutdown -h now

    Get
      get <name> <value>

      where value is one of

    • filesystems
      Lists the filesystems that can be monitored.

    Attributes
    • noSSH
    • disable
      keep timers running but disable collection of statistics.
    • filesystems
      List of comma separated filesystems (not mountpoints) that should be monitored.
      Examples:
        attr sysstat filesystems /dev/md0,/dev/md2
        attr sysstat filesystems /dev/.*
        attr sysstat filesystems 1,3,5
    • disabledForIntervals HH:MM-HH:MM HH:MM-HH-MM...
    • mibs
      space separated list of <mib>:<reding> pairs that sould be polled.
    • readings
      Newline separated liste aus <reading>:<command> pairs should be executed.
      attr  readings processes:ps ax | wc -l\
                              temperature:snmpwalk -c public -v 1 10.0.1.21 .1.3.6.1.4.1.6574.1.2.0 | grep -oE ..$
      
    • readingsFormat
      attr  readings temperature:cat /sys/class/thermal/thermal*/temp\
                              temperatures:cat /sys/class/thermal/thermal*/temp\
                              frequency:cat /sys/devices/system/cpu/cpu*/cpufreq/scaling_cur_freq
      
      attr  readingsFormat { frequency => '{ $VALUE = [map {int($_ / 1000)} split("\n", $VALUE)] }',\
                              temperature => '{ $VALUE = [map {$_ / 1000} split("\n", $VALUE)] }',\
                              temperatures => '{ $VALUE =~ s/\n/ /g }' }
      
    • showpercent
      If set the usage is shown in percent. If not set the remaining free space in bytes is shown.
    • snmp
      1 -> use snmp to monitor load, uptime and filesystems (including physical and virtual memory)
    • stat
      1 -> monitor user,system,idle and iowait percentage of system utilization (available only for linux targets)
    • raspberrytemperature
      If set and > 0 the raspberry pi on chip termal sensor is read.
      If set to 2 a geometric average over the last 4 values is created.
    • synologytemperature
      If set and > 0 the main temperaure of a synology diskstation is read. requires snmp.
      If set to 2 a geometric average over the last 4 values is created.
    • raspberrycpufreq
      If set and > 0 the raspberry pi on chip termal sensor is read.
    • uptime
      If set and > 0 the system uptime is read.
      If set to 2 the uptime is displayed in seconds.
    • load
      If set and = 0 the system load is not read.
    • useregex
      If set the entries of the filesystems list are treated as regex.
    • ssh_user
      The username for ssh remote access.
    • snmpVersion
    • snmpCommunity
    • readingFnAttributes
=end html =begin html_DE

SYSSTAT

    Das Modul stellt Systemstatistiken für den Rechner, auf dem FHEM läuft bzw. für ein entferntes Linux System, das per vorkonfiguriertem ssh Zugang ohne Passwort erreichbar ist, zur Vefügung.

    Notes:
    • Dieses Modul benötigt Sys::Statistics::Linux für Linux.
      Es kann mit 'cpan install Sys::Statistics::Linux'
      bzw. auf Debian mit 'apt-get install libsys-statistics-linux-perl' installiert werden.
    • Um einen Zielrechner mit snmp zu überwachen, muss Net::SNMP installiert sein.
    • Um die Lastwerte zu plotten, kann der folgende Code verwendet werden:
        define sysstatlog FileLog /usr/local/FHEM/var/log/sysstat-%Y-%m.log sysstat
        attr sysstatlog nrarchive 1
        define svg_sysstat SVG sysstatlog:sysstat:CURRENT
        attr wl_sysstat label "Load Min: $data{min1}, Max: $data{max1}, Aktuell: $data{currval1}"
        attr wl_sysstat room System
        
    • Um das Wurzel-Dateisystem (Mountpunkt '/') bei Plots der Plattennutzung zu erhalten, sollte dieser Code '#FileLog 4:/\x3a:0:' bzw. '#FileLog 4:\s..\s:0:' und nicht dieser Code '#FileLog 4:/:0:' verwendet werden, da der letztere alle Mountpunkte darstellt.
    • .
    Define
      define <name> SYSSTAT [<interval> [<interval_fs>] [<host>]]

      definiert ein SYSSTAT Device.

      Die (Prozessor)last wird alle <interval> Sekunden aktualisiert. Standard bzw. Minimum ist 60.

      Die Plattennutzung wird alle <interval_fs> Sekunden aktualisiert. Standardwert ist <interval>*60 und Minimum ist 60. <interval_fs> wird nur angenähert und funktioniert am Besten, wenn <interval_fs> ein ganzzahliges Vielfaches von <interval> ist.

      Wenn <host> angegeben wird, muss der Zugang per ssh ohne Passwort möglich sein.

      Beispiele:
        define sysstat SYSSTAT
        define sysstat SYSSTAT 300
        define sysstat SYSSTAT 60 600

    Readings
    • load
      die durchschnittliche (Prozessor)last der letzten 1 Minute (für Windows Rechner mit snmp angenähertem Wert)
    • state
      die durchschnittliche (Prozessor)last der letzten 1, 5 und 15 Minuten (für Windows Rechner die Nutzung pro CPU via snmp ermittelt)
    • user, system, idle, iowait
      den Prozentsatz der entsprechenden Systemlast (nur für Linux Systeme)
    • <mountpoint>
      Anzahl der freien Bytes für <mountpoint>

    Set
      set <name> <value>

      Werte für value sind

    • raw <command>
      Sendet <command> per ssh and das entfernte System.
      set <name> raw shutdown -h now

    Get
      get <name> <value>

      Werte für value sind

    • filesystems
      zeigt die Dateisysteme an, die überwacht werden können.

    Attributes
    • noSSH
    • disable
      lässt die Timer weiterlaufen, aber stoppt die Speicherung der Daten.
    • filesystems
      Liste mit Komma getrennten Dateisystemen (nicht Mountpunkten) die überwacht werden sollen.
      Beispiele:
        attr sysstat filesystems /dev/md0,/dev/md2
        attr sysstat filesystems /dev/.*
        attr sysstat filesystems 1,3,5
    • mibs
      Leerzeichen getrennte Liste aus <mib>:<reding> Paaren die abgefragt werden sollen.
    • readings
      Newline getrennte Liste aus <reading>:<kommando> Paaren die ausgeführt werden sollen.
      attr  readings processes:ps ax | wc -l\
                              temperature:snmpwalk -c public -v 1 10.0.1.21 .1.3.6.1.4.1.6574.1.2.0 | grep -oE ..$
      
    • readingsFormat
      attr  readings temperature:cat /sys/class/thermal/thermal*/temp\
                              temperatures:cat /sys/class/thermal/thermal*/temp\
                              frequency:cat /sys/devices/system/cpu/cpu*/cpufreq/scaling_cur_freq
      
      attr  readingsFormat { frequency => '{ $VALUE = [map {int($_ / 1000)} split("\n", $VALUE)] }',\
                              temperature => '{ $VALUE = [map {$_ / 1000} split("\n", $VALUE)] }',\
                              temperatures => '{ $VALUE =~ s/\n/ /g }' }
      
    • showpercent
      Wenn gesetzt, wird die Nutzung in Prozent angegeben. Wenn nicht gesetzt, wird der verfübare Platz in Bytes angezeigt.
    • snmp
      1 -> snmp wird verwendet, um Last, Einschaltzeit und Dateisysteme (inkl. physikalischem und virtuellem Speicher) zu überwachen
    • stat
      1 -> überwacht Prozentsatz der user, system, idle und iowait Last (nur auf Linux Systemen verfügbar)
    • raspberrytemperature
      Wenn gesetzt und > 0 wird der Temperatursensor auf dem Raspberry Pi ausgelesen.
      Wenn Wert 2 ist, wird ein geometrischer Durchschnitt der letzten 4 Werte dargestellt.
    • synologytemperature
      Wenn gesetzt und > 0 wird die Temperatur einer Synology Diskstation ausgelesen (erfordert snmp).
      Wenn Wert 2 ist, wird ein geometrischer Durchschnitt der letzten 4 Werte dargestellt.
    • raspberrycpufreq
      Wenn gesetzt und > 0 wird die Raspberry Pi CPU Frequenz ausgelesen.
    • uptime
      Wenn gesetzt und > 0 wird die Betriebszeit (uptime) des Systems ausgelesen.
      Wenn Wert 2 ist, wird die Betriebszeit (uptime) in Sekunden angezeigt.
    • load
      Wenn gesetzt und = 0 wird die last (load) des nicht Systems ausgelesen.
    • useregex
      Wenn Wert gesetzt, werden die Einträge der Dateisysteme als regex behandelt.
    • ssh_user
      Der Username für den ssh Zugang auf dem entfernten Rechner.
    • snmpVersion
    • snmpCommunity
    • readingFnAttributes
=end html_DE =cut