diff --git a/templates/templates.go b/templates/templates.go index 3a0eb8cabb..f4614fd754 100644 --- a/templates/templates.go +++ b/templates/templates.go @@ -17,6 +17,9 @@ import ( "bytes" "errors" "fmt" + "math" + "regexp" + "sort" "text/template" clientmodel "github.com/prometheus/client_golang/model" @@ -34,6 +37,23 @@ type sample struct { } type queryResult []*sample +type queryResultByLabelSorter struct { + results queryResult + by string +} + +func (q queryResultByLabelSorter) Len() int { + return len(q.results) +} + +func (q queryResultByLabelSorter) Less(i, j int) bool { + return q.results[i].Labels[q.by] < q.results[j].Labels[q.by] +} + +func (q queryResultByLabelSorter) Swap(i, j int) { + q.results[i], q.results[j] = q.results[j], q.results[i] +} + // Expand a template, using the given data, time and storage. func Expand(text string, name string, data interface{}, timestamp clientmodel.Timestamp, storage metric.PreloadingPersistence) (result string, resultErr error) { @@ -68,6 +88,55 @@ func Expand(text string, name string, data interface{}, timestamp clientmodel.Ti "strvalue": func(s *sample) string { return s.Labels["__value__"] }, + "reReplaceAll": func(pattern, repl, text string) string { + re := regexp.MustCompile(pattern) + return re.ReplaceAllString(text, repl) + }, + "sortByLabel": func(label string, v queryResult) queryResult { + sorter := queryResultByLabelSorter{v[:], label} + sort.Stable(sorter) + return v + }, + "humanize": func(v float64) string { + if v == 0 { + return fmt.Sprintf("%.4g ", v) + } + if math.Abs(v) >= 1 { + prefix := "" + for _, p := range []string{"k", "M", "G", "T", "P", "E", "Z", "Y"} { + if math.Abs(v) < 1000 { + break + } + prefix = p + v /= 1000 + } + return fmt.Sprintf("%.4g %s", v, prefix) + } else { + 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 %s", v, prefix) + } + }, + "humanize1024": func(v float64) string { + if math.Abs(v) <= 1 { + return fmt.Sprintf("%.4g ", v) + } + prefix := "" + for _, p := range []string{"ki", "Mi", "Gi", "Ti", "Pi", "Ei", "Zi", "Yi"} { + if math.Abs(v) < 1024 { + break + } + prefix = p + v /= 1024 + } + return fmt.Sprintf("%.4g %s", v, prefix) + }, } var buffer bytes.Buffer diff --git a/templates/templates_test.go b/templates/templates_test.go index 7b789973c7..eaf124bc21 100644 --- a/templates/templates_test.go +++ b/templates/templates_test.go @@ -69,6 +69,26 @@ func TestTemplateExpansion(t *testing.T) { text: "{{ (query \"missing\").banana }}", shouldFail: true, }, + { + // Regex replacement. + text: "{{ reReplaceAll \"(a)b\" \"x$1\" \"ab\" }}", + output: "xa", + }, + { + // Sorting. + text: "{{ range query \"metric\" | sortByLabel \"instance\" }}{{.Labels.instance}} {{end}}", + output: "a b ", + }, + { + // Humanize. + text: "{{ 0.0 | humanize }}:{{ 1.0 | humanize }}:{{ 1234567.0 | humanize }}:{{ .12 | humanize }}", + output: "0 :1 :1.235 M:120 m", + }, + { + // Humanize1024. + text: "{{ 0.0 | humanize1024 }}:{{ 1.0 | humanize1024 }}:{{ 1048576.0 | humanize1024 }}:{{ .12 | humanize1024}}", + output: "0 :1 :1 Mi:0.12 ", + }, } time := clientmodel.Timestamp(0)