Skip to content

Commit 789222a

Browse files
authored
Merge pull request #627 from freak12techno/add-time-template-helpers
feat: add time template helpers
2 parents 6846990 + b2fc541 commit 789222a

File tree

4 files changed

+242
-0
lines changed

4 files changed

+242
-0
lines changed

go.mod

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ require (
99
github.com/julienschmidt/httprouter v1.3.0
1010
github.com/mwitkow/go-conntrack v0.0.0-20190716064945-2f068394615f
1111
github.com/prometheus/client_model v0.6.1
12+
github.com/stretchr/testify v1.8.2
1213
golang.org/x/net v0.24.0
1314
golang.org/x/oauth2 v0.19.0
1415
google.golang.org/protobuf v1.34.0
@@ -19,15 +20,18 @@ require (
1920
github.com/alecthomas/units v0.0.0-20211218093645-b94a6e3cc137 // indirect
2021
github.com/beorn7/perks v1.0.1 // indirect
2122
github.com/cespare/xxhash/v2 v2.2.0 // indirect
23+
github.com/davecgh/go-spew v1.1.1 // indirect
2224
github.com/go-logfmt/logfmt v0.5.1 // indirect
2325
github.com/jpillora/backoff v1.0.0 // indirect
26+
github.com/pmezard/go-difflib v1.0.0 // indirect
2427
github.com/prometheus/client_golang v1.19.0 // indirect
2528
github.com/prometheus/procfs v0.12.0 // indirect
2629
github.com/rogpeppe/go-internal v1.10.0 // indirect
2730
github.com/xhit/go-str2duration/v2 v2.1.0 // indirect
2831
golang.org/x/sys v0.19.0 // indirect
2932
golang.org/x/text v0.14.0 // indirect
3033
gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c // indirect
34+
gopkg.in/yaml.v3 v3.0.1 // indirect
3135
)
3236

3337
retract v0.50.0 // Critical bug in counter suffixes, please read issue https://github.com/prometheus/common/issues/605

go.sum

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj
88
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
99
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
1010
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
11+
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
1112
github.com/go-kit/log v0.2.1 h1:MRVx0/zhvdseW+Gza6N9rVzU/IVzaeE1SFI4raAhmBU=
1213
github.com/go-kit/log v0.2.1/go.mod h1:NwTd00d/i8cPZ3xOwwiv2PO5MOcx78fFErGNcVmBjv0=
1314
github.com/go-logfmt/logfmt v0.5.1 h1:otpy5pqBCBZ1ng9RQ0dPu4PN7ba75Y/aA+UpowDyNVA=
@@ -36,8 +37,13 @@ github.com/prometheus/procfs v0.12.0/go.mod h1:pcuDEFsWDnvcgNzo4EEweacyhjeA9Zk3c
3637
github.com/rogpeppe/go-internal v1.10.0 h1:TMyTOH3F/DB16zRVcYyreMH6GnZZrwQVAoYjRBZyWFQ=
3738
github.com/rogpeppe/go-internal v1.10.0/go.mod h1:UQnix2H7Ngw/k4C5ijL5+65zddjncjaFoBhdsK/akog=
3839
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
40+
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
41+
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
3942
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
43+
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
44+
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
4045
github.com/stretchr/testify v1.8.2 h1:+h33VjcLVPDHtOdpUCuF+7gSuG3yGIftsP1YvFihtJ8=
46+
github.com/stretchr/testify v1.8.2/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
4147
github.com/xhit/go-str2duration/v2 v2.1.0 h1:lxklc02Drh6ynqX+DdPyp5pCKLUQpRT8bp8Ydu2Bstc=
4248
github.com/xhit/go-str2duration/v2 v2.1.0/go.mod h1:ohY8p+0f07DiV6Em5LKB0s2YpLtXVyJfNt1+BlmyAsU=
4349
golang.org/x/net v0.24.0 h1:1PcaxkF854Fu3+lvBIx5SYn9wRlBzzcnHZSiaFFAb0w=
@@ -56,4 +62,6 @@ gopkg.in/check.v1 v1.0.0-20201130134442-10cb98267c6c/go.mod h1:JHkPIbrfpd72SG/EV
5662
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
5763
gopkg.in/yaml.v2 v2.4.0 h1:D8xgwECY7CYvx+Y2n4sBz93Jn9JRvxdiyyo8CTfuKaY=
5864
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
65+
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
5966
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
67+
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=

helpers/templates/time.go

Lines changed: 89 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,89 @@
1+
// Copyright 2024 The Prometheus Authors
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
package templates
15+
16+
import (
17+
"fmt"
18+
"math"
19+
"strconv"
20+
"time"
21+
)
22+
23+
func convertToFloat(i interface{}) (float64, error) {
24+
switch v := i.(type) {
25+
case float64:
26+
return v, nil
27+
case string:
28+
return strconv.ParseFloat(v, 64)
29+
case int:
30+
return float64(v), nil
31+
case uint:
32+
return float64(v), nil
33+
case int64:
34+
return float64(v), nil
35+
case uint64:
36+
return float64(v), nil
37+
case time.Duration:
38+
return v.Seconds(), nil
39+
default:
40+
return 0, fmt.Errorf("can't convert %T to float", v)
41+
}
42+
}
43+
44+
func HumanizeDuration(i interface{}) (string, error) {
45+
v, err := convertToFloat(i)
46+
if err != nil {
47+
return "", err
48+
}
49+
50+
if math.IsNaN(v) || math.IsInf(v, 0) {
51+
return fmt.Sprintf("%.4g", v), nil
52+
}
53+
if v == 0 {
54+
return fmt.Sprintf("%.4gs", v), nil
55+
}
56+
if math.Abs(v) >= 1 {
57+
sign := ""
58+
if v < 0 {
59+
sign = "-"
60+
v = -v
61+
}
62+
duration := int64(v)
63+
seconds := duration % 60
64+
minutes := (duration / 60) % 60
65+
hours := (duration / 60 / 60) % 24
66+
days := duration / 60 / 60 / 24
67+
// For days to minutes, we display seconds as an integer.
68+
if days != 0 {
69+
return fmt.Sprintf("%s%dd %dh %dm %ds", sign, days, hours, minutes, seconds), nil
70+
}
71+
if hours != 0 {
72+
return fmt.Sprintf("%s%dh %dm %ds", sign, hours, minutes, seconds), nil
73+
}
74+
if minutes != 0 {
75+
return fmt.Sprintf("%s%dm %ds", sign, minutes, seconds), nil
76+
}
77+
// For seconds, we display 4 significant digits.
78+
return fmt.Sprintf("%s%.4gs", sign, v), nil
79+
}
80+
prefix := ""
81+
for _, p := range []string{"m", "u", "n", "p", "f", "a", "z", "y"} {
82+
if math.Abs(v) >= 1 {
83+
break
84+
}
85+
prefix = p
86+
v *= 1000
87+
}
88+
return fmt.Sprintf("%.4g%ss", v, prefix), nil
89+
}

helpers/templates/time_test.go

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
// Copyright 2024 The Prometheus Authors
2+
// Licensed under the Apache License, Version 2.0 (the "License");
3+
// you may not use this file except in compliance with the License.
4+
// You may obtain a copy of the License at
5+
//
6+
// http://www.apache.org/licenses/LICENSE-2.0
7+
//
8+
// Unless required by applicable law or agreed to in writing, software
9+
// distributed under the License is distributed on an "AS IS" BASIS,
10+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
11+
// See the License for the specific language governing permissions and
12+
// limitations under the License.
13+
14+
package templates
15+
16+
import (
17+
"testing"
18+
19+
"github.com/stretchr/testify/require"
20+
)
21+
22+
func TestHumanizeDurationSecondsFloat64(t *testing.T) {
23+
tc := []struct {
24+
name string
25+
input float64
26+
expected string
27+
}{
28+
{name: "zero", input: 0, expected: "0s"},
29+
{name: "one second", input: 1, expected: "1s"},
30+
{name: "one minute", input: 60, expected: "1m 0s"},
31+
{name: "one hour", input: 3600, expected: "1h 0m 0s"},
32+
{name: "one day", input: 86400, expected: "1d 0h 0m 0s"},
33+
{name: "one day and one hour", input: 86400 + 3600, expected: "1d 1h 0m 0s"},
34+
{name: "negative duration", input: -(86400*2 + 3600*3 + 60*4 + 5), expected: "-2d 3h 4m 5s"},
35+
{name: "using a float", input: 899.99, expected: "14m 59s"},
36+
}
37+
38+
for _, tt := range tc {
39+
t.Run(tt.name, func(t *testing.T) {
40+
result, err := HumanizeDuration(tt.input)
41+
require.NoError(t, err)
42+
require.Equal(t, tt.expected, result)
43+
})
44+
}
45+
}
46+
47+
func TestHumanizeDurationSubsecondAndFractionalSecondsFloat64(t *testing.T) {
48+
tc := []struct {
49+
name string
50+
input float64
51+
expected string
52+
}{
53+
{name: "millseconds", input: .1, expected: "100ms"},
54+
{name: "nanoseconds", input: .0001, expected: "100us"},
55+
{name: "milliseconds + nanoseconds", input: .12345, expected: "123.5ms"},
56+
{name: "minute + millisecond", input: 60.1, expected: "1m 0s"},
57+
{name: "minute + milliseconds", input: 60.5, expected: "1m 0s"},
58+
{name: "second + milliseconds", input: 1.2345, expected: "1.234s"},
59+
{name: "second + milliseconds rounded", input: 12.345, expected: "12.35s"},
60+
}
61+
62+
for _, tt := range tc {
63+
t.Run(tt.name, func(t *testing.T) {
64+
result, err := HumanizeDuration(tt.input)
65+
require.NoError(t, err)
66+
require.Equal(t, tt.expected, result)
67+
})
68+
}
69+
}
70+
71+
func TestHumanizeDurationErrorString(t *testing.T) {
72+
_, err := HumanizeDuration("one")
73+
require.Error(t, err)
74+
}
75+
76+
func TestHumanizeDurationSecondsString(t *testing.T) {
77+
tc := []struct {
78+
name string
79+
input string
80+
expected string
81+
}{
82+
{name: "zero", input: "0", expected: "0s"},
83+
{name: "second", input: "1", expected: "1s"},
84+
{name: "minute", input: "60", expected: "1m 0s"},
85+
{name: "hour", input: "3600", expected: "1h 0m 0s"},
86+
{name: "day", input: "86400", expected: "1d 0h 0m 0s"},
87+
}
88+
89+
for _, tt := range tc {
90+
t.Run(tt.name, func(t *testing.T) {
91+
result, err := HumanizeDuration(tt.input)
92+
require.NoError(t, err)
93+
require.Equal(t, tt.expected, result)
94+
})
95+
}
96+
}
97+
98+
func TestHumanizeDurationSubsecondAndFractionalSecondsString(t *testing.T) {
99+
tc := []struct {
100+
name string
101+
input string
102+
expected string
103+
}{
104+
{name: "millseconds", input: ".1", expected: "100ms"},
105+
{name: "nanoseconds", input: ".0001", expected: "100us"},
106+
{name: "milliseconds + nanoseconds", input: ".12345", expected: "123.5ms"},
107+
{name: "minute + millisecond", input: "60.1", expected: "1m 0s"},
108+
{name: "minute + milliseconds", input: "60.5", expected: "1m 0s"},
109+
{name: "second + milliseconds", input: "1.2345", expected: "1.234s"},
110+
{name: "second + milliseconds rounded", input: "12.345", expected: "12.35s"},
111+
}
112+
113+
for _, tt := range tc {
114+
t.Run(tt.name, func(t *testing.T) {
115+
result, err := HumanizeDuration(tt.input)
116+
require.NoError(t, err)
117+
require.Equal(t, tt.expected, result)
118+
})
119+
}
120+
}
121+
122+
func TestHumanizeDurationSecondsInt(t *testing.T) {
123+
tc := []struct {
124+
name string
125+
input int
126+
expected string
127+
}{
128+
{name: "zero", input: 0, expected: "0s"},
129+
{name: "negative", input: -1, expected: "-1s"},
130+
{name: "second", input: 1, expected: "1s"},
131+
{name: "days", input: 1234567, expected: "14d 6h 56m 7s"},
132+
}
133+
134+
for _, tt := range tc {
135+
t.Run(tt.name, func(t *testing.T) {
136+
result, err := HumanizeDuration(tt.input)
137+
require.NoError(t, err)
138+
require.Equal(t, tt.expected, result)
139+
})
140+
}
141+
}

0 commit comments

Comments
 (0)