Spikes im Grafana mit recoding_rules aus Prometheus

So langsam muss ich wieder anfangen, über die kleinen Erfolge zu bloggen, die sich in den letzten Jahren ergeben haben. Hier geht es nun um unwillkommene Spikes im Trafficgraphen, wenn dieser sich aus einer generalisierten Metrik erzeugt.

Trafficcounter in heterogenden Netzen kommen aus vielen unterschiedlichen Quellen, die alle ihre eigene Syntax und Labelstruktur aufweisen. Mit sowas will man möglichst schnell nichts mehr zu tun haben. Stattdessen soll eine generalisierte Metrik common:interface:counter die ganzen hässlichen Details wegabstrahieren.

groups:
- name: Leitungen
  rules:
  - record: common:interface:counter
    expr: >-
      sum by (device,port,type,direction,description) (
        label_replace(label_replace(
          sum by (direction,instance,intf,type,description) (
            intfCounter + on(instance,intf) group_left(description)
              (0*label_replace(interfaceDescription,"intf","$1","interface","(.*)"))
          ) or sum by (direction,instance,intf,type) (
            label_replace(intfLagCounter,"type","$1","unnamedLabel4","(.*)")
          ),
          "device","$1","instance","(.*):.*"),
          "port","$1","intf","(.*)")
      or label_replace(label_replace(
          label_replace(label_replace(cisco_interface_receive_bytes,"direction","in","job",".*"),"type","Octets","job",".*") or
          label_replace(label_replace(cisco_interface_transmit_bytes,"direction","out","job",".*"),"type","Octets","job",".*") or
          label_replace(label_replace(cisco_interface_receive_errors,"direction","out","job",".*"),"type","Errors","job",".*") or
          label_replace(label_replace(cisco_interface_transmit_errors,"direction","out","job",".*"),"type","Errors","job",".*") or
          label_replace(label_replace(cisco_interface_receive_drops,"direction","out","job",".*"),"type","Discards","job",".*") or
          label_replace(label_replace(cisco_interface_transmit_drops,"direction","out","job",".*"),"type","Discards","job",".*"),
          "device","$1","target","(.*)"),
          "port","$1","name","(.*)")
      or label_replace(label_replace(
          label_replace(label_replace(junos_interface_receive_bytes,"direction","in","job",".*"),"type","Octets","job",".*") or
          label_replace(label_replace(junos_interface_transmit_bytes,"direction","out","job",".*"),"type","Octets","job",".*") or
          label_replace(label_replace(junos_interface_receive_errors,"direction","out","job",".*"),"type","Errors","job",".*") or
          label_replace(label_replace(junos_interface_transmit_errors,"direction","out","job",".*"),"type","Errors","job",".*") or
          label_replace(label_replace(junos_interface_receive_drops,"direction","out","job",".*"),"type","Discards","job",".*") or
          label_replace(label_replace(junos_interface_transmit_drops,"direction","out","job",".*"),"type","Discards","job",".*") ,
          "device","$1","target","(.*)"),
          "port","$1","name","(.*)")
      ) 

Das sieht zunächst komplett wahnsinnig aus, ist aber relativ schnell erklärt:

Mit or werden Metriken, die eigentlich nichts miteinander zu tun haben, zu einer Metrik vereinigt. Dabei bleiben die Detail-Labels erhalten, der Name der Metrik fällt nur weg. Da ich hinterher eine Metrik mit neuen Label-Bezeichnern haben will, muss die die neuen Label-Namen erzeugen und mit den Werten der alten Labels befüllen. Die Funktion label_replace tut dies: Sie befüllt ein neues Label mit den Treffern des regulären Ausdrucks eines existierenden Labels. Dies kann man besonders schön an den letzten Zeilen sehen, wo das neue Label port mit dem vollen Text des alten Labels name gefüllt wird. So werden also nacheinander die Labels direction, type, device und port befüllt.

Um hinterher nicht mit einem Zoo weiterer Alt-Labels rumlaufen zu müssen, wir per sum by (labels) einfach die "Summe" ohne die unnötigen Labels ausgeführt: Da sie verbleibenden Labels eindeutig sind, ist es eine Summe über jeweils genau einen Eintrag, der einfach die nicht benötigten Labels weg wirft. Das ist ein typisches Idiom in PromQL.

Schwieriger ist der erst Teil, bei dem die Zählerstände zwar schon schön vorliegen, aber die Informationen wie description fehlen. Man muss also hier die Labels aus einer ganz anderen Metrik passend dazu pappen. Das zugehörige Idiom addiert nun also zwei Metriken (unter Berücksichtigung ausgewählter Labels als passend angesehen) und übernimmt das Label description. Damit die Summe aber nichts kaputt macht, multipliziert man die zweite Metrik mit 0.

Auf diese Weise entsteht eine neue Metrik common:interface:counter mit den Labels device, port, type, direction und description, die nun universell einsetzbar ist.

Schaut man sich die Traffiggraphen solcher Metriken an, so haben die häuftige und störende Ausreißer:

Screenshot 2022-09-23 235337
Screenshot 2022-09-23 235455

Beiden Bildern ist anzusehen, wie zittrig oder sprunghaft der Graph ist. Dieser Effekt stört mich seit mindestens einem Jahr und ich hatte ich bisher nicht in den Griff bekommen.

Mwhr durch Zufall kam ich heute dazu, den generierte Metrik und die Originalmetrik mal übereinander zu legen. Das Ergebis war, dass die Metriken fast identisch sind. Jedoch läuft die originale Metrik der generierten Metrik etwas voraus, bis die generierte Metrik den Fehler nach Minuten oder Stunden wieder einholt, dann aber sprunghaft.

Wie kann das sein? Wann wird denn die generierte Metrik erzeugt? Sicher nicht immer dann, wenn irgendeine der Metriken irgendeinen Update bekommt. Nein, das geschieht in regelmäßigen Intervallen durch den Prometheus selbst.

global:
  scrape_interval:     59s
  evaluation_interval: 1m 

Den Werte habe ich so gesetzt, damit er kurz nach neu eintreffenden Werten diese verarbeiten kann und diese nicht verpasst. Im Laufe der Zeit hatten sich dann andere Geräte mit scrape_interval von 29s bis 3m eingefunden. Die Daten kommen also zu sehr unterschiedlichen Zeiten rein.

Aber selbst diese Sekunde Unterschied bedeutet, dass die generierende Metrik jede Stunde (60x aufgerufen) doppelte Daten sieht. Diesen Takt kann man in der gelben Kurve ziemlich gut erkennen. Es kommen dann ja auch noch so Dinge rein, wie Verzögerungen beim Auslesen der Metriken, etc. pp.

Worin besteht also die Lösung? Viel häufiger die Metrik generieren! Das neue evaluation_interval beträgt nun die Hälfte des kleines Scrape-Intervals also 10 Sekunden.

Glücklicherweise ist Prometheus sehr effizient mit dem Abspeichern von "unveränderlichen" Passagen von Metriken. So ist das zumindest kein Plattenfresser.

Achja, ganz rechts im Bild ist die Änderung wirksam. Die Kurven haben aufgehört zu zittern.

Post a comment

Related content