# $Id$ package main; use strict; use warnings; use Sys::Statistics::Linux::LoadAVG; use Sys::Statistics::Linux::DiskUsage; sub SYSSTAT_Initialize($) { my ($hash) = @_; $hash->{DefFn} = "SYSSTAT_Define"; $hash->{UndefFn} = "SYSSTAT_Undefine"; $hash->{GetFn} = "SYSSTAT_Get"; $hash->{AttrFn} = "SYSSTAT_Attr"; $hash->{AttrList} = "filesystems raspberrycpufreq:1 raspberrytemperature:0,1,2 showpercent:1 uptime:1,2 useregex:1 ssh_user ". $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->{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_InitSys( $hash ); RemoveInternalTimer($hash); InternalTimer(gettimeofday()+$hash->{INTERVAL}, "SYSSTAT_GetUpdate", $hash, 0); return undef; } sub SYSSTAT_InitSys($) { my ($hash) = @_; if( defined($hash->{HOST}) ) { my $cmd = qx(which ssh); chomp( $cmd ); my $user = AttrVal($hash->{NAME}, "ssh_user", undef ); $cmd .= ' '; $cmd .= $user."\@" if( defined($user) ); $cmd .= $hash->{HOST}." df -kP 2>/dev/null"; $hash->{loadavg} = Sys::Statistics::Linux::LoadAVG->new; $hash->{diskusage} = Sys::Statistics::Linux::DiskUsage->new( cmd => { path => '', df => $cmd } ); } else { $hash->{loadavg} = Sys::Statistics::Linux::LoadAVG->new; $hash->{diskusage} = Sys::Statistics::Linux::DiskUsage->new; } } sub SYSSTAT_Undefine($$) { my ($hash, $arg) = @_; RemoveInternalTimer($hash); return undef; } sub SYSSTAT_Get($@) { my ($hash, @a) = @_; my $name = $a[0]; return "$name: get needs at least one parameter" if(@a < 2); my $cmd= $a[1]; if($cmd eq "filesystems") { my $filesystems = $hash->{diskusage}->get; my $ret; $ret .= " <= \n"; foreach my $filesystem (keys %$filesystems ) { $ret .= $filesystem ." <= ". $filesystems->{$filesystem}->{mountpoint} ."\n"; } return $ret; } return "Unknown argument $cmd, choose one of filesystems:noArg"; } sub SYSSTAT_Attr($$$) { my ($cmd, $name, $attrName, $attrVal) = @_; $attrVal= "" unless defined($attrVal); my $orig = $attrVal; $attrVal= "1" if($attrName eq "useregex"); $attrVal= "1" if($attrName eq "showpercent"); $attrVal= "1" if($attrName eq "raspberrycpufreq"); if( $attrName eq "filesystems") { my $hash = $defs{$name}; my @filesystems = split(",",$attrVal); @{$hash->{filesystems}} = @filesystems; } elsif( $attrName eq "ssh_user") { $attr{$name}{$attrName} = $attrVal; my $hash = $defs{$name}; SYSSTAT_InitSys( $hash ); } if( $cmd eq "set" ) { if( $orig ne $attrVal ) { $attr{$name}{$attrName} = $attrVal; return $attrName ." set to ". $attrVal; } } return; } sub SYSSTAT_getLoadAVG($); sub SYSSTAT_getPiTemp($); sub SYSSTAT_getUptime($); sub SYSSTAT_GetUpdate($) { my ($hash) = @_; my $name = $hash->{NAME}; if(!$hash->{LOCAL}) { RemoveInternalTimer($hash); InternalTimer(gettimeofday()+$hash->{INTERVAL}, "SYSSTAT_GetUpdate", $hash, 1); } #my $load = $hash->{loadavg}->get; my $load = SYSSTAT_getLoadAVG( $hash ); readingsBeginUpdate($hash); my $state = $load->{avg_1} . " " . $load->{avg_5} . " " . $load->{avg_15} if( defined($load->{avg_1}) ); readingsBulkUpdate($hash,"state",$state); readingsBulkUpdate($hash,"load",$load->{avg_1}); 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( $do_diskusage && $#{$hash->{filesystems}} >= 0 ) { my $usage = $hash->{diskusage}->get; 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( AttrVal($name, "raspberrytemperature", "0") > 0 ) { my $temp = SYSSTAT_getPiTemp($hash); if( $temp > 0 && $temp < 200 ) { if( AttrVal($name, "raspberrytemperature", "0") eq 2 ) { $temp = sprintf( "%.1f", (3 * ReadingsVal($name,"temperature",$temp) + $temp ) / 4 ); } readingsBulkUpdate($hash,"temperature",$temp); } } if( AttrVal($name, "raspberrycpufreq", "0") > 0 ) { my $freq = SYSSTAT_getPiFreq($hash); readingsBulkUpdate($hash,"cpufreq",$freq); } if( AttrVal($name, "uptime", "0") > 0 ) { my $uptime = SYSSTAT_getUptime($hash); readingsBulkUpdate($hash,"uptime",$uptime); } readingsEndUpdate($hash,defined($hash->{LOCAL} ? 0 : 1)); } sub SYSSTAT_getLoadAVG($ ) { my ($hash) = @_; if( defined($hash->{HOST}) ) { no strict; no warnings 'redefine'; local *Sys::Statistics::Linux::LoadAVG::get = sub { my $self = shift; my $class = ref $self; my $file = $self->{files}; my %lavg = (); my $cmd = qx(which ssh); chomp( $cmd ); my $user = AttrVal($hash->{NAME}, "ssh_user", undef ); $cmd .= ' '; $cmd .= $user."\@" if( defined($user) ); $cmd .= $hash->{HOST}." cat /proc/loadavg 2>/dev/null"; my $fh; if( open($fh, "$cmd|" ) ) { ( $lavg{avg_1} , $lavg{avg_5} , $lavg{avg_15} ) = (split /\s+/, <$fh>)[0..2]; close($fh); } return \%lavg; }; return $hash->{loadavg}->get; } return $hash->{loadavg}->get; } sub SYSSTAT_readCmd($$$) { my ($hash,$command,$default) = @_; my $value = $default; if( defined($hash->{HOST}) ) { my $cmd = qx(which ssh); chomp( $cmd ); my $user = AttrVal($hash->{NAME}, "ssh_user", undef ); $cmd .= ' '; $cmd .= $user."\@" if( defined($user) ); $cmd .= $hash->{HOST}." $command 2>/dev/null"; if( open(my $fh, "$cmd|" ) ) { $value = <$fh>; close($fh); } } else { if( open( my $fh, "$command|" ) ) { $value = <$fh>; close($fh); } } return $value; } sub SYSSTAT_readFile($$$) { my ($hash,$filename,$default) = @_; my $value = $default; if( defined($hash->{HOST}) ) { my $cmd = qx(which ssh); chomp( $cmd ); my $user = AttrVal($hash->{NAME}, "ssh_user", undef ); $cmd .= ' '; $cmd .= $user."\@" if( defined($user) ); $cmd .= $hash->{HOST}." cat ". $filename ." 2>/dev/null"; if( open(my $fh, "$cmd|" ) ) { $value = <$fh>; close($fh); } } 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",-1); return $temp / 1000; } sub SYSSTAT_getPiFreq($) { my ($hash) = @_; my $freq = SYSSTAT_readFile($hash,"/sys/devices/system/cpu/cpu0/cpufreq/scaling_cur_freq",0); return $freq / 1000; } sub SYSSTAT_getUptime($) { my ($hash) = @_; my $name = $hash->{NAME}; my $uptime = SYSSTAT_readCmd($hash,"uptime",0); $uptime = $1 if( $uptime =~ m/up\s+(((\d+)\D+,\s+)?(\d+):(\d+))/ ); $uptime = "0 days, $uptime" if( !$2); if( AttrVal($name, "uptime", "0") == 2 ) { my $days = $3?$3:0; my $hours = $4; my $minutes = $5; $uptime = $days * 24; $uptime += $hours; $uptime *= 60; $uptime += $minutes; $uptime *= 60; } return $uptime; } 1; =pod =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:
    • currently only Linux is supported.
    • This module needs Sys::Statistics::Linux on Linux.
      It can be installed with 'cpan install Sys::Statistics::Linux'
      or on debian with 'apt-get install libsys-statistics-linux-perl'
    • 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 wl_sysstat weblink fileplot 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
    • state
      the 1, 5 and 15 minute load averages
    • <mountpoint>
      free bytes for <mountpoint>

    Get
      get <name> <value>

      where value is one of

    • filesystems
      Lists the filesystems that can be monitored.

    Attributes
    • filesystems
      List of comma separated filesystems (not mountpoints) that should be monitored.
      Examples:
        attr sysstat filesystems /dev/md0,/dev/md2
        attr sysstat filesystems /dev/.*
    • showpercent
      If set the usage is shown in percent. If not set the remaining free space in bytes is shown.
    • 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.
    • raspberrycpufreq
      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.
    • uptime
      If set and > 0 the system uptime is read.
      If set to 2 the uptime is displayed in seconds.
    • useregex
      If set the entries of the filesystems list are treated as regex.
    • ssh_user
      The username for ssh remote access.
    • readingFnAttributes
=end html =cut