diff --git a/check_docker.sh b/check_docker.sh index c4a79264667168b51124868b82df1e58b14fc488..880b890e7ebb877e689317703befe995749f6b62 100755 --- a/check_docker.sh +++ b/check_docker.sh @@ -1,5 +1,8 @@ #!/bin/bash +# Copyright © 2023 Le Filament (<http://www.le-filament.com>) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + # Default threshold values. cpu_threshold_warning=25 cpu_threshold_critical=50 diff --git a/check_fail2ban.sh b/check_fail2ban.sh index 22e35bfee4d3cdeb7524059908c0e7872e00c409..7588c95fc22a259ba9875f2d0a06cea8110c0db3 100755 --- a/check_fail2ban.sh +++ b/check_fail2ban.sh @@ -1,5 +1,8 @@ #!/bin/bash +# Copyright © 2023 Le Filament (<http://www.le-filament.com>) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + # Default threshold values. ips_threshold_warning=20 ips_threshold_critical=40 diff --git a/check_psi.py b/check_psi.py new file mode 100755 index 0000000000000000000000000000000000000000..c4ac5275c2ea7e9cbf3c17cecc028c8bc3d838ca --- /dev/null +++ b/check_psi.py @@ -0,0 +1,147 @@ +#!/usr/bin/env python3 + +# Copyright © 2025 Le Filament (<http://www.le-filament.com>) +# License AGPL-3.0 or later (http://www.gnu.org/licenses/agpl.html). + +import sys + + +SELECTED_KEYS = ["avg10", "avg60", "avg300", "total_share"] + + +def read_psi(psi_path): + with open(psi_path, "r") as psi_file: + psi_data = {} + for line in psi_file.readlines(): + line = line.split() + metric = line[0] + values = line[1:] + key_values = [kv.split("=") for kv in values] + psi_data[metric] = {k: float(v) for k, v in key_values} + return psi_data + + +class MetricPSIConfig: + def __init__(self, argv): + self.ressource = None + self.metric = None + self.thresholds = {"warning": {}, "critical": {}} + + argv = iter(argv) + for arg in argv: + match arg: + case "-r" | "--ressource": + self.ressource = next(argv) + case "-m" | "--metric": + self.metric = next(argv).split(",") + case "-w" | "--warning": + all_metric_warnings = next(argv).split(";") + for metric_warnings in all_metric_warnings: + metric, warnings = metric_warnings.split(":") + warnings = warnings.split(",") + self.thresholds["warning"][metric] = { + "avg10": float(warnings[0]), + "avg60": float(warnings[1]), + "avg300": float(warnings[2]), + "total_share": float(warnings[3]), + } + case "-c" | "--critical": + all_metric_criticals = next(argv).split(";") + for metric_criticals in all_metric_criticals: + metric, criticals = metric_criticals.split(":") + criticals = criticals.split(",") + self.thresholds["critical"][metric] = { + "avg10": float(criticals[0]), + "avg60": float(criticals[1]), + "avg300": float(criticals[2]), + "total_share": float(criticals[3]), + } + + +class MetricPSI: + def __init__(self, config, psi_data, uptime_us): + self.config = config + self.psi_data = psi_data + self.uptime_us = uptime_us + + for metric in self.psi_data.keys(): + self.psi_data[metric]["total_share"] = ( + self.psi_data[metric]["total"] / self.uptime_us * 100 + ) + + def status(self): + states = { + "warning": {metric: False for metric in self.config.metric}, + "critical": {metric: False for metric in self.config.metric}, + } + data_str_by_metric = {} + perfdata_str = "" + + for metric in self.config.metric: + data_str_by_metric[metric] = f"{metric} " + for key in SELECTED_KEYS: + if key in self.psi_data[metric]: + data_str_by_metric[metric] += ( + f"{key}={self.psi_data[metric][key]:.2f}% " + ) + perfdata_str += f"{metric}_{key}={self.psi_data[metric][key]:.2f}%;{self.config.thresholds['warning'][metric][key]:.2f};{self.config.thresholds['critical'][metric][key]:.2f};0;100 " + for state in states.keys(): + if ( + self.psi_data[metric][key] + > self.config.thresholds[state][metric][key] + ): + states[state][metric] = True + + return_code = ( + 2 + if any(states["critical"].values()) + else 1 + if any(states["warning"].values()) + else 0 + ) + + text_output = "" + for index, metric in enumerate(self.config.metric): + state = ( + "CRITICAL" + if states["critical"][metric] + else "WARNING" + if states["warning"][metric] + else "OK" + ) + terminaison = "\\n" if index + 1 < len(self.config.metric) else "" + text_output += f"{state} {data_str_by_metric[metric].strip()}{terminaison}" + + return { + "return_code": return_code, + "text_output": text_output.strip(), + "perfdata": perfdata_str.strip(), + } + + +def get_uptime_us(): + with open("/proc/uptime", "r") as uptime_file: + return float(uptime_file.read().split()[0]) * 1000000 + + +def main(argv: [str, ...]): + config = MetricPSIConfig(argv) + + match config.ressource: + case "cpu": + psi_path = "/proc/pressure/cpu" + case "io": + psi_path = "/proc/pressure/io" + case "memory": + psi_path = "/proc/pressure/memory" + + psi_data = read_psi(psi_path) + uptime_us = get_uptime_us() + metric_psi = MetricPSI(config, psi_data, uptime_us) + output = metric_psi.status() + print(f"{output['text_output']} | {output['perfdata']}") + sys.exit(output["return_code"]) + + +if __name__ == "__main__": + main(sys.argv)