|
<< Click to Display Table of Contents >> Navigation: Prometheus - Kennzahlen > Prometheus, Gemstone App |
Kommen wir nun zur Frage, wie man Prometheus für eigene Statistiken in einer Gemstone/S App nutzen kann.
Das Grundprinzip ist recht einfach
•Man muß die gewünschten Metriken erarbeiten
•Bei der Initialisierung der Datenbank muß man entsprechenden Code schreiben, damit diese Metriken definiert und persistent gemacht werden
•Im Programmablauf werden die Metriken gepflegt
•In einem separaten Prozeß werden die Metriken regelmäßig (z.B. alle 30 Sekunden) in eine JSON-Struktur aufbereitet und via RabbitMQ an die MetricsBridge geschickt.
•Die MetricsBridge empfängt via RabbitMQ alle Metriiken von allen angeschlossenen Metriksammlern. Der Prozeß läuft auf der Prometheus-Maschine und ist daher lokal ohne Probleme erreichbar. Prometheus fragt die Bridge regelmäßig ab (z.B: alle 60 Sekunden) und pflegt die Daten in die eigene Datenbasis ein.
Da wir es mit einer Anwendung zu tun haben, die extrem von concurrency geplagt ist, erfolgt die Pflege der Metriken indirekt. Es werden im laufenden Ausführungsstrang Meßpunkte erzeugt, in der DB gespeichert und diese werden dann in dem separaten Prozeß ausgewertet und so die Metriken gepflegt.
Das bedeutet natürlich auch, daß die Daten ggfs auflaufen - auch wenn der separate Prozeß nicht läuft.
Die dafür notwendigen Klassen findet man in dem Paket "MSKExtensions".
Zuerst einmal sollte man sich eine Instanz der Klasse "PASMetricsRegistry" erzeugen und dafür sorgen, daß diese Instanz in die Persistenz eingebunden ist. Dazu definiert man in PUM ein Attribut (unhandled mit Typ Unicode) in einer persistenten PUM-Klasse und weist die Instanz von PASMetricsRegistry dem Attribut zu.
Die Registry hat eine RcQueue um MetrikPunkte zur späteren Auswertung abzulegen. Außerdem hat die Registry einen Schalter, um die Ablage generell an- und auszuschalten.
Der folgende Programmcode zeigt, wie man prinzipiell die Metriken definiert.
| registry requests queueLength requestDuration errorsByType |
registry := PASMetricsRegistry new
enabled: true ;
setProcessType: 'esystem' instance: 'main' ;
yourself.
requests := PASMetricCounter
name: 'es_http_requests_total'
help: 'Gesamtzahl HTTP-Requests'
labelNames: #('url' 'method' 'status').
registry register: requests.
queueLength := PASMetricGauge
name: 'es_app_queue_length'
help: 'Aktuelle Länge der internen Warteschlange'
labelNames: #().
registry register: queueLength.
requestDuration := PASMetricHistogram
name: 'es_http_request_duration_seconds'
help: 'Dauer der Requestverarbeitung'
labelNames: #('url')
buckets: #(0.1 0.5 1.0 2.0 5.0).
registry register: requestDuration.
errorsByType := PASMetricCounter
name: 'es_app_errors_total'
help: 'Gesamtzahl aufgetretener Fehler'
labelNames: #('error_type').
registry register: errorsByType.
Die Instanz der Klasse "PASMetricsRegistry" sollte in die Persistenz eingebunden werden. Über "enabled:" kann man die Speicherung der Datenpunkte aktivieren/unterbinden - dann füllt sich die Gemstone nicht mit ungewünschten Daten. Über "setProcessType:instance:" kann man den Ursprung der Statistik definieren. Es könnte ja angehen, daß mehrere Instanzen von dieser Datenbank laufen und alle speichern die Daten in das gleiche Prometheus-System ab und bei der Auswertung möchte man das gerne unterscheiden können (über "instance").
Haben wir mehrere unterschiedliche Anwendungen geschrieben, kann man das mit "processType" kenntlich machen.
Bei der Übertragung der Daten durch die MetricsBridge an Prometheus, werden diese Information als zusätzliche Labels bei den Metriken definiert. Dadurch kann man diese dann in der Datenbank von Prometheus und in Grafana unterscheiden.
Außerdem würde ich empfehlen, den Metriknamen ein Kürzel voranzustellen (z.B. "es_" als Abkürzung von ESystem Metriken) und die Metriknamen - wenn sinnvoll - über verschiedene Prozeßtypen gleichartig zu benennen.
Nachdem nun die Metriken definiert sind, diese in dem persistenten Baum der Anwendung abgelegt wurden, muß man diese Metrken auch mit Werten füllen. Das kann man auf verschiedene Werte machen:
•Direktes Befüllen einer Metrik mit Informationen
•Indirektes Befüllen einer Metrik über die Ablage von Metrikpunkten und asynchrones direktes Befüllen in einem eigenen Thread
Der folgende Code zeigt, wie man eine Metrik mit Daten füllt. Die Metrik haben wir über den Namen in der MetrikRegistry gefunden:
"In der Request-Verarbeitung, z.B. für DELETE auf /cmd/delete:"
metric increment: (MetricLabels withPairs:
#('url' '/cmd/delete' 'method' 'POST' 'status' '200')).
In diesem Beispiel sieht man die n-dimensionale Struktur der Statistikerfassungen. Hier haben wir drei Dimensionen ('urL', 'method' und 'status', ungerade Indices beim Zugriff auf das Labels-Array). Die Einteilung der jeweiligen Achsen erfolgt durch die Ausprägungen (z.B: "POST" in der Dimension "method").
Man sollte bedenken, daß eine Datenbank in zahlreichen Prozessen abläuft und alle Prozesse eventuell Metrikdaten sammeln - das führt bei der Bearbeitung unweigerlich zu concurrency-Probleme. Daher bietet sich ein anderes Verfahren an: die indirekte Befüllung der Metriken.
Hier ein entsprechendes Beispiel mit indirekter Befüllung:
"In der Request-Verarbeitung, z.B. für DELETE auf /cmd/delete:"
registry
registerPoint: (PASMetricPoint metricNamed: 'es_http_requests_total' value: 1 labels: (PASMetricLabels withPairs: #('url' '/api/users' 'method' 'POST' 'status' '200'))) ;
Das Systerm erzeugt einen Metrikpunkt und legt in in der Registry ab. Die Speicherung dort erfolgt in einer RcQueue - also ohne concurrency Probleme. Ein Auswertungsprozess liest diese Queue aus und füllt die Daten in die Metriken.
Bei der indirekten Befüllung muß es einen eigenen Prozeß geben, der die Metrikpunkte regelmäßig auswertet und in die Metriken speichert. Hier der zentrale Rumpf der Logik dazu (konzeptioneller Code):
nextPrometheusAppExportTS := DateAndTime now + registry exportInterval.
[
[abortFlag] whileFalse: [
now := DateAndTime now.
(now >= nextPrometheusAppExportTS)
ifTrue:[
nextPrometheusAppExportTS := now + registry exportInterval.
registry handleAllPoints.
jsonContentString := NeoJSONWriter toString: registry asJsonMessage.
self createAndSendMetricsInfo: jsonContentString via: 'amq.topic' routedBy: 'metrics'.
System commitTransaction.
]
]
Man bedenke auch hier - das Senden des Telegrammes geschieht nicht hier - sondern nur die Erzeugung des Telegrammen und die Ablage in den Versand geschieht hier. Der eigentliche Versand geschiet in einem anderen Prozeß. Alles transaktionsorientiert.
In diesem Beispiel wurden die folgenden Zeiten gewählt (aufeinander abgestimmt):
•Jede Anwendung sendet alle 30 Sekunden die Statistikdaten
•Prometheus fragt alle 60 Sekunden bei der MetricsBridge ab
•Die MetricsBridge verwirft seinen Inhalt nach 90 Sekunden.