Objekte/Strukturen aus dem Data Stack von FHEM miteinander vergleichen..

Begonnen von hasselh, 13 April 2025, 12:15:28

Vorheriges Thema - Nächstes Thema

hasselh

Hallo zusammen:

Die ursprüngliche Idee kommt eigentlich aus einem anderen Thread (Memory leak in 01_FHEMWEB.pm ?).
Aber ich dachte ich poste es mal hier, falls es jemand gebrauchen kann:

#!/bin/bash

/opt/fhem/fhem.pl 7072 ' \
{ no strict "refs";; no warnings "redefine";; \
  open(my $fh, ">", "/opt/fhem/log/mem.dump") or die;; \
  my %refs;; \
  sub traverse { \
    my ($data, $path, $depth) = @_;; \
    return if ($depth <= 0);; \
    if (length $data && $refs{"$data"}) { \
       print $fh "$path->".ref($data)."\n";; return };; \
    $refs{"$data"}={} if (ref($data) =~ m/HASH|ARRAY/);; \
    if (ref($data) eq "HASH") { \
      foreach my $key (sort keys %$data) { \
        traverse($data->{$key},"$path\{\"$key\"\}",$depth-1)} } \
    elsif (ref($data) eq "ARRAY") { \
      my $index = 0;; \
      foreach my $item (@$data) { \
        traverse($item, "$path\[$index\]", $depth-1);; \
        $index++;; } } \
    else { print $fh "$path\n";; } } \
  foreach (sort keys %main::) {  \
    if    (@$_) { traverse(\@$_,"\$$_",20) }  \
    elsif (%$_) { traverse(\%$_,"\$$_",20) }  \
    elsif ($$_) { traverse($_,"\$$_"  ,20) } };; \
  close $fh;; \
  return undef;;  }'

dump=/opt/fhem/log/mem.dump
if [ ! -f $dump ]; then
  echo No dump file $dump !!; exit; fi

target="/opt/fhem/log/`date +%Y-%m-%d`.dump"
rotate_log $target 2>/dev/null
cat $dump | sort -u >$target
echo $target
rm $dump

Der Vorteil hier (im Vergleich zu Data::Dumper), ist das ich die (vor-)sortierten Objekte/Strukturen von zwei Dumps (die ich zu unterschiedlichen Zeiten gezogen habe) mit einem einfachen diff vergleichen kann:

diff -y -W 200 --suppress-common-lines <dumpfile-1> <dumpfile-2>

Falls jemand noch eine idee hat, wie es besser/eleganter geht.. immer her damit :)

bismosa

Hallo!

Interessanter Ansatz. Werde ich auch mal testen.
Ich habe auch täglich wachsende Datenstrukturen und bin auf der Suche warum dies so ist.
Mein Ansatz sieht bisher so aus:
sub myUtils_write_defs_to_files {
    use JSON;
    use POSIX qw(strftime);
    use File::Path qw(make_path);
use Data::Dumper;
    my ($defs) = @_;

    my $defs_copy = {};
    foreach my $key (keys %$defs) {
        $defs_copy->{$key} = filter_non_serializable($defs->{$key}, 0);
    }

    my $timestamp = strftime("%Y-%m-%d_%H-%M-%S", localtime);
    my $dir = "$timestamp";

    eval {
        make_path($dir) or die "Kann Verzeichnis $dir nicht erstellen: $!";
        my $json = JSON->new->allow_blessed(1)->convert_blessed(1)->pretty->max_depth(1000);

        open(my $log_fh, '>>', 'write_defs_log.txt') or die "Kann Log-Datei nicht öffnen: $!";

        foreach my $key (keys %$defs_copy) {
            my $file = "$dir/$key.json";
            eval {
##print_to_file($file, $json->encode($defs_copy->{$key}));
##besser als dump! Dann gehen auch alle!
                print_to_file($file, Dumper($defs_copy->{$key}));
                print $log_fh "Datei $file erfolgreich geschrieben.\n";
            } or do {
                my $err = $@ || 'Unbekannter Fehler';
                print $log_fh "Fehler beim Schreiben von $file: $err\n";
            };
        }

np_filter_additional_symbols($dir, $log_fh);

        close($log_fh);
        1;
    } or do {
        my $error = $@ || 'Unbekannter Fehler';
        open(my $log_fh, '>>', 'write_defs_log.txt') or die "Kann Log-Datei nicht öffnen: $!";
        print $log_fh "Fehler: $error\n";
        close($log_fh);
        return "Fehler: $error";
    };
open(my $log_fh, '>>', 'write_defs_log.txt') or die "Kann Log-Datei nicht öffnen: $!";
print $log_fh "FERTIG\n";
    close($log_fh);

    return "Erfolgreich geschrieben in $dir";
}

sub filter_non_serializable {
    my ($data, $depth) = @_;
    $depth //= 0;
    return '**MAX_DEPTH**' if $depth > 5;

    if (ref($data) eq 'HASH') {
        my %filtered;
        foreach my $k (keys %$data) {
            $filtered{$k} = filter_non_serializable($data->{$k}, $depth + 1);
        }
        return \%filtered;
    } elsif (ref($data) eq 'ARRAY') {
        return [ map { filter_non_serializable($_, $depth + 1) } @$data ];
    } elsif (ref($data) =~ /^(SCALAR|REF)$/) {
        return filter_non_serializable($$data, $depth + 1);
    } elsif (ref($data)) {
        return "UNSERIALIZABLE: " . ref($data);
    } else {
        return $data;
    }
}

sub np_filter_additional_symbols {
    my ($dir, $log_fh) = @_;

    no strict 'refs';
    foreach my $symbol (keys %main::) {
        next unless defined $main::{$symbol};
        my $ref = eval {
            *{$main::{$symbol}}{HASH} ||
            *{$main::{$symbol}}{ARRAY} ||
            *{$main::{$symbol}}{SCALAR} ||
            *{$main::{$symbol}}{CODE};
        };
        next unless defined $ref;

        my $dumper_file = "$dir/${symbol}.dmp";
        eval {
            print_to_file($dumper_file, Dumper($ref));
            print $log_fh "Datei $dumper_file erfolgreich geschrieben.\n";
        } or do {
            my $err = $@ || 'Unbekannter Fehler';
            print $log_fh "Fehler beim Schreiben von $dumper_file: $err\n";
        };
    }
}

sub print_to_file {
    my ($file, $content) = @_;
    open(my $fh, '>', $file) or die "Kann Datei $file nicht öffnen: $!";
    print $fh $content;
    close($fh);
}

sub myUtils_write_defs_done {
    my ($result) = @_;
    Log3("global", 4, $result);
}

sub myUtils_start_write_defs {
    my $defs_copy = { %defs };  # Kopie von %defs erstellen
    BlockingCall("myUtils_write_defs_to_files", $defs_copy, "myUtils_write_defs_done");
}
Mit der Ausführung:
{myUtils_start_write_defs()} in der FHEM Kommandozeile wird ein Verzeichnis mit dem Aktuellen Datum und der Uhrzeit im FHEM Verzeichnis erzeugt.
Hier werden dann alle Objekte als Textdatei hinterlegt.
So kann ich schnell sehen, ob neue hinzugekommen sind, und diese am Windows-Rechner bequem anschauen.

Nur den Verursacher konnte ich so bisher nicht identifizieren...

Gruß
Bismosa
1x nanoCUL 433MHz (SlowRF Intertechno) für Fenstersensoren
1x nanoCUL 868Mhz für MAX (9x HT 1xWT)
1x ZigBee CUL
Weiteres: Squeezebox server, Kindle Display, ESP8266, Löterfahrung, ...

bismosa

Ich konnte nun einige Verbraucher ausfindig machen. Wenn auch eher schwieriger.
Mit den geschriebenen Dateien und der Analyse wurde das nur bedingt etwas.
Ich konnte hier sehen, das im data.dmp doch sehr viel im SolarForecast Device vorhanden ist. Das war auch der Hauptverbraucher.

Weitere Verbraucher die ich ausfindig machen konnte:
1x SB_SERVER und 6x SB_PLAYER (ca. 20MB)
FHEM_Installer (ca. 30MB)

Aber welche Datenstruktur nun wirklich wächst...das war bisher nicht nachweisbar.

Ich konnte nun erstmal mein System "optimieren". Der FhemInstaller wird nicht dauerhaft benötigt und beim SolarForecast habe ich auch einiges angepasst.
Gruß
Bismosa
1x nanoCUL 433MHz (SlowRF Intertechno) für Fenstersensoren
1x nanoCUL 868Mhz für MAX (9x HT 1xWT)
1x ZigBee CUL
Weiteres: Squeezebox server, Kindle Display, ESP8266, Löterfahrung, ...