From ce1bf8b15aebea9f2f7b2381a5c63f9583e89202 Mon Sep 17 00:00:00 2001 From: "Jonathan K. Stevens" <6549363+jrkt@users.noreply.github.com> Date: Thu, 14 Jul 2022 15:45:32 -0600 Subject: [PATCH] template: adding formatTime function to TemplateExpander (#10993) Signed-off-by: Jonathan Stevens Signed-off-by: Jonathan Stevens Co-authored-by: Jonathan Stevens --- docs/configuration/template_reference.md | 15 +++--- template/template.go | 35 ++++++++++--- template/template_test.go | 63 ++++++++++++++++++++++++ 3 files changed, 100 insertions(+), 13 deletions(-) diff --git a/docs/configuration/template_reference.md b/docs/configuration/template_reference.md index 5c96ee597..12effae32 100644 --- a/docs/configuration/template_reference.md +++ b/docs/configuration/template_reference.md @@ -51,13 +51,14 @@ 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. | +| 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 diff --git a/template/template.go b/template/template.go index 0b1659ce0..d5f80006f 100644 --- a/template/template.go +++ b/template/template.go @@ -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 +} diff --git a/template/template_test.go b/template/template_test.go index 45d1a66e7..4d7524d6f 100644 --- a/template/template_test.go +++ b/template/template_test.go @@ -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) + } + }) + } +}