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

@ -52,12 +52,13 @@ If functions are used in a pipeline, the pipeline value is passed as the last ar
### Numbers
| 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. |
| 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
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",
Help: "The total number of template text expansions.",
})
errNaNOrInf = errors.New("value is NaN or Inf")
)
func init() {
@ -315,15 +317,24 @@ func NewTemplateExpander(
if err != nil {
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
case err != nil:
return "", err
}
timestamp := v * 1e9
if timestamp > math.MaxInt64 || timestamp < math.MinInt64 {
return "", fmt.Errorf("%v cannot be represented as a nanoseconds timestamp since it overflows int64", v)
return fmt.Sprint(tm), nil
},
"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 {
return externalURL.Path
@ -446,3 +457,15 @@ func (te Expander) ParseTest() error {
}
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"
"math"
"net/url"
"reflect"
"testing"
"time"
@ -429,6 +430,16 @@ func TestTemplateExpansion(t *testing.T) {
text: `{{ "1435065584.128" | humanizeTimestamp }}`,
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.
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)
}
})
}
}