End2End im eigenen Code

Sie finden auf der MSXFAQ sehr viele Seiten zum Ende zu Ende Monitoring, mit denen ich das klassische Monitoring erweitere. Die meisten Monitoring-Funktionen basieren darauf, Kennzahlen wie CPU-Last, freier Diskplatz oder gemittelte Netzwerkauslastung auszulesen. Meine End2End-Skripte simulieren aber eine Last und messen die Performance. Auf dieser Seite möchte ich aber dafür plädieren, dass jede Applikation das selbst macht. Programmierer also bitte weiterlesen.

Messen ist wichtig!

Programme zeichnen sich primär dadurch aus, dass Sie Eingaben annehmen, verarbeiten und wieder ausgehen. Dabei ist es völlig normal, dass Berechnungen angestellt, Dateien gelesen und geschrieben und Daten übertragen werden. All diese Aktionen benötigen Zeit und natürlich möchte jeder Entwickler, dass der Anwender zufrieden ist. Eine schicke GUI oder der passende Funktionsumfang ist aber nicht alles. Die Performance ist ein wesentlicher Faktor und oftmals sogar entscheidet. Vermutlich essen daher viele Menschen auch Fastfood anstatt sich selbst in die Küche zu stellen.

Der Einbau von Performance Countern in die Applikation ist aber aus mehreren Gründen wichtig und sollte genauso umgesetzt werden wie Checks der Funktion und Logging und Alarmierung in Dateien und Eventlogs.

  • Messen für eigene Optimierungen
    Nur wer die Performance von einzelnen Code-Teilen misst, kann kritische Pfade erkennen, bei denen eine Aktion lange dauert oder aufgrund von Abhängigkeiten auf andere Dinge warten muss. Die meisten Entwicklungsumgebungen haben sogar entsprechende „Profiling“-Werkzeuge eingebaut, mit denen der Entwickler seinen Code überprüfen kann. Hier geht es aber darum solche Messungen auch beim finalen Code mit einzubauen.
  • Messen zum Monitoring
    Wenn die Messungen im Code auch nach extern, z.B. als Performance Counter, publiziert werden, dann kann eine Überwachungslösung die Funktion einer Software viel besser und detaillierter ermitteln. Das kann auf einzelne Dienste oder Prozess und sogar Funktionen herunter gebrochen werden. Es obliegt dem Entwickler in seinem Code die entsprechenden Quellen anzulegen.
  • Messen zum Vergleich
    Es ist egal, ob es sich nun um Individual–Software für einen Kunden auf einem PC oder um weltweit installierte Massensoftware handelt. Wer relevante Werte erfasst kann einen Vorher/Nachher-Vergleich anstellen und damit z.B. Wechsel von Hardware, Treibern o.ä. besser bewerten. Beim Vergleich zwischen unterschiedlichen Systemen können „langsame“ Systeme erkannt werden.
  • Messen zur Fehlerbeseitigung
    Exchange ist mein Lieblingsbeispiel, wie Monitoring und Fehlerbeseitigung direkt im Code verbunden werden können. Der Exchange Store überwacht z.B. wie lange ein Disk-IO benötigt. Die Daten sind natürlich mit Perfmon auslesbar. Aber der Store schreibt zudem Eventlogs, wenn es „zu lange“ dauert und wenn was länger als 30 Sekunden dauert, dann geht der Prozess von einem echten Problem aus. Sei es ein RAID-Controller, Treiberproblem, SAN-Problem und wenn Exchange eh nicht mehr schreiben kann, kann auch niemand mehr damit arbeiten. Es könnte ja sogar noch schlimmer sein, dass der Fehler Daten verändert. Also schickt der Store Exchange mit einem Bluescreen zum Neustart in der Hoffnung, dass es danach wieder besser ist. Getreu dem Motto „Reboot tut gut“ und in der Cloud kann man ja die „faulen Server“ an der Anzahl der Reboots pro Zeiteinheit erkennen. Das geht natürlich besonders gut, wenn das System selbst hochverfügbar ist und der Partner übernehmen kann.

Für einen Entwickler bedeutet das aber, dass er erst einmal mehr Code schreiben und warten muss. Auch dieser „Überwachungscode“ kann ja selbst Probleme verursachen.

Beispiele mit PowerShell

Aber selbst wenn Sie „nur“ mit PowerShell ein kleines Skript schreiben, ist es hier relativ einfach die Dauer einer Aktion zu messen. Sie können den Aufruf der Funktion in ein „measure-Command“  einbetten oder sie starten vorher einen Timer oder merken sich die Zeit. Es gibt also keinen Grund keine Messwerte zu erfassen und irgendwo erst mal zu speichern oder mit festen Werte zu vergleichen.

Write-host "ich starte"
$dauer = (Measure-command {start-sleep -seconds 2}).totalseconds
Write-host "ich bin fertig"
if ($dauer  -ge 1) {
   Write-warning "laenger als 1 Sek"
}

Das Problem mit „Measure-Command“ ist natürlich, dass mehrzeilige Aktionen nicht so einfach anzuschauen sind und keine STDOUT-Ausgabe weiter verarbeitet wird.

Daher ist es manchmal auch besser über eine Zeiterfassung mit Variablen arbeiten. Das kann ein einfaches get-date sein.

Write-host "ich starte"
$start = get-date

# hier dann der oder die Code-Zeilen
start-sleep -seconds 2
Write-host "ich bin fertig"

#Dauer ermitteln
$dauer = ((get-date) - $start).totalseconds
If ($dauer  -ge 1) {
   Write-warning "laenger als 1 Sek"
}

Damit sind aber noch nicht alle Wege beschrieben. Es gibt im .NET Framework eine "Stoppuhr", die dann auch im Hintergrund läuft.

# Stoppuhr einrichten und starten
[System.Reflection.Assembly]::LoadWithPartialName("System.Diagnostics")
$stoppuhr = new-object system.diagnostics.stopwatch
$stoppuhr.Start()

# hier dann der oder die Code-Zeilen
start-sleep -seconds 2
Write-host "ich bin fertig"

#Dauer ermitteln
$dauer = $stoppuhr.Elapsed.totalseconds
If ($dauer  -ge 1) {
   Write-warning "laenger als 1 Sek"
}

Damit haben Sie schon mal drei einfache Möglichkeiten ihren Code um eine grundlegende Kennzahlenerfassung zu erweitern und darauf zu reagieren.

Watchdog und Timer

Zuletzt habe ich noch Beispiele gesehen, bei denen über einen "Timer" bestimmte Aktionen gestartet werden. Ein Timer läuft im Hintergrund und kann zu bestimmten Zeiten Aktionen auslösen. Natürlich kann man damit auch Zeiten messen, z.B. indem man den Timer jede Sekunde einen Code ausführen lässt, der einen Zähler inkrementiert. Ein Timer ist aber viel leistungsfähiger und kann z.B. als Waatchdog eingesetzt werden. Sie wissen, wie lange ihr Befehl brauchen darf und stellen sich einen Timer, der nach dem Ablauf eine Aktion, z.B. eine Warnung ausgeben, auslöst. Nun können Sie am Anfang des Blocks den Timer starten und am ende des Blocks wieder anhalten. Wenn ihr Code schnell genug war, ist nichts passiert.

# Timer instanzieren
$timer = new-object timers.timer
$timer.interval = 1000    # default sind 100ms
$timer.AutoReset=$false   # nur einmal auslösen

# Aktion hinterlegen
$aktion = {write-host "Timer abgelaufen um: $(get-date -Format 'HH:mm:ss')"}
Register-ObjectEvent `
   -InputObject $timer `
   -EventName Elapsed `
   -SourceIdentifier  end2endTimer `
   -Action $aktion

Interessant ist, dass der durch den Timer gestartet Code im gleichen Prozessraum läuft und z.B. Variablen überwachen kann. Man könnte so also einfach einen Timer einbauen, der immer wieder die "Gesundheit" prüft und die Daten meldet. Das kann den Code durchaus lesbarer machen, wenn ein "Watcher" nicht immer wieder aufgerufen werden muss.

Erweiterte Funktionen

Die obigen Beispiele sind natürlich nur ein Anfang um die Performance eines Skripts zu überwachen. Wenn Sie z.B. Dateien schreiben, dann können Sie die Dauer ja durch die Größe dividieren und so einen "Durchsatz" ermitteln, der recht gut die Leistung des Disk-IO-Systems ermessen kann. Richtig rund wird es, wenn Sie die Werte von den vorher gemessenen Werten und anderen Vergleichsgrößen abhängig machen, so dass sie die Performance ihres Skripts zu anderen Skripten auf anderen Systemen vergleichen können. Dann haben Sie quasi die Funktionen der anderen End2End-Lösungen wie End2End-CPU, End2End File, End2End-LDAP quasi nebenbei in ihrem Code mit eingebaut und messen wirklich die Performance des eigenen Codes und lassen nicht von einem Skript "daneben" durch zusätzlich generierte Checks hoffentlich die gleiche Performance messen.

Mir hat mal jemand geflüstert, dass bei Exchange ca. 30% aus Code zur Überwachung und Fehlerbehandlung bestehen würde. Exchange liefert ja auch sehr viele Performance Counter und auch die Eigenüberwachung (Siehe Exchange 2013 Managed Availability) ist gehören dazu. Es kostet natürlich mehr Zeit, mehr Speicher und der Code wird größer aber anscheinend lohnt sich der Einsatz.

Weitere Links