mirror of
https://github.com/prometheus/prometheus
synced 2024-12-23 15:04:13 +00: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
@ -51,13 +51,13 @@ If functions are used in a pipeline, the pipeline value is passed as the last ar
|
||||
|
||||
### Numbers
|
||||
|
||||
| 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).
|
||||
| humanize1024 | number | 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. |
|
||||
| humanizePercentage | number | string | Converts a ratio value to a fraction of 100. |
|
||||
| humanizeTimestamp | number | string | Converts a Unix timestamp in seconds to a more readable format. |
|
||||
| Name | Arguments | Returns | Notes |
|
||||
| ------------------ | -----------------| --------| --------- |
|
||||
| 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 or string | string | Like `humanize`, but uses 1024 as the base rather than 1000. |
|
||||
| humanizeDuration | number or string | string | Converts a duration in seconds to a more readable format. |
|
||||
| humanizePercentage | number or string | string | Converts a ratio value to a fraction of 100. |
|
||||
| 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
|
||||
by humans, and are not guaranteed to return the same results between Prometheus
|
||||
|
@ -22,6 +22,7 @@ import (
|
||||
"net/url"
|
||||
"regexp"
|
||||
"sort"
|
||||
"strconv"
|
||||
"strings"
|
||||
text_template "text/template"
|
||||
"time"
|
||||
@ -97,6 +98,17 @@ func query(ctx context.Context, q string, ts time.Time, queryFn QueryFunc) (quer
|
||||
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.
|
||||
type Expander struct {
|
||||
text string
|
||||
@ -163,9 +175,13 @@ func NewTemplateExpander(
|
||||
sort.Stable(sorter)
|
||||
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) {
|
||||
return fmt.Sprintf("%.4g", v)
|
||||
return fmt.Sprintf("%.4g", v), nil
|
||||
}
|
||||
if math.Abs(v) >= 1 {
|
||||
prefix := ""
|
||||
@ -176,7 +192,7 @@ func NewTemplateExpander(
|
||||
prefix = p
|
||||
v /= 1000
|
||||
}
|
||||
return fmt.Sprintf("%.4g%s", v, prefix)
|
||||
return fmt.Sprintf("%.4g%s", v, prefix), nil
|
||||
}
|
||||
prefix := ""
|
||||
for _, p := range []string{"m", "u", "n", "p", "f", "a", "z", "y"} {
|
||||
@ -186,11 +202,15 @@ func NewTemplateExpander(
|
||||
prefix = p
|
||||
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) {
|
||||
return fmt.Sprintf("%.4g", v)
|
||||
return fmt.Sprintf("%.4g", v), nil
|
||||
}
|
||||
prefix := ""
|
||||
for _, p := range []string{"ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi", "Yi"} {
|
||||
@ -200,14 +220,18 @@ func NewTemplateExpander(
|
||||
prefix = p
|
||||
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) {
|
||||
return fmt.Sprintf("%.4g", v)
|
||||
return fmt.Sprintf("%.4g", v), nil
|
||||
}
|
||||
if v == 0 {
|
||||
return fmt.Sprintf("%.4gs", v)
|
||||
return fmt.Sprintf("%.4gs", v), nil
|
||||
}
|
||||
if math.Abs(v) >= 1 {
|
||||
sign := ""
|
||||
@ -221,16 +245,16 @@ func NewTemplateExpander(
|
||||
days := int64(v) / 60 / 60 / 24
|
||||
// For days to minutes, we display seconds as an integer.
|
||||
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 {
|
||||
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 {
|
||||
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.
|
||||
return fmt.Sprintf("%s%.4gs", sign, v)
|
||||
return fmt.Sprintf("%s%.4gs", sign, v), nil
|
||||
}
|
||||
prefix := ""
|
||||
for _, p := range []string{"m", "u", "n", "p", "f", "a", "z", "y"} {
|
||||
@ -240,17 +264,25 @@ func NewTemplateExpander(
|
||||
prefix = p
|
||||
v *= 1000
|
||||
}
|
||||
return fmt.Sprintf("%.4g%ss", v, prefix)
|
||||
return fmt.Sprintf("%.4g%ss", v, prefix), nil
|
||||
},
|
||||
"humanizePercentage": func(v float64) string {
|
||||
return fmt.Sprintf("%.4g%%", v*100)
|
||||
"humanizePercentage": func(i interface{}) (string, error) {
|
||||
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) {
|
||||
return fmt.Sprintf("%.4g", v)
|
||||
return fmt.Sprintf("%.4g", v), nil
|
||||
}
|
||||
t := model.TimeFromUnixNano(int64(v * 1e9)).Time().UTC()
|
||||
return fmt.Sprint(t)
|
||||
return fmt.Sprint(t), nil
|
||||
},
|
||||
"pathPrefix": func() string {
|
||||
return externalURL.Path
|
||||
|
@ -179,45 +179,109 @@ func TestTemplateExpansion(t *testing.T) {
|
||||
output: "xa",
|
||||
},
|
||||
{
|
||||
// Humanize.
|
||||
// Humanize - float64.
|
||||
text: "{{ range . }}{{ humanize . }}:{{ end }}",
|
||||
input: []float64{0.0, 1.0, 1234567.0, .12},
|
||||
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 }}",
|
||||
input: []float64{0.0, 1.0, 1048576.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 }}",
|
||||
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:",
|
||||
},
|
||||
{
|
||||
// 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 }}",
|
||||
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:",
|
||||
},
|
||||
{
|
||||
// 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 }}",
|
||||
input: []float64{math.Inf(1), math.Inf(-1), math.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 }}",
|
||||
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 }}",
|
||||
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.
|
||||
text: "{{ \"aa bb CC\" | title }}",
|
||||
|
Loading…
Reference in New Issue
Block a user