diff --git a/fhem/FHEM/98_freezemon.pm b/fhem/FHEM/98_freezemon.pm index 627b88a53..3f26603bb 100644 --- a/fhem/FHEM/98_freezemon.pm +++ b/fhem/FHEM/98_freezemon.pm @@ -1,4 +1,4 @@ -# $Id$ +# $Id$ ############################################################################## # # 98_FreezeMon.pm @@ -6,7 +6,7 @@ # # Copyright by KernSani # based on 99_PERFMON and apptime -# +# # Fhem is free software: you can redistribute it and/or modify # it under the terms of the GNU General Public License as published by # the Free Software Foundation, either version 2 of the License, or @@ -19,47 +19,49 @@ # # You should have received a copy of the GNU General Public License # along with fhem. If not, see . -# -############################################################################## +# +############################################################################## # Changelog: +# 0.0.12: problem with older perl versions (Forum #764462) +# Small improvement in device detection +# added ignoreDev and ignorMode attribute # 0.0.11: added date to "get freeze" popup -# fixed readingsbulkupdate behaviour -# fixed that freezemon is inactive after restart -# removed gplots +# fixed readingsbulkupdate behaviour +# fixed that freezemon is inactive after restart +# removed gplots # 0.0.10: added set commands active, inactive and clear # added gplot files -# minor bug fixes +# minor bug fixes # 0.0.09: fixed incomplete renaming of Attribute fm_freezeThreshold -# 0.0.08: trimming of very long lines in "get freeze" +# 0.0.08: trimming of very long lines in "get freeze" # start freezemon only after INITIALIZED|REREADCFG (and as late as possible) # 0.0.07: just for fun - added some color to "get freeze" # Fixed bug with uninitialized value (Thanks Micheal.Winkler) # 0.0.06: Code cleanup # Fixed bug with dayLast reading # 0.0.05: Experimental coding to improve bad guy detection -# German and English documentation added +# German and English documentation added # 0.0.04: Added Get function to get last 20 freezes # 0.0.03: Added dynamic loglevel attribute fm_log # Added missing "isDisabled" check in define function # Do some checks not every second -# Fixed PERL WARNING "uninitialized value" if no Device found +# Fixed PERL WARNING "uninitialized value" if no Device found # minor adjustments and bugfixes # 0.0.02: Fixed logical issue with freezetime Attribute # Renamed Attributes -# added dayLast readings -# fixed delete attribute "disable" +# added dayLast readings +# fixed delete attribute "disable" # fixed issue with missing svref_2object -# minor adjustments and bugfixes -# 0.0.01: initial version +# minor adjustments and bugfixes +# 0.0.01: initial version ############################################################################## ############################################################################## # Todo: # adjust to optimized handleTimeout -# -# +# +# ############################################################################## - package main; use strict; @@ -69,35 +71,33 @@ use POSIX; use Time::HiRes qw(gettimeofday); use B qw(svref_2object); - - -my $version = "0.0.10"; +my $version = "0.0.12"; ################################### sub freezemon_Initialize($) { my ($hash) = @_; - my $name = $hash->{NAME}; - + my $name = $hash->{NAME}; + # Module specific attributes my @freezemon_attr = - ( "fm_forceApptime:0,1 fm_freezeThreshold disable:0,1 fm_log"); + ("fm_forceApptime:0,1 fm_freezeThreshold disable:0,1 fm_log fm_ignoreDev fm_ignoreMode:off,single,all"); + + $hash->{GetFn} = "freezemon_Get"; + $hash->{SetFn} = "freezemon_Set"; + $hash->{DefFn} = "freezemon_Define"; + $hash->{UndefFn} = "freezemon_Undefine"; + $hash->{NotifyFn} = "freezemon_Notify"; + $hash->{NotifyDev} = "global"; + $hash->{NotifyOrderPrefix} = "99-"; # we want to be notified late. + $hash->{AttrFn} = "freezemon_Attr"; + $hash->{AttrList} = join( " ", @freezemon_attr ) . " " . $readingFnAttributes; + + #map new Attribute names + $hash->{AttrRenameMap} = { + "fmForceApptime" => "fm_forceApptime:", + "fmFreezeTime" => "fm_freezeThreshold" + }; - $hash->{GetFn} = "freezemon_Get"; - $hash->{SetFn} = "freezemon_Set"; - $hash->{DefFn} = "freezemon_Define"; - $hash->{UndefFn} = "freezemon_Undefine"; - $hash->{NotifyFn} = "freezemon_Notify"; - $hash->{NotifyDev} = "global"; - $hash->{NotifyOrderPrefix} = "99-"; # we want to be notified late. - $hash->{AttrFn} = "freezemon_Attr"; - $hash->{AttrList} = join( " ", @freezemon_attr ) . " " . $readingFnAttributes; - - - #map new Attribute names - $hash->{AttrRenameMap} = { "fmForceApptime" => "fm_forceApptime:", - "fmFreezeTime" => "fm_freezeThreshold" - }; - } ################################### @@ -106,8 +106,8 @@ sub freezemon_Define($$) { my ( $hash, $def ) = @_; my @a = split( "[ \t][ \t]*", $def ); - RemoveInternalTimer($hash); - + RemoveInternalTimer($hash); + my $usage = "syntax: define freezemon"; my ( $name, $type ) = @a; @@ -118,19 +118,19 @@ sub freezemon_Define($$) { Log3 $name, 3, "freezemon defined $name $type"; $hash->{VERSION} = $version; - $hash->{NAME} = $name; + $hash->{NAME} = $name; + + # start the timer + Log3 $name, 5, "[$name] => Define IsDisabled:" . IsDisabled($name) . " init_done:$init_done"; + if ( !IsDisabled($name) && $init_done ) { + freezemon_start($hash); + } + elsif ( IsDisabled($name) ) { + $hash->{STATE} = "inactive"; + $hash->{helper}{DISABLED} = 1; + } + $hash->{VERSION} = $version; - # start the timer - Log3 $name, 5, "[$name] => Define IsDisabled:".IsDisabled($name)." init_done:$init_done"; - if (!IsDisabled($name) && $init_done) { - freezemon_start($hash) - } - elsif (IsDisabled($name)) { - $hash->{STATE} = "inactive"; - $hash->{helper}{DISABLED} = 1; - } - $hash->{VERSION} = $version; - return undef; } @@ -144,164 +144,218 @@ sub freezemon_Undefine($$) { return undef; } ################################### -sub freezemon_Notify($$) -{ - my ($hash, $dev) = @_; - my $name = $hash->{NAME}; # own name / hash - my $events = deviceEvents($dev,1); - - return "" if(IsDisabled($name)); # Return without any further action if the module is disabled - return if(!grep(m/^INITIALIZED|REREADCFG$/, @{$events})); - - freezemon_start($hash); - } -################################### -sub freezemon_ProcessTimer($) -{ - my ($hash) = @_; - my $name = $hash->{NAME}; - - #RemoveInternalTimer($hash); - - my $now = gettimeofday(); - my $freeze = $now - $hash->{helper}{TIMER}; - - - #Check Freezes - if ($freeze > AttrVal($name, "fm_freezeThreshold",1)) - { - - my $dev = $hash->{helper}{apptime}; - my $guys = ""; - $dev //= ""; - my $start = strftime("%H:%M:%S",localtime($hash->{helper}{TIMER})); - my $end = strftime("%H:%M:%S",localtime($now)); - $freeze = int($freeze * 1000) / 1000; +sub freezemon_Notify($$) { + my ( $hash, $dev ) = @_; + my $name = $hash->{NAME}; # own name / hash + my $events = deviceEvents( $dev, 1 ); - # Find the internal timers that are still in the hash - my @olddev = split (" ",$dev); - my @newdev = split (" ",freezemon_apptime()); - - my %nd = map { $_ => 1 } @newdev -; - foreach my $d (@olddev) { - if (!exists($nd{$d})) { - my @a = split ("-",$d); - $guys .= $a[1]." "; - } - } + return "" + if ( IsDisabled($name) ); # Return without any further action if the module is disabled + return if ( !grep( m/^INITIALIZED|REREADCFG$/, @{$events} ) ); - $dev = $guys; - $dev =~ s/^\s+|\s+$//g; - if ($dev eq "") { - $dev = "no bad guy found :-("; - } - # Build hash with 20 last freezes - my @freezes = (); - push @freezes, split(",", ReadingsVal($name,".fm_freezes",undef)); - push @freezes, strftime("%Y-%m-%d",localtime).": s:$start e:$end f:$freeze d:$dev"; - while (keys @freezes > 20) { - shift @freezes; - } - my $freezelist = join(",",@freezes); - - # determine relevant loglevel - my $loglevel = 1; - my %params = map{split /\:/, $_}(split /\ /, AttrVal($name, "fm_log", "")); - foreach my $param (reverse sort {$a <=> $b} keys %params) { - if ($freeze > $param) { - $loglevel = $params{$param}; - last; - } - } - - Log3 $name, $loglevel, strftime("FreezeMon: $name possible freeze starting at %H:%M:%S, delay is $freeze possibly caused by $dev", localtime($hash->{helper}{TIMER})); - my $fcDay = ReadingsVal($name,"fcDay",0)+1; - my $ftDay = ReadingsVal($name,"ftDay",0)+$freeze; - readingsBeginUpdate($hash); - readingsBulkUpdate($hash, ".fm_freezes", $freezelist, 0); - readingsBulkUpdate($hash, "state", "s:$start e:$end f:$freeze d:$dev"); - readingsBulkUpdate($hash, "freezeTime", $freeze); - readingsBulkUpdate($hash, "fcDay", $fcDay); - readingsBulkUpdate($hash, "ftDay", $ftDay); - readingsBulkUpdate($hash, "freezeDevice",$dev); - readingsEndUpdate($hash,1); - } - - # ---- Some stuff not required every second - $hash->{helper}{intCount} //= 0; - $hash->{helper}{intCount} += 1; - if ($hash->{helper}{intCount} >= 60) { - $hash->{helper}{intCount} = 0; - - #Update dayLast readings if we have a new day - my $last = ReadingsVal($name,".lastDay",""); - my $dnow = strftime("%Y-%m-%d",localtime); - if ($last eq "") { - readingsSingleUpdate($hash, ".lastDay", $dnow,0); - } - elsif ($dnow gt $last) - { - my $fcDay = ReadingsVal($name,"fcDay",0); - my $ftDay = ReadingsVal($name,"ftDay",0); - readingsBeginUpdate($hash); - readingsBulkUpdate($hash, "fcDayLast", $fcDay); - readingsBulkUpdate($hash, "ftDayLast", $ftDay); - readingsBulkUpdate($hash, "fcDay", 0); - readingsBulkUpdate($hash, "ftDay", 0); - readingsBulkUpdate($hash, ".lastDay", $dnow, 0); - readingsEndUpdate($hash,1); - } - - if (AttrVal($name,"fm_forceApptime",0) ==1 and !defined($cmds{"apptime"})) { - fhem("apptime",1); - } - } - # start next timer - $hash->{helper}{fn} = ""; - $hash->{helper}{apptime} = freezemon_apptime(); - $hash->{helper}{TIMER} = int($now) + 1; - InternalTimer($hash->{helper}{TIMER}, 'freezemon_ProcessTimer', $hash, 0); + freezemon_start($hash); } ################################### -sub freezemon_Set($@) -{ - my ( $hash, $name, $cmd, @args ) = @_; +sub freezemon_ProcessTimer($) { + my ($hash) = @_; + my $name = $hash->{NAME}; - return "\"set $name\" needs at least one argument" unless(defined($cmd)); + #RemoveInternalTimer($hash); - if($cmd eq "inactive") { - RemoveInternalTimer($hash); - readingsSingleUpdate($hash, "state", "inactive",1); - $hash->{helper}{DISABLED} = 1; - } - elsif($cmd eq "active") { - if (IsDisabled($name)) { - freezemon_start($hash); - } else { - return "Freezemon $name is already active"; - } - } - elsif($cmd eq "clear") { - readingsBeginUpdate($hash); - readingsBulkUpdate($hash, "fcDayLast", 0, 1); - readingsBulkUpdate($hash, "ftDayLast", 0, 1); - readingsBulkUpdate($hash, "fcDay", 0, 1); - readingsBulkUpdate($hash, "ftDay", 0, 1); - readingsBulkUpdate($hash, ".lastDay", "", 1); - readingsBulkUpdate($hash, ".fm_freezes", "", 1); - if (!IsDisabled($name)) { - readingsBulkUpdate($hash, "state", "initialized", 1); - } - readingsBulkUpdate($hash, "freezeTime", 0, 1); - readingsBulkUpdate($hash, "freezeDevice","", 1); - readingsEndUpdate($hash,1); - } - else - { - return "Unknown argument $cmd, choose one of active:noArg inactive:noArg clear:noArg"; - } -return undef; + my $now = gettimeofday(); + my $freeze = $now - $hash->{helper}{TIMER}; + + #Check Freezes + if ( $freeze > AttrVal( $name, "fm_freezeThreshold", 1 ) ) { + + my $dev = $hash->{helper}{apptime}; + my $guys = ""; + $dev //= ""; + my $start = strftime( "%H:%M:%S", localtime( $hash->{helper}{TIMER} ) ); + my $end = strftime( "%H:%M:%S", localtime($now) ); + $freeze = int( $freeze * 1000 ) / 1000; + + # Find the internal timers that are still in the hash + my @olddev = split( " ", $dev ); + my @newdev = split( " ", freezemon_apptime() ); + + my %nd = map { $_ => 1 } @newdev; + foreach my $d (@olddev) { + if ( !exists( $nd{$d} ) ) { + my @a = split( "-", $d ); + $guys .= $a[1] . " "; + } + } + + $dev = $guys; + $dev =~ s/^\s+|\s+$//g; + my $exists = undef; + + if ( $dev eq "" ) { + $dev = "no bad guy found :-("; + $exists = 1; + } + else { + #check ignorDev + my $imode = "off"; + my %devs = map { split /\:/, $_ } ( split /\ /, $dev ); + my @idevs = split( ",", AttrVal( $name, "fm_ignoreDev", "" ) ); + my %id = map { $_ => 1 } @idevs; + + if ( AttrVal( $name, "fm_ignoreDev", undef ) ) { + $imode = AttrVal( $name, "fm_ignoreMode", "all" ); + } + + #In "all" mode all found devices have to be in ignoreDevs (i.e. we're done if one is not in ignoreDev + if ( $imode eq "all" ) { + foreach my $d ( values %devs ) { + if ( !exists( $id{$d} ) ) { + Log3 $name, 5, "FreezeMon $name logging $dev in $imode mode, because $d is not ignored"; + $exists = 1; + last; + } + } + } + + #In "single" mode a single found device has to be in ignoreDevs (i.e. we're done if one is in ignoreDev + elsif ( $imode eq "single" ) { + $exists = 1; + foreach my $d ( values %devs ) { + if ( exists( $id{$d} ) ) { + Log3 $name, 5, "FreezeMon $name ignoring $dev in $imode mode, because $d is ignored"; + $exists = undef; + last; + } + } + } + else { + $exists = 1; + } + + #format output + $dev =~ s/\:/\(/g; + $dev =~ s/\s/\) /g; + $dev .= ")"; + } + + if ($exists) { + + # Build hash with 20 last freezes + my @freezes = (); + push @freezes, split( ",", ReadingsVal( $name, ".fm_freezes", undef ) ); + push @freezes, strftime( "%Y-%m-%d", localtime ) . ": s:$start e:$end f:$freeze d:$dev"; + + #while (keys @freezes > 20) { #problem with older Perl versions + while ( scalar(@freezes) > 20 ) { + shift @freezes; + } + my $freezelist = join( ",", @freezes ); + + # determine relevant loglevel + my $loglevel = 1; + my %params = map { split /\:/, $_ } ( split /\ /, AttrVal( $name, "fm_log", "" ) ); + foreach my $param ( reverse sort { $a <=> $b } keys %params ) { + if ( $freeze > $param ) { + $loglevel = $params{$param}; + last; + } + } + + Log3 $name, $loglevel, + strftime( + "FreezeMon: $name possible freeze starting at %H:%M:%S, delay is $freeze possibly caused by $dev", + localtime( $hash->{helper}{TIMER} ) ); + my $fcDay = ReadingsVal( $name, "fcDay", 0 ) + 1; + my $ftDay = ReadingsVal( $name, "ftDay", 0 ) + $freeze; + readingsBeginUpdate($hash); + readingsBulkUpdate( $hash, ".fm_freezes", $freezelist, 0 ); + readingsBulkUpdate( $hash, "state", "s:$start e:$end f:$freeze d:$dev" ); + readingsBulkUpdate( $hash, "freezeTime", $freeze ); + readingsBulkUpdate( $hash, "fcDay", $fcDay ); + readingsBulkUpdate( $hash, "ftDay", $ftDay ); + readingsBulkUpdate( $hash, "freezeDevice", $dev ); + readingsEndUpdate( $hash, 1 ); + } + else { + Log3 $name, 5, "Freezemon: $name - $dev was ignored"; + } + } + + # ---- Some stuff not required every second + $hash->{helper}{intCount} //= 0; + $hash->{helper}{intCount} += 1; + if ( $hash->{helper}{intCount} >= 60 ) { + $hash->{helper}{intCount} = 0; + + #Update dayLast readings if we have a new day + my $last = ReadingsVal( $name, ".lastDay", "" ); + my $dnow = strftime( "%Y-%m-%d", localtime ); + if ( $last eq "" ) { + readingsSingleUpdate( $hash, ".lastDay", $dnow, 0 ); + } + elsif ( $dnow gt $last ) { + my $fcDay = ReadingsVal( $name, "fcDay", 0 ); + my $ftDay = ReadingsVal( $name, "ftDay", 0 ); + readingsBeginUpdate($hash); + readingsBulkUpdate( $hash, "fcDayLast", $fcDay ); + readingsBulkUpdate( $hash, "ftDayLast", $ftDay ); + readingsBulkUpdate( $hash, "fcDay", 0 ); + readingsBulkUpdate( $hash, "ftDay", 0 ); + readingsBulkUpdate( $hash, ".lastDay", $dnow, 0 ); + readingsEndUpdate( $hash, 1 ); + } + + if ( AttrVal( $name, "fm_forceApptime", 0 ) == 1 + and !defined( $cmds{"apptime"} ) ) + { + fhem( "apptime", 1 ); + } + } + + # start next timer + $hash->{helper}{fn} = ""; + $hash->{helper}{apptime} = freezemon_apptime(); + $hash->{helper}{TIMER} = int($now) + 1; + InternalTimer( $hash->{helper}{TIMER}, 'freezemon_ProcessTimer', $hash, 0 ); +} +################################### +sub freezemon_Set($@) { + my ( $hash, $name, $cmd, @args ) = @_; + + return "\"set $name\" needs at least one argument" unless ( defined($cmd) ); + + if ( $cmd eq "inactive" ) { + RemoveInternalTimer($hash); + readingsSingleUpdate( $hash, "state", "inactive", 1 ); + $hash->{helper}{DISABLED} = 1; + } + elsif ( $cmd eq "active" ) { + if ( IsDisabled($name) ) { + freezemon_start($hash); + } + else { + return "Freezemon $name is already active"; + } + } + elsif ( $cmd eq "clear" ) { + readingsBeginUpdate($hash); + readingsBulkUpdate( $hash, "fcDayLast", 0, 1 ); + readingsBulkUpdate( $hash, "ftDayLast", 0, 1 ); + readingsBulkUpdate( $hash, "fcDay", 0, 1 ); + readingsBulkUpdate( $hash, "ftDay", 0, 1 ); + readingsBulkUpdate( $hash, ".lastDay", "", 1 ); + readingsBulkUpdate( $hash, ".fm_freezes", "", 1 ); + if ( !IsDisabled($name) ) { + readingsBulkUpdate( $hash, "state", "initialized", 1 ); + } + readingsBulkUpdate( $hash, "freezeTime", 0, 1 ); + readingsBulkUpdate( $hash, "freezeDevice", "", 1 ); + readingsEndUpdate( $hash, 1 ); + } + else { + return "Unknown argument $cmd, choose one of active:noArg inactive:noArg clear:noArg"; + } + return undef; } ################################### @@ -309,37 +363,35 @@ sub freezemon_Get($@) { my ( $hash, @a ) = @_; my $name = $hash->{NAME}; my $state = $hash->{STATE}; - my $ret = ""; - return "No Argument given" if ( !defined( $a[1] ) ); - - Log3 $name, 5, - "freezemon $name: called function freezemon_Get() with " . Dumper(@a); + my $ret = ""; + return "No Argument given" if ( !defined( $a[1] ) ); - my $usage = - "Unknown argument " . $a[1] . ", choose one of freeze:noArg"; + Log3 $name, 5, "freezemon $name: called function freezemon_Get() with " . Dumper(@a); + + my $usage = "Unknown argument " . $a[1] . ", choose one of freeze:noArg"; my $error = undef; # Get freeze entries if ( $a[1] eq "freeze" ) { - my @colors = ("red","yellow","green","white","gray"); - my @freezes = split(",", ReadingsVal($name,".fm_freezes","")); - foreach (@freezes) { - my $loglevel = 1; - my $freeze = $_; - if ($freeze =~ /f:(.*)d:/) { - $freeze = $1; - } - my %params = map{split /\:/, $_}(split /\ /, AttrVal($name, "fm_log", "")); - foreach my $param (reverse sort {$a <=> $b} keys %params) { - if ($freeze > $param) { - $loglevel = $params{$param}; - last; - } - } - $_ =~s/(?<=.{160}).{1,}$/.../; - $ret .= "".$loglevel." - ".$_."
"; - } - return $ret; + my @colors = ( "red", "yellow", "green", "white", "gray" ); + my @freezes = split( ",", ReadingsVal( $name, ".fm_freezes", "" ) ); + foreach (@freezes) { + my $loglevel = 1; + my $freeze = $_; + if ( $freeze =~ /f:(.*)d:/ ) { + $freeze = $1; + } + my %params = map { split /\:/, $_ } ( split /\ /, AttrVal( $name, "fm_log", "" ) ); + foreach my $param ( reverse sort { $a <=> $b } keys %params ) { + if ( $freeze > $param ) { + $loglevel = $params{$param}; + last; + } + } + $_ =~ s/(?<=.{160}).{1,}$/.../; + $ret .= "" . $loglevel . " - " . $_ . "
"; + } + return $ret; } # return usage hint @@ -352,53 +404,50 @@ sub freezemon_Get($@) { sub freezemon_Attr($) { my ( $cmd, $name, $aName, $aVal ) = @_; - my $hash = $defs{$name}; + my $hash = $defs{$name}; + # $cmd can be "del" or "set" # $name is device name # aName and aVal are Attribute name and value - Log3 $name, 3, "$cmd $aName $aVal"; + Log3 $name, 3, "$cmd $aName $aVal"; if ( $cmd eq "set" ) { - if ( $aName eq "fm_forceApptime" ) { if ( $aVal > 1 or $aVal < 0 ) { - Log3 $name, 3, - "$name: $aName is either 0 or 1: $aVal"; - return "Attribute " . $aName - . " is either 0 or 1"; + Log3 $name, 3, "$name: $aName is either 0 or 1: $aVal"; + return "Attribute " . $aName . " is either 0 or 1"; } } - if ( $aName eq "fm_freezeThreshold" ) { - if (!looks_like_number($aVal) ) { - return "Attribute " . $aName. " has to be a number (seconds) "; - } - } - if ( $aName eq "fm_freezeThreshold" ) { - - } - - if ( $aName eq "disable" ) { - if ($aVal == 1) { - RemoveInternalTimer($hash); - readingsSingleUpdate($hash, "state", "inactive",1); - $hash->{helper}{DISABLED} = 1; - } - elsif ($aVal == 0) { - freezemon_start($hash); - } + if ( $aName eq "fm_freezeThreshold" ) { + if ( !looks_like_number($aVal) ) { + return "Attribute " . $aName . " has to be a number (seconds) "; + } + } + if ( $aName eq "fm_freezeThreshold" ) { - } - } - elsif ( $cmd eq "del" ) { - if ( $aName eq "disable" ) { - freezemon_start($hash); - } + } + + if ( $aName eq "disable" ) { + if ( $aVal == 1 ) { + RemoveInternalTimer($hash); + readingsSingleUpdate( $hash, "state", "inactive", 1 ); + $hash->{helper}{DISABLED} = 1; + } + elsif ( $aVal == 0 ) { + freezemon_start($hash); + } + + } + } + elsif ( $cmd eq "del" ) { + if ( $aName eq "disable" ) { + freezemon_start($hash); + } } return undef; - -} +} ################################### # Helper Functions # @@ -406,96 +455,124 @@ sub freezemon_Attr($) { ################################### sub freezemon_start($) { - my ($hash) = @_; - my $name = $hash->{NAME}; + my ($hash) = @_; + my $name = $hash->{NAME}; - readingsSingleUpdate($hash, "state", "initialized",0) if(exists($hash->{helper}{DISABLED}) and $hash->{helper}{DISABLED} == 1); - $hash->{helper}{DISABLED} = 0; - my $next = int(gettimeofday()) + 1; - $hash->{helper}{TIMER} = $next; - InternalTimer($next, 'freezemon_ProcessTimer', $hash, 0); - Log3 $name, 2, "FreezeMon: $name ready to watch out for delays greater than ".AttrVal($name, "fm_freezeThreshold",1)." second(s)"; + readingsSingleUpdate( $hash, "state", "initialized", 0 ) + if ( exists( $hash->{helper}{DISABLED} ) + and $hash->{helper}{DISABLED} == 1 ); + $hash->{helper}{DISABLED} = 0; + my $next = int( gettimeofday() ) + 1; + $hash->{helper}{TIMER} = $next; + InternalTimer( $next, 'freezemon_ProcessTimer', $hash, 0 ); + Log3 $name, 2, + "FreezeMon: $name ready to watch out for delays greater than " + . AttrVal( $name, "fm_freezeThreshold", 1 ) + . " second(s)"; } ################################### sub freezemon_apptime() { -my @intAtKeys = keys(%intAt); -my $now = gettimeofday(); -my $minCoverExec = 10; # Let's see if we can find more if we look ahead further -my $minCoverWait = 0.00; -my $ret = ""; + my @intAtKeys = keys(%intAt); + my $now = gettimeofday(); + my $minCoverExec = 10; # Let's see if we can find more if we look ahead further + my $minCoverWait = 0.00; + my $ret = ""; - my @intAtSort = (sort {$intAt{$a}{TRIGGERTIME} <=> - $intAt{$b}{TRIGGERTIME} } - (grep {($intAt{$_}->{TRIGGERTIME}-$now) <= $minCoverExec} - @intAtKeys)); # get the timers to execute due to timeout and sort ascending by time -my ($fn,$tim,$cv,$fnname,$arg, $shortarg); - -foreach my $i (@intAtSort) { - $tim = $intAt{$i}{TRIGGERTIME}; - if ($tim - gettimeofday() > $minCoverWait) { - #next; + my @intAtSort = + ( sort { $intAt{$a}{TRIGGERTIME} <=> $intAt{$b}{TRIGGERTIME} } + ( grep { ( $intAt{$_}->{TRIGGERTIME} - $now ) <= $minCoverExec } @intAtKeys ) ) + ; # get the timers to execute due to timeout and sort ascending by time + my ( $fn, $tim, $cv, $fnname, $arg, $shortarg ); + + foreach my $i (@intAtSort) { + $tim = $intAt{$i}{TRIGGERTIME}; + if ( $tim - gettimeofday() > $minCoverWait ) { + + #next; + } + + if ( $intAt{$i}{FN} eq "freezemon_ProcessTimer" ) { + next; + } + + $fn = $intAt{$i}{FN}; + + if ( ref($fn) ne "" ) { + $cv = svref_2object($fn); + $fnname = $cv->GV->NAME; + $ret .= $intAt{$i}{TRIGGERTIME} . "-" . $fnname; + Log3 undef, 3, "Freezemon: Reference found: " . ref($fn) . "/$fnname/$fn"; + } + else { + $ret .= $intAt{$i}{TRIGGERTIME} . "-" . $fn; + } + $arg = $intAt{$i}{ARG}; + + $shortarg = ( defined($arg) ? $arg : "" ); + if ( ref($shortarg) eq "HASH" ) { + if ( !defined( $shortarg->{NAME} ) ) { + if ( $fn eq "BlockingKill" ) { + $shortarg = $shortarg->{abortArg}{NAME}; + } + elsif ( $fn eq "HttpUtils_Err" ) { + $shortarg = $shortarg->{hash}{hash}{NAME}; + } + else { + Log3 undef, 5, "Freezemon: found something without a name $fn" . Dumper($shortarg); + $shortarg = "N/A"; + } + } + else { + $shortarg = $shortarg->{NAME}; + } + } + elsif ( ref($shortarg) eq "REF" ) { + if ( $fn eq "DOIF_TimerTrigger" ) { + my $deref = ${$arg}; #seems like $arg is a reference to a scalar which in turm is a reference to a hash + $shortarg = $deref->{'hash'}{NAME}; #at least in DOIF_TimerTrigger + } + else { + Log3 undef, 5, "Freezemon: found a REF $fn " . Dumper( ${$arg} ); + } + } + else { + #Log3 undef, 3, "Freezemon: found something that's not a HASH $fn ".ref($shortarg)." ".Dumper($shortarg); + $shortarg = "N/A"; + } + if ( !$shortarg ) { + Log3 undef, 5, "Freezemon: something went wrong $fn " . Dumper($arg); + $shortarg = ""; + } + else { + ( $shortarg, undef ) = split( /:|;/, $shortarg, 2 ); + } + $ret .= ":" . $shortarg . " "; } - if ($intAt{$i}{FN} eq "freezemon_ProcessTimer") { - next; - } - - $fn = $intAt{$i}{FN}; - if (ref($fn) ne "") { - $cv = svref_2object($fn); - $fnname = $cv->GV->NAME; - $ret .= $intAt{$i}{TRIGGERTIME}."-".$fnname; - Log3 undef, 3, "Freezemon: Reference found: ".ref($fn)."/$fnname/$fn"; + if (%prioQueues) { + my $nice = minNum( keys %prioQueues ); + my $entry = shift( @{ $prioQueues{$nice} } ); + Log3 undef, 3, "Freezemon: found a prioQueue" . Dumper($entry); + $cv = svref_2object( $entry->{fn} ); + $fnname = $cv->GV->NAME; + $ret .= ":" . $shortarg; + $shortarg = ( defined( $entry->{arg} ) ? $entry->{arg} : "" ); + if ( ref($shortarg) eq "HASH" ) { + + if ( !defined( $shortarg->{NAME} ) ) { + $shortarg = "N/A"; + } + else { + $shortarg = $shortarg->{NAME}; + } + } + ( $shortarg, undef ) = split( /:|;/, $shortarg, 2 ); + $ret .= ":" . $shortarg . " "; } - else { - $ret .= $intAt{$i}{TRIGGERTIME}."-".$fn; - } - $arg = $intAt{$i}{ARG}; - - - $shortarg = (defined($arg)?$arg:""); - if (ref($shortarg) eq "HASH") { - - if (!defined($shortarg->{NAME})) { - $shortarg = "N/A"; - } - else { - $shortarg = $shortarg->{NAME}; - - if( $fn eq 'notify_Exec' || $fn eq "DOIF_TimerTrigger") { - Log3 undef, 3, "Freezemon: found a DOIF or notify $fn".Dumper($arg); - #my $events = deviceEvents($arg[1], AttrVal($arg[0]->{NAME}, "addStateEvent", 0)); - #$shortarg .= ">>".join( ',', @{$events})."<<"; - } - } - } - ($shortarg,undef) = split(/:|;/,$shortarg,2); - $ret.= "(".$shortarg.") "; -} -if(%prioQueues) { - my $nice = minNum(keys %prioQueues); - my $entry = shift(@{$prioQueues{$nice}}); - - $cv = svref_2object($entry->{fn}); - $fnname = $cv->GV->NAME; - $ret .= $fnname; - $shortarg = (defined($entry->{arg})?$entry->{arg}:""); - if (ref($shortarg) eq "HASH") { - if (!defined($shortarg->{NAME})) { - $shortarg = "N/A"; - } - else { - $shortarg = $shortarg->{NAME}; - } - } - ($shortarg,undef) = split(/:|;/,$shortarg,2); - $ret.= "(".$shortarg.") "; - } - - return $ret; + + return $ret; } - =pod =item helper =item summary An adjusted version of PERFMON that helps detecting freezes @@ -514,31 +591,31 @@ if(%prioQueues) { It's recommended to deactivate PERFMON once FREEZEMON is active. They anyways detect the same freezes thus everything would be duplicated.

Please note! FREEZEMON just does an educated guess, which device could have caused the freeze based on timers that were supposed to run. There might be a lot of other factors (internal or external) causing freezes. FREEZEMON doesn't replace a more detailed analysis. The module just tries to give you some hints what could be optimized.

- - Define + + Define - - Set +

+ define <devicename> freezemon

+ With that freezemon is active (and you should see a message in the log)

+ + + Set - - Get + + Get - - - Readings + freeze: returns the last 20 (in compact view, like in state) - This is for a quick overview. For detailed analysis the data should be logged.

+ + + + Readings - - + + + Attributes - + =end html - + =begin html_DE - +

freezemon

-
+
    - FREEZEMON überwacht - ähnlich wie PERFMON mögliche Freezes, allerdings ist FREEZEMON ein echtes Modul und hat daher:
    + FREEZEMON überwacht - ähnlich wie PERFMON mögliche Freezes, allerdings ist FREEZEMON ein echtes Modul und hat daher:
      -
    • Readings - die geloggt werden können und damit viel einfacher ausgewertet werden können
    • +
    • Readings - die geloggt werden können und damit viel einfacher ausgewertet werden können
    • Attribute - mit denen das Verhalten von freezemon beeinflusst werden kann
    • -
    • zusätzliche Funktionalität - die versucht das den Freeze verursachende Device zu identifizieren
    • +
    • zusätzliche Funktionalität - die versucht das den Freeze verursachende Device zu identifizieren
    - Ich würde empfehlen, PERFMON zu deaktivieren, wenn FREEZEMON aktiv ist, da beide auf die selbe Art Freezes erkennen und dann nur alles doppelt kommt. - Bitte beachten! FREEZEMON versucht nur intelligent zu erraten, welches Device einen freeze verursacht haben könnte (basierend auf den Timern die laufen sollten). Es gibt eine Menge anderer Faktoren (intern oder extern) die einen Freeze verursachen können. FREEZEMON ersetzt keine detaillierte Analyse. Das Modul versucht nur Hinweise zu geben, was optimiert werden könnte.

    + Ich würde empfehlen, PERFMON zu deaktivieren, wenn FREEZEMON aktiv ist, da beide auf die selbe Art Freezes erkennen und dann nur alles doppelt kommt. + Bitte beachten! FREEZEMON versucht nur intelligent zu erraten, welches Device einen freeze verursacht haben könnte (basierend auf den Timern die laufen sollten). Es gibt eine Menge anderer Faktoren (intern oder extern) die einen Freeze verursachen können. FREEZEMON ersetzt keine detaillierte Analyse. Das Modul versucht nur Hinweise zu geben, was optimiert werden könnte.


    -
    - - Define +
    + + Define
      - FREEZEMON wird ohne Parameter definiert.

      - define <devicename> freezemon

      + FREEZEMON wird ohne Parameter definiert.

      + define <devicename> freezemon

      damit ist der Freezemon aktiv (im Log sollte eine entsprechende Meldung geschrieben werden) -

      -
    - - Set +

    +
+ + Set
    • inactive: deaktiviert das Device (identisch zum Attribut "disable", aber ohne die Notwendigkeit su "saven".
    • active: reaktiviert das Device nachdem es auf inactive gesetzt wurde
    • -
    • clear: Löscht alle readings (inklusive der Liste der letzten 20 Freezes).
    • +
    • clear: Löscht alle readings (inklusive der Liste der letzten 20 Freezes).
    - -
- - Get + + + + Get
    - freeze: gibt die letzten 20 freezes zurück (in Kompakter Darstellung, wie im state) - Dies dient einem schnellen Überblick, für detailliertere Auswertungen empfehle ich die Daten zu loggen.

    -
- - - Readings + freeze: gibt die letzten 20 freezes zurück (in Kompakter Darstellung, wie im state) - Dies dient einem schnellen Überblick, für detailliertere Auswertungen empfehle ich die Daten zu loggen.

+ + + + Readings
    • freezeTime: Dauer des Freezes
    • -
    • freezeDevice: Liste von möglicherweise den Freeze auslösenden Funktionen(Devices)
    • +
    • freezeDevice: Liste von möglicherweise den Freeze auslösenden Funktionen(Devices)
    • fcDay: kumulierte Anzahl der Freezes pro Tag
    • ftDay: kumulierte Dauer der Freezes pro Tag
    • fcDayLast: speichert die kumulierte Anzahl der Freezes des vergangenen Tages (um tageweise plots zu erstellen)
    • fcDayLast: speichert die kumulierte Dauer der Freezes des vergangenen Tages (um tageweise plots zu erstellen)
    • state: s: e:f: d:
    • -
    -
- - - Attribute + + + + + Attribute
      -
    • fm_freezeThreshold: Wert in Sekunden (Default: 1) - Nur Freezes länger als fm_freezeThreshold werden als Freeze betrachtet
    • +
    • fm_freezeThreshold: Wert in Sekunden (Default: 1) - Nur Freezes länger als fm_freezeThreshold werden als Freeze betrachtet
    • fm_forceApptime: Wenn FREEZEMON aktiv ist wird automatisch apptime gestartet (falls nicht aktiv)
    • +
    • fm_ignoreDev: Liste von Komma-getrennten Devices. Wenn einzelne möglicherweise einen Freeze verursachenden Device in dieser Liste sind, wird der Freeze ignoriert (nicht geloggt). Bitte das Attribut fm_ignoreMode beachten
    • +
    • fm_ignoreMode: Kann die Werte off,single oder all annehmen. Wenn in fm_ignoreDev Devices angegeben sind wirken sich der ignoreMode wie folgt aus:
      + all: Ein Freeze wird nur dann ignoriert, wenn alle möglicherweise den Freeze verursachenden Devices in der Ignore-Liste enthalten sind. Dies führt unter Umständen dazu, dass mehr Freezes geloggt werden als erwartet.
      + single: Ein Freeze wird ignoriert, sobald ein möglicher Verursacher in der Ignorierliste enthalten ist. Dies führt möglicherweise dazu, dass Freezes übersehen werden.
      + off: Alle Freezes werden geloggt.
      + Sofern das Attribut nicht gesetzt ist, aber Ignore-Devices angegeben sind, wird im Modus "all" ignoriert.
    • fm_log: dynamischer Loglevel, nimmt einen String der Form 10:1 5:2 1:3 entgegen, was bedeutet: Freezes > 10 Sekunden werden mit Loglevel 1 geloggt, >5 Sekunden mit Loglevel 2 usw...
    • disable: aktivieren/deaktivieren der Freeze-Erkennung
    • -
    +
- + -
+
=end html_DE -=cut +=cut