template: adding formatTime function to TemplateExpander (#10993)

Signed-off-by: Jonathan Stevens <jonathanstevens89@gmail.com>
Signed-off-by: Jonathan Stevens <jon.stevens@getweave.com>

Co-authored-by: Jonathan Stevens <jon.stevens@getweave.com>
This commit is contained in:
Jonathan K. Stevens 2022-07-14 15:45:32 -06:00 committed by GitHub
parent 97d7e09e0b
commit ce1bf8b15a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
3 changed files with 100 additions and 13 deletions

View file

@ -51,13 +51,14 @@ 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 or string | 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 or string | 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 or string | 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 or string | 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 or string | 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. |
| toTime | number or string | *time.Time | Converts a Unix timestamp in seconds to a time.Time. |
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

View file

@ -45,6 +45,8 @@ var (
Name: "prometheus_template_text_expansions_total", Name: "prometheus_template_text_expansions_total",
Help: "The total number of template text expansions.", Help: "The total number of template text expansions.",
}) })
errNaNOrInf = errors.New("value is NaN or Inf")
) )
func init() { func init() {
@ -315,15 +317,24 @@ func NewTemplateExpander(
if err != nil { if err != nil {
return "", err return "", err
} }
if math.IsNaN(v) || math.IsInf(v, 0) {
tm, err := floatToTime(v)
switch {
case errors.Is(err, errNaNOrInf):
return fmt.Sprintf("%.4g", v), nil return fmt.Sprintf("%.4g", v), nil
case err != nil:
return "", err
} }
timestamp := v * 1e9
if timestamp > math.MaxInt64 || timestamp < math.MinInt64 { return fmt.Sprint(tm), nil
return "", fmt.Errorf("%v cannot be represented as a nanoseconds timestamp since it overflows int64", v) },
"toTime": func(i interface{}) (*time.Time, error) {
v, err := convertToFloat(i)
if err != nil {
return nil, err
} }
t := model.TimeFromUnixNano(int64(timestamp)).Time().UTC()
return fmt.Sprint(t), nil return floatToTime(v)
}, },
"pathPrefix": func() string { "pathPrefix": func() string {
return externalURL.Path return externalURL.Path
@ -446,3 +457,15 @@ func (te Expander) ParseTest() error {
} }
return nil return nil
} }
func floatToTime(v float64) (*time.Time, error) {
if math.IsNaN(v) || math.IsInf(v, 0) {
return nil, errNaNOrInf
}
timestamp := v * 1e9
if timestamp > math.MaxInt64 || timestamp < math.MinInt64 {
return nil, fmt.Errorf("%v cannot be represented as a nanoseconds timestamp since it overflows int64", v)
}
t := model.TimeFromUnixNano(int64(timestamp)).Time().UTC()
return &t, nil
}

View file

@ -17,6 +17,7 @@ import (
"context" "context"
"math" "math"
"net/url" "net/url"
"reflect"
"testing" "testing"
"time" "time"
@ -429,6 +430,16 @@ func TestTemplateExpansion(t *testing.T) {
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",
}, },
{
// ToTime - model.SampleValue input - float64.
text: `{{ (1435065584.128 | toTime).Format "2006" }}`,
output: "2015",
},
{
// ToTime - model.SampleValue input - string.
text: `{{ ("1435065584.128" | toTime).Format "2006" }}`,
output: "2015",
},
{ {
// Title. // Title.
text: "{{ \"aa bb CC\" | title }}", text: "{{ \"aa bb CC\" | title }}",
@ -560,3 +571,55 @@ func testTemplateExpansion(t *testing.T, scenarios []scenario) {
} }
} }
} }
func Test_floatToTime(t *testing.T) {
type args struct {
v float64
}
tests := []struct {
name string
args args
want *time.Time
wantErr bool
}{
{
"happy path",
args{
v: 1657155181,
},
func() *time.Time {
tm := time.Date(2022, 7, 7, 0, 53, 1, 0, time.UTC)
return &tm
}(),
false,
},
{
"more than math.MaxInt64",
args{
v: 1.79769313486231570814527423731704356798070e+300,
},
nil,
true,
},
{
"less than math.MinInt64",
args{
v: -1.79769313486231570814527423731704356798070e+300,
},
nil,
true,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got, err := floatToTime(tt.args.v)
if (err != nil) != tt.wantErr {
t.Errorf("floatToTime() error = %v, wantErr %v", err, tt.wantErr)
return
}
if !reflect.DeepEqual(got, tt.want) {
t.Errorf("floatToTime() got = %v, want %v", got, tt.want)
}
})
}
}