PromQL обрабатывает аномальный результат вывода данных NaN

Kubernetes
PromQL обрабатывает аномальный результат вывода данных NaN

Первоначально опубликовано в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