diff --git a/api/api.go b/api/api.go new file mode 100644 index 0000000000..9fda6fa012 --- /dev/null +++ b/api/api.go @@ -0,0 +1,11 @@ +package api + +import ( + "code.google.com/p/gorest" +) + +type MetricsService struct { + gorest.RestService `root:"/api/" consumes:"application/json" produces:"application/json"` + + query gorest.EndPoint `method:"GET" path:"/query?{expr:string}&{json:string}&{start:string}&{end:string}" output:"string"` +} diff --git a/api/query.go b/api/query.go new file mode 100644 index 0000000000..6d5ff33d59 --- /dev/null +++ b/api/query.go @@ -0,0 +1,21 @@ +package api + +import ( + "github.com/matttproud/prometheus/rules" + "github.com/matttproud/prometheus/rules/ast" + "time" +) +func (serv MetricsService) Query(Expr string, Json string, Start string, End string) (result string) { + exprNode, err := rules.LoadExprFromString(Expr) + if err != nil { + return err.Error() + } + + timestamp := time.Now() + + format := ast.TEXT + if Json != "" { + format = ast.JSON + } + return ast.EvalToString(exprNode, ×tamp, format) +} diff --git a/main.go b/main.go index d47add1e92..1546a98947 100644 --- a/main.go +++ b/main.go @@ -14,7 +14,9 @@ package main import ( + "code.google.com/p/gorest" "fmt" + "github.com/matttproud/prometheus/api" "github.com/matttproud/golang_instrumentation" "github.com/matttproud/prometheus/config" "github.com/matttproud/prometheus/retrieval" @@ -66,20 +68,24 @@ func main() { } go func() { + gorest.RegisterService(new(api.MetricsService)) exporter := registry.DefaultRegistry.YieldExporter() + + http.Handle("/", gorest.Handle()) http.Handle("/metrics.json", exporter) + http.Handle("/static/", http.StripPrefix("/static/", http.FileServer(http.Dir("static")))) http.ListenAndServe(":9090", nil) }() for { select { case scrapeResult := <-scrapeResults: - fmt.Printf("scrapeResult -> %s\n", scrapeResult) + //fmt.Printf("scrapeResult -> %s\n", scrapeResult) for _, sample := range scrapeResult.Samples { persistence.AppendSample(&sample) } case ruleResult := <-ruleResults: - fmt.Printf("ruleResult -> %s\n", ruleResult) + //fmt.Printf("ruleResult -> %s\n", ruleResult) for _, sample := range ruleResult.Samples { persistence.AppendSample(sample) } diff --git a/rules/ast/printer.go b/rules/ast/printer.go index 2baf65166b..3738af375b 100644 --- a/rules/ast/printer.go +++ b/rules/ast/printer.go @@ -2,11 +2,19 @@ package ast import ( "fmt" + "encoding/json" "sort" "strings" "time" ) +type OutputFormat int + +const ( + TEXT OutputFormat = iota + JSON +) + func binOpTypeToString(opType BinOpType) string { opTypeMap := map[BinOpType]string{ ADD: "+", @@ -34,6 +42,16 @@ func aggrTypeToString(aggrType AggrType) string { return aggrTypeMap[aggrType] } +func exprTypeToString(exprType ExprType) string { + exprTypeMap := map[ExprType]string{ + SCALAR: "scalar", + VECTOR: "vector", + MATRIX: "matrix", + STRING: "string", + } + return exprTypeMap[exprType] +} + func durationToString(duration time.Duration) string { seconds := int64(duration / time.Second) factors := map[string]int64{ @@ -81,6 +99,96 @@ func (vector Vector) ToString() string { return strings.Join(metricStrings, "\n") } +func (matrix Matrix) ToString() string { + metricStrings := []string{} + for _, sampleSet := range matrix { + metricName, ok := sampleSet.Metric["name"] + if !ok { + panic("Tried to print matrix without metric name") + } + labelStrings := []string{} + for label, value := range sampleSet.Metric { + if label != "name" { + labelStrings = append(labelStrings, fmt.Sprintf("%v='%v'", label, value)) + } + } + sort.Strings(labelStrings) + valueStrings := []string{} + for _, value := range sampleSet.Values { + valueStrings = append(valueStrings, + fmt.Sprintf("\n%v @[%v]", value.Value, value.Timestamp)) + } + metricStrings = append(metricStrings, + fmt.Sprintf("%v{%v} => %v", + metricName, + strings.Join(labelStrings, ","), + strings.Join(valueStrings, ", "))) + } + sort.Strings(metricStrings) + return strings.Join(metricStrings, "\n") +} + +func errorToJSON(err error) string { + errorStruct := struct { + Type string + Error string + }{ + Type: "error", + Error: err.Error(), + } + + errorJSON, err := json.MarshalIndent(errorStruct, "", "\t") + if err != nil { + return "" + } + return string(errorJSON) +} + +func typedValueToJSON(data interface{}, typeStr string) string { + dataStruct := struct { + Type string + Value interface{} + }{ + Type: typeStr, + Value: data, + } + dataJSON, err := json.MarshalIndent(dataStruct, "", "\t") + if err != nil { + return errorToJSON(err) + } + return string(dataJSON) +} + +func EvalToString(node Node, timestamp *time.Time, format OutputFormat) string { + switch node.Type() { + case SCALAR: + scalar := node.(ScalarNode).Eval(timestamp) + switch format { + case TEXT: return fmt.Sprintf("scalar: %v", scalar) + case JSON: return typedValueToJSON(scalar, "scalar") + } + case VECTOR: + vector := node.(VectorNode).Eval(timestamp) + switch format { + case TEXT: return vector.ToString() + case JSON: return typedValueToJSON(vector, "vector") + } + case MATRIX: + matrix := node.(MatrixNode).Eval(timestamp) + switch format { + case TEXT: return matrix.ToString() + case JSON: return typedValueToJSON(matrix, "matrix") + } + case STRING: + str := node.(StringNode).Eval(timestamp) + switch format { + case TEXT: return str + case JSON: return typedValueToJSON(str, "string") + } + } + panic("Switch didn't cover all node types") +} + func (node *VectorLiteral) ToString() string { metricName, ok := node.labels["name"] if !ok {