2020-09-30 19:38

Welche Sprache für Checkmk Local Checks?

Das Monitoring-System Checkmk erlaubt es, die eingebauten Standard-Checks durch eigene Erweiterungen zu ergänzen. Meistens werden dabei Local Checks verwendet.

(Nachtrag 2020-10-25: Messwerte für PHP hinzugefügt.)

Local Checks sind Skripte oder Programme, die man auf dem zu überwachenden System in einem speziellen Verzeichnis ablegt. Sie ermitteln einen Messwert für das Monitoring und geben ihn in einem definierten Format auf der Standardausgabe aus. Das Format ist sehr einfach: pro Check wird eine Zeile ausgegeben. Diese enthält vier Werte, durch Leerzeichen getrennt. Ein einfaches Beispiel sieht so aus:

0 Mein_Check - Beschreibung des Checks

Das erste Feld enthält den Status in numerischer Form; 0 steht für OK (grün), 1 für WARN (gelb), 2 für CRIT (rot) und 3 für UNKNOWN (orange).
Das zweite Feld gibt den Namen des Checks an. Dieser sollte immer gleich sein, da er vom Checkmk Server bei der Inventarisierung gelernt und dann bei jedem Check-Durchlauf ein Eintrag dieses Namens erwartet wird.
Das dritte Feld kann optional quantitative Messwerte enthalten; wenn es keine gibt, wird ein “-” angegeben.
Im vierten Feld steht eine Beschreibung des Checks und des Ergebnisses; es wird so in der Monitoring-Konsole angezeigt und bei Benachrichtungen verschickt, z.B. als E-Mail.

Welche Skriptsprache?

Einzelne Local Check Skripte sind normalerweise recht kurz. In realen Umgebungen werden aber oft sehr viele davon ausgerollt. Diese werden bei jedem Check-Durchlauf einzeln aufgerufen, so dass die Performance durchaus eine Rolle spielt.

Daher kommt bei jeder Checkmk Einführung früher oder später die Frage auf, welche Programmiersprache man am besten für Local Checks verwendet.

Viele Local Checks werden als Shellskript in Bash geschrieben. Das liegt nahe, denn Bash dürfte der einzige Skriptinterpreter sein, der auf wirklich jedem Linux-System vorhanden ist. Wenn aber die Komplexität etwas höher wird und zum Beispiel Arrays, Hashes oder Regular Expressions benötigt werden, stößt man schnell an die Grenzen von Bash und greift lieber zu einer “richtigen” Programmiersprache.

Von den vier großen stabilen Linux-Distributionen – CentOS, Debian stable, OpenSUSE Leap und Ubuntu LTS – bringen drei (alle außer CentOS) bereits in der Minimalinstallation einen Perl5 Interpreter mit. Weitere beliebte Skriptsprachen, die bei allen genannten Distributionen enthalten sind, umfassen PHP, Python und Ruby. Diese sind nicht Bestandteil der Minimalinstallation, lassen sich aber über die Standard-Paketquellen nachinstallieren; ebenso wie Perl auf CentOS.

Bei Python muss beachtet werden, dass derzeit zwei verschiedene Sprachversionen, Python 2 und 3, im Umlauf sind. Diese sind nicht kompatibel; abgesehen von Trivialfällen muss ein Skript für die jeweilige Sprachversion geschrieben werden. Python 2 wurde bereits abgekündigt. Die stabilen Distributionen unterstützen es noch im Rahmen ihrer Lebenszyklen, ich würde es jedoch für Neuentwicklungen nicht mehr in Betracht ziehen.

System Bash Perl PHP Python 3 Ruby
CentOS 8 4.4.19 5.26.3 7.2.24 3.6.8 und 3.8.0 2.5.5
Debian 10 5.0.3 5.28.1 7.3.19 3.7.3 2.5.1
OpenSUSE Leap 15 4.4.23 5.26.1 7.4.6 3.6.10 2.5.8
Ubuntu 20.04 LTS 5.0.17 5.30.0 7.4.3 3.8.2 2.7

Startup-Zeiten

Wenn viele kleine Skripte nacheinander ausgeführt werden, ist die Startup-Zeit des Interpreters ein entscheidender Faktor für die Gesamtperformance. Auf einem System mit dem Ryzen 7-1700 Prozessor unter Ubuntu 20.04 LTS dauert die Ausführung eines leeren Programmes wie folgt:

$ time bash -c ''

real	0m0,003s
user	0m0,002s
sys	0m0,000s

$ time perl -e ''

real	0m0,003s
user	0m0,003s
sys	0m0,001s

$ time php -r ''

real	0m0,017s
user	0m0,008s
sys	0m0,009s

$ time python3 -c ''

real	0m0,027s
user	0m0,023s
sys	0m0,004s

$ time ruby -e ''

real	0m0,057s
user	0m0,040s
sys	0m0,016s

Die Initialisierungszeit für den Bash und Perl liegt gleichauf im Bereich weniger Millisekunden. PHP benötigt knapp 20 und Python 3 (und 2 ebenfalls) bereits knapp 30 Millisekunden, Ruby sogar nochmal das doppelte.

Natürlich sind die Messungen mit time nicht sehr präzise, zumal sich keine höhere Genauigkeit als Millisekunden ausgeben lässt. Eine Tendenz ist aber durchaus erkennbar. Ein Lauf mit strace -c zeigt die Häufigkeit und Dauer von Syscalls und liefert eine Begründung für die Unterschiede:

$ strace -c bash -c ''

% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 32,12    0,000185           9        20         9 stat
 17,36    0,000100           7        14           rt_sigaction
  7,47    0,000043           7         6           rt_sigprocmask
  6,42    0,000037           7         5         1 access
  6,42    0,000037           7         5           geteuid
  6,42    0,000037           7         5           getegid
  4,86    0,000028           5         5           getgid
  4,69    0,000027           5         5           getuid
  3,12    0,000018          18         1           sysinfo
  2,60    0,000015           7         2         1 ioctl
  2,43    0,000014           7         2           getpid
  1,22    0,000007           7         1         1 getpeername
  1,22    0,000007           7         1           uname
  1,22    0,000007           7         1           getppid
  1,22    0,000007           7         1           getpgrp
  1,22    0,000007           7         1           prlimit64
  0,00    0,000000           0         3           read
  0,00    0,000000           0         7           close
  0,00    0,000000           0         6           fstat
  0,00    0,000000           0        18           mmap
  0,00    0,000000           0         6           mprotect
  0,00    0,000000           0         1           munmap
  0,00    0,000000           0         3           brk
  0,00    0,000000           0         6           pread64
  0,00    0,000000           0         1           execve
  0,00    0,000000           0         2         1 arch_prctl
  0,00    0,000000           0         7           openat
------ ----------- ----------- --------- --------- ----------------
100.00    0,000576                   135        13 total


$ strace -c perl -e ''

harald@moonrise:~$ strace -c perl -e ''
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 30,29    0,000495           7        69           rt_sigaction
 20,26    0,000331          12        27           mmap
  6,61    0,000108          12         9           openat
  6,12    0,000100          12         8           mprotect
  4,22    0,000069           7         9           close
  3,92    0,000064           8         8           pread64
  3,30    0,000054           9         6         6 stat
  3,18    0,000052           6         8           fstat
  2,88    0,000047           7         6           read
  2,26    0,000037           7         5           fcntl
  2,02    0,000033          33         1           readlink
  1,96    0,000032           8         4         1 ioctl
  1,77    0,000029           9         3           geteuid
  1,71    0,000028           7         4         3 lseek
  1,59    0,000026           6         4           brk
  1,53    0,000025          25         1           munmap
  1,41    0,000023           7         3           getgid
  1,35    0,000022           7         3           getuid
  1,35    0,000022           7         3           getegid
  0,49    0,000008           4         2         1 arch_prctl
  0,49    0,000008           8         1           prlimit64
  0,43    0,000007           7         1           rt_sigprocmask
  0,43    0,000007           7         1           set_tid_address
  0,43    0,000007           7         1           set_robust_list
  0,00    0,000000           0         1         1 access
  0,00    0,000000           0         1           execve
------ ----------- ----------- --------- --------- ----------------
100.00    0,001634                   189        12 total


$ strace -c php -r ''

% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
100,00    0,000790          11        69           munmap
  0,00    0,000000           0       109           read
  0,00    0,000000           0       116           close
  0,00    0,000000           0        30           stat
  0,00    0,000000           0       150           fstat
  0,00    0,000000           0        12         7 lstat
  0,00    0,000000           0         1           lseek
  0,00    0,000000           0       314           mmap
  0,00    0,000000           0       111           mprotect
  0,00    0,000000           0        15           brk
  0,00    0,000000           0        80           rt_sigaction
  0,00    0,000000           0         2           rt_sigprocmask
  0,00    0,000000           0        30        30 ioctl
  0,00    0,000000           0         8           pread64
  0,00    0,000000           0         2         1 access
  0,00    0,000000           0         1           execve
  0,00    0,000000           0         1           getcwd
  0,00    0,000000           0         2           readlink
  0,00    0,000000           0         1           sysinfo
  0,00    0,000000           0         2         1 arch_prctl
  0,00    0,000000           0        18           futex
  0,00    0,000000           0         2           getdents64
  0,00    0,000000           0         1           set_tid_address
  0,00    0,000000           0       116         3 openat
  0,00    0,000000           0         1           set_robust_list
  0,00    0,000000           0         2           prlimit64
  0,00    0,000000           0         2           getrandom
------ ----------- ----------- --------- --------- ----------------
100.00    0,000790                  1198        42 total


$ strace -c python3 -c ''

% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 25,63    0,000256           1       163        37 stat
 19,32    0,000193           9        20           getdents64
 12,91    0,000129           1        75           read
 10,31    0,000103           1        56         2 openat
  9,91    0,000099           1        89           fstat
  8,61    0,000086           1        67         3 lseek
  8,01    0,000080           1        57           close
  5,01    0,000050           1        42        36 ioctl
  0,30    0,000003           0        50           mmap
  0,00    0,000000           0        12           mprotect
  0,00    0,000000           0         6           munmap
  0,00    0,000000           0        11           brk
  0,00    0,000000           0        68           rt_sigaction
  0,00    0,000000           0         1           rt_sigprocmask
  0,00    0,000000           0         8           pread64
  0,00    0,000000           0         1         1 access
  0,00    0,000000           0         3           dup
  0,00    0,000000           0         1           execve
  0,00    0,000000           0         1           fcntl
  0,00    0,000000           0         2         1 readlink
  0,00    0,000000           0         1           sysinfo
  0,00    0,000000           0         1           getuid
  0,00    0,000000           0         1           getgid
  0,00    0,000000           0         1           geteuid
  0,00    0,000000           0         1           getegid
  0,00    0,000000           0         3           sigaltstack
  0,00    0,000000           0         2         1 arch_prctl
  0,00    0,000000           0         1           futex
  0,00    0,000000           0         1           set_tid_address
  0,00    0,000000           0         1           set_robust_list
  0,00    0,000000           0         1           prlimit64
  0,00    0,000000           0         1           getrandom
------ ----------- ----------- --------- --------- ----------------
100.00    0,000999                   748        81 total


$ strace -c ruby -e ''

% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
 18,61    0,000809           2       355       202 openat
 18,08    0,000786           2       313         3 lstat
 10,10    0,000439           2       216           read
  9,89    0,000430           3       114         6 stat
  7,54    0,000328           2       153           close
  5,86    0,000255           1       156           fstat
  4,90    0,000213           4        49           mprotect
  4,16    0,000181           1        93        90 ioctl
  2,60    0,000113           3        32           brk
  2,25    0,000098           5        19           rt_sigaction
  2,21    0,000096           1        51           fcntl
  2,12    0,000092           1        50           lseek
  2,00    0,000087           1        57           mmap
  1,52    0,000066           1        42           geteuid
  1,47    0,000064           6        10           getdents64
  1,45    0,000063           1        41           getgid
  1,43    0,000062           1        42           getegid
  1,40    0,000061           1        41           getuid
  0,44    0,000019           9         2           getrandom
  0,39    0,000017           8         2           eventfd2
  0,37    0,000016           3         5           getpid
  0,32    0,000014           4         3           rt_sigprocmask
  0,18    0,000008           8         1           prctl
  0,16    0,000007           7         1           sigaltstack
  0,16    0,000007           7         1           timer_create
  0,16    0,000007           7         1           clock_gettime
  0,14    0,000006           6         1           sysinfo
  0,09    0,000004           4         1           futex
  0,00    0,000000           0         2           munmap
  0,00    0,000000           0         8           pread64
  0,00    0,000000           0         1         1 access
  0,00    0,000000           0         1           execve
  0,00    0,000000           0         2         1 arch_prctl
  0,00    0,000000           0         1           sched_getaffinity
  0,00    0,000000           0         1           set_tid_address
  0,00    0,000000           0         1           timer_delete
  0,00    0,000000           0         1           set_robust_list
  0,00    0,000000           0         3           prlimit64
------ ----------- ----------- --------- --------- ----------------
100.00    0,004348                  1873       303 total

Die konkreten Zeitangaben sind hier gar nicht so interessant, da strace sich zwischen Programm und Syscall schiebt und damit die Laufzeiten beeinflusst. Entscheidend ist die Anzahl: Bash und Perl setzen bei der Initialisierung weniger als 200 Syscalls ab; Python etwa 750, PHP knapp 1.200 und Ruby sogar mehr als 1.800.

Fazit

Bezüglich der Startup-Zeiten für den Interpreter liegt es durch die Messungen nahe, bei ansonsten gleicher Eignung Perl5 den Vorzug gegenüber anderen Skriptsprachen wie PHP, Python 3 oder Ruby zu geben.

Sehr einfache Bash-Skripte sind sogar noch etwas performanter als Perl. Allerdings kehrt sich der Vorteil zugunsten von Perl um, sobald die Skripte größer werden. Denn Bash-Skripte rufen meistens viele andere externe Kommandos wie sed, awk, grep oder tr auf, was zu weiteren Syscalls führt. Perl hat entsprechende Funktionen oft bereits eingebaut, ggf. über Bibliotheken.

Natürlich ist die Performance nicht das alleinige Kriterium für die Auswahl der Programmiersprache. Viele Faktoren können hier entscheidend sein, etwa die Standardisierung im gesamten Kontext des Systems, die Lesbarkeit, die eigenen Skills und die Verfügbarkeit von Libraries, die zur Erhebung der Monitoring-Ergebnisse ggf. benötigt werden.

Die hier getätigten Aussagen gelten nur für Umgebungen mit vielen kleinen Local Check Skripten; wenn man deren Funktionen zu wenigen größeren Skripten zusammenfassen kann, spielen die Startup-Zeiten eine geringere Rolle. Die Performance hängt dann eher am Parsen und Ausführen des jeweiligen Programmes, was hier gar nicht gemessen wurde. Auch kann es eine valide Option sein, anstelle einer Skriptsprache zu einer compilierten Sprache wie C, C++ oder Go zu greifen. Diese wurden hier ebenfalls nicht betrachtet.