Speicherleck im VDR oder einem Plugin

  • Ich habe ein paar allocs aus dem libleak-log rausgepickt -> https://pastebin.com/raw/Tsjyp43M Das sind überwiegend reallocs. callstack[2957]z.B. ist da richtig gut dabei. Vielleicht hilfts ja.

    Libleak hatte ich letztes Jahr oder so auch schon mal versucht. Leider bekommt man da alle EPG-Einträge als falsch positiv angezeigt, weil die z.T. eine Lebensdauer von 2Wochen oder sogar mehr haben.


    Für mich sieht es aber aus, als ob die Bereiche korrekt wieder freigegeben werden.

    Das würde ich auch bei den Anderen sagen.

    Allenfalls könnte bei den "components" irgendwo ein Rechenfehler beim Index vorliegen, da sollte nochmal wer anders einen Blick drauf werfen.


    Die Meldungen kannst du IMHO jedenfalls ignorieren und raus filtern, sonst siehst du den Wald vor Bäumen nicht.

    Ich schätze aber, danach bleibt auch noch eine Menge über.


    Ich hatte es damals übrigens anders herum versucht, das EPG zu unterdrücken. Das hat aber nicht wirklich gut geklappt.

    Man könnte noch versuchen den EPG periodisch mittels svdrp "CLRE" zu leeren, so dass die Einträge nicht älter als "LEAK_EXPIRE" werden können. Das hatte ich noch nicht versucht.

    Auf einem Produktiv-System mit EPGsearch ist das halt keine gute Idee und ich habe am Arbeitsplatzrechner leider keinen DVB-Empfang.


    Was mit aber aufgefallen ist, dass hier ganz oft 1byte alloziert wird, auch wenn strlen(ShortText) == 0 ist. Wäre es nicht richtig, es so zu machen, wie hier?

    IMHO ist es legitim leere Strings zu erzeugen.

    Es kann am Ende praktischer sein, einmal sicherzustellen, dass der String existiert, als immer wieder auf Nullpointer abfragen zu müssen.

    Viel Speicher braucht das auch nicht.


    Code
    117      int l = max(dest ? strlen(dest) : 0, strlen(src)) + 1; // don't let the block get smaller!

    Wobei mir eben die Zeile aufgefallen ist:

    strlen(dest) +1 kann doch kleiner sein als der reservierte Block?

    Der Block kann dann doch kleiner werden, aber halt erst beim zweiten Aufruf der Funktion.

    Oder täusche ich mich da?

    Ein Speicherleck ist das aber wohl eher nicht.

    Gruss
    SHF


  • Guter Tip, für mich war es erstmal interessant zu sehen, wer überhaupt zur Laufzeit Speicher haben will. Wenn man die alle durchgeht, sollte der Kandidat ja dann dabei sein?


    Zu den leeren Strings kann ich nichts sagen, das kann Klaus wohl beurteilen.


    Hier mein Speicherverbrauch über einen Tag protokolliert:


    Rund 12MB/24h. Etwas weniger als bei den Kollegen. Ich bin mir auch nicht mehr sicher, ob das bei aktiviertem EPG-Scan nicht normal ist, wenn ständig reallocs stattfinden? Ich habe übrigens den Code so geändert, dass kein Speicher für leere Strings beansprucht wird.


    Gruß

    Andreas

  • Ich habe den code von libleak mal angepasst, damit nur jeder 100. Fund geloggt wird. libleak war so eingestellt, dass es sich erst nach 1h eingeschalten hat und auch die allocs am Programmstart habe ich überspringen lassen. Alles, was in vdr gelinkt ist ("ldd vdr") habe ich auf die Blacklist. Der VDR ist eine gute Stunde gelaufen.


    Ich komme erst später dazu, genau reinzuschauen, aber wenn jemand Zeit und Interesse übrig hat, kann er ja mal reinschauen... Vielleicht findet sich was auffälliges. Viel Spaß ;)

    Das Log liegt hier: http://lima.imkreisrum.de/vdr/


    Gruß

    Andreas

  • Ich habe kurz in die Datei reingesehen. VonListGarbageCollector oder Purge habe ich nichts gefunden, nur cSchedules::Cleanup().

    Leider sagen mir diese Meldungen nichts.

    LG Helmut

  • So leider auch meine Erfahrung mit den ganzen "Leck-Detektoren" die sowas eigentlich einfacher machen sollten.


    Bisschen auch ein Nachteil der Sprache an sich. Ja, C/C++ ist extrem maschinennah und entsprechend performant. Aber damit kommt leider auch enorm viel Verantwortung für den Programmierer. Und wir sind halt alle nur Menschen...


    Man müsste im Prinzip, sobald man eingegrenzt hat wo das Leck sein könnte, manuell für jedes "alloc" oder "new" prüfen ob hier auch in jeder denkbaren Konstellation ein "free" oder "delete" folgt. Oder ggf. manuell loggen. Sobald man den Pointer hat den als Logmessage ausgeben und das gleiche beim "Wegräumen". Nach längerer Laufzeit sollte es keine größere Menge Pointer mehr geben die nicht weggeräumt wurden.


    Um das mal weiterzuspinnen: Kann man nicht auch gezielt ein "Wegräumen" erzwingen? Also nur mal angenommen ich lasse einen VDR nur ein paar Stunden laufen mit einem Logging wie oben beschrieben. Wenn das Aufräumen überall sauber läuft, dann sollte bei sauberem Beenden des VDR ja kein einziger Pointer nicht sauber freigegeben worden sein. Andernfalls kann ja was nicht stimmen. Natürlich räumt der Kernel dann den ganzen Prozess weg, aber sauber wäre eben wenn das Programm vor dem Eingreifen vom Kernel alles schon freigegeben hat.

  • Ich komme mit den "Leck-Detektoren" auch nicht klar. Ich habe bei markad genau so eine Logging Funktion mal eingebaut. Jedes alloc/new wird mit dem Objektnamen in ein Array geschrieben, jedes free/delete löscht den Eintrag aus dem Array. Am Programmende wird das gelogged, was noch im Array drin ist.

    War zwar erst mal ein großer Aufwand das überall einzubauen, deckt jetzt aber neue Leaks sofort auf.

    Das Ganze ist aus Performance Gründe natürlich per default über Compiler Direktive aus.

  • Für den ganzen VDR würde im Prinzip wohl reichen nur den Pointer (Adresse) auszugeben. Könnte zwar theoretisch neu vergeben werden, aber bei überschaubarer Laufzeit wohl unwahrscheinlich. Auswertung dann im Nachgang z.B. mit einem Python-Skript das das Log parst und entsprechend eine Liste aufbaut.

  • Ich komme mit den "Leck-Detektoren" auch nicht klar. Ich habe bei markad genau so eine Logging Funktion mal eingebaut. Jedes alloc/new wird mit dem Objektnamen in ein Array geschrieben, jedes free/delete löscht den Eintrag aus dem Array. Am Programmende wird das gelogged, was noch im Array drin ist.

    War zwar erst mal ein großer Aufwand das überall einzubauen, deckt jetzt aber neue Leaks sofort auf.

    Das Ganze ist aus Performance Gründe natürlich per default über Compiler Direktive aus.

    Nur weiter gedacht: Der Detektor von oben hängt sich ja zwischen jedes alloc und free, wenn ich es richtig verstanden habe. Könnte man den nicht so anpassen, dass er genau das macht? Stelle ich mir einfacher vor, als zu versuchen, alles im Programm selbst zu suchen? Dh. ein Array mit dem pointer und dem zugewiesenem Speicher füllen und leeren und sich am Ende ansehen, was übrig bleibt? Oder auch mal zwischendurch schauen, was im Array so drin ist?


    Gruß

    Andreas

  • Der Grund für libleak war bei mir in erster Linie, damit man sieht, was ziemlich häufig und ständig Speicher will. Ob am Ende wirklich geleakt wurde, wollte ich erstmal gar nicht wissen. Wie zuverlässig und aussagekrüftig diese Tools sind, kann ich nicht sagen... ich habe vorher noch nie eins eingesetzt ;)


    EDIT: Wenn ich mir den letzten Eintrag aus dem Log z.B. ansehe

    Code
    callstack[182]: may-leak=635 (7693 bytes)
        expired=635 (7693 bytes), free_expired=0 (0 bytes)
        alloc=635 (7693 bytes), free=0 (0 bytes)
        freed memory live time: min=0 max=0 average=0
        un-freed memory live time: max=4826

    lese ich es so, dass hier wohl 7693 bytes an Speicher alloziert wurden, aber am Ende nicht gefree'd wurden?

    Das wäre für mich schonmal ein Ansatz zu schauen, ob das wirklich so ist.


    EDIT2: Wenn ich dann noch //FcFini(); // older versions of fontconfig are broken - and FcInit() can be called more than oncesehe, sieht das auf den ersten Blick für mich als Laien, oder jemanden, der noch nicht genau in den Code geschaut hat, verdächtig aus :p

    Einmal editiert, zuletzt von rell ()

  • Das mit "FcFini" passt tatsächlich so. Fontconfig hält globale Variablen und auch Plugins nutzen fontconfig. Wenn da jeder potentiell dem anderen das Fontconfig "wegräumt" gibt es unkontrolliert Crashes. Nur der VDR dürfte "FcFini" aufrufen, aber ganz am Ende. Also erst dann wenn alle Plugins, ... auch komplett weg sind. Aber auch das ist nun kein kritisches Problem. Hier würde dann eben der Kernel final aufräumen.

  • Man kann "new" und "delete" überschreiben und dann eigene Logfunktionen bauen, nichts anderes machen sicherlich die Leck-Detektoren.


    Oder man stellt um auf std::unique_ptr bzw. shared_ptr.

    https://en.cppreference.com/w/cpp/memory/unique_ptr

    https://en.cppreference.com/w/cpp/memory/shared_ptr

  • mini73 "*alloc" und "free" für C und zusätzlich "new" und "delete" für C++ oder werden Konstruktor und Destruktor im Endeffekt auch über alloc/free erfasst?

    ... bin kein C++'ler


    IMHO sollte es nicht so schwer sein, die notwendigen Wrapper zu schreiben und dann kann man auch eine Statistik füllen und abfragen. Per LD_PRELOAD wäre das wohl auch sauber einzubinden. Vielleicht schreibe ich da mal was zusammen...


    Gruß

    Andreas

  • Wenn man nur den vdr betrachtet, kann man ja schon viel über den Präprozessor machen. Aber das wäre dann nicht über LD_PRELOAD machbar.


    Wenn du das nur für bestimmte Klassen machen willst, sollte man einfach die entsprechenden Operatoren überladen können.


    https://www.geeksforgeeks.org/…ng-new-delete-operator-c/

  • Aber vdr verwendet doch meist malloc und free.

    Nicht wirklich - ein kurzes "git grep" zeigt beides in ausrecheinder Menge an...

  • Hm.., hilft new und delete zu überschreiben auch bei


    class F;

    (..)

    F Instance;



    Oder aber nur bei


    F* Instance = new F( /*arglist*/ );

    (..)

    delete Instance;


  • Danke(!) - manchmal ist man sich solcher 'Nebensachen' nicht so ganz bewußt.

  • Da ich bei meinen Test immer den eitFilter deaktiviere und nur die Events der epg.data einlese habe ich in epg.c immer mehr Informationen daraus übersprungen und nicht in cEvents eingelesen. Schlußendlich habe ich auch die beiden Hashes für Starttime und EventID nicht mehr befüllt und es wurden nur noch (fast) leere Events zu den Schedule-Listen hinzugefügt.

    Es hat sich aber auch hier gezeigt, das nach dem Entfernen von fast 50% der Events kein RAM feigegeben wird: Ein Memory-Leak würde ich aber durch die starke Vereinfachung nun mit ziemlicher Sicherheit auch ausschließen.


    Vielleicht hat diese "Nichtfreigabe" von eigentlich unbenutzten Speicher auch einen tieferen Sinn:

    Der VDR alloziert vermutlich einige Hundert kleine Speicheblöcke pro Minute um sie in diverse Listen zu verarbeiten und anschließend wieder freizugegeben. Es wird sich damit zwangsweise eine starke Fragmentierung im RAM ergeben das zwar viele, aber eher kleine zusammenhängende Bereiche von freiem Speicher enthält.

    Möglicherweise hält sich der Speicher-Manager - solange es noch genügend "grossen" freien physischen Speicher gibt - zurück und wartet, ob sich durch kommende free's/delete's mehrere kleine Speicheblöcke zu einem grösseren Vereinen.

    Das ist aber nur eine Vermutung.


    Ich bin dann auf die Funktion malloc_trim() gestossen, Durch diese wird der Speichermanager veranlasst, den im obersten Bereich des Heap nicht benötigten Speicher sofort freizugegeben.


    Ich habe diese Funktion jetzt in den ListGarbageCollector eingebaut und kann beobachten, daß sich seit dem zweiten Transponderdurchlauf der Wert von "RES" ziemlich eingependelt hat und sich nun - ausgehend von ca. 155 MiB - seit ca. 1 Stunde ziemlich Konstant zwischen 251 MiB und 253 MiB bewegt.


    Im Anhang der malloc_trim Patch, vielleicht kann noch jemand dieses Verhalten bestätigen.

    Zusätzlich auch noch ein Patch der beim Schedule-Cleanup die Anzahl und Veränderung der EPG-Events ins syslog schreibt.

Jetzt mitmachen!

Sie haben noch kein Benutzerkonto auf unserer Seite? Registrieren Sie sich kostenlos und nehmen Sie an unserer Community teil!