3 votes

Détecter correctement un changement dans une métrique de comptage Prometheus

J'ai essayé d'écrire une requête PromQL pour détecter un changement dans une métrique de comptage.

Mon intervalle de collecte est de 15 secondes.

Je fais une requête sur la métrique de cette manière :

http_server_requests_seconds_count{outcome!="REDIRECTION",outcome!="SUCCESS"}

Cela montre combien de toutes les http_server_requests n'étaient pas des redirections et n'étaient pas réussies.

Ma tentative d'écrire une expression d'alerte utilisant cette métrique ressemble à ceci :

sum by(service, method, outcome, status, uri) (
  rate(
    http_server_requests_seconds_count{
      outcome!="REDIRECTION",
      outcome!="SUCCESS"
    }[1m]
  )
) * 60

Je pense que le taux pour [1m] multiplié par 60 secondes serait 1 lorsqu'un changement se produit, mais autant que je puisse dire, j'obtiens 2 ?

Ces graphiques montrent cela clairement :

Graphique Prometheus

Le graphique du haut est l'expression de la somme, et le graphique du bas est le changement dans le comptage des demandes au serveur. Lorsque le graphique du bas compte +1, le graphique du haut devrait temporairement augmenter à 1 également (mais en réalité il monte à 2).

Qu'est-ce que je fais de travers ? Est-ce que j'ai mal compris quelque chose ? Comment puis-je écrire une requête qui me donne la valeur 1 lorsqu'un changement se produit ? Devrais-je m'attendre à pouvoir écrire une telle requête ?

Merci !

6voto

Alin Sînpălean Points 1709

Cela est dû au fait que Prometheus privilégie une définition cohérente de ce qu'est une plage plutôt que la précision. C'est-à-dire qu'il définit toujours une plage comme étant tous les échantillons se situant dans l'intervalle (inclusif) [now() - plage, now()]. Cette définition a tout son sens pour les jauges : si vous voulez calculer une avg_over_time() avec une plage temporelle égale à l'intervalle, vous souhaitez que chaque échantillon d'entrée soit inclus dans le calcul d'un seul échantillon de sortie.

Mais il en est autrement pour les compteurs. Avec une plage temporelle égale à l'intervalle, une valeur d'entrée (c'est-à-dire l'augmentation entre deux échantillons successifs) est essentiellement jetée. (Voir les problèmes de Prometheus #3746 et 3806 pour BEAUCOUP plus de détails.) Pour compenser les données qu'il rejette, Prometheus utilise l'extrapolation pour ajuster le résultat du calcul.

Cela signifie que si (comme dans votre cas) vous utilisez une plage temporelle qui est 2 fois votre intervalle de grattage (1m de plage pour un intervalle de grattage de 30s), Prometheus trouvera (en moyenne) 2 échantillons dans chaque plage, mais la plage temporelle réelle couverte par ces 2 échantillons sera d'environ 30s. Ainsi, Prometheus extrapolera la valeur pour obtenir le 1m demandé en doublant la valeur. D'où le résultat de 2 au lieu de l'attendu 1. Vous remarquerez également que parce que certaines augmentations entre des échantillons successifs sont rejetées (même si aucun échantillon n'est rejeté), toutes les augmentations de votre compteur n'apparaissent pas dans votre graphique rate(). (C'est-à-dire qu'il n'y a pas de saut dans le rate() correspondant à la troisième augmentation du compteur. Si vous actualisez à différents moments, différentes augmentations apparaîtront et disparaîtront. Grafana a "résolu" ce dernier problème en alignant toujours les plages demandées avec l'intervalle et en manquant ainsi systématiquement les mêmes augmentations.)

La solution suggérée par les développeurs de Prometheus est de calculer des taux sur des durées plus longues. Mais tout cela ne fait que réduire l'erreur (vous obtenez 1,5 avec un facteur 3, 1,33 avec un facteur 4, 1,25 pour un facteur 5, etc.), sans jamais s'en débarrasser. L'extrapolation de Prometheus est suffisamment bien cachée par les compteurs qui augmentent de manière régulière, mais elle est flagrante avec des compteurs comme le vôtre, qui augmentent rarement.)

La seule solution de contournement pour ce problème (à part corriger Prometheus, pour lequel j'ai soumis une PR et je maintiens un fork) est de rétro-ingénierie de l'implémentation de rate() de Prometheus. C'est-à-dire qu'en supposant un intervalle de grattage de 30s, une expression comme rate(foo[1m]) doit être remplacée par :

rate(foo[90s]) * 60 / 90

ou de manière plus générale (notez que l'expression entre crochets doit être une durée temporelle littérale, elle ne peut pas être un calcul)

rate(foo[intended_range + scrape_interval]) * intended_range / (intended_range + scrape_interval)

La raison pour laquelle cela fonctionne est que la plage intended_range + scrape_interval vous donnera suffisamment d'échantillons pour couvrir les augmentations sur intended_range, ce qui est ce que vous souhaitez. Mais ensuite, vous devez annuler le changement introduit par l'extrapolation de Prometheus, d'où la multiplication et la division qui suivent. C'est un hack peu élégant et dépend de vous connaissant votre intervalle de grattage et de le coder en dur dans vos règles d'enregistrement et/ou vos requêtes Grafana.

À noter que quelle que soit la méthode utilisée, vous n'obtiendrez probablement pas une valeur exacte de 1. En raison des services, du réseau et de la latence interne de Prometheus, les échantillons ne seront généralement pas alignés sur la milliseconde, de sorte que le taux d'augmentation par seconde sera légèrement inférieur ou légèrement supérieur à la valeur attendue.

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X