Code

server: Add support for GroupBy in graphs.
[sysdb/webui.git] / server / graph.go
1 //
2 // Copyright (C) 2014 Sebastian 'tokkee' Harl <sh@tokkee.org>
3 // All rights reserved.
4 //
5 // Redistribution and use in source and binary forms, with or without
6 // modification, are permitted provided that the following conditions
7 // are met:
8 // 1. Redistributions of source code must retain the above copyright
9 //    notice, this list of conditions and the following disclaimer.
10 // 2. Redistributions in binary form must reproduce the above copyright
11 //    notice, this list of conditions and the following disclaimer in the
12 //    documentation and/or other materials provided with the distribution.
13 //
14 // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
15 // ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
16 // TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
17 // PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR
18 // CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
19 // EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
20 // PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
21 // OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY,
22 // WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR
23 // OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF
24 // ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 package server
28 // Helper functions for handling and plotting graphs.
30 import (
31         "bytes"
32         "fmt"
33         "io"
34         "net/http"
35         "strings"
36         "time"
38         "github.com/gonum/plot/vg"
39         "github.com/sysdb/go/sysdb"
40         "github.com/sysdb/webui/graph"
41 )
43 var urldate = "20060102150405"
45 func (s *Server) graph(w http.ResponseWriter, req request) {
46         if len(req.args) < 2 || 4 < len(req.args) {
47                 s.badrequest(w, fmt.Errorf("Missing host/metric information"))
48                 return
49         }
51         end := time.Now()
52         start := end.Add(-24 * time.Hour)
53         var err error
54         if len(req.args) > 2 {
55                 if start, err = time.Parse(urldate, req.args[2]); err != nil {
56                         s.badrequest(w, fmt.Errorf("Invalid start time: %v", err))
57                         return
58                 }
59         }
60         if len(req.args) > 3 {
61                 if end, err = time.Parse(urldate, req.args[3]); err != nil {
62                         s.badrequest(w, fmt.Errorf("Invalid start time: %v", err))
63                         return
64                 }
65         }
67         g := &graph.Graph{
68                 Start: start,
69                 End:   end,
70         }
71         if req.args[0] == "q" || len(req.args[0]) > 1 && req.args[0][:2] == "q/" {
72                 if g.Metrics, err = s.queryMetrics(req.args[1]); err != nil {
73                         s.badrequest(w, fmt.Errorf("Failed to query metrics: %v", err))
74                         return
75                 }
77                 if req.args[0] != "q" {
78                         for _, arg := range strings.Split(req.args[0][2:], "/") {
79                                 if arg := strings.SplitN(arg, "=", 2); len(arg) == 2 {
80                                         if arg[0] == "g" {
81                                                 g.GroupBy = strings.Split(arg[1], ",")
82                                         }
83                                 }
84                         }
85                 }
86         } else {
87                 g.Metrics = []graph.Metric{{Hostname: req.args[0], Identifier: req.args[1]}}
88         }
90         p, err := g.Plot(s.c)
91         if err != nil {
92                 s.internal(w, err)
93                 return
94         }
96         pw, err := p.WriterTo(vg.Length(500), vg.Length(200), "svg")
97         if err != nil {
98                 s.internal(w, fmt.Errorf("Failed to write plot: %v", err))
99                 return
100         }
102         var buf bytes.Buffer
103         if _, err := pw.WriteTo(&buf); err != nil {
104                 s.internal(w, fmt.Errorf("Failed to write plot: %v", err))
105                 return
106         }
107         w.Header().Set("Content-Type", "image/svg+xml")
108         w.WriteHeader(http.StatusOK)
109         io.Copy(w, &buf)
112 func (s *Server) queryMetrics(q string) ([]graph.Metric, error) {
113         raw, err := parseQuery(q)
114         if err != nil {
115                 return nil, err
116         }
117         if raw.typ != "" && raw.typ != "metrics" {
118                 return nil, fmt.Errorf("Invalid object type %q for graphs", raw.typ)
119         }
121         var args string
122         for name, value := range raw.args {
123                 if len(args) > 0 {
124                         args += " AND"
125                 }
127                 if name == "name" {
128                         args += fmt.Sprintf(" name =~ %s", value)
129                 } else {
130                         args += fmt.Sprintf(" %s = %s", name, value)
131                 }
132         }
134         res, err := s.c.Query("LOOKUP metrics MATCHING" + args)
135         if err != nil {
136                 return nil, err
137         }
138         hosts, ok := res.([]sysdb.Host)
139         if !ok {
140                 return nil, fmt.Errorf("LOOKUP did not return a list of hosts but %T", res)
141         }
142         var metrics []graph.Metric
143         for _, h := range hosts {
144                 for _, m := range h.Metrics {
145                         metric := graph.Metric{
146                                 Hostname:   h.Name,
147                                 Identifier: m.Name,
148                                 Attributes: make(map[string]string),
149                         }
150                         for _, attr := range m.Attributes {
151                                 metric.Attributes[attr.Name] = attr.Value
152                         }
153                         metrics = append(metrics, metric)
154                 }
155         }
156         return metrics, nil
159 // vim: set tw=78 sw=4 sw=4 noexpandtab :