Skip to content

Commit

Permalink
Merge pull request #627 from freak12techno/add-time-template-helpers
Browse files Browse the repository at this point in the history
feat: add time template helpers
  • Loading branch information
gotjosh committed Jun 3, 2024
2 parents 6846990 + b2fc541 commit 789222a
Show file tree
Hide file tree
Showing 4 changed files with 242 additions and 0 deletions.
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ require (
github.com/julienschmidt/httprouter v1.3.0
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f
github.com/prometheus/client_model v0.6.1
github.com/stretchr/testify v1.8.2
golang.org/x/net v0.24.0
golang.org/x/oauth2 v0.19.0
google.golang.org/protobuf v1.34.0
Expand All @@ -19,15 +20,18 @@ require (
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect
github.com/beorn7/perks v1.0.1 // indirect
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/go-logfmt/logfmt v0.5.1 // indirect
github.com/jpillora/backoff v1.0.0 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/prometheus/client_golang v1.19.0 // indirect
github.com/prometheus/procfs v0.12.0 // indirect
github.com/rogpeppe/go-internal v1.10.0 // indirect
github.com/xhit/go-str2duration/v2 v2.1.0 // indirect
golang.org/x/sys v0.19.0 // indirect
golang.org/x/text v0.14.0 // indirect
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)

retract v0.50.0 // Critical bug in counter suffixes, please read issue https://github.com/prometheus/common/issues/605
8 changes: 8 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU=
github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA=
Expand Down Expand Up @@ -36,8 +37,13 @@ github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3c
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc=
github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
Expand All @@ -56,4 +62,6 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
89 changes: 89 additions & 0 deletions helpers/templates/time.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
// Copyright 2024 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package templates

import (
"fmt"
"math"
"strconv"
"time"
)

func convertToFloat(i interface{}) (float64, error) {
switch v := i.(type) {
case float64:
return v, nil
case string:
return strconv.ParseFloat(v, 64)
case int:
return float64(v), nil
case uint:
return float64(v), nil
case int64:
return float64(v), nil
case uint64:
return float64(v), nil
case time.Duration:
return v.Seconds(), nil
default:
return 0, fmt.Errorf("can't convert %T to float", v)
}
}

func HumanizeDuration(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), nil
}
if v == 0 {
return fmt.Sprintf("%.4gs", v), nil
}
if math.Abs(v) >= 1 {
sign := ""
if v < 0 {
sign = "-"
v = -v
}
duration := int64(v)
seconds := duration % 60
minutes := (duration / 60) % 60
hours := (duration / 60 / 60) % 24
days := duration / 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), nil
}
if hours != 0 {
return fmt.Sprintf("%s%dh %dm %ds", sign, hours, minutes, seconds), nil
}
if minutes != 0 {
return fmt.Sprintf("%s%dm %ds", sign, minutes, seconds), nil
}
// For seconds, we display 4 significant digits.
return fmt.Sprintf("%s%.4gs", sign, v), nil
}
prefix := ""
for _, p := range []string{"m", "u", "n", "p", "f", "a", "z", "y"} {
if math.Abs(v) >= 1 {
break
}
prefix = p
v *= 1000
}
return fmt.Sprintf("%.4g%ss", v, prefix), nil
}
141 changes: 141 additions & 0 deletions helpers/templates/time_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
// Copyright 2024 The Prometheus Authors
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.

package templates

import (
"testing"

"github.com/stretchr/testify/require"
)

func TestHumanizeDurationSecondsFloat64(t *testing.T) {
tc := []struct {
name string
input float64
expected string
}{
{name: "zero", input: 0, expected: "0s"},
{name: "one second", input: 1, expected: "1s"},
{name: "one minute", input: 60, expected: "1m 0s"},
{name: "one hour", input: 3600, expected: "1h 0m 0s"},
{name: "one day", input: 86400, expected: "1d 0h 0m 0s"},
{name: "one day and one hour", input: 86400 + 3600, expected: "1d 1h 0m 0s"},
{name: "negative duration", input: -(86400*2 + 3600*3 + 60*4 + 5), expected: "-2d 3h 4m 5s"},
{name: "using a float", input: 899.99, expected: "14m 59s"},
}

for _, tt := range tc {
t.Run(tt.name, func(t *testing.T) {
result, err := HumanizeDuration(tt.input)
require.NoError(t, err)
require.Equal(t, tt.expected, result)
})
}
}

func TestHumanizeDurationSubsecondAndFractionalSecondsFloat64(t *testing.T) {
tc := []struct {
name string
input float64
expected string
}{
{name: "millseconds", input: .1, expected: "100ms"},
{name: "nanoseconds", input: .0001, expected: "100us"},
{name: "milliseconds + nanoseconds", input: .12345, expected: "123.5ms"},
{name: "minute + millisecond", input: 60.1, expected: "1m 0s"},
{name: "minute + milliseconds", input: 60.5, expected: "1m 0s"},
{name: "second + milliseconds", input: 1.2345, expected: "1.234s"},
{name: "second + milliseconds rounded", input: 12.345, expected: "12.35s"},
}

for _, tt := range tc {
t.Run(tt.name, func(t *testing.T) {
result, err := HumanizeDuration(tt.input)
require.NoError(t, err)
require.Equal(t, tt.expected, result)
})
}
}

func TestHumanizeDurationErrorString(t *testing.T) {
_, err := HumanizeDuration("one")
require.Error(t, err)
}

func TestHumanizeDurationSecondsString(t *testing.T) {
tc := []struct {
name string
input string
expected string
}{
{name: "zero", input: "0", expected: "0s"},
{name: "second", input: "1", expected: "1s"},
{name: "minute", input: "60", expected: "1m 0s"},
{name: "hour", input: "3600", expected: "1h 0m 0s"},
{name: "day", input: "86400", expected: "1d 0h 0m 0s"},
}

for _, tt := range tc {
t.Run(tt.name, func(t *testing.T) {
result, err := HumanizeDuration(tt.input)
require.NoError(t, err)
require.Equal(t, tt.expected, result)
})
}
}

func TestHumanizeDurationSubsecondAndFractionalSecondsString(t *testing.T) {
tc := []struct {
name string
input string
expected string
}{
{name: "millseconds", input: ".1", expected: "100ms"},
{name: "nanoseconds", input: ".0001", expected: "100us"},
{name: "milliseconds + nanoseconds", input: ".12345", expected: "123.5ms"},
{name: "minute + millisecond", input: "60.1", expected: "1m 0s"},
{name: "minute + milliseconds", input: "60.5", expected: "1m 0s"},
{name: "second + milliseconds", input: "1.2345", expected: "1.234s"},
{name: "second + milliseconds rounded", input: "12.345", expected: "12.35s"},
}

for _, tt := range tc {
t.Run(tt.name, func(t *testing.T) {
result, err := HumanizeDuration(tt.input)
require.NoError(t, err)
require.Equal(t, tt.expected, result)
})
}
}

func TestHumanizeDurationSecondsInt(t *testing.T) {
tc := []struct {
name string
input int
expected string
}{
{name: "zero", input: 0, expected: "0s"},
{name: "negative", input: -1, expected: "-1s"},
{name: "second", input: 1, expected: "1s"},
{name: "days", input: 1234567, expected: "14d 6h 56m 7s"},
}

for _, tt := range tc {
t.Run(tt.name, func(t *testing.T) {
result, err := HumanizeDuration(tt.input)
require.NoError(t, err)
require.Equal(t, tt.expected, result)
})
}
}

0 comments on commit 789222a

Please sign in to comment.