Ermittlung von devices für NOTIFYDEV mittels notifyRegexpChanged

Begonnen von fruemmel, 12 Januar 2022, 13:07:41

Vorheriges Thema - Nächstes Thema

fruemmel

Hallo allerseits,

mir ist bei der Nutzung von apptime aufgefallen, dass sich viele meiner Devices (z. B. vom Typ notify, FileLog) mit Events beschäftigen, die für das device irrelevant sind. Ursache ist, dass die Variable NOTIFYDEV nicht gefüllt wird.
Das liegt meistens daran, wie die relevanten events definiert werden und tritt bei mir insbesondere bei notify und FileLog auf.

So wird z. B. bei der Definition eines FileLogs die regexp presentDevice:(absent|present) nicht erkannt (NOTIFYDEV nicht vorhanden). Wenn ich die regexp jedoch so schreibe:  (presentDevice:absent|presentDevice:present) dann wird NOTIFDEV mit dem Gerät presentDevice gefüllt. Ich finde aber, dass gerade eine Definition der Art <devicename>:(reading1|reading2...|readingN) besser lesbar und einfacher zu schreiben ist.

Ich würde mich freuen, wenn sich ein kompetenter Mensch die Funktion notifyRegexpChanged daraufhin einmal ansieht. Der dort verwendete reguläre Ausdruck ist schon jetzt nicht ganz ohne, ich traue mich da nicht so richtig ran. Zumal schon früh in der Funktion die regexp über | gesplittet wird.

Schade finde ich in dem Zusammenhang auch, dass wohl einige Module (mir aufgefallen bei 31_LightScene) die Variable NOTIFYDEV komplett ignorieren und sich um jedes Event kümmern. Ich kann zwar nicht bemessen, ob das in Summe wirklich relevante Rechenzeit kostet, aber bei vielen Devices ist das evtl. nicht zu vernachlässigen. Bei mir treten bei gut 1000 devices pro Stunde ca. 2000-3000 events auf. Wenn diese von vielen unbeteiligten Geräten betrachtet werden, kommt da einiges zusammen.

Wenn ich irgendwie mit Tests unterstützen kann, bin ich natürlich gerne mit dabei.

Gruß Wolfgang

Otto123

Viele Grüße aus Leipzig  ⇉  nächster Stammtisch an der Lindennaundorfer Mühle
RaspberryPi B B+ B2 B3 B3+ ZeroW,HMLAN,HMUART,Homematic,Fritz!Box 7590,WRT3200ACS-OpenWrt,Sonos,VU+,Arduino nano,ESP8266,MQTT,Zigbee,deconz

fruemmel

Hallo Otto,

oh danke, den Thread hatte ich nicht gefunden. Ich vermute aber, dass sich viele User über die Zusammenhänge mit dem NOTIFYDEV keine Gedanken machen. Und eben vielleicht auch der ein oder andere Modulentwickler. Ich bin auch erst erschrocken, als ich über apptime gesehen habe, wieviele devices sich bei mir mit den Events beschäftigen. Ich bastel mir gerade eine Hilfskonstruktion, um wenigstens die "schwarzen Schafe" (unglückliche RegExp in den Definitionen) zu finden und manuell anzupassen. Oder gibt es in der Richtung evtl. auch schon was?

Beta-User

Server: HP-elitedesk@Debian 12, aktuelles FHEM@ConfigDB | CUL_HM (VCCU) | MQTT2: MiLight@ESP-GW, BT@OpenMQTTGw | MySensors: seriell, v.a. 2.3.1@RS485 | ZWave | ZigBee@deCONZ | SIGNALduino | MapleCUN | RHASSPY
svn: u.a MySensors, Weekday-&RandomTimer, Twilight,  div. attrTemplate-files

fruemmel

Danke für die Links. Thema sieht nach Wespennest aus :-)

Wird denn notifyRegexpChanged nur aufgerufen, wenn ein device definiert oder umdefiniert wurde? Dann wäre es ja bzgl Performance unkritisch, wenn man hier noch etwas mehr Prüfung einbaut. Insb. für meine oben erwähnte regexp 
<devicename>:(reading1|reading2...|readingN)
fände ich super, wenn die erkannt würde und zu einem Eintrag in NOTIFYDEV führen würde. Ich nutze diesen Aufbau insb. bei FileLog-Definitionen relativ häufig.

Beta-User

Thema ist komplex... (und geht mir auch auf die Nerven).

Es ist aber nicht so einfach, die Bestimmung des NOTIFYDEV neu zu erfinden, Rudi hat (m.E. zurecht) Angst davor, das anzufassen - obwohl allen klar ist, dass es wünschenswert wäre, wenn das rückwärtskompatibel verbessert werden könnte.

Du hast mich aber auf den Gedanken gebracht, dass evtl. die Option bestehen könnte, das ganze nicht an der Pipe zu splitten, sondern iterativ vorzugehen und zu versuchen, das am Doppelpunkt zu versuchen und Klammern zu zählen.

Mal sehen, wird aber dauern...
Server: HP-elitedesk@Debian 12, aktuelles FHEM@ConfigDB | CUL_HM (VCCU) | MQTT2: MiLight@ESP-GW, BT@OpenMQTTGw | MySensors: seriell, v.a. 2.3.1@RS485 | ZWave | ZigBee@deCONZ | SIGNALduino | MapleCUN | RHASSPY
svn: u.a MySensors, Weekday-&RandomTimer, Twilight,  div. attrTemplate-files

Damian

Man muss die Sache nicht überbewerten. Früher hatte mein Modul ohne NOTIFYDEV gearbeitet. Inzwischen habe ich den Filter eingebaut und noch einen zusätzlichen , siehe https://forum.fhem.de/index.php/topic,103401.msg977507.html#msg977507

Meine Messung haben ca. 30 % weniger Last im Wartezustand ergeben. Allerding 30 % von "ursprünglich bereits wenig" ist nicht viel ;)
Programmierte FHEM-Module: DOIF-FHEM, DOIF-Perl, DOIF-uiTable, THRESHOLD, FHEM-Befehl: IF

rudolfkoenig

ZitatMan muss die Sache nicht überbewerten.
Ganz meine Rede :)

fruemmel

ZitatMan muss die Sache nicht überbewerten.

Ja das will ich auch gar nicht in Frage stellen. Ich stelle nur fest, dass mein fhem trotz eines ordentlichen Raspberry Pi 4 immer träger wird, und versuche die Ursachen einzugrenzen. Da fielen eben durch apptime die vielen unnötigen Event-Handler auf. Das ist sicher nicht die alleinige oder gar Hauptursache für die Trägheit, aber vielleicht ein Mosaiksteinchen was ich schonmal ausschließen möchte.
Ich überarbeite derzeit die suboptimalen regexp meiner devices und programmiere zum Test für mich eine Erweiterung der Funktion notifyRegexpChanged. Wenn das etwas bringen sollte, werde ich das hier natürlich kundtun.
Bis hierhin aber vielen Dank für Eure Antworten.

Damian

Zitat von: fruemmel am 13 Januar 2022, 08:37:29
Ja das will ich auch gar nicht in Frage stellen. Ich stelle nur fest, dass mein fhem trotz eines ordentlichen Raspberry Pi 4 immer träger wird, und versuche die Ursachen einzugrenzen. Da fielen eben durch apptime die vielen unnötigen Event-Handler auf. Das ist sicher nicht die alleinige oder gar Hauptursache für die Trägheit, aber vielleicht ein Mosaiksteinchen was ich schonmal ausschließen möchte.
Ich überarbeite derzeit die suboptimalen regexp meiner devices und programmiere zum Test für mich eine Erweiterung der Funktion notifyRegexpChanged. Wenn das etwas bringen sollte, werde ich das hier natürlich kundtun.
Bis hierhin aber vielen Dank für Eure Antworten.

Die einfachste und effizienteste Maßnahme um Systemlast drastisch zu reduzieren, ist das Setzen des event-on-change-Attributes. Ich habe es einfach für alle Devices gesetzt und musste es nur bei einem rückgängig machen. Sonst kannst du schauen, welche Events häufig kommen und ob sie ohne Änderung nötig sind.

Jedes Event im System produziert je nach Umfang ca. die 50-fache (5000 %) Last im System im Vergleich zum Setzen eines Readings ohne Event.
Programmierte FHEM-Module: DOIF-FHEM, DOIF-Perl, DOIF-uiTable, THRESHOLD, FHEM-Befehl: IF

Beta-User

Das Reduzieren der Events ist sicher auch ein extrem wichtiger Baustein, die "Holzhammer-Methode" mit "alles auf .*" sagt mir allerdings aus verschiedenen Gründen nicht zu (auch wenn das _sehr oft_ paßt oder zumindest Teil der Lösung ist!).

Falls Homematic (CUL_HM) im Spiel ist: "commStInCh" (in den Hauptdevices) auf "off" zu stellen wäre auch eine gute Idee.

Auch wenn es "nur" um 30% Performance-Verlust geht, hier mal eine erste Variante mit geänderter Split-Logik. Das ganze ist sicher sehr viel unperformanter bei der Ermittlung der Devices, und ob das insgesamt ein gangbarer Weg ist oder doch unüberbrückbare Lücken auftreten, habe ich bisher noch nicht intensiver untersucht:
sub
notifyRegexpChanged2
{
  my ($hash, $re, $disableNotifyFn) = @_;

  %ntfyHash = ();
  if($disableNotifyFn) {
    delete($hash->{NOTIFYDEV});
    $hash->{disableNotifyFn}=1;
    return;
  }
  delete($hash->{disableNotifyFn});
  my $first = 1;
  my $outer = $re =~ m{\A\s*\((.+)\)\s*\z}x; #check if outer brackets are given
  my @list;
  my $numdef = keys %defs;
  while ($re) {
    (my $dev, $re) = split m{:}x, $re, 2; #get the first seperator for device/reading+rest?
    if ( $first && $outer ) {
        $first = 0;
        my $ops = $dev =~ tr/(//;
        my $clos = $dev =~ tr/)//;
        if ( $ops > $clos ) {
            chop($re);
            $dev =~ s{\A.}{}x;
        }
    }
    $dev =~ s{\A\s*\((.+)\)\s*\z}{$1}x; #remove outer brackets if given
    #Log3('global',3 , "re splitted to $dev and $re") if $re;
    return delete $hash->{NOTIFYDEV} if $dev eq '.*';

    while ($dev) {
      (my $part, $dev) = splitByPipe($dev);
      #Log3('global',3 , "dev splitted to $part and $dev") if $dev;

      return delete $hash->{NOTIFYDEV} if $part eq '.*';
      my @darr = devspec2array($part);
      return delete $hash->{NOTIFYDEV} if !@darr || !$defs{$part} && $darr[0] eq $part || $numdef == @darr;
      @list = (@list, @darr);
    }
    (undef, $re) = splitByPipe($re);
  }
  return delete($hash->{NOTIFYDEV}) if !@list;
  my %h = map { $_ => 1 } @list;
  @list = keys %h; # remove duplicates
  $hash->{NOTIFYDEV} = join q{,}, @list;
  return;
}

sub splitByPipe {
    my $string = shift // return (undef,undef);
    # String in pipe-getrennte Tokens teilen, wenn die Klammerebene 0 ist
    my $lastChar = q{x};
    my $bracketLevel = 0;
    my $token = q{};
    my @chars = split q{}, $string;
    my $i = 0;
    my $repl;
    for my $char ( @chars ) {
        #Log3('global',3,"char is $char, last was $lastChar, level is $bracketLevel");
        if ($char eq '|' && $lastChar ne '\\' && !$bracketLevel) {
            $repl = "$token" . q{|};
            $repl =~ s{[{()}|*]}{.}g;
            $string =~ s{\A$repl}{}x;
            return ($token, $string);
        }
        $i++;
        if ($char eq q<(> && $lastChar ne '\\') {
            $bracketLevel++;
        }
        elsif ($char eq q<)> && $lastChar ne '\\') {
            $bracketLevel--;
        }
        $token .= $char;
        if ( $i == scalar @chars ) {
          $repl = $token;
          $repl =~ s{[{}()\|\*]}{.}g;
          $string =~ s{\A$repl}{}x;
        }
        $lastChar = $char;
    }
    return ($token, $string);
}
Server: HP-elitedesk@Debian 12, aktuelles FHEM@ConfigDB | CUL_HM (VCCU) | MQTT2: MiLight@ESP-GW, BT@OpenMQTTGw | MySensors: seriell, v.a. 2.3.1@RS485 | ZWave | ZigBee@deCONZ | SIGNALduino | MapleCUN | RHASSPY
svn: u.a MySensors, Weekday-&RandomTimer, Twilight,  div. attrTemplate-files

rudolfkoenig

Zitathier mal eine erste Variante mit geänderter Split-Logik.
Ich fuerchte, das is zu hoch fuer mich, geschweige denn, dass ich die Nebeneffekte begreifen wuerde.

Ich habe jetzt 'ne Stunde ueber die alte Logik gebruetet, und mir ist dabei (wieder) bewusst beworden, dass auch diese Variante mit leicht aufwendigeren Regexps falsch "positive" Ergebnisse produziert. Eine Alternative ist ein explizites notifydev Attribut, ich kriege das aber nicht ohne Nebeneffekte generisch, als Teil des Frameworks hin.

Diese geschaetzten(!) 30% Verluste sind nur in speziellen Faellen relevant: es gibt viele Devices, die Events generieren, viele Events, viele "notify" Instanzen, und man setzt ueberall die "falschen" Regexps ein.
In diesem Fall empfehle ich die "falschen" Regexps mit { notifyRegexpCheck(<regexp>) } zu optimieren.

Beta-User

Vielleicht nochmal einen Schritt zurück:
Zitat von: rudolfkoenig am 16 Januar 2022, 13:39:06
In diesem Fall empfehle ich die "falschen" Regexps mit { notifyRegexpCheck(<regexp>) } zu optimieren.
Wer ist denn eigentlich unsere Zielgruppe?

Es kommen in Frage: Developer und User.
"An sich" handelt es sich bei notifyRegexpChanged() um eine interne Funktion, die dafür gemacht ist, dass Developer sie in Ihre Module einbauen. Wird aber tatsächlich nur von einem Teil so gehandhabt, s.u..

Bei den Usern gibt es zwei Guppen: Die, die das "Problem" kennen. Für die gibt es Hilfsmittel und Rückmeldungen wie diese hier: Muss es so unkomfortabel sein?
Und die User, die sich dazu keinen Kopf machen (meine Einschätzung: die ganz weit überwiegende Mehrheit!). Schlicht, weil "schlechte Beispiele" c&p übernommen werden (btw.: auch von mir gibt es solche im Wiki...).

MAn. sollte man die letzteren im Auge haben, die anderen beiden Gruppen können ihre Probleme selbst lösen, wenn sie wollen...

Zitat von: rudolfkoenig am 16 Januar 2022, 13:39:06
Ich fuerchte, das is zu hoch fuer mich, geschweige denn, dass ich die Nebeneffekte begreifen wuerde.
Dass das mit den Nebeneffekten en Riesen-Thema ist, ist unbestritten, Teil 1 glaube ich zwar nicht, werd's aber trotzdem nochmal erläutern.

Wir stellen fest: die bisherige Methode ist nicht in der Lage, NOTIFYDEV zu ermitteln, wenn irgendwo eine "oder"-regex auftaucht wie "Display:(testa|testb)", weil der split an der Pipe dazu führt, dass (meistens) keine passenden Geräte für die entstehenden Teile gefunden werden können.
Ergo: der "ungeprüfte Split" an einer Pipe als erste Stufe ist die eigentliche Ursache, dass in sehr vielen Fällen kein NOTIFYDEV ermittelt wird.

Meine gedanklichen Puzzleteile zur eventuellen Lösung:
- Grundsätzlich geht es darum, Devices zu ermitteln, an denen für das Zielgerät "relevante" Events stattfinden können.
- Diese Devices werden entweder
-- direkt (auch als regexp) reingeschrieben, es gibt sonst keine weiteren "ablenkenden" Infos (wie Reading-Namen);
-- durch einen Doppelpunkt vom "zugehörigen Rest" getrennt.
- User setzen gerne mal lieber eine Klammer zu viel wie zu wenig, man muss aufpassen, wenn "drumrum" Klammern sind. Die könnten zusammengehören (Sonderthema, macht es leider unleserlicher).

Ergo wird der übergebene String von vorne nach hinten zerstückelt, und zwar erst mal am ersten Doppelpunkt in zwei Teile. Alles, was nach dem Doppelpunkt bis zur nächsten Pipe (auf der "0"-Klammer-Ebene) kommt, ist für die Ermittlung von passenden Devices unnötiges Beiwerk und wird verworfen. Da bestimmte Sonderzeichen schwierig sind, werden die ersetzt durch einzelne Punkte.
Nach der "0-er"-Pipe beginnt das Spielchen von vorne.

Das war es im Prinzip auch schon.

Als "Bonus" gibt es noch die Option, devices per devspec zu markern. Das war bisher nicht vorgesehen, wird aber von einigen Modulautoren so gemacht - per direktem Hash-Zugriff.

Für eventuelle Tester:
Meine ersten Versuche sahen in etwa so aus:
{notifyRegexpChanged2($defs{weather}, '.*a|Display:(testa|testb)|TYPE=AMAD.*', 0)}
Nachdem die weitere Aufgabe aber darin bestand, erst mal festzustellen, ob und ggf. wo/bei welcher Art Ausdrücken wir ein (neues) Problem haben, habe ich das ganze jetzt mal im Testsystem wie folgt in fhem.pl eingebaut:

sub
notifyRegexpChanged($$;$)
{
  my ($hash, $re, $disableNotifyFn) = @_;
  notifyRegexpChanged2($hash, $re, $disableNotifyFn);

  %ntfyHash = ();
  if($disableNotifyFn) {
    delete($hash->{NOTIFYDEV});
    delete($hash->{NOTIFYDEV_1});
    $hash->{disableNotifyFn}=1;
    return;
  }
  delete($hash->{disableNotifyFn});
  my @list2 = split(/\|/, $re);
  my @list = grep { m/./ }                                     # Forum #62369
             map  { (m/^\(?([A-Za-z0-9\.\_]+(?:\.[\+\*])?)(?::.*)?\)?$/ &&
                     ($defs{$1} || devspec2array($1) ne $1)) ? $1 : ""} @list2;
  if(@list && int(@list) == int(@list2)) {
    my %h = map { $_ => 1 } @list;
    @list = keys %h; # remove duplicates
    $hash->{NOTIFYDEV} = join(",", @list);
    $hash->{NOTIFYDEV_1} = join(",", @list);
  } else {
    delete($hash->{NOTIFYDEV});
    delete($hash->{NOTIFYDEV_1});
  }
}

sub
notifyRegexpChanged2
{
  my ($hash, $re, $disableNotifyFn) = @_;

  #Log3('global',3 , "nRC2 called for $hash->{NAME}");
  #%ntfyHash = ();
  if($disableNotifyFn) {
    delete($hash->{NOTIFYDEV_2});
  #  $hash->{disableNotifyFn}=1;
    return;
  }
  #delete($hash->{disableNotifyFn});
  my $first = 1;
  my $outer = $re =~ m{\A\s*\((.+)\)\s*\z}x; #check if outer brackets are given
  my @list;
  my $numdef = keys %defs;
  while ($re) {
    (my $dev, $re) = split m{:}x, $re, 2; #get the first seperator for device/reading+rest?
    if ( $first && $outer ) {
        $first = 0;
        my $ops = $dev =~ tr/(//;
        my $clos = $dev =~ tr/)//;
        if ( $ops > $clos ) {
            chop($re);
            $dev =~ s{\A.}{}x;
        }
    }
    $dev =~ s{\A\s*\((.+)\)\s*\z}{$1}x; #remove outer brackets if given
    #Log3('global',3 , "re splitted to $dev and $re") if $re;
    return delete $hash->{NOTIFYDEV_2} if $dev eq '.*';

    while ($dev) {
      (my $part, $dev) = splitByPipe($dev);
      #Log3('global',3 , "dev splitted to $part and $dev") if $dev;

      return delete $hash->{NOTIFYDEV_2} if $part eq '.*';
      my @darr = devspec2array($part);
      return delete $hash->{NOTIFYDEV_2} if !@darr || !$defs{$part} && $darr[0] eq $part || $numdef == @darr;
      @list = (@list, @darr);
    }
    (undef, $re) = splitByPipe($re);
  }
  return delete($hash->{NOTIFYDEV2}) if !@list;
  my %h = map { $_ => 1 } @list;
  @list = keys %h; # remove duplicates
  $hash->{NOTIFYDEV_2} = join q{,}, @list;
  return;
}

sub splitByPipe {
    my $string = shift // return (undef,undef);
    # String in pipe-getrennte Tokens teilen, wenn die Klammerebene 0 ist
    my $lastChar = q{x};
    my $bracketLevel = 0;
    my $token = q{};
    my @chars = split q{}, $string;
    my $i = 0;
    my $repl;
    for my $char ( @chars ) {
        #Log3('global',3,"char is $char, last was $lastChar, level is $bracketLevel");
        if ($char eq '|' && $lastChar ne '\\' && !$bracketLevel) {
            $repl = "$token" . q{|};
            $repl =~ s{[{()}|*]}{.}g;
            $string =~ s{\A$repl}{}x;
            return ($token, $string);
        }
        $i++;
        if ($char eq q<(> && $lastChar ne '\\') {
            $bracketLevel++;
        }
        elsif ($char eq q<)> && $lastChar ne '\\') {
            $bracketLevel--;
        }
        $token .= $char;
        if ( $i == scalar @chars ) {
          $repl = $token;
          $repl =~ s{[{}()\|\*]}{.}g;
          $string =~ s{\A$repl}{}x;
        }
        $lastChar = $char;
    }
    return ($token, $string);
}


Startet man FHEM damit, kann man sich die Unterschiede schön auflisten lassen:
list .* NOTIFYDEV NOTIFYDEV_1 NOTIFYDEV_2
Zumindest im Testsystem (mit optimierten regexps) hatte ich keine Unterschiede zwischen NOTIFYDEV_1 (fhem.pl-Version aus notifyRegexpChanged()) und NOTIFYDEV_2 (per notifyRegexpChanged2() ermittelt), aber man sieht sehr schön, dass es einige Module gibt, die die Funktion nicht nutzen, die Liste mit NOTIFYDEV ist deutlich länger als die ersten beiden...
Server: HP-elitedesk@Debian 12, aktuelles FHEM@ConfigDB | CUL_HM (VCCU) | MQTT2: MiLight@ESP-GW, BT@OpenMQTTGw | MySensors: seriell, v.a. 2.3.1@RS485 | ZWave | ZigBee@deCONZ | SIGNALduino | MapleCUN | RHASSPY
svn: u.a MySensors, Weekday-&RandomTimer, Twilight,  div. attrTemplate-files

Damian

Es ist ein hausgemachtes Problem. Könnte der Entwickler passend zur der Schnittstelle einen reinen Device-Filter setzen, wäre die Filterung recht banal:

if ($dev->{NAME} !~ /$hash->{helper}{DEVFILTER}/) {return ""};

So mache ich es in meinem Modul, weil ich vom Anfang an die Trennung zwischen Device-Regex und Event-Regex an den User weiter gegeben habe, Syntax:

["<Regex für Devices>:<Regex für Events>"]
Programmierte FHEM-Module: DOIF-FHEM, DOIF-Perl, DOIF-uiTable, THRESHOLD, FHEM-Befehl: IF

Benni

Zitat von: Beta-User am 17 Januar 2022, 14:01:25
-- durch einen Doppelpunkt vom "zugehörigen Rest" getrennt.

Kleine Anmerkung dazu:

Die Regex muss keinen Doppelpunkt enthalten. Da sie nur matchen muss, kann es auch notify geben (gibt es auch!) in der Art:


device.event


Das wird aber auch aktuell von notifyRegexpCheck nicht erkannt.

gb#