Первоначально опубликовано вkuricat.com
Феномен
При использовании PromQL для простого запроса сводного типа метрик (при условии, что имя метрикиA
), произошло довольно странное явление.
Во-первых, мы не используем функции для запроса, просто перечисляем необработанные результаты:
// 对应的 PromQL
A{quantile="0.5"}
Далее используем функцию на приведенных выше данныхavg
бери среднее.
// 对应的 PromQL
avg(A{quantile="0.5"})
В настоящее время,Странные вещи произошли, согласно здравому смыслу, если мы используем функцию avg дляA
найти среднее (avg(A{quantile="0.5"})
), следует рассчитать среднюю кривую Метрики A, но после использования функции avg обнаруживается, что данные по изображению отсутствуют.
Затем, просмотрев исходный ответ на запрос Prometheus, мы обнаружили, что на самом деледанные возвращаются, но так как всеNaN
, поэтому на графике нет изображения.
В то время автор считал, что некоторые данные были прерывистыми, что привело к влияниюavg 函数
, и начал пробовать использоватьsum
, и другие функции, чтобы убедиться, что эта проблема вызвана разрывными данными.Оказывается, что все эти функции имеют указанные выше проблемы.Но что более неожиданно, так это то, чтоmax
а такжеmin
Все функции ведут себя нормально.
решение
Сначала решение публикуется здесь. Вы можете добавить фильтр к вектору A, чтобы удалить образцы, значение которых равно NaN. Пример кода выглядит следующим образом:
// before
avg(A{label="label"})
// after
avg(A{label="label"} > 0)
// ^^^^^^ 注意这里
чтобы результаты отображались так, как ожидалось.
причина
Эта проблема появилась недавно, в то время мы делали некоторые модификации производства и сбора Метрики на стороне PHP, и перенесли производство и сбор Метрики наGolang 中间件
Ответственный.Но после того, как мы завершили преобразование, мы обнаружили, что в Grafana некоторые информационные панели, связанные с сводкой, не могут правильно отображать изображения.
Затем возникает проблема с позиционированием.Прямой просмотр тела ответа на запрос Grafana к Prometheus обнаруживает, что ответ возвращается нормально, но все являются NaN, что означает, что проблема не вызвана неправильным выражением PromQL.
Итак, поскольку это не проблема с выражениями PromQL, возможно ли, что причина исключения связана сPromQL 的处理机制
и тутNaN 响应
Связанный?
На мой взгляд, вPrometheus/client_golang
Только индикатор Сводка будет иметь доходность, связанную с NaN, поэтому мы переходим кprometheus/client_golangНайдите связанные ключевые слова в списке проблем, и вы найдете две связанные проблемы.#860а также#8860, нашел, прочитав Выпускрешение.
Решение здесь на самом деле довольно простое, взяв в качестве примера приведенный выше фрагмент кода, нам нужно толькоA
добавить один после вектора>0
Достаточно отфильтровать образцы со значением NaN, но поскольку я обычно пишу PromQL с мыслью о написании вызовов функций, я не осознавал, чтоA
является вектором, а не обычной переменной, поэтому я написал неправильное выражение, чтобы вызвать эту проблему.
Генерация данных NaN
Производство NaN обычно происходит вprometheus/client_golangпо краткому выполнению.
В конфигурации Summary естьMaxAge
Элемент конфигурации используется для указания того, как долго только что полученный образец будет храниться в наборе образцов Сводки.По истечении времени MaxAge (без добавления новых данных)prometheus/client_golang
преобразует каждый из сводныхquantile(分位数)
Значение установлено в NaN, как показано ниже.
// before
a_metrics{quantile="0.2"} 181
a_metrics{quantile="0.5"} 181
a_metrics{quantile="0.9"} 181
a_metrics{quantile="0.99"} 181
a_metrics_sum 3.246959e+06
a_metrics_count 17939
// after (after maxAge)
a_metrics{quantile="0.2"} NaN
a_metrics{quantile="0.5"} NaN
a_metrics{quantile="0.9"} NaN
a_metrics{quantile="0.99"} NaN
a_metrics_sum 3.246959e+06
a_metrics_count 17939
Затем эти данные собираются Prometheus и сохраняются в Prometheus.
Почему бы не установить его в 0 , а установить в NaN
Тогда это приводит к другому вопросу, почему значение устаревших данных должно быть установлено в NaN вместо 0. Если оно установлено в 0, это не повлияет на работу PromQL.
По этому поводу, собственно, и обсуждалось в Выпуске аналогичное,#85 (2015) ,
-
beorn7
: Спустя долгое время мы надеемся, что Сводка будет соблюдать衰减在图像上
вместо冻结最后一个的样本的值在图像上
, если замораживание изображения, отображаемого в сводке, неточно, мы даже не можем судить о том, является ли служба нормальной, основываясь на результате замораживания.Следующие два метода могут получить затухание изображения, но я (beorn7) Я не уверен, какой из них будет лучше -
Сводка устанавливает все квантили в 0 и возвращает значение, но это ошибка! Поскольку в настоящее время сводка не имеет никакого значения, а на изображении вы выглядите как 0, это может привести к некоторым неверным суждениям.
-
NaN будет более точно отражать текущее состояние сводки, но это может сбивать с толку.
-
juliusv
: значение 0 - это неправильный показатель, он вообще не отражает реальность, он просто говорит вам "о, это хорошо",冻结最后一个值在图像上
Я думаю, что решение может быть хорошим, но NaN кажется более правильным решением, и я рекомендую его больше -
Продолжение по этому вопросу
哪些值需要被设置成 NaN
Если вы заинтересованы, вы можете перейти непосредственно к проблеме, чтобы узнать об обсуждении. Здесь она не будет чрезмерно расширена.
Почему данные NaN мешают запросам PromQL
Свойства NaN
согласно сIEEE 754
стандарт, Golang должен использовать значение NaN для идентификацииnot-a-number(不是一个数值)
, например в следующем коде, получит возвращаемое значение, которое на самом деле равно NaN.
q := math.Sqrt(-1) // 对 -1 开平方 (只能对正数开平方)
fmt.Print(q) // NaN
согласно сIEEE 754
, правильноNaN
Выполнение арифметической операции по существу оперирует с непредставимым значением (это может быть даже не значение, а диапазон), поэтому результатом будетNaN
, как в примере ниже
NaN := math.NaN()
fmt.Println(NaN + 2) // NaN
fmt.Println(NaN - 2) // NaN
fmt.Println(NaN * 2) // NaN
fmt.Println(NaN / 2) // NaN
fmt.Println(math.Pow(NaN, 10)) // NaN
Логика обработки функций Prometheus
Теперь, когда я понимаюNaN
характеристики, тогда давайте посмотрим наsum
функция иavg
Как исходный код функции обрабатывает запуск и обработку данных, читатели, которые хотят перейти непосредственно к местоположению файла, могут щелкнуть эти две ссылки, чтобы перейти непосредственно к соответствующему файлу:sum / avg
// sum
func funcSumOverTime(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector {
return aggrOverTime(vals, enh, func(values []Point) float64 {
var sum float64
for _, v := range values {
sum += v.V // 这里可以看到, 直接累加全部的收集到的 Metrics 的值,
}
return sum
})
}
// avg
func funcAvgOverTime(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector {
return aggrOverTime(vals, enh, func(values []Point) float64 {
var mean, count float64
for _, v := range values {
count++
mean += (v.V - mean) / count // 这里也是类似, 把和现在差值直接加上去
}
return mean
})
}
Из приведенного выше кода мы можем знать, что если значение Metrics смешано со значением NaN, это напрямую загрязнит весь результат, что приведет к выходному результату, как указано выше, все из которых являются NaN, Скорость такая же, как и функция stddev, здесь не представлена одна за другой.
Но снова возникает вопрос, почему не затрагиваются функции Max и Min?
Причины, по которым не затрагиваются функции max и min
Точно так же мы по-прежнему идем прямо к исходному коду, вы также можете щелкнуть эти две ссылки, чтобы перейти непосредственно к местоположению файла:max / min
// Max
func funcMaxOverTime(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector {
return aggrOverTime(vals, enh, func(values []Point) float64 {
max := values[0].V
for _, v := range values {
if v.V > max || math.IsNaN(max) { // 过滤 NaN
max = v.V
}
}
return max
})
}
// Min
func funcMinOverTime(vals []parser.Value, args parser.Expressions, enh *EvalNodeHelper) Vector {
return aggrOverTime(vals, enh, func(values []Point) float64 {
min := values[0].V
for _, v := range values {
if v.V < min || math.IsNaN(min) { // 过滤 NaN
min = v.V
}
}
return min
})
}
Здесь мы можем увидеть код реализации Max и Min, используяIsNaN 函数
По значению было принято решение, и вирусная инфекция NaN была отфильтрована, поэтому функции Max и Min все еще могут работать нормально.В то же время также была обнаружена проблема.#4385, пользователь, отправивший вопросjacksontj
Указывает, что NaN мешает результату его операции PromQL, и предлагаетPRчтобы исправить эту ошибку.
Суммировать
До сих пор проблема产生原因
а также解决方案
На этом введение закончилось.На самом деле автор мучился с этой проблемой около двух недель.Сначала автор думал,что неправильно используется функция или данные прерываются и мешают лишние метки,а потом я все пытался различные функции PromQL, такие какlabel_replace
.
В итоге проблема так и не была решена в итоге, а гугл долгое время безуспешно (ведь если причина проблемы не установлена, гугл ничего сделать не может) Пока однажды, когда я не нажал на исходный ответ на запрос Prometheus, я обнаружил, что было возвращено много NaN, и я начал сомневаться, была ли это проблема NaN, я начал поиск связанных ключевых слов в Google, чтобы найти решение.
Здесь я возвращаюсь к тому, что много раз говорил Вождь:Способ локализации проблемы имеет решающее значение, В этот раз проблема может так долго тянуться из-за того, что проблема вообще не локализована, а потом пробовать наобум...
В конце концов, эта проблема была наконец решена, что очень приятно, поэтому она записана здесь для справки.
Ref
- Prometheus Docs https://prometheus.io/docs/prometheus/latest/querying/functions/
- Prometheus Issue https://github.com/prometheus/prometheus/issues/860
- Prometheus Issue https://github.com/grafana/grafana/issues/8860
- Prometheus Issue https://github.com/prometheus/prometheus/issues/4385
- Prometheus Issue https://github.com/prometheus/prometheus/pull/4386
- wikipedia https://zh.wikipedia.org/wiki/NaN