Ideensammlung: Stabilitätsprobleme frühzeitig erkennen

Ich plane ein Skript zu schreiben, welches periodisch aufgerufen wird und in dem bestimmte Systemparameter beobachtet werden, um im Falle von „verdächtigem“ und „instabilem“ Verhalten eine Meldung abgeben zu können.

Folgende Ideen habe ich für Trigger:

  • über längere Zeit werden unaufhörlich immer mehr Objekte irgendwo in der Struktur erstellt
  • Root-Objekt wird umbenannt (das hat bei mir schon öfter auf Fehler in Skripten hingedeutet)
  • Archiv einer Variable übersteigt eine bestimmte Größe
  • Mindestens ein Thread läuft „extrem“ lange
  • Mehrere Threads laufen „relativ“ lange
  • Fast alle Threads belegt
  • Anteil der Threads, die zum selben Skript gehören, ist zu hoch
  • Kernel-Partition füllt sich bzw. ist voll
  • Arbeitsspeicher füllt sich bzw. ist voll
  • Logdatei wächst sehr stark pro Zeiteinheit
  • Logdatei insgesamt zu groß
  • Skript hat bei wiederholten Prüfungen Fehler-Flag

Ich möchte möglichst wenig „falschen Alarm“ generieren.
Bin auf Input gespannt.

1 „Gefällt mir“

Das Symcon Integrity-Check Modul kennst du?

Das deckt schon einige deiner Wünsche ab, eventuell kann das ja durch @demel42 erweitert werden.

Kannte ich noch nicht, interessant. Sieht aber aus, als ob es bei dem Modul in erster Linie darum geht, „tote“ Referenzen aufzuspüren. Allerdings das Prüfen von Threads auf „Langläufer“ gibt es wohl.

Ergänzend nutze ich noch Linux-Server-Info, auch von @demel42 mit einer Alarmierung per Script drum herum.

Die Basis sieht in meinem IPSview dann so aus

Links unter den Buttons habe ich dann die Grafik vom Variablenverlauf.

Der Integrity-Check liefert z.B. so etwas

Hier mal eine erste „Betaversion“ meines Skripts. Es werden eine Reihe Variablen unterhalb des Skripts erstellt für temporäre Werte sowie Konfigurationseinstellungen. Es ist möglich, eine Mailer-Instanz anzugeben sowie ein Ziel (Kategorie/Dummy Module) für Visualisierungs-Links.

Ist der Mailer aktiviert, so bekommt man eine Mail wenn ein Problem festgestellt wird sowie eine „Entwarnung“ wenn es nicht mehr besteht.

Aufgabe des Skripts ist, sich anbahnende Stabilitätsprobleme und unerkannte Fehler zu melden, ohne möglichst Fehlalarmmeldungen zu generieren. Folgende Features:

  • Überwachung des CPU-Auslastung (es wird über 5 Minuten gemittelt)
  • Überwachung des Arbeitsspeicherverbrauchs
  • Überwachung des freien Speicherplatzes auf der Kernel-Partition
  • Überwachung der Größe des Log-Ordners
  • Überwachung der Wachstumsrate der aktuellen Logdatei (es wird über 15 Minuten gemittelt)
  • Meldung von als fehlerhaft markierten Skripten während der letzten 24 Stunden (mit Zeitangabe zwecks Ursachenforschung im Log)
  • Überwachung der Objektanzahl und Warnung bei massenhaftem oder kontinuierlichem Erstellen neuer Objekte
  • Warnung, wenn das Root-Objekt umbenannt wird (passiert gerne mal wenn irgendwo ein false nicht richtig abgefangen und irrtümlich als Objekt-ID weiter verarbeitet wird).
  • Überwachung der Archiv-Größe pro Variable sowie insgesamt
  • Überwachung belegter Thread-Slots (insgesamt, pro Skript)
  • Warnung bei lange laufenden Threads

Feedback willkommen.

<?php
// Mailer-Instanz für Problemmeldung
$mailer_id = false;

// Visualisierungs-Instanz (bspw ein Dummy Modul), in dem Links zur Konfiguration erstellt werden
$visu_id = false;

/////////////////////////////////////////////////////////////////////////////////////
// Aktionsskript für Variablenänderung
if($_IPS['SENDER'] == "WebFront") {
    SetValue($_IPS['VARIABLE'], $_IPS['VALUE']);
    return;
}

$visu_link_count = 0;

// ID vom Archive Control ermitteln
$ac_id = IPS_GetInstanceListByModuleID("{43192F0B-135B-4CE7-A0A7-1475603F3060}" /* Module ID of Archive Control */)[0];

// Warnstufen
define("LEVEL_NOTHING", 0); // keine Probleme
define("LEVEL_WARNING", 1); // Probleme, die nicht unmittelbar die Stabilität bedrohen
define("LEVEL_ALARM", 2); // Probleme, die unmittelbar die Stabilität des Systems gefährden

$report = "";
$report_html = "";
$report_lines = "";
$level = LEVEL_NOTHING;

// Variablenprofile erstellen
$prof_name_mb = "MemorySize.MB";
if(!IPS_VariableProfileExists($prof_name_mb)) {
    IPS_CreateVariableProfile($prof_name_mb, 1);
    IPS_SetVariableProfileText($prof_name_mb, "", " MB");
    IPS_SetVariableProfileValues($prof_name_mb, 0, 8192, 16);
}

$prof_name_bps = "MemorySizeGrowth.Bps";
if(!IPS_VariableProfileExists($prof_name_bps)) {
    IPS_CreateVariableProfile($prof_name_bps, 1);
    IPS_SetVariableProfileText($prof_name_bps, "", " B/s");
    IPS_SetVariableProfileValues($prof_name_bps, 32, 1024, 5);
}

$prof_name_minutes = "Duration.Minutes";
if(!IPS_VariableProfileExists($prof_name_minutes)) {
    IPS_CreateVariableProfile($prof_name_minutes, 1);
    IPS_SetVariableProfileText($prof_name_minutes, "", " Minuten");
    IPS_SetVariableProfileValues($prof_name_minutes, 5, 60, 1);
}

$prof_name_sec = "Duration.Seconds";
if(!IPS_VariableProfileExists($prof_name_sec)) {
    IPS_CreateVariableProfile($prof_name_sec, 1);
    IPS_SetVariableProfileText($prof_name_sec, "", " Sekunden");
    IPS_SetVariableProfileValues($prof_name_sec, 5, 300, 1);
}

// CPU-Auslastung überwachen
$current_cpu_load = Sys_GetCPUInfo()["CPU_AVG"];
$current_cpu_load_id = config_var_id("CPU_LOAD", 1, -1, false, "~Intensity.100"); // aktuelle CPU-Auslastung
$cpu_load_trigger_id = config_var_id("CPU_LOAD_THRESHOLD", 1, 75, true, "~Intensity.100"); // Warnschwelle durchschnittliche CPU-Auslastung
$cpu_load_trigger = GetValue($cpu_load_trigger_id);
SetValue($current_cpu_load_id, $current_cpu_load);
$avg_cpu_load = round(var_avg($current_cpu_load_id, 300, $count));
if($count > 5) {
    if($avg_cpu_load >= 90) { // hardcoded Schwelle für Alarm
        issue(LEVEL_ALARM, "Durchschnittliche CPU-Last während der letzten 15 Minuten bei " . $avg_cpu_load . "%!");
    } else if($avg_cpu_load > $cpu_load_trigger) { // einstellbare Schwelle für Warnung
        issue(LEVEL_WARNING, "Durchschnittliche CPU-Last während der letzten 15 Minuten bei " . $avg_cpu_load . "%!");
    }
}

// Arbeitsspeicher-Auslastung überwachen
$ram_occupation_trigger_id = config_var_id("RAM_MAX_OCCUPIED", 1, 80, true, "~Intensity.100"); // Max. belegter Arbeitsspeicher in %
$ram_occupation_trigger = GetValue($ram_occupation_trigger_id);
$mem_info = Sys_GetMemoryInfo();
$ram_total = $mem_info["TOTALPHYSICAL"];
$ram_free = $mem_info["AVAILPHYSICAL"];
$ram_free_mb = $ram_free / 1024 / 1024;
$ram_occupied_percent = 100 - round(($ram_free / $ram_total) * 100);
if($ram_occupied_percent > $ram_occupation_trigger) {
    issue(LEVEL_ALARM, "Es sind " . $ram_occupied_percent . "% des Arbeitsspeichers belegt (" . $ram_free_mb . " MB frei)!");
}

// Festplattenspeicher / Logdateigröße überwachen
$partition_space_trigger_id = config_var_id("KERNEL_DIR_SPACE_MAX_OCCUPIED", 1, 80, true, "~Intensity.100"); // Max. belegter Festplattenspeicher in %
$partition_space_trigger = GetValue($partition_space_trigger_id);
$log_dir_space_trigger_id = config_var_id("LOG_DIR_MAX_SIZE", 1, 150, true, $prof_name_mb); // Max. größe des Log-Verzeichnisses in MB
$log_dir_space_trigger = GetValue($log_dir_space_trigger_id);
$current_ips_log_file_size_id = config_var_id("LOG_FILE_SIZE", 1, -1, false); // aktuelle Größe der aktuellen Logdatei
$current_ips_log_file_growth_rate_trigger_id = config_var_id("LOG_FILE_MAX_GROWTH", 1, 100, true, $prof_name_bps); // Max. Wachstum der aktuellen Logdatei in B/s
$current_ips_log_file_growth_rate_trigger = GetValue($current_ips_log_file_growth_rate_trigger_id);

$partition_space_total = IPS_GetKernelDirSpace()["Total"];
$partition_space_free = IPS_GetKernelDirSpace()["Available"];
$partition_space_free_mb = intval($partition_space_free / 1024 / 1024);
$partition_space_occupied_percent = 100 - round(($partition_space_free / $partition_space_total) * 100);

if($partition_space_occupied_percent > $partition_space_trigger) {
    issue(LEVEL_ALARM, "Es sind " . $partition_space_occupied_percent . "% der Kernel-Partition belegt (" . $partition_space_free_mb . " MB frei)!");
}

// Größe der Logdatei(en) ermitteln
$log_file_list = scandir(IPS_GetLogDir());
$log_file_size_sum = 0;
$current_ips_log_file_size = false;
foreach($log_file_list as $file_name) {
    $path = IPS_GetLogDir() . $file_name;
    $size = filesize($path);
    if($file_name == "logfile.log") {
        $current_ips_log_file_size = $size;
    }
    $log_file_size_sum += $size;
}

if($current_ips_log_file_size === false) {
    issue(LEVEL_WARNING, "Die aktuelle Logdatei wurde nicht gefunden (\"" . IPS_GetLogDir() . "logfile.log\").");
    SetValue($current_ips_log_file_size_id, -1);
} else {
    SetValue($current_ips_log_file_size_id, $current_ips_log_file_size);
    $growth = var_growth($current_ips_log_file_size_id, 900) / 900;
    if($growth > $current_ips_log_file_growth_rate_trigger) {
        issue(LEVEL_WARNING, "Aktuelle Logdatei wächst zu schnell (" . $growth . " Bytes/s).");
    }
}

$log_file_dir_size_mb = intval($log_file_size_sum / 1024 / 1024);
if($log_file_dir_size_mb > $log_dir_space_trigger) {
    issue(LEVEL_WARNING, "Die Gesamtgröße des Log-Ordners \"" . IPS_GetLogDir() . "\" beträgt " . $log_file_dir_size_mb . " MB!");
}

// Fehlerhafte Skripte überwachen
$broken_scripts_list_id = config_var_id("BROKEN_SCRIPTS", 3, "{}"); // temporäre Liste aller fehlerhaften Skripte / 24h
$broken_scripts_list_str = GetValue($broken_scripts_list_id);
$broken_scripts_list = json_decode($broken_scripts_list_str, true);

$script_ids = IPS_GetScriptList();
foreach($script_ids as $script_id) {
    $script = IPS_GetScript($script_id);
    $id_str = strval($script_id);

    if($script["ScriptIsBroken"]) { // Skript ist als fehlerhaft markiert
        $script_id = $script["ScriptID"];
        $ts_last = $script["ScriptExecuted"];
        if(time() - $ts_last > 60 * 60 * 24) { // verwerfen, falls letzte Ausführung > 24h her
            if(array_key_exists($id_str, $broken_scripts_list)) {
                unset($broken_scripts_list[$id_str]);
            }
            continue; 
        }
        // Listeneintrag in temporärer Liste erstellen
        if(!array_key_exists($id_str, $broken_scripts_list)) $broken_scripts_list[$id_str] = array();
        for($index = 0; $index < count($broken_scripts_list[$id_str]); $index++) {
            $ts = $broken_scripts_list[$id_str][$index];
            if(time() - $ts > 60 * 60 * 24) { // Einträge die >24h her sind aus Liste entfernen
                array_splice($broken_scripts_list[$id_str], $index, 1);
                $index--;
            }
            if($ts == $ts_last) continue 2; // nicht mehrfach zählen falls Ausführungszeitstempel unverändert
        }
        $broken_scripts_list[$id_str][] = $ts_last; // Zeitstempel in Liste eintragen
    } else if(array_key_exists($id_str, $broken_scripts_list)) { // prüfen ob bestehende Einträge veraltet sind
        $ts_latest = 0;
        for($index = 0; $index < count($broken_scripts_list[$id_str]); $index++) {
            $ts = $broken_scripts_list[$id_str][$index];
            if($ts > $ts_latest) $ts_latest = $ts;
            if(time() - $ts > 60 * 60 * 24) { // Einträge die >24h her sind aus Liste entfernen
                array_splice($broken_scripts_list[$id_str], $index, 1);
                $index--;
            }
        }
        if($script["ScriptUpdated"] > $ts_latest) { // falls Skript verändert wurde, alle Einträge hierfür löschen
            unset($broken_scripts_list[$id_str]);
        } else if(count($broken_scripts_list[$id_str]) == 0) { // falls keine Zeitstempel mehr in Liste, ganzen Eintrag löschen
            unset($broken_scripts_list[$id_str]);
        }
    }
}

// Liste fehlerhafter Skripte für Report aufbereiten
foreach($broken_scripts_list as $id_str => $times) {
    $script_name = get_full_name(intval($id_str));
    $ts_latest = 0;
    foreach($times as $ts) {
        if($ts > $ts_latest) $ts_latest = $ts;
    }
    $count = count($times);
    if($count > 0) {
        issue(
            LEVEL_WARNING,
            "Skript \"" . $script_name . "\" mit ID " . $id_str .
            " war in den letzten 24 Stunden " . $count .
            "x fehlerhaft markiert (zuletzt " . date("H:i:s", $ts_latest) . ")!"
        );
    }

}
// Liste abspeichern
$broken_scripts_list_str = json_encode($broken_scripts_list);
SetValue($broken_scripts_list_id, $broken_scripts_list_str);

// Entwicklung der Objekt-Anzahl überwachen
$total_obj_count_id = config_var_id("OBJECTS_TOTAL"); // Anzahl Objekte
SetValue($total_obj_count_id, count(IPS_GetObjectList()));
$total_obj_growth = var_growth($total_obj_count_id, 60);

$total_obj_trigger_instant_id = config_var_id("OBJECTS_TOTAL_MAX_GROWTH_MINUTE", 1, 100, true); // Max. Anzahl neuer Objekte pro Minute
$total_obj_trigger_hour_id = config_var_id("OBJECTS_TOTAL_MAX_GROWTH_HOUR", 1, 200, true); // Max. Anzahl neuer Objekte pro Stunde
$total_obj_persistant_growth_trigger_id = config_var_id("OBJECTS_TOTAL_PERSISTENT_GROWTH_MINUTES", 1, 30, true, $prof_name_minutes); // Max. Dauer von Anwachsender Objektanzahl pro Stunde

if($total_obj_growth >= GetValue($total_obj_trigger_instant_id)) {
    issue(LEVEL_WARNING, "Es wurden " . $total_obj_growth . " Objekte innerhalb der letzten Minute erstellt!");
} else if($total_obj_growth >= 1) {
    $minutes_growing_count = 0;
    $growth_sum = 0;
    for($i = 0; $i < 60; $i++) {
        $growth = var_growth($total_obj_count_id, 60, time() - $i * 60);
        if($growth > 0) {
            $minutes_growing_count++;
            $growth_sum += $growth;
        }
    }
    if($minutes_growing_count >= GetValue($total_obj_persistant_growth_trigger_id) || $growth_sum > GetValue($total_obj_trigger_hour_id)) {
        issue(LEVEL_WARNING, "Es wurden " . $growth_sum . " Objekte innerhalb der letzten Stunde über einen Zeitraum von " . $minutes_growing_count . " Minute(n) erstellt!");
    }
}


// Umbenennung von Root-Objekt melden
$root_name = IPS_GetName(0);
$root_name_old_id = config_var_id("ROOT_OBJECT_NAME", 3, $root_name);
$root_name_old = GetValue($root_name_old_id);
if($root_name != $root_name_old) {
    issue(LEVEL_WARNING, "Das-Root-Objekt wurde umbenannt von \"" . $root_name_old . "\" in \"" . $root_name . "\".");
}

// Archivgröße überwachen
$archive_trigger_per_variable_id = config_var_id("MAX_ARCHIVE_SIZE_PER_VARIABLE", 1, 100, true, $prof_name_mb); // Max. Archivgröße pro Variable in MB
$archive_trigger_per_variable = GetValue($archive_trigger_per_variable_id);
$archive_trigger_total_id = config_var_id("MAX_ARCHIVE_SIZE_TOTAL", 1, 1000, true, $prof_name_mb); // Max. Archivgröße gesamt in MB
$archive_trigger_total = GetValue($archive_trigger_total_id);

$entries = AC_GetAggregationVariables($ac_id, true);
$size_total_mb = 0;
$size_total_bytes = 0;
foreach ($entries as $entry) {
    $size = $entry["RecordSize"];
    $size_total_bytes += $size;
    $size_mb = round(($size / 1024) / 1024);
    $size_total_mb += $size_mb;
    $var_id = $entry["VariableID"];
    $var_name = get_full_name($var_id);

    if($size_mb > $archive_trigger_per_variable) {
        issue(LEVEL_WARNING, "Archivgröße für Variable mit ID " . $var_id . " (" . $var_name . ") ist " . $size_mb . " MB!");
    }
}
if($size_total_mb > $archive_trigger_total) {
    issue(LEVEL_WARNING, "Archiv Gesamtgröße ist " . $size_total_mb . " MB!");
}

// Threads überwachen
$thread_single_runtime_trigger_id = config_var_id("MAX_THREAD_RUNTIME", 1, 120, true, $prof_name_sec); // Max. Thread-Laufzeit in Sekunden
$thread_single_runtime_trigger = GetValue($thread_single_runtime_trigger_id);

$threads_long_running_trigger_id = config_var_id("MAX_THREADS_LONGER_THAN_MINUTE", 1, 5, true); // Max. Anzahl Threads die länger als eine Minute laufen
$threads_long_running_trigger = GetValue($threads_long_running_trigger_id);

$threads_per_script_trigger_id = config_var_id("MAX_THREADS_PER_SCRIPT", 1, 5, true); // Max. Anzahl Threads pro Skript
$threads_per_script_trigger = GetValue($threads_per_script_trigger_id);

$threads_busy_percentage_trigger_id = config_var_id("MAX_THREADS_BUSY_PERCENTAGE", 1, 80, true, "~Intensity.100"); // Max. Prozentsatz belegter Threads
$threads_busy_percentage_trigger = GetValue($threads_busy_percentage_trigger_id);

$thread_count = 0;
$thread_count_busy = 0;
$thread_count_long_running = 0;
$threads_per_script = array();
foreach(IPS_GetScriptThreadList() as $thread_id) {
    $thread_count++;
    $info = IPS_GetScriptThread($thread_id);
    if($info["StartTime"] != 0) {
        $thread_count_busy++;
        //print_r($info);
        $run_time = time() - $info["StartTime"];
        if($run_time > $thread_single_runtime_trigger) {
            issue(LEVEL_WARNING, "Thread #" . $thread_id . " (\"" . $info["FilePath"] . "\") läuft seit " . $run_time . " Sekunden!");
        }
        if($run_time >= 60) {
            $thread_count_long_running++;
        }
        $script_id = $info["ScriptID"];
        if($script_id) {
            if(!array_key_exists($script_id, $threads_per_script)) {
                $threads_per_script[$script_id] = 1;
            } else {
                $threads_per_script[$script_id]++;
            }
        }
    }
}

$busy_percent = round(($thread_count_busy / $thread_count) * 100);
if($busy_percent > $threads_busy_percentage_trigger) {
    issue(LEVEL_ALARM, "Es sind " . $thread_count_busy . " von " . $thread_count . " Threads aktiv (" . $busy_percent . "%)!");
}

if($thread_count_long_running > $threads_long_running_trigger) {
    issue(LEVEL_ALARM, "Es laufen " . $thread_count_long_running . " Threads seit mindestens einer Minute!");
}

foreach($threads_per_script as $script_id => $count) {
    if($count > $threads_per_script_trigger) {
        issue(LEVEL_WARNING, "Es laufen " . $count . " Threads mit der Skript-ID " . $script_id . "(" . get_full_name($script_id) . ").");
    }
}

// Reporting
switch($level) {
    case LEVEL_WARNING:
        $report = "Warnung(en) vorhanden!\n\n" . $report;
        break;
    case LEVEL_ALARM:
        $report = "Alarmmeldung(en) vorhanden!\n\n" . $report;
        break;
}

$report_id = config_var_id("REPORT", 3);
$report_old = GetValue($report_id);
$report_changed = $report != $report_old;

$issue_mailing = false;

$report_lines_id = config_var_id("REPORT_LINES", 3);
$report_lines_old = GetValue($report_lines_id);
$reissue_hours = 12;
if($level >= LEVEL_ALARM) $reissue_hours = 1;
if(!$report_changed || time() - IPS_GetVariable($report_lines_id)["VariableChanged"] < 60 * 60 * $reissue_hours) {
    $issue_mailing = $report_lines != $report_lines_old;
}

if($_IPS['SENDER'] != "TimerEvent") $issue_mailing = false;
if($mailer_id === false) $issue_mailing = false;

if($level > LEVEL_NOTHING) {
    if($_IPS['SENDER'] == "Execute") {
        echo $report;
        echo "\n";
    }
    if($issue_mailing) SMTP_SendMail(
        $mailer_id,
        IPS_GetName($_IPS['SELF']),
        $report
    );
} else {
    if($issue_mailing) SMTP_SendMail(
        $mailer_id,
        IPS_GetName($_IPS['SELF']),
        "Es liegen keine Meldungen mehr vor."
    );
}

SetValue($report_id, $report);
SetValue($report_lines_id, $report_lines);
$report_html_id = config_var_id("REPORT_HTML", 3);
SetValue($report_html_id, $report_html);

if($visu_id !== false) {
    $link_ids = @IPS_GetChildrenIDs($visu_id);
    if($link_ids !== false) {
        $link_exists = false;
        foreach($link_ids as $link_id) {
            $link = @IPS_GetLink($link_id);
            if($link === false) continue;
            if($link["TargetID"] == $report_html_id) {
                $link_exists = true;
                break;
            }
        }
        if(!$link_exists) {
            $link_id = IPS_CreateLink();
            IPS_SetParent($link_id, $visu_id);
            IPS_SetLinkTargetID($link_id, $report_html_id);
        }
        IPS_SetHidden($link_id, $report == "");
    }
}

if($_IPS['SENDER'] == "Execute") {
    IPS_SetScriptTimer($_IPS['SELF'], 60);
}

// Funktionen
// Konfigurations-/Statusvariable erstellen bzw. finden und ID zurückgeben
function config_var_id($ident, $type = 1, $default_value = 0, $editable = false, $profile_name = "") {
    global $visu_id, $visu_link_count;
    $id = @IPS_GetObjectIDByIdent($ident, $_IPS['SELF']);

    if($id === false) {
        $id = IPS_CreateVariable($type);
        IPS_SetParent($id, $_IPS['SELF']);
        IPS_SetIdent($id, $ident);
        IPS_SetName($id, $ident);
        SetValue($id, $default_value);
        IPS_SetVariableCustomProfile($id, $profile_name);
        IPS_SetVariableCustomAction($id, $editable ? $_IPS["SELF"] : 0);
    }

    // Link von der Visu-Instanz erstellen
    if($editable && $visu_id !== false) {
        $link_ids = @IPS_GetChildrenIDs($visu_id);
        if($link_ids !== false) {
            $link_exists = false;
            foreach($link_ids as $link_id) {
                $link = @IPS_GetLink($link_id);
                if($link === false) continue;
                if($link["TargetID"] == $id) {
                    $link_exists = true;
                    break;
                }
            }
            if(!$link_exists) {
                $link_id = IPS_CreateLink();
                IPS_SetParent($link_id, $visu_id);
                IPS_SetLinkTargetID($link_id, $id);
            }
            $visu_link_count++;
            IPS_SetPosition($link_id, $visu_link_count);
        }
    }

    return $id;
} // config_var_id

// Wachstum der Variablen im angegebenen zeitraum zurückgeben
function var_growth($var_id, $seconds, $ts = false) {
    global $ac_id;

    if(!AC_GetLoggingStatus($ac_id, $var_id)) {
        AC_SetLoggingStatus($ac_id, $var_id, true);
        return 0;
    }

    if($ts === false) {
        $ts = time();
        $new_value = GetValue($var_id);
    } else {
        $hist = @AC_GetLoggedValues($ac_id, $var_id, 0, $ts, 1);
        if($hist !== false && count($hist) > 0) {
            $new_value = $hist[0]["Value"];
        } else {
            return 0;
        }
    }

    $hist = @AC_GetLoggedValues($ac_id, $var_id, 0, $ts - $seconds, 1);
    if($hist !== false && count($hist) > 0) {
        $seconds_hist = min([$ts - $hist[0]["TimeStamp"], $seconds]);
        $old_value = $hist[0]["Value"];
        /*echo $seconds_hist . "\n";
        echo $seconds . "\n";
        echo $new_value . "\n";
        echo $old_value . "\n"; //*/
        $result = round(($new_value - $old_value) * ($seconds_hist / $seconds));
        //echo $result ."\n";
        return $result;
    } else {
        return 0;
    }
} // var_growth

// Gewichteten Durchschnitt im angegebenen Zeitraum zurückgeben
function var_avg($var_id, $seconds, &$count) {
    global $ac_id;

    $count = 0;

    if(!AC_GetLoggingStatus($ac_id, $var_id)) {
        AC_SetLoggingStatus($ac_id, $var_id, true);
        return GetValue($var_id);
    }

    $hist = AC_GetLoggedValues($ac_id, $var_id, time() - $seconds, time(), 0);
    if($hist !== false) {
        $seconds = 0;
        $sum = 0;
        foreach($hist as $entry) {
            $seconds += $entry["Duration"];
            $sum += $entry["Value"] * $entry["Duration"];
            $count++;
        }
        $avg = $sum / $seconds;
        return $avg;
    } else {
        return GetValue($var_id);
    }
} // var_avg

// ein Problem protokollieren
function issue($escalate_to, $msg) {
    global $level, $report, $report_lines, $report_html;
    if($escalate_to > $level) $level = $escalate_to;
    if($escalate_to == LEVEL_WARNING) {
        $report .= "[WARNUNG] ";
        $report_html .= "<font color=\"#FFFF00\">[WARNUNG]</font> ";
    } else if($escalate_to == LEVEL_ALARM) {
        $report .= "[ALARM] ";
        $report_html .= "<font color=\"#FF0000\">[ALARM]</font> ";
    }
    $report .= $msg . "\n";
    $report_html .= $msg . "<br>\n";
    $report_lines .= debug_backtrace()[0]["line"] . ",";
}

function get_full_name($obj_id) {
    $name = "";
    do {
        if(!IPS_ObjectExists($obj_id)) break;
        $name = IPS_GetName($obj_id) . $name;
        $parent_id = IPS_GetObject($obj_id)["ParentID"];
        if($parent_id != 0) {
            $obj_id = $parent_id;
            $name = '/' . $name;
        }
    } while($parent_id != 0);
    return $name;
}
?>

was soll ich sagen, läuft :slight_smile: .

Aber meine Größen sind eher normal, die Berechnung der Logfilevergößerung ist etwas sonderbar, aber in Summe wahrscheinlich auch korrekt.

[WARNUNG] Aktuelle Logdatei wächst zu schnell (10392.853333333 Bytes/s).
[WARNUNG] Die Gesamtgröße des Log-Ordners "/var/log/symcon/" beträgt 7374 MB!
[WARNUNG] Archivgröße für Variable mit ID 59006 (Bad/GeCoS_WSens/Luftfeuchtigkeit (rel)) ist 125 MB!
[WARNUNG] Archivgröße für Variable mit ID 55519 (Büro/TuYa-AirQ-1 Büro/Luftfeuchte) ist 182 MB!
[WARNUNG] Archivgröße für Variable mit ID 56764 (Büro/TuYa-AirQ-1 Büro/CO2) ist 111 MB!
[WARNUNG] Archivgröße für Variable mit ID 11052 (Büro/TuYa-AirQ-1 Büro/Temperatur) ist 155 MB!
[WARNUNG] Archivgröße für Variable mit ID 14598 (Bad/GeCoS_WSens/Luftdruck (rel)) ist 120 MB!
[WARNUNG] Archivgröße für Variable mit ID 42188 (Bad/GeCoS_WSens/Luftdruck (abs)) ist 119 MB!
[WARNUNG] Archiv Gesamtgröße ist 2899 MB!

Danke fürs ausprobieren.

Habe bislang halt noch keine Größenordnung, was auf anderen Installationen „normal“ und unbedenklich ist.

Was ist denn sonderbar, außer der per default eingestellten Wachstumsrate?

Ich passe die Werte mal etwas an, beim Log war ich über 100 B/s etwas verwundert.

Die Werte sind jetzt alle so gewählt, wie es bei mir „normal“ ist. Das kann aber sicherlich von Installation zu Installation stark abweichen und wahrscheinlich muss man es überall justieren.

Alternativ könnte man noch einen „Lernmodus“ einbauen und bspw. eine Woche lang das Grundrauschen beobachten.

Dein Archiv und der Log-Ordner sind im Vergleich zu meinem gigantisch. Das Log versuche ich, normalerweise klein zu halten. Wenn es zu schnell wächst ist das ein Hinweis darauf, dass irgendwo eine große Anzahl Meldungen generiert werden, die es „normalerweise“ nicht geben sollte.

Das Archiv ist für einige Werte über 17 Jahre alt und somit für einige Werte sehr groß. Da könnte ich sicher mal löschen, aber Plattenplatz ist ja kein Problem mehr.

Und bei Logs fahre ich die andere Strategie, im Sinne von „haben ist besser als brauchen“, d.h. nahezu alles loggen um im Problemfall die Infos zu haben.

Ja, das klingt auch nicht unvernünftig.

Nach einigen Wochen Testen, folgende Einfälle zur Verbesserung:

  • „Skript war als fehlerhaft markiert“ ist grundsätzlich nützlich, aber es nervt mich persönlich, wenn ich gerade an einem Skript aktiv entwickle und dann dauernd Emails kommen. Also ggf. diese Meldung nur dann anzeigen, wenn zwischen letzter Änderung und letzter Ausführung des Skripts eine gewisse Zeit vergangen ist?

  • Meldung, dass ein Thread ungewöhnlich lange läuft, ist grundsätzlich auch nützlich, allerdings gibt es seltene Ausnahmen, in denen das ok ist (bspw. SymconBackup Modul). Ggf eine Whitelist vorsehen? Oder gibt es noch schlauere Ideen? Cool wäre auch wenn man sehen könnte ob ein Thread noch etwas „macht“ aber das ist wohl schwierig zu definieren und zu prüfen. Aber ein Skript das einfach sehr lange braucht, aber kontinuierlich etwas tut wäre ja weniger ein Problem (außer es sind viele davon) als ein Skript, das bspw bis zum St. Nimmerleinstag auf Godot wartet.

Was genau wird denn hierfür geprüft?

[ALARM] Es sind 97% der Kernel-Partition belegt (36 MB frei)!

Bei mir läuft Symcon auf einem PI, die Belegung ist weit weg von irgendwelchen engen Füllständen.

Filesystem              Size  Used Avail Use% Mounted on
/dev/root               118G   16G   97G  15% /
devtmpfs                1.7G     0  1.7G   0% /dev
tmpfs                   1.9G     0  1.9G   0% /dev/shm
tmpfs                   759M  1.2M  758M   1% /run
tmpfs                   5.0M  4.0K  5.0M   1% /run/lock
/dev/sda1               255M   51M  205M  20% /boot

$partition_space_total = IPS_GetKernelDirSpace()["Total"];
$partition_space_free = IPS_GetKernelDirSpace()["Available"];
$partition_space_occupied_percent = 100 - round(($partition_space_free / $partition_space_total) * 100);

Das hatte ich natürlich gesehen, würde aber bedeuten, dass Symcon eventuell mal Quatsch liefert, weil gerade manuell sind die Zahlen groß und ich wüßte nicht, warum sie mal sehr klein sein sollten.

Array
(
    [Total] => 1219477504
    [Free] => 1055903744
    [Available] => 208769024
)

Dann wäre es doch interessant wenn du im Code bei Auslösen dieser Warnung einmal die Ausgabe von IPS_GetKernelDirSpace() irgendwo speicherst. Mir ist eine Fehlauslösung an dieser Stelle noch nicht untergekommen.

Ich ergänz das mal, das ist ein 128GB Stick und der ist eher leer :open_mouth: .

Dass es sich um ein gefälschtes Exemplar handelt, kannst du ausschließen? Wäre auch in puncto Datenintegrität problematisch.

Es ist tatsächlich eine M2.SSD in einem USB Gehäuse und ich bin mir sicher, dass es keine Fälschung ist.

Ich bekomme vereinzelt beim manuellen Aufrufen negative Werte

Array
(
    [Total] => 1219477504
    [Free] => 693444608
    [Available] => -153690112
)

Dabei wundert mich dann auch die Aussage in der Doku zu
IPS_GetKernelDirSpace()

gesamten/freien/verfügbaren Speicherplatz

@paresy Was ist denn der Unterschied zwischen Frei und Verfügbar?

Ich könnte negative Werte als workaround herausfiltern, aber ich denke das sollte wohl an anderer Stelle behoben werden.