2
0
mirror of https://github.com/fhem/fhem-mirror.git synced 2025-03-03 16:56:54 +00:00

98_freezemon.pm: New attributes to ignore Devices + bug fixes.

git-svn-id: https://svn.fhem.de/fhem/trunk@16196 2b470e98-0d58-463d-a4d8-8e2adae1ed80
This commit is contained in:
KernSani 2018-02-16 20:15:39 +00:00
parent 17243db401
commit 6da01bcfcd

View File

@ -22,6 +22,9 @@
#
##############################################################################
# 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
@ -59,7 +62,6 @@
#
##############################################################################
package main;
use strict;
@ -69,9 +71,7 @@ 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($) {
@ -80,7 +80,7 @@ sub freezemon_Initialize($) {
# 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";
@ -92,9 +92,9 @@ sub freezemon_Initialize($) {
$hash->{AttrFn} = "freezemon_Attr";
$hash->{AttrList} = join( " ", @freezemon_attr ) . " " . $readingFnAttributes;
#map new Attribute names
$hash->{AttrRenameMap} = { "fmForceApptime" => "fm_forceApptime:",
$hash->{AttrRenameMap} = {
"fmForceApptime" => "fm_forceApptime:",
"fmFreezeTime" => "fm_freezeThreshold"
};
@ -123,7 +123,7 @@ sub freezemon_Define($$) {
# start the timer
Log3 $name, 5, "[$name] => Define IsDisabled:" . IsDisabled($name) . " init_done:$init_done";
if ( !IsDisabled($name) && $init_done ) {
freezemon_start($hash)
freezemon_start($hash);
}
elsif ( IsDisabled($name) ) {
$hash->{STATE} = "inactive";
@ -144,20 +144,19 @@ sub freezemon_Undefine($$) {
return undef;
}
###################################
sub freezemon_Notify($$)
{
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 ( 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($)
{
sub freezemon_ProcessTimer($) {
my ($hash) = @_;
my $name = $hash->{NAME};
@ -166,10 +165,8 @@ sub freezemon_ProcessTimer($)
my $now = gettimeofday();
my $freeze = $now - $hash->{helper}{TIMER};
#Check Freezes
if ($freeze > AttrVal($name, "fm_freezeThreshold",1))
{
if ( $freeze > AttrVal( $name, "fm_freezeThreshold", 1 ) ) {
my $dev = $hash->{helper}{apptime};
my $guys = "";
@ -182,8 +179,7 @@ sub freezemon_ProcessTimer($)
my @olddev = split( " ", $dev );
my @newdev = split( " ", freezemon_apptime() );
my %nd = map { $_ => 1 } @newdev
;
my %nd = map { $_ => 1 } @newdev;
foreach my $d (@olddev) {
if ( !exists( $nd{$d} ) ) {
my @a = split( "-", $d );
@ -193,14 +189,64 @@ sub freezemon_ProcessTimer($)
$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) {
#while (keys @freezes > 20) { #problem with older Perl versions
while ( scalar(@freezes) > 20 ) {
shift @freezes;
}
my $freezelist = join( ",", @freezes );
@ -215,7 +261,10 @@ sub freezemon_ProcessTimer($)
}
}
Log3 $name, $loglevel, strftime("FreezeMon: $name possible freeze starting at %H:%M:%S, delay is $freeze possibly caused by $dev", localtime($hash->{helper}{TIMER}));
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);
@ -227,6 +276,10 @@ sub freezemon_ProcessTimer($)
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;
@ -240,8 +293,7 @@ sub freezemon_ProcessTimer($)
if ( $last eq "" ) {
readingsSingleUpdate( $hash, ".lastDay", $dnow, 0 );
}
elsif ($dnow gt $last)
{
elsif ( $dnow gt $last ) {
my $fcDay = ReadingsVal( $name, "fcDay", 0 );
my $ftDay = ReadingsVal( $name, "ftDay", 0 );
readingsBeginUpdate($hash);
@ -253,10 +305,13 @@ sub freezemon_ProcessTimer($)
readingsEndUpdate( $hash, 1 );
}
if (AttrVal($name,"fm_forceApptime",0) ==1 and !defined($cmds{"apptime"})) {
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();
@ -264,8 +319,7 @@ sub freezemon_ProcessTimer($)
InternalTimer( $hash->{helper}{TIMER}, 'freezemon_ProcessTimer', $hash, 0 );
}
###################################
sub freezemon_Set($@)
{
sub freezemon_Set($@) {
my ( $hash, $name, $cmd, @args ) = @_;
return "\"set $name\" needs at least one argument" unless ( defined($cmd) );
@ -278,7 +332,8 @@ sub freezemon_Set($@)
elsif ( $cmd eq "active" ) {
if ( IsDisabled($name) ) {
freezemon_start($hash);
} else {
}
else {
return "Freezemon $name is already active";
}
}
@ -297,8 +352,7 @@ sub freezemon_Set($@)
readingsBulkUpdate( $hash, "freezeDevice", "", 1 );
readingsEndUpdate( $hash, 1 );
}
else
{
else {
return "Unknown argument $cmd, choose one of active:noArg inactive:noArg clear:noArg";
}
return undef;
@ -312,11 +366,9 @@ sub freezemon_Get($@) {
my $ret = "";
return "No Argument given" if ( !defined( $a[1] ) );
Log3 $name, 5,
"freezemon $name: called function freezemon_Get() with " . Dumper(@a);
Log3 $name, 5, "freezemon $name: called function freezemon_Get() with " . Dumper(@a);
my $usage =
"Unknown argument " . $a[1] . ", choose one of freeze:noArg";
my $usage = "Unknown argument " . $a[1] . ", choose one of freeze:noArg";
my $error = undef;
# Get freeze entries
@ -353,19 +405,17 @@ sub freezemon_Attr($) {
my ( $cmd, $name, $aName, $aVal ) = @_;
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";
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" ) {
@ -399,7 +449,6 @@ sub freezemon_Attr($) {
}
###################################
# Helper Functions #
###################################
@ -409,12 +458,17 @@ sub freezemon_start($) {
my ($hash) = @_;
my $name = $hash->{NAME};
readingsSingleUpdate($hash, "state", "initialized",0) if(exists($hash->{helper}{DISABLED}) and $hash->{helper}{DISABLED} == 1);
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)";
Log3 $name, 2,
"FreezeMon: $name ready to watch out for delays greater than "
. AttrVal( $name, "fm_freezeThreshold", 1 )
. " second(s)";
}
###################################
@ -425,22 +479,25 @@ my $minCoverExec = 10; # Let's see if we can find more if we look ahead
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 @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;
@ -452,35 +509,56 @@ foreach my $i (@intAtSort) {
}
$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};
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})."<<";
}
}
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.") ";
}
$ret .= ":" . $shortarg . " ";
}
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 .= $fnname;
$ret .= ":" . $shortarg;
$shortarg = ( defined( $entry->{arg} ) ? $entry->{arg} : "" );
if ( ref($shortarg) eq "HASH" ) {
if ( !defined( $shortarg->{NAME} ) ) {
$shortarg = "N/A";
}
@ -489,13 +567,12 @@ if(%prioQueues) {
}
}
( $shortarg, undef ) = split( /:|;/, $shortarg, 2 );
$ret.= "(".$shortarg.") ";
$ret .= ":" . $shortarg . " ";
}
return $ret;
}
=pod
=item helper
=item summary An adjusted version of PERFMON that helps detecting freezes
@ -557,6 +634,12 @@ if(%prioQueues) {
<ul>
<li>fm_freezeThreshold: Value in seconds (Default: 1) - Only freezes longer than fm_freezeThreshold will be considered as a freeze</li>
<li>fm_forceApptime: When FREEZEMON is active, apptime will automatically be started (if not yet active)</li>
<li>fm_ignoreDev: list of comma separated Device names. If all devices possibly causing a freeze are in the list, the freeze will be ignored (not logged)</li>
<li>fm_ignoreMode: takes the values off,single or all. If you have added devices to fm_ignoreDev then ignoreMode acts as follows: <br>
all: A freeze will only be ignored, if all devices probably causing the freeze are part of the ignore list. This might result in more freezes being logged than expected.<br>
single: A freeze will be ignored as soon as one device possibly causing the freeze is listed in the ignore list. With this setting you might miss freezes.<br>
off: All freezes will be logged.<br>
If the attribute is not set, while the ignore list is maintained, mode "all" will be used.</li>
<li>fm_log: dynamic loglevel, takes a string like 10:1 5:2 1:3 , which means: freezes > 10 seconds will be logged with loglevel 1 , >5 seconds with loglevel 2 etc...</li>
<li>disable: activate/deactivate freeze detection</li>
</ul>
@ -575,14 +658,14 @@ if(%prioQueues) {
<h3>freezemon</h3>
<div>
<ul>
FREEZEMON überwacht - ähnlich wie PERFMON gliche Freezes, allerdings ist FREEZEMON ein echtes Modul und hat daher:<br>
FREEZEMON überwacht - ähnlich wie PERFMON gliche Freezes, allerdings ist FREEZEMON ein echtes Modul und hat daher:<br>
<ul>
<li>Readings - die geloggt werden können und damit viel einfacher ausgewertet werden können</li>
<li>Readings - die geloggt werden kÃnnen und damit viel einfacher ausgewertet werden kÃnnen</li>
<li>Attribute - mit denen das Verhalten von freezemon beeinflusst werden kann</li>
<li>zusätzliche Funktionalität - die versucht das den Freeze verursachende Device zu identifizieren</li>
<li>zusätzliche Funktionalität - die versucht das den Freeze verursachende Device zu identifizieren</li>
</ul>
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.
<b>Bitte beachten!</b> 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.<br><br>
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.
<b>Bitte beachten!</b> 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.<br><br>
<br>
<br>
<a name="freezemonDefine"></a>
@ -599,14 +682,14 @@ if(%prioQueues) {
<ul>
<li>inactive: deaktiviert das Device (identisch zum Attribut "disable", aber ohne die Notwendigkeit su "saven".</li>
<li>active: reaktiviert das Device nachdem es auf inactive gesetzt wurde</li>
<li>clear: Löscht alle readings (inklusive der Liste der letzten 20 Freezes).</li>
<li>clear: LÃscht alle readings (inklusive der Liste der letzten 20 Freezes).</li>
</ul>
</ul>
<a name="freezemonGet"></a>
<b>Get</b>
<ul>
freeze: gibt die letzten 20 freezes zurück (in Kompakter Darstellung, wie im state) - Dies dient einem schnellen Überblick, r detailliertere Auswertungen empfehle ich die Daten zu loggen.<br><br>
freeze: gibt die letzten 20 freezes zurück (in Kompakter Darstellung, wie im state) - Dies dient einem schnellen Ãœberblick, ¼r detailliertere Auswertungen empfehle ich die Daten zu loggen.<br><br>
</ul>
<a name="freezemonReadings"></a>
@ -614,7 +697,7 @@ if(%prioQueues) {
<ul>
<ul>
<li>freezeTime: Dauer des Freezes</li>
<li>freezeDevice: Liste von möglicherweise den Freeze auslösenden Funktionen(Devices)</li>
<li>freezeDevice: Liste von mÃglicherweise den Freeze auslÃsenden Funktionen(Devices)</li>
<li>fcDay: kumulierte Anzahl der Freezes pro Tag</li>
<li>ftDay: kumulierte Dauer der Freezes pro Tag </li>
<li>fcDayLast: speichert die kumulierte Anzahl der Freezes des vergangenen Tages (um tageweise plots zu erstellen)</li>
@ -627,8 +710,14 @@ if(%prioQueues) {
<b>Attribute</b>
<ul>
<ul>
<li>fm_freezeThreshold: Wert in Sekunden (Default: 1) - Nur Freezes länger als fm_freezeThreshold werden als Freeze betrachtet </li>
<li>fm_freezeThreshold: Wert in Sekunden (Default: 1) - Nur Freezes länger als fm_freezeThreshold werden als Freeze betrachtet </li>
<li>fm_forceApptime: Wenn FREEZEMON aktiv ist wird automatisch apptime gestartet (falls nicht aktiv)</li>
<li>fm_ignoreDev: Liste von Komma-getrennten Devices. Wenn einzelne glicherweise einen Freeze verursachenden Device in dieser Liste sind, wird der Freeze ignoriert (nicht geloggt). Bitte das Attribut fm_ignoreMode beachten</li>
<li>fm_ignoreMode: Kann die Werte off,single oder all annehmen. Wenn in fm_ignoreDev Devices angegeben sind wirken sich der ignoreMode wie folgt aus: <br>
all: Ein Freeze wird nur dann ignoriert, wenn alle glicherweise den Freeze verursachenden Devices in der Ignore-Liste enthalten sind. Dies ¼hrt unter Umständen dazu, dass mehr Freezes geloggt werden als erwartet.<br>
single: Ein Freeze wird ignoriert, sobald ein glicher Verursacher in der Ignorierliste enthalten ist. Dies ¼hrt glicherweise dazu, dass Freezes übersehen werden.<br>
off: Alle Freezes werden geloggt.<br>
Sofern das Attribut nicht gesetzt ist, aber Ignore-Devices angegeben sind, wird im Modus "all" ignoriert.</li>
<li>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...</li>
<li>disable: aktivieren/deaktivieren der Freeze-Erkennung</li>
</ul>