Logs sind bei der Nachverfolgung von Problemen äußerst hilfreich, es gibt aber auch Fragen, die leichter mit dem Zustand des Systems als mit der Information, wie es zu dem Zustand kam, beantwortet werden können. Wie viele Bestellungen hat mein System gestern abgewickelt? Wie viele Requests pro Sekunde verarbeiten wir genau jetzt? Wie viele waren es vor einem Jahr? Hat sich der Durchsatz der Anwendung verschlechtert, und wenn ja, weshalb? Ist vielleicht der DB-Pool zu gesättigt?

Solche Fragen lassen sich mit Logeinträgen kaum beantworten, und sekündlich die Sättigung der Pools in die Logdatei zu schreiben, entspricht auch nicht dem Sinn des (Logging-)Erfinders.

Zusätzlich muss bei komplexen Anwendungen meist nicht nur ein System, sondern eine ganze Reihe von Systemen überwacht werden. Der Gesundheitsstatus des Gesamtsystems lässt sich dann nur über einen ganzheitlichen Ansatz bewerten. Während man früher nur den Application Server oder Cluster und die Datenbank überwachen musste, kommt mit der Popularität der Microservices und dem anti- monolithischen Architekturtrend auch mehr Vielfalt in die Systemlandschaft.

Um dennoch den Überblick über das Gesamtsystem zu behalten, kann das konsequente Erfassen von Laufzeitmetriken eine Lösung sein. Zentral aggregiert und aufbereitet werden die Metriken schließlich so persistiert, dass eine Abfrage und Visualisierung über die Zeit möglich ist.

Laufzeitmetriken können auf drei verschiedenen Ebenen erfasst werden. Auf der höchsten Abstraktionsstufe stehen fachliche Metriken, in der Fachsprache auch Key Performance Indikators (KPIs) genannt und eher aus BPM-Projekten bekannt. Solche Metriken drücken in der Regel direkte Geschäftswerte aus und haben nur noch wenig Bezug zu technischen Details. Beispiele dafür sind die Höhe des erwirtschafteten Umsatzes pro Tag, die Anzahl der verkauften Produkte oder die durchschnittliche Dauer einer Schadensregulierung. Ein kontinuierliches Monitoring solcher Kennzahlen erlaubt dem Unternehmen, die Gesundheit der Geschäftsprozesse zu überwachen und hilft ihm, Probleme frühzeitig zu erkennen und regulierend einzugreifen.

Auf der mittleren Abstraktionsstufe sind die Anwendungsmetriken angesiedelt. Sie enthalten schon deutlich mehr technische Details und beziehen sich jeweils auf eine einzelne Anwendung. Beispiele für diesen Bereich sind die Anzahl der GET Requests pro Sekunde, die Zahl der 500er- oder 400er-Statuscodes oder die Sättigung der Threadpools. Schließlich bleiben noch die Systemmetriken, die die niedrigste Abstraktion darstellen. Hier geht es um Werte wie die CPU-Last, Speicherauslastung, Festplattenplatz und Netzwerk-IO.

Korrelation hilfreich

Spannend wird es, wenn man diese Metrikgruppen übergreifend betrachtet und nach Zusammenhängen sucht. Wenn beispielsweise die Anzahl der verkauften Produkte sinkt, kann das durchaus technische Gründe haben. Hat sich die Anzahl der Requests pro Sekunde ebenfalls verringert und die Dauer der einzelnen Requests deutlich erhöht, muss man sogar davon ausgehen. Doch auch das kann viele verschiedene Gründe haben. Je mehr Anwendungs- und Systemmetriken man erfasst, umso wahrscheinlicher ist es, dass ein Muster von auffälligen Werten erkennbar ist und man so das Problem einfacher isolieren kann. Möglicherweise ist es der Anwendung in ihrem Zuhause auch einfach nur zu klein geworden. Ohne die Erhebung von Metriken ist es schwierig, diese Frage eindeutig zu beantworten. Es ergibt also durchaus Sinn, die Systemlandschaft feinmaschig zu überwachen.

Diese Idee ist natürlich bei Weitem nicht neu, allerdings war man lange eher unzufrieden mit den Möglichkeiten und Werkzeugen. Im Frühjahr 2011 hat sich unter dem Twitter-Hashtag #monitoringsucks (inzwischen umgetauft zu dem alltagstauglicherem #monitoringlove) eine Open-Source-DevOps-Bewegung formiert, die dem Thema einen zweiten Frühling verschaffte und eine Vielzahl neuer Werkzeuge hervorbrachte.

Im Wesentlichen geht es dabei darum, Entwicklern die Möglichkeit zu geben, Metriken einfach zu erfassen, um sie dann zentral weiterverarbeiten und darstellen zu können. Daraus resultiert die in Abbildung 1 skizzierte Pipeline, die auch als Kategorisierungshilfe für eine Vielzahl von freien Werkzeugen betrachtet werden kann.

Abb. 1: Pipeline zur Erfassung, Speicherung und Darstellungen von Laufzeitmetriken

Im ersten Schritt der Pipeline müssen die Metriken erhoben werden. Wichtig für die Weiterverarbeitung sind drei Merkmale: der Name und der Wert der Metrik sowie der Zeitpunkt der Messung. Zur Erhebung eignen sich Frameworks, wie die später beschriebene Dropwizard-Metrics-Bibliothek von Coda Hale. Die Tripel können aber auch manuell erfasst und per UDP oder TCP an einen Metriksammelpunkt weitergereicht werden.

Die Sammelpunkte stellen den zweiten Block in der Metrics-Pipeline dar. Sie haben die Aufgabe, den Metrikstrom zu bündeln und mittels Sampling zu vereinheitlichen. Das Sampling sorgt auch dafür, dass nicht für jedes Ereignis ein Datenpunkt in der Datenbank gespeichert werden muss, sondern über ein sinnvoll gesetztes Intervall zusammengefasst wird. Dadurch wird die Übermittlung und die Persistenz performanter und stört den eigentlichen Betriebsablauf kaum. Solche dedizierten Sammelpunkte sind in der Regel kleine, schlanke Services, die über das Netzwerk die Messdaten entgegennehmen, umrechnen und dann in definierbaren Intervallen an die nächste Pipelinestufe weitergeben. Wenn man eine Metrics-Bibliothek verwendet, bietet diese schon Möglichkeiten, die Werte vor der Übermittlung zu sammeln und macht die Sammelstelle obsolet.

Der nächste und dritte Schritt ist nun das Persistieren der Daten. Die meisten Timeseries-Datenbanken benutzen dafür einen an die Round-Robin-Datenbank angelehnten Ansatz. Man kann sich das wie eine Kaskade von Datentöpfen vorstellen, die ein bestimmtes Volumen (z. B. eine Stunde) haben, das sie mit Werten in einer bestimmten Auflösung (z. B. zwei Sekunden) speichern. Das Aggregat dieser Werte läuft dann in den nächsten Topf mit einer gröberen Auflösung und einer höheren Vorratszeit, z. B. einen Tag bei einer Auflösung von 30 Sekunden. Auf diese Weise kann die Entwicklung der Werte über einen großen Zeitraum erhalten bleiben. Die Reduktion der Datenauflösung macht die Datenmenge beherrschbar, und da die fein aufgelösten Details der Vergangenheit meist nicht mehr so wichtig sind, funktioniert dieser Ansatz sehr gut.

Der letzte Schritt in der Pipeline ist die Auswertung und Visualisierung der Daten. Dazu müssen die Werte aus dem Datenspeicher abgefragt und aufbereitet werden. Oft ist das arithmetische Mittel, der Median oder die 95ste Perzentile aussagekräftiger als jeden einzelnen Wert pro Zeiteinheit anzuzeigen, da die Diagramme dann oft wegen Ausreißern schwerer zu lesen sind. Teils können schon die Sammelstellen diese Werte berechnen und zusätzlich an die Metrikdatenbank weitergeben, andernfalls muss die Datenbank Aggregatsfunktionen bereitstellen.

Auf Basis der Auswertungen folgt nun der wichtigste Teil: die Darstellung und Interpretierung der Daten. Die Darstellung auf Dashboards ist sicher der interessanteste Anwendungsfall, doch auch hier gibt es unterschiedliche Zielgruppen. Grundsätzlich kann man alle Metriken durch Liniendiagramme mit der Zeit auf der x-Achse darstellen. Für fachliche Dashboards sind diese Diagramme aber oft zu detailliert. Stattdessen ist es sinnvoll, die Metriken zu interpretieren und über einfachere Widgets darzustellen. Das kann z. B. ein Trendindikator sein, der die Werte mit denen der Vorwoche vergleicht oder eine Ampel, die auf Rot springt, wenn ein bestimmter Schwellwert überschritten ist. Ebenfalls Einfluss auf die geeignete Darstellung hat die Zielpräsentation des Dashboards. Wird das Dashboard auf einem großen Fernseher im Büro angezeigt, wird man die Diagramme anders darstellen müssen, als wenn man sie bei Bedarf im Webbrowser öffnet.

Neben der reinen Darstellung spielt auch noch das Alerting eine Rolle, also das Auslösen von Alarmen bei signifikanten Veränderungen der Werte. Dafür ist es oft nötig, Metriken im Vergleich zu beobachten und Anomalien zu erkennen.

Werkzeuge der Wahl

Seit sich die DevOps-Bewegung des Themas angenommen hat, ist eine Vielzahl von neuen Bibliotheken und Werkzeugen entstanden. Im Folgenden wird ein kleiner, subjektiver Überblick gegeben.

Für die Erfassung von Metriken in JVM-Anwendungen eignet sich die Dropwizard- Metrics-Bibliothek von Coda Hale. Den Einstieg in die Bibliothek stellt eine zentrale Metrics-Registry dar, die man seiner Anwendung entweder manuell über ein Singleton oder über Dependency Injection bekannt macht. In der Registry werden alle Metriken der Anwendung registriert. Sie kümmert sich auch um periodische Abfragen von Werten, um das Sampling sowie die Weitergabe an Metrikdatenbanken. Coda Hale unterscheidet fünf Arten von Metriken:

Gauges sind Instrumente zur Messung von genau einem Wert zum aktuellen Zeitpunkt. Coda Hale hat sich für ein Pull-Modell entschieden, d. h. eine Gauge muss eine getValue-Methode implementieren, die von der Metrics- Bibliothek periodisch aufgerufen wird und dann den entsprechenden Wert zurückgeben soll. Typischerweise benutzt man Gauges für Werte, die sich nicht andauernd ändern und gezielt im System nachgesehen werden können, z. B. die Anzahl der aktiven Threads oder Bestellungen. Wenn die Berechnung des Werts aufwändiger ist, kann die Bibliothek die Werte cachen und nach einer festgelegten Zeit invalidieren, um sie dann erneut zu berechnen. Der Speicherbedarf pro Gauge liegt bei ungefähr 0,7 Kilobyte, man kann sie also großzügig im Anwendungscode verteilen.

Counter sind für sich schnell ändernde Werte gedacht, die man nicht nachschlagen muss, sondern direkt zählen kann. Das Counter-API stellt dafür zwei Methoden zum Erhöhen und Senken des Zählers bereit:

final Counter activeUsers
    = registry.counter(name(UserRepository.class, "active-users"));
activeUsers.inc();
activeUsers.inc(3);
activeUsers.dec();
activeUsers.dec(2);

In der ersten Zeile des Listings wird von der Registry ein neuer Counter angefordert. Die name-Methode ist ein statischer Import der MetricsRegistry-Klasse und hilft bei der Erzeugung von einheitlichen Metrikbezeichnern. Danach wird die Anzahl der aktiven Benutzer erhöht und gesenkt. So ein Counter belegt ca. 0,8 Kilobyte.

Meters messen die Rate eines Ereignisses pro Zeiteinheit, also beispielsweise die Anzahl von HTTP-Requests pro Sekunde. Dazu wird wie bei den Countern von der Registry ein Meter-Objekt angefordert und später bei jedem Ereignis die mark()-Methode aufgerufen:

final Meter getRequests
    = registry.meter(name(Servlet.class, "get-requests", "requests"));
getRequests.mark();

Meters speichern nicht nur die Rate, sondern auch die Durchschnittsrate (mean rate) während der Laufzeit der Anwendung. Da diese Daten meist nicht besonders aussagekräftig sind, wird zusätzlich der gleitende Durchschnitt der letzten Minute, der letzten fünf und der letzten fünfzehn Minuten gespeichert. Ein Meter belegt ca. 1,8 Kilobyte im Hauptspeicher.

Histograms messen die statistische Verteilung von Werten eines Ereignisses. Als Beispiel für so ein Ereignis soll eine Suchanfrage dienen. Während man mit einem Counter die Anzahl der Suchanfragen oder mit Meters die Anzahl Suchanfragen pro Sekunde erfassen kann, helfen Histogramme, wenn man noch einen numerischen Wert mit dem Ereignis verknüpfen möchte, z. B. die Anzahl der Suchergebnisse für diese Anfrage. Da Histogramme neben einfachen statistischen Merkmalen wie min, max, Durchschnitt und Standardabweichung auch verschiedene Quantile berechnen, lassen sich mit ihrer Hilfe Aussagen wie „75 Prozent aller Suchanfragen gaben 50 oder weniger Ergebnisse“ treffen. Das folgende Listing zeigt ein Beispiel für das Histograms-API:

final Histogram searchResults
    = registry.histogram(name(SearchManager.class, "search-results"));
searchResults.update(results.length);

Naiv betrachtet erfordert die Berechnung von Quantilen eine sortierte Liste der Messwerte. Damit der Speicherbedarf aber beherrschbar bleibt, verwendet die Bibliothek eine Technik namens „Reservoir Sampling“ und stellt verschiedene Reservoir-Implementierungen bereit, die auf kleinem Platz eine stets statistisch repräsentative Stichprobe vorhalten. Ein Histogramm ist somit mit einem Speicherverbrauch zwischen 9,3 und 87 Kilobyte am speicherhungrigsten.

Timers erfassen die Zeit, die ein Ereignis benötigt hat, und speichern sie in einem Histogramm. Mit Timern sind Aussagen wie „95 Prozent aller Suchanfragen haben weniger als 200 Millisekunden gedauert“ möglich. Ein Beispiel:

final Timer searchDuration
    = registry.timer(name(SearchManager.class, "search-duration"));

final Timer.Context context = searchDuration.time();
try {
  doSearch(...)
} finally {
  context.stop();
}

Im nächsten Schritt müssen die erfassten und gesampelten Metriken dem Framework wieder entlockt werden. Die Bibliothek verwendet dafür so genannte Reporter, die mit der Metrics-Registry verknüpft werden. Von Haus aus können die Ergebnisse periodisch auf die Konsole (Listing 1) oder in CSV-Dateien geschrieben, als MBeans per JMX verfügbar oder als JSON-Dokument über das Web abrufbar gemacht werden. Außerdem gibt es Reporter für Graphite und Ganglia, um die Metriken darüber abfragbar zu machen (Listing 2).

Für einige Java-Frameworks wie Jersey, Jetty, Ehcache etc. bringt die Bibliothek bereits eine Instrumentierung mit. Für Spring gibt es eine Erweiterung, die eine Instrumentierung von Webanwendungen per Annotation sehr einfach macht.

ConsoleReporter reporter = ConsoleReporter.forRegistry(registry)
  .convertRatesTo(TimeUnit.SECONDS)
  .convertDurationsTo(TimeUnit.MILLISECONDS)
  .build();
reporter.start(1, TimeUnit.SECONDS);
Listing 1: Metrics-Reporterkonfiguration zur sekündlichen Ausgabe der Werte auf der Konsole
Graphite graphite
    = new Graphite(new InetSocketAddress("graphite.example.org", 2003));
GraphiteReporter reporter = GraphiteReporter.forRegistry(registry)
  .prefixedWith("mywebshop")
  .convertRatesTo(TimeUnit.SECONDS)
  .convertDurationsTo(TimeUnit.MILLISECONDS)
  .filter(MetricFilter.ALL)
  .build(graphite);
reporter.start(1, TimeUnit.MINUTES);
Listing 2: Metrics-Reporterkonfiguration zur Weitergabe der Metriken an Graphite

Hat man nicht die Möglichkeit, eine solche Metrics-Bibliothek zu verwenden, beispielsweise weil man Metriken aus Nicht-Java-Anwendungen erfassen möchte, bietet statsd einen guten Metriksammelpunkt. Für Node.js geschrieben, erwartet es UDP-Pakete in der Form <metricname>:<value>|<type>, sammelt und sampelt die Werte und leitet sie schließlich periodisch an eine Datenbank wie Graphite weiter. Statsd unterscheidet Counters, Gauges und Timers. Bei Counters wird die Rate für das periodische Intervall berechnet. Sind beispielsweise sieben Erhöhungsereignisse für eine Metrik per UDP eingegangen und werden die Daten alle zehn Sekunden an die Datenbank weitergeleitet, so wird dann der Wert 0,7 (Erhöhungen pro Sekunde) gespeichert. Gauges dagegen geben immer den letzten Wert weiter, den sie per UDP empfangen haben. Timers sind ähnlich wie die oben beschriebenen Histogramme und geben zusätzlich zu den Werten noch eine statistische Auswertung weiter.

Um zusätzlich zu den fachlichen und anwendungsspezifischen Metriken auch Systemmetriken in den Datenstrom zu integrieren, eignen sich Werkzeuge wie collectd und Diamond. Sie sammeln nicht nur die Metriken, die das Betriebssystem bereitstellt, (wie Load, Speicherauslastung) sondern auch Metriken von Infrastrukturdiensten wie Apache, nginx, Postfix, MySQL etc.

Die bekanntesten Vertreter in der Gruppe der Metrikdatenbanken sind das RRDtool von Tobias Oetiker, Graphite, Ganglia, InfluxDB oder OpenTSDB. Es gibt auch Hybride aus Sammelpunkt und Datenspeicher, wie z. B. Cube von Square. Am meisten verbreitet erscheint Graphite, ein in Python geschriebenes Programm, denn obwohl es nicht ganz einfach zu installieren ist, bietet es die nötige Robustheit und Skalierbarkeit für die Speicherung und Darstellung von Metriken in Produktionsumgebungen. Graphite ist in drei Komponenten unterteilt: Carbon ist für den Empfang von Metriken zuständig, Whisper kümmert sich um die Persistenz und die Graphite-Web-App sorgt für die Visualisierung. Letztere kann man wie einen Visualisierungsdienst verwenden. Es erlaubt die Eingabe von Abfragen (auch mit komplexen Aggregatsfunktionen) und gibt entweder die Daten oder ein Bild zurück. Dabei können auch mehrere Metriken in einem Bild zusammengeführt werden. Diese Bilder lassen sich recht einfach auf Dashboards verlinken.

Wer es schöner mag, kann sich die vielen Dashboards für Graphite ansehen. Einen besonderen Blick wert ist Grafana, besonders wenn man schon Kibana für die Loganalyse im Einsatz hat. Grafana ist ein Frontend für Graphite und gleicht Kibana bis aufs Haar. Es erlaubt die Erstellung von umfangreichen Dashboards mit wenigen Klicks, dank des Query-Editors ist auch eine komplexe Anfrage schnell erstellt.

Abb. 2: Screenshot von Grafana

Auch für das Firmendashboard auf dem 50"-Fernseher gibt es quelloffene Tools. Dashing z. B. ist ein in Ruby geschriebenes, HTML-basiertes Dashboard und zeigt verschiedenste Widgets als Kacheln im Browser an. Das Spektrum der Widgets ist sehr groß: Von Kacheln mit Zahlen oder Diagrammen (ideal für Metriken) über Build-Status und Twitter-Wall bis hin zu Busabfahrzeiten finden sich alle möglichen Widgets. Eigene Kacheln lassen sich leicht selbst implementieren.

Scheut man den Aufwand, eine solche Messinfrastruktur selbst aufzubauen, kann man natürlich auch auf die zahlreichen SaaS-Anbieter zurückgreifen. Die Kosten hängen meistens von der Auflösung und Vorhaltezeit ab. Hosted Graphite bietet z. B. einen Graphite-Endpunkt und Grafana als Frontend zur Visualisierung der Metriken. Librato, NewRelic und Datadog sind eher schon Fullstack-Angebote, sie bringen Agents mit, mit denen sich existierende Systeme fast automatisch instrumentieren lassen.

Fazit

Die Erfassung von System-, Anwendungs- und Fachmetriken hilft bei der feinmaschigen Überwachung des Gesamtsystems und erlaubt eine schnellere Reaktion auf Probleme. Die vorgestellten Werkzeuge ermöglichen die Einrichtung der benötigten Infrastruktur und sind so konzipiert, dass der Performance- Overhead möglichst gering bleibt. Je mehr Metriken erfasst werden, umso eher kann man bei Problemen Zusammenhänge erkennen. Messen Sie also möglichst viel, aber wählen Sie die Messinstrumente mit Bedacht, da sie teils unterschiedlich teuer (Speicher/Performance) sind. Dank der Dashboards können die Entwickler, der Betrieb, aber auch die Fachabteilung stets die Leistung der Anwendung im Auge behalten, in einer für die jeweilige Zielgruppe angepassten Darstellung.