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)
110 }
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
157 }
159 // vim: set tw=78 sw=4 sw=4 noexpandtab :