mirror of
https://github.com/prometheus/prometheus.git
synced 2024-12-24 21:24:05 -08:00
Enable parsing strings in humanize functions (#8682)
* Enable parsing strings in humanize functions This is useful to humanize count_values or buckets labels. Signed-off-by: Julien Pivotto <roidelapluie@inuits.eu>
This commit is contained in:
parent
62afcabd01
commit
ea6f6bba74
|
@ -52,12 +52,12 @@ If functions are used in a pipeline, the pipeline value is passed as the last ar
|
||||||
### Numbers
|
### Numbers
|
||||||
|
|
||||||
| Name | Arguments | Returns | Notes |
|
| Name | Arguments | Returns | Notes |
|
||||||
| ------------- | --------------| --------| --------- |
|
| ------------------ | -----------------| --------| --------- |
|
||||||
| humanize | number | string | Converts a number to a more readable format, using [metric prefixes](https://en.wikipedia.org/wiki/Metric_prefix).
|
| humanize | number or string | string | Converts a number to a more readable format, using [metric prefixes](https://en.wikipedia.org/wiki/Metric_prefix).
|
||||||
| humanize1024 | number | string | Like `humanize`, but uses 1024 as the base rather than 1000. |
|
| humanize1024 | number or string | string | Like `humanize`, but uses 1024 as the base rather than 1000. |
|
||||||
| humanizeDuration | number | string | Converts a duration in seconds to a more readable format. |
|
| humanizeDuration | number or string | string | Converts a duration in seconds to a more readable format. |
|
||||||
| humanizePercentage | number | string | Converts a ratio value to a fraction of 100. |
|
| humanizePercentage | number or string | string | Converts a ratio value to a fraction of 100. |
|
||||||
| humanizeTimestamp | number | string | Converts a Unix timestamp in seconds to a more readable format. |
|
| humanizeTimestamp | number or string | string | Converts a Unix timestamp in seconds to a more readable format. |
|
||||||
|
|
||||||
Humanizing functions are intended to produce reasonable output for consumption
|
Humanizing functions are intended to produce reasonable output for consumption
|
||||||
by humans, and are not guaranteed to return the same results between Prometheus
|
by humans, and are not guaranteed to return the same results between Prometheus
|
||||||
|
|
|
@ -22,6 +22,7 @@ import (
|
||||||
"net/url"
|
"net/url"
|
||||||
"regexp"
|
"regexp"
|
||||||
"sort"
|
"sort"
|
||||||
|
"strconv"
|
||||||
"strings"
|
"strings"
|
||||||
text_template "text/template"
|
text_template "text/template"
|
||||||
"time"
|
"time"
|
||||||
|
@ -97,6 +98,17 @@ func query(ctx context.Context, q string, ts time.Time, queryFn QueryFunc) (quer
|
||||||
return result, nil
|
return result, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func convertToFloat(i interface{}) (float64, error) {
|
||||||
|
switch v := i.(type) {
|
||||||
|
case float64:
|
||||||
|
return v, nil
|
||||||
|
case string:
|
||||||
|
return strconv.ParseFloat(v, 64)
|
||||||
|
default:
|
||||||
|
return 0, fmt.Errorf("can't convert %T to float", v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Expander executes templates in text or HTML mode with a common set of Prometheus template functions.
|
// Expander executes templates in text or HTML mode with a common set of Prometheus template functions.
|
||||||
type Expander struct {
|
type Expander struct {
|
||||||
text string
|
text string
|
||||||
|
@ -163,9 +175,13 @@ func NewTemplateExpander(
|
||||||
sort.Stable(sorter)
|
sort.Stable(sorter)
|
||||||
return v
|
return v
|
||||||
},
|
},
|
||||||
"humanize": func(v float64) string {
|
"humanize": func(i interface{}) (string, error) {
|
||||||
|
v, err := convertToFloat(i)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
if v == 0 || math.IsNaN(v) || math.IsInf(v, 0) {
|
if v == 0 || math.IsNaN(v) || math.IsInf(v, 0) {
|
||||||
return fmt.Sprintf("%.4g", v)
|
return fmt.Sprintf("%.4g", v), nil
|
||||||
}
|
}
|
||||||
if math.Abs(v) >= 1 {
|
if math.Abs(v) >= 1 {
|
||||||
prefix := ""
|
prefix := ""
|
||||||
|
@ -176,7 +192,7 @@ func NewTemplateExpander(
|
||||||
prefix = p
|
prefix = p
|
||||||
v /= 1000
|
v /= 1000
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("%.4g%s", v, prefix)
|
return fmt.Sprintf("%.4g%s", v, prefix), nil
|
||||||
}
|
}
|
||||||
prefix := ""
|
prefix := ""
|
||||||
for _, p := range []string{"m", "u", "n", "p", "f", "a", "z", "y"} {
|
for _, p := range []string{"m", "u", "n", "p", "f", "a", "z", "y"} {
|
||||||
|
@ -186,11 +202,15 @@ func NewTemplateExpander(
|
||||||
prefix = p
|
prefix = p
|
||||||
v *= 1000
|
v *= 1000
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("%.4g%s", v, prefix)
|
return fmt.Sprintf("%.4g%s", v, prefix), nil
|
||||||
},
|
},
|
||||||
"humanize1024": func(v float64) string {
|
"humanize1024": func(i interface{}) (string, error) {
|
||||||
|
v, err := convertToFloat(i)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
if math.Abs(v) <= 1 || math.IsNaN(v) || math.IsInf(v, 0) {
|
if math.Abs(v) <= 1 || math.IsNaN(v) || math.IsInf(v, 0) {
|
||||||
return fmt.Sprintf("%.4g", v)
|
return fmt.Sprintf("%.4g", v), nil
|
||||||
}
|
}
|
||||||
prefix := ""
|
prefix := ""
|
||||||
for _, p := range []string{"ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi", "Yi"} {
|
for _, p := range []string{"ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi", "Yi"} {
|
||||||
|
@ -200,14 +220,18 @@ func NewTemplateExpander(
|
||||||
prefix = p
|
prefix = p
|
||||||
v /= 1024
|
v /= 1024
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("%.4g%s", v, prefix)
|
return fmt.Sprintf("%.4g%s", v, prefix), nil
|
||||||
},
|
},
|
||||||
"humanizeDuration": func(v float64) string {
|
"humanizeDuration": func(i interface{}) (string, error) {
|
||||||
|
v, err := convertToFloat(i)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
if math.IsNaN(v) || math.IsInf(v, 0) {
|
if math.IsNaN(v) || math.IsInf(v, 0) {
|
||||||
return fmt.Sprintf("%.4g", v)
|
return fmt.Sprintf("%.4g", v), nil
|
||||||
}
|
}
|
||||||
if v == 0 {
|
if v == 0 {
|
||||||
return fmt.Sprintf("%.4gs", v)
|
return fmt.Sprintf("%.4gs", v), nil
|
||||||
}
|
}
|
||||||
if math.Abs(v) >= 1 {
|
if math.Abs(v) >= 1 {
|
||||||
sign := ""
|
sign := ""
|
||||||
|
@ -221,16 +245,16 @@ func NewTemplateExpander(
|
||||||
days := int64(v) / 60 / 60 / 24
|
days := int64(v) / 60 / 60 / 24
|
||||||
// For days to minutes, we display seconds as an integer.
|
// For days to minutes, we display seconds as an integer.
|
||||||
if days != 0 {
|
if days != 0 {
|
||||||
return fmt.Sprintf("%s%dd %dh %dm %ds", sign, days, hours, minutes, seconds)
|
return fmt.Sprintf("%s%dd %dh %dm %ds", sign, days, hours, minutes, seconds), nil
|
||||||
}
|
}
|
||||||
if hours != 0 {
|
if hours != 0 {
|
||||||
return fmt.Sprintf("%s%dh %dm %ds", sign, hours, minutes, seconds)
|
return fmt.Sprintf("%s%dh %dm %ds", sign, hours, minutes, seconds), nil
|
||||||
}
|
}
|
||||||
if minutes != 0 {
|
if minutes != 0 {
|
||||||
return fmt.Sprintf("%s%dm %ds", sign, minutes, seconds)
|
return fmt.Sprintf("%s%dm %ds", sign, minutes, seconds), nil
|
||||||
}
|
}
|
||||||
// For seconds, we display 4 significant digits.
|
// For seconds, we display 4 significant digits.
|
||||||
return fmt.Sprintf("%s%.4gs", sign, v)
|
return fmt.Sprintf("%s%.4gs", sign, v), nil
|
||||||
}
|
}
|
||||||
prefix := ""
|
prefix := ""
|
||||||
for _, p := range []string{"m", "u", "n", "p", "f", "a", "z", "y"} {
|
for _, p := range []string{"m", "u", "n", "p", "f", "a", "z", "y"} {
|
||||||
|
@ -240,17 +264,25 @@ func NewTemplateExpander(
|
||||||
prefix = p
|
prefix = p
|
||||||
v *= 1000
|
v *= 1000
|
||||||
}
|
}
|
||||||
return fmt.Sprintf("%.4g%ss", v, prefix)
|
return fmt.Sprintf("%.4g%ss", v, prefix), nil
|
||||||
},
|
},
|
||||||
"humanizePercentage": func(v float64) string {
|
"humanizePercentage": func(i interface{}) (string, error) {
|
||||||
return fmt.Sprintf("%.4g%%", v*100)
|
v, err := convertToFloat(i)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
|
return fmt.Sprintf("%.4g%%", v*100), nil
|
||||||
},
|
},
|
||||||
"humanizeTimestamp": func(v float64) string {
|
"humanizeTimestamp": func(i interface{}) (string, error) {
|
||||||
|
v, err := convertToFloat(i)
|
||||||
|
if err != nil {
|
||||||
|
return "", err
|
||||||
|
}
|
||||||
if math.IsNaN(v) || math.IsInf(v, 0) {
|
if math.IsNaN(v) || math.IsInf(v, 0) {
|
||||||
return fmt.Sprintf("%.4g", v)
|
return fmt.Sprintf("%.4g", v), nil
|
||||||
}
|
}
|
||||||
t := model.TimeFromUnixNano(int64(v * 1e9)).Time().UTC()
|
t := model.TimeFromUnixNano(int64(v * 1e9)).Time().UTC()
|
||||||
return fmt.Sprint(t)
|
return fmt.Sprint(t), nil
|
||||||
},
|
},
|
||||||
"pathPrefix": func() string {
|
"pathPrefix": func() string {
|
||||||
return externalURL.Path
|
return externalURL.Path
|
||||||
|
|
|
@ -179,45 +179,109 @@ func TestTemplateExpansion(t *testing.T) {
|
||||||
output: "xa",
|
output: "xa",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Humanize.
|
// Humanize - float64.
|
||||||
text: "{{ range . }}{{ humanize . }}:{{ end }}",
|
text: "{{ range . }}{{ humanize . }}:{{ end }}",
|
||||||
input: []float64{0.0, 1.0, 1234567.0, .12},
|
input: []float64{0.0, 1.0, 1234567.0, .12},
|
||||||
output: "0:1:1.235M:120m:",
|
output: "0:1:1.235M:120m:",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Humanize1024.
|
// Humanize - string.
|
||||||
|
text: "{{ range . }}{{ humanize . }}:{{ end }}",
|
||||||
|
input: []string{"0.0", "1.0", "1234567.0", ".12"},
|
||||||
|
output: "0:1:1.235M:120m:",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Humanize - string with error.
|
||||||
|
text: `{{ humanize "one" }}`,
|
||||||
|
shouldFail: true,
|
||||||
|
errorMsg: `strconv.ParseFloat: parsing "one": invalid syntax`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Humanize1024 - float64.
|
||||||
text: "{{ range . }}{{ humanize1024 . }}:{{ end }}",
|
text: "{{ range . }}{{ humanize1024 . }}:{{ end }}",
|
||||||
input: []float64{0.0, 1.0, 1048576.0, .12},
|
input: []float64{0.0, 1.0, 1048576.0, .12},
|
||||||
output: "0:1:1Mi:0.12:",
|
output: "0:1:1Mi:0.12:",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// HumanizeDuration - seconds.
|
// Humanize1024 - string.
|
||||||
|
text: "{{ range . }}{{ humanize1024 . }}:{{ end }}",
|
||||||
|
input: []string{"0.0", "1.0", "1048576.0", ".12"},
|
||||||
|
output: "0:1:1Mi:0.12:",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Humanize1024 - string with error.
|
||||||
|
text: `{{ humanize1024 "one" }}`,
|
||||||
|
shouldFail: true,
|
||||||
|
errorMsg: `strconv.ParseFloat: parsing "one": invalid syntax`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// HumanizeDuration - seconds - float64.
|
||||||
text: "{{ range . }}{{ humanizeDuration . }}:{{ end }}",
|
text: "{{ range . }}{{ humanizeDuration . }}:{{ end }}",
|
||||||
input: []float64{0, 1, 60, 3600, 86400, 86400 + 3600, -(86400*2 + 3600*3 + 60*4 + 5), 899.99},
|
input: []float64{0, 1, 60, 3600, 86400, 86400 + 3600, -(86400*2 + 3600*3 + 60*4 + 5), 899.99},
|
||||||
output: "0s:1s:1m 0s:1h 0m 0s:1d 0h 0m 0s:1d 1h 0m 0s:-2d 3h 4m 5s:14m 59s:",
|
output: "0s:1s:1m 0s:1h 0m 0s:1d 0h 0m 0s:1d 1h 0m 0s:-2d 3h 4m 5s:14m 59s:",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// HumanizeDuration - subsecond and fractional seconds.
|
// HumanizeDuration - seconds - string.
|
||||||
|
text: "{{ range . }}{{ humanizeDuration . }}:{{ end }}",
|
||||||
|
input: []string{"0", "1", "60", "3600", "86400"},
|
||||||
|
output: "0s:1s:1m 0s:1h 0m 0s:1d 0h 0m 0s:",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// HumanizeDuration - subsecond and fractional seconds - float64.
|
||||||
text: "{{ range . }}{{ humanizeDuration . }}:{{ end }}",
|
text: "{{ range . }}{{ humanizeDuration . }}:{{ end }}",
|
||||||
input: []float64{.1, .0001, .12345, 60.1, 60.5, 1.2345, 12.345},
|
input: []float64{.1, .0001, .12345, 60.1, 60.5, 1.2345, 12.345},
|
||||||
output: "100ms:100us:123.5ms:1m 0s:1m 0s:1.234s:12.35s:",
|
output: "100ms:100us:123.5ms:1m 0s:1m 0s:1.234s:12.35s:",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// Humanize* Inf and NaN.
|
// HumanizeDuration - subsecond and fractional seconds - string.
|
||||||
|
text: "{{ range . }}{{ humanizeDuration . }}:{{ end }}",
|
||||||
|
input: []string{".1", ".0001", ".12345", "60.1", "60.5", "1.2345", "12.345"},
|
||||||
|
output: "100ms:100us:123.5ms:1m 0s:1m 0s:1.234s:12.35s:",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// HumanizeDuration - string with error.
|
||||||
|
text: `{{ humanizeDuration "one" }}`,
|
||||||
|
shouldFail: true,
|
||||||
|
errorMsg: `strconv.ParseFloat: parsing "one": invalid syntax`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// Humanize* Inf and NaN - float64.
|
||||||
text: "{{ range . }}{{ humanize . }}:{{ humanize1024 . }}:{{ humanizeDuration . }}:{{humanizeTimestamp .}}:{{ end }}",
|
text: "{{ range . }}{{ humanize . }}:{{ humanize1024 . }}:{{ humanizeDuration . }}:{{humanizeTimestamp .}}:{{ end }}",
|
||||||
input: []float64{math.Inf(1), math.Inf(-1), math.NaN()},
|
input: []float64{math.Inf(1), math.Inf(-1), math.NaN()},
|
||||||
output: "+Inf:+Inf:+Inf:+Inf:-Inf:-Inf:-Inf:-Inf:NaN:NaN:NaN:NaN:",
|
output: "+Inf:+Inf:+Inf:+Inf:-Inf:-Inf:-Inf:-Inf:NaN:NaN:NaN:NaN:",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// HumanizePercentage - model.SampleValue input.
|
// Humanize* Inf and NaN - string.
|
||||||
|
text: "{{ range . }}{{ humanize . }}:{{ humanize1024 . }}:{{ humanizeDuration . }}:{{humanizeTimestamp .}}:{{ end }}",
|
||||||
|
input: []string{"+Inf", "-Inf", "NaN"},
|
||||||
|
output: "+Inf:+Inf:+Inf:+Inf:-Inf:-Inf:-Inf:-Inf:NaN:NaN:NaN:NaN:",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// HumanizePercentage - model.SampleValue input - float64.
|
||||||
text: "{{ -0.22222 | humanizePercentage }}:{{ 0.0 | humanizePercentage }}:{{ 0.1234567 | humanizePercentage }}:{{ 1.23456 | humanizePercentage }}",
|
text: "{{ -0.22222 | humanizePercentage }}:{{ 0.0 | humanizePercentage }}:{{ 0.1234567 | humanizePercentage }}:{{ 1.23456 | humanizePercentage }}",
|
||||||
output: "-22.22%:0%:12.35%:123.5%",
|
output: "-22.22%:0%:12.35%:123.5%",
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
// HumanizeTimestamp - model.SampleValue input.
|
// HumanizePercentage - model.SampleValue input - string.
|
||||||
|
text: `{{ "-0.22222" | humanizePercentage }}:{{ "0.0" | humanizePercentage }}:{{ "0.1234567" | humanizePercentage }}:{{ "1.23456" | humanizePercentage }}`,
|
||||||
|
output: "-22.22%:0%:12.35%:123.5%",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// HumanizePercentage - model.SampleValue input - string with error.
|
||||||
|
text: `{{ "one" | humanizePercentage }}`,
|
||||||
|
shouldFail: true,
|
||||||
|
errorMsg: `strconv.ParseFloat: parsing "one": invalid syntax`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
// HumanizeTimestamp - model.SampleValue input - float64.
|
||||||
text: "{{ 1435065584.128 | humanizeTimestamp }}",
|
text: "{{ 1435065584.128 | humanizeTimestamp }}",
|
||||||
output: "2015-06-23 13:19:44.128 +0000 UTC",
|
output: "2015-06-23 13:19:44.128 +0000 UTC",
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
// HumanizeTimestamp - model.SampleValue input - string.
|
||||||
|
text: `{{ "1435065584.128" | humanizeTimestamp }}`,
|
||||||
|
output: "2015-06-23 13:19:44.128 +0000 UTC",
|
||||||
|
},
|
||||||
{
|
{
|
||||||
// Title.
|
// Title.
|
||||||
text: "{{ \"aa bb CC\" | title }}",
|
text: "{{ \"aa bb CC\" | title }}",
|
||||||
|
|
Loading…
Reference in a new issue