Memory Leak..die 1000ste. Verständnissfrage

Begonnen von kask, 20 April 2024, 18:38:06

Vorheriges Thema - Nächstes Thema

kask

Hallo,
ich hatte jetzt die Tage massive Probleme mit memory leaks.
Die RAMnutzung ist in 5h bis zu 1,5GB angewachsen unter meiner Beobachtung.
Aufgefallen ist mir das weil der Perl Process so ca. alle 40h neugestartet ist die letzten Tage.
Habe keine großen Änderungen gemacht. Dachte ich zumindest. Alles was ich in den letzten Tagen angefasst hatte, habe ich daraufhin disabled.
Aber der leak bestand trotzdem. Fand ich schon komisch.

Um das Ramloch zu lokalisieren habe ich via apptime erst einmal geschaut was am meisten auftaucht/aufgerufen wird.
Bin auch ziemlich schnell dann fündig geworden.
Ich habe ein Victron System was ziemlich viele Daten bereitstellt. Damit werden etliche notfies und do_if's angetriggert.

Um den Ram in den grief zubekommen habe ich meine ganzen sub's und variabelen in den notifie's und do_if's undefined am ende.
my $var = blah;
my @arr = [];

..codegemüse..

undef($var);
undef(@arr);

Nach dem ganzen umschreiben blieb mein RAM wieder Konstant.

Jetzt kommt die egentlich Frage.
Theoretisch sollte doch die variabele von Perl selbst freigegeben werden. Bzw. der genutzte Speicher der Variabelen.
Kann es sein das wenn sehr viele Aufrufe von der gleichen sub oder der Notify etc. auflaufen, dass Perl sich da verschluckt bzw. dessen Speichermanager?
Es wird ein Raspberry4 (4GB) genutzt und der load average liegt oft > 1 in 1,5&15 timing. Ich weiß ist suboptimal aber kann das ein Grund sein für die memory leaks?

Auf dem grafana pic sieht man beim ersten flat wo ich das ursachen Modul lokalisiert hatte + aktivieren mit den undef modifikationen.
Dann ein Neustart und der speicher blieb auch da konstant.
Das RPi-Monitor pic zeigt wie mein Ram die letzten Tage aussah.


rudolfkoenig

ZitatKann es sein das wenn sehr viele Aufrufe von der gleichen sub oder der Notify etc. auflaufen, dass Perl sich da verschluckt bzw. dessen Speichermanager?
Unwahrscheinlich.

Perl arbeitet mit Referenz-Zaehler, d.h. zirkulaere Referenzen muss man explizit freigeben.

Z.Bsp. muss man die Daten der XML-Bibliothek explizit freigeben.
Zu meiner Ueberraschung sind auch eingebettete Funktionen (sub) ein Problem, wenn sie Variablen der aeusseren Funktion referenzieren.
Vmtl. gibt es noch weitere Faelle, die ich nicht kenne.

Triviale Ursache sind z.Bsp. Daten, die man in einem Hash mit immer neuen Schluessel speichert, und nie entfernt.

kask

#2
Ja das ist Fluch und Segen mit dem Hash.
Schön immer einen gepakten Koffer zu haben, da ist alles drin was man braucht auf der Reise. Nur muss man die getragene Wäsche auch rausnehmen und nicht immer nur frische Wäsche reinpacken. Denn irgendwann ist der Koffer so voll das man den nicht mehr tragen kann.

Ich greife aber nicht auf den Hash zu oder manipuliere diesen.
Ich greife nur mittels FHEM Mitteln (ReadingsNum/Val/Timestamp) auf Device variabelen zu oder setzte eventuell mit "setreading" diverse Werte in den Devices.
Sozusagen nur lokale temporäre Variabelen werden erzeugt. So sagt es mein Verständnis zumindest.

Ich weiß auch nicht ob Perl in der sub nach einem return überhaupt noch code aufarbeitet wie es z.b in pascal möglich ist.
Oder ob Perl da eher vorgeht wie Python damit umgeht, wo es als ein exit absolut zu verstehen ist.

Mit den undef(x) in sämtlichen Code den ich eingepflegt habe ist das Speicherloch verschwunden. Also scheint Perl code nach return zuzulassen.
Oder der compiler hat an irgend einer Stelle versagt (bug?), weil im irgendein codeteil/zeichen nicht gepasst hat und durch meine Änderung hat sich das aufgelöst.
Kenne so ähnliches von älteren Delphi(Pascal) versionen, da habe ich etwas mehr kenne von als von Perl.

Hier mal ein Bruchteil der subs aus meinem 99_myUtils.pm Modul. Zum zeigen wie unproffessional ich da Werte abarbeite ;) :
sub minvalue($$) {
    my ($valx, $valy) = @_;
    if ($valy < $valx) {
        $valx = $valy;
    }
    return $valx;
    undef($valy);
    undef($valx);
}

sub maxvalue($$) {
    my ($valx, $valy) = @_;
    if ($valy > $valx) {
        $valx = $valy;
    }
    return $valx;
    undef($valy);
    undef($valx);
}

sub limitvalue($$$) {
    my ($valmin, $val, $valmax) = @_;
    if ($valmin > $val) {
        $val = $valmin;
    } elsif ($valmax < $val) {
        $val = $valmax;
    }
    return $val;
    undef($valmin);
    undef($val);
    undef($valmax);
}

sub CalcSystemEfficiency($$) {
    my ($pdc, $pac) = @_;
    my $phigh = maxvalue(abs($pac), abs($pdc));
    my $plow = minvalue(abs($pac), abs($pdc));
   
    my $value = 0;
    if (($phigh != 0) & ($plow != 0)) {
       if ($pac < 0) {
        $value = -(100 / $phigh * $plow);
       } else {
        $value = (100 / $phigh * $plow);
       }
    }
    return sprintf("%.1f",($value));
    undef($pdc);
    undef($pac);
    undef($phigh);
    undef($plow);
    undef($value);
}

sub CalcPowerLost($$) {
    my ($pdc, $pac) = @_;
    my $value = (maxvalue(abs($pac), abs($pdc))-minvalue(abs($pac), abs($pdc)));
    if ($pac < 0) {
        $value = -$value;
    }
    return $value;
    undef($pdc);
    undef($pac);
    undef($value);
}


Im userreading ruf ich dann z.b. so eine sub auf:

...
System_Efficiency:BAT512_Power_value.* { CalcSystemEfficiency( ReadingsNum($name,"BAT512_Power_value",'0'), ReadingsNum($name,"P_AC_InOut","0") ) ;; },
...

Beta-User

Hmmm, es würde mein Perl-Weltbild ziemlich durcheinanderwürfeln, wenn das mit den undef-Anweisungen nach den return-statements tatsächlich irgendwelche Auswirkungen hätte....
Sonstiges:
- wenn man "min"-Werte braucht, könnte man auch das eingebaute minNum() verwenden, oder `List::Util::min($x,$y);`
- da du hier "ständig" zwei abs-Werte vergleichen willst, könnte man das in eine einzige Funktion auslagern und dann die beiden gewünschten Absolut-Werte als array zurückgeben*
- das "einfache &" in `    if (($phigh != 0) & ($plow != 0)) {` ist Absicht?

*so in etwa:
sub sortTwoValuesAbs() {
    my $valx = abs(shift) // return (0,0);
    my $valy = abs(shift) // return (0,0);
    if ($valy < $valx) {
        return ($valy, $valy);
    }
    return ($valx, $valy);
}
....
    my ($plow, $phigh) = sortTwoValuesAbs($pac,$pdc);
(kann sein, dass man erst die defined-or checken muss und dann erst die abs-Anweisungen auf die Werte loslassen darf)
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

kask

Wie gesagt bin absoluter Laie in perl. "1000 wegen führen nach Rom". Mir ist auch klar das das alles Verbesserungspotenzial aufweist.
Werde mich auf jeden Fall mal mit deinen Anregungen auseinandersetzen um das Laufzeittechnisch zu optimieren.
Dank dir.

kask

Das mit dem & ist keine Absicht. Funktioniert aber vermutlich weil beide Vergleicher ein bool ausgeben und da wäre es ja nun theoretisch egal ob bitweise verglichen wird.
Ist vermutlich deshalb nicht aufgefallen.

Das mit dem low/high array klingt optimiert. Zumindest wenn bei der Übergabe im positiv If case nicht zweimal der selbe Wert ausgegeben wird ;)

Die undefs hinter dem return bringen nix. Komisch, ich bin weiterhin verwirrt.
Folgende sub funktioniert auch richtig, also kann nach dem return nix mehr kommen.

sub sortTwoValuesAbs {
    my $valx = abs(shift) // return (0,0);
    my $valy = abs(shift) // return (0,0);
    if ($valy < $valx) {
        return ($valy, $valx);
    }
    return ($valx, $valy);
    return ($valy, $valx);
}

Muss ja eigentlich auch so sein, sonst würde die Funktion der sub als solche ja nie funktionieren.

Was ist nun also anders. bzw. warum frisst FHEM jetzt kein RAM mehr.
Ich werde jetzt mal alle undef wieder schrittweise rückgängig machen und beobachten was dann passiert.







kask

Ich habe mein 99_myUtils.pm angepasst mit den Tests oben ohne "undef" entfernungen etc. und mein RAM geht wieder hoch.
Lediglich die sub eingepflegt und "reload 99_myUtils.pm" ausgeführt. Über webcommand dann die sub getestet.
Ich verstehe es nicht. Da muß irgend wo ein Problem in dem 99_myUtils Modul sein. bzw. dessen reload.
Jetzt was geändert und der RAM geht wieder hoch. Änderung rückgängig gemacht und der RAM steigt trotzdem immernoch.
Nach einem kompletten fhem neustart bleibt es stabil.

Kann ich das 99_myUtils.pm Modul nicht mit reload "on-the-fly" "refreshen".
Dort sind doch nur Funktionen hinterlegt die nur durch einen explizieten call aufgerufen werden.


Hatte bis jetzt so immer funktioniert.

Oder sollte das mein Problem generell sein. Ein Modul reload ohne Neustart?

frank

wie sieht es aus, wenn du anstatt reload ein fhem restart machst?
FHEM: 6.0(SVN) => Pi3(buster)
IO: CUL433|CUL868|HMLAN|HMUSB2|HMUART
CUL_HM: CC-TC|CC-VD|SEC-SD|SEC-SC|SEC-RHS|Sw1PBU-FM|Sw1-FM|Dim1TPBU-FM|Dim1T-FM|ES-PMSw1-Pl
IT: ITZ500|ITT1500|ITR1500|GRR3500
WebUI [HMdeviceTools.js (hm.js)]: https://forum.fhem.de/index.php/topic,106959.0.html

kask

Dann bleibt der Speicher im Rahmen des möglichen/sinnigen.
Zumindest soweit ich das jetzt während meiner Fehleranalyse beobachtet habe.

Ich vermute jetzt der reload passierte dann in einem ungünstigen Zeitpunkt, in der vieleicht die Funktionen aktiv waren und dann hat sich perl "verzeigert".
Vermutlich macht Perl da keine Prüfung auf Konsistenz während des reloads, sondern hämmert das einfach rein wie es kommt.
Ist so vermutlich auch nich angedacht Module im laufenden Betrieb neuzuladen.
Nur weil es möglich ist heisst es ja nicht das man das machen sollte/darf ohne Konsequenzen.

rudolfkoenig

ZitatIch vermute jetzt der reload passierte dann in einem ungünstigen Zeitpunkt, in der vieleicht die Funktionen aktiv waren und dann hat sich perl "verzeigert".
FHEM ist singlethreaded, ich kann mir in diesem Zusammenhang keinen unguenstigen Zeitpunkt vorstellen.

Vermutlich macht Perl da keine Prüfung auf Konsistenz während des reloads, sondern hämmert das einfach rein wie es kommt.
Ich habe in 99_myUtils eine einfache Funktion gebaut, FHEM gestartet, dann die Funktion kaputtgemacht (Syntax Error), und danach reload 88_myUtils ausgefuehrt.
Der Syntax-Error wurde in FHEM-Log vermerkt, und die alte Funktion nicht ueberschrieben.

kask

Ja der Syntax check funktioniert. Das ist mir bekannt.
Mit Konsistenz meine ich das gerade im Speicher genutzte Funktionen nicht der "Stuhl unter dem Ar*** weggezogen bekommen, während der Abarbeitung". Soll heißen der genutzte Arbeitsbereich des Speichers wird nicht während der Abarbeitung ausgetauscht.
Sondern richtig getimed ausgetauscht werden. Bzw. jeder neue Aufruf der Funktion wird auf einen neuen Speicherbereich verwiesen. Der alte wird reallocated wenn kein thread mehr aktiv ist der auf dem alten Speicherbereich mehr zeigert oder ähnlich.
Macht das Perl so oder ähnlich? Ich weiß es absolut nicht wie sich das verhält in Perl.
Deshalb auch soviele Vermutungen in meinen Posts.


rudolfkoenig


kask

Ja, da du das so laut schreist gehe ich davon aus das du mir sagen möchtest das eins nach dem anderen erfolgt.
Ein Reload kann nur erfolgen wenn nichts anderes mehr in der Pipeline liegt bzw. die vorherige Aktion abgeschlossen ist.
Somit kann da garnichts schiefgehen bei einem reload.


Und nicht das FHEM selbst nur auf einem Thread läuft und selbst nicht Multithreading fähig ist.
Was ja nicht ausschliessen würde das mehrere threads angetriggert werden und z.b. über callbacks den Hauptthread informieren.
So wie es hatte verstanden werden können und ich habe.

Das ganze verbessert meine Situation leider nicht und ich weiß nicht warum ich die Probleme habe.
Schön ist aber das mein Horizont erweitert wird. Ich versuche weiter die Ursache ausfindig zumachen.
Ich bin mir sicher das das Problem bzw. die Ursache des Problems vor meinem Monitor sitzt. Vor Unwissenheit strozend.