@ -21,7 +21,9 @@ import (
"regexp"
"sort"
"strings"
"text/template"
html_template "html/template"
text_template "text/template"
clientmodel "github.com/prometheus/client_golang/model"
@ -55,128 +57,181 @@ 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 ) {
func query ( q string , timestamp clientmodel . Timestamp , storage metric . PreloadingPersistence ) ( queryResult , error ) {
exprNode , err := rules . LoadExprFromString ( q )
if err != nil {
return nil , err
}
queryStats := stats . NewTimerGroup ( )
vector , err := ast . EvalToVector ( exprNode , timestamp , storage , queryStats )
if err != nil {
return nil , err
}
// It'd better to have no alert description than to kill the whole process
// if there's a bug in the template. Similarly with console templates.
defer func ( ) {
if r := recover ( ) ; r != nil {
var ok bool
resultErr , ok = r . ( error )
if ! ok {
resultErr = fmt . Errorf ( "Panic expanding template: %v" , r )
}
// ast.Vector is hard to work with in templates, so convert to
// base data types.
var result = make ( queryResult , len ( vector ) )
for n , v := range vector {
s := sample {
Value : float64 ( v . Value ) ,
Labels : make ( map [ string ] string ) ,
}
} ( )
for label , value := range v . Metric {
s . Labels [ string ( label ) ] = string ( value )
}
result [ n ] = & s
}
return result , nil
}
funcMap := template . FuncMap {
"query" : func ( q string ) ( queryResult , error ) {
return query ( q , timestamp , storage )
} ,
"first" : func ( v queryResult ) ( * sample , error ) {
if len ( v ) > 0 {
return v [ 0 ] , nil
}
return nil , errors . New ( "first() called on vector with no elements" )
} ,
"label" : func ( label string , s * sample ) string {
return s . Labels [ label ]
} ,
"value" : func ( s * sample ) float64 {
return s . Value
} ,
"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 )
} ,
"match" : regexp . MatchString ,
"title" : strings . Title ,
"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
type templateExpander struct {
text string
name string
data interface { }
funcMap text_template . FuncMap
}
func NewTemplateExpander ( text string , name string , data interface { } , timestamp clientmodel . Timestamp , storage metric . PreloadingPersistence ) * templateExpander {
return & templateExpander {
text : text ,
name : name ,
data : data ,
funcMap : text_template . FuncMap {
"query" : func ( q string ) ( queryResult , error ) {
return query ( q , timestamp , storage )
} ,
"first" : func ( v queryResult ) ( * sample , error ) {
if len ( v ) > 0 {
return v [ 0 ] , nil
}
return nil , errors . New ( "first() called on vector with no elements" )
} ,
"label" : func ( label string , s * sample ) string {
return s . Labels [ label ]
} ,
"value" : func ( s * sample ) float64 {
return s . Value
} ,
"strvalue" : func ( s * sample ) string {
return s . Labels [ "__value__" ]
} ,
"args" : func ( args ... interface { } ) map [ string ] interface { } {
result := make ( map [ string ] interface { } )
for i , a := range args {
result [ fmt . Sprintf ( "arg%d" , i ) ] = a
}
return result
} ,
"reReplaceAll" : func ( pattern , repl , text string ) string {
re := regexp . MustCompile ( pattern )
return re . ReplaceAllString ( text , repl )
} ,
"safeHtml" : func ( text string ) html_template . HTML {
return html_template . HTML ( text )
} ,
"match" : regexp . MatchString ,
"title" : strings . Title ,
"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
}
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 )
}
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 {
for _ , p := range [ ] string { "ki" , "Mi" , "Gi" , "Ti" , "Pi" , "Ei" , "Zi" , "Yi" } {
if math . Abs ( v ) < 1024 {
break
}
prefix = p
v *= 1000
v /= 1024
}
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 )
} ,
} ,
}
}
// Expand a template.
func ( te templateExpander ) Expand ( ) ( result string , resultErr error ) {
// It'd better to have no alert description than to kill the whole process
// if there's a bug in the template.
defer func ( ) {
if r := recover ( ) ; r != nil {
var ok bool
resultErr , ok = r . ( error )
if ! ok {
resultErr = fmt . Errorf ( "Panic expanding template %v: %v" , te . name , r )
}
}
} ( )
var buffer bytes . Buffer
tmpl , err := template . New ( name ) . Funcs ( funcMap ) . Parse ( text )
tmpl , err := text_te mplate . New ( te . name ) . Funcs ( te . funcMap ) . Parse ( te . text )
if err != nil {
return "" , fmt . Errorf ( "Error parsing template %v: %v" , name , err )
return "" , fmt . Errorf ( "Error parsing template %v: %v" , te . name , err )
}
err = tmpl . Execute ( & buffer , data )
err = tmpl . Execute ( & buffer , te . data )
if err != nil {
return "" , fmt . Errorf ( "Error executing template %v: %v" , name , err )
return "" , fmt . Errorf ( "Error executing template %v: %v" , te . name , err )
}
return buffer . String ( ) , nil
}
func query ( q string , timestamp clientmodel . Timestamp , storage metric . PreloadingPersistence ) ( queryResult , error ) {
exprNode , err := rules . LoadExprFromString ( q )
if err != nil {
return nil , err
}
queryStats := stats . NewTimerGroup ( )
vector , err := ast . EvalToVector ( exprNode , timestamp , storage , queryStats )
// Expand a template with HTML escaping, with templates read from the given files.
func ( te templateExpander ) ExpandHTML ( templateFiles [ ] string ) ( result string , resultErr error ) {
defer func ( ) {
if r := recover ( ) ; r != nil {
var ok bool
resultErr , ok = r . ( error )
if ! ok {
resultErr = fmt . Errorf ( "Panic expanding template %v: %v" , te . name , r )
}
}
} ( )
var buffer bytes . Buffer
tmpl , err := html_template . New ( te . name ) . Funcs ( html_template . FuncMap ( te . funcMap ) ) . Parse ( te . text )
if err != nil {
return nil , err
return "" , fmt . Errorf ( "Error parsing template %v: %v" , te . name , err )
}
// ast.Vector is hard to work with in templates, so convert to
// base data types.
var result = make ( queryResult , len ( vector ) )
for n , v := range vector {
s := sample {
Value : float64 ( v . Value ) ,
Labels : make ( map [ string ] string ) ,
}
for label , value := range v . Metric {
s . Labels [ string ( label ) ] = string ( value )
if len ( templateFiles ) > 0 {
_ , err = tmpl . ParseFiles ( templateFiles ... )
if err != nil {
return "" , fmt . Errorf ( "Error parsing template files for %v: %v" , te . name , err )
}
result [ n ] = & s
}
return result , nil
err = tmpl . Execute ( & buffer , te . data )
if err != nil {
return "" , fmt . Errorf ( "Error executing template %v: %v" , te . name , err )
}
return buffer . String ( ) , nil
}