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 "errors"
33 "fmt"
34 "io"
35 "net/http"
36 "time"
38 "github.com/gonum/plot"
39 "github.com/gonum/plot/plotter"
40 "github.com/gonum/plot/plotutil"
41 "github.com/gonum/plot/vg"
42 "github.com/sysdb/go/client"
43 "github.com/sysdb/go/sysdb"
44 )
46 var urldate = "20060102150405"
48 func (s *Server) graph(w http.ResponseWriter, req request) {
49 if len(req.args) < 2 || 4 < len(req.args) {
50 s.badrequest(w, fmt.Errorf("Missing host/metric information"))
51 return
52 }
54 end := time.Now()
55 start := end.Add(-24 * time.Hour)
56 var err error
57 if len(req.args) > 2 {
58 if start, err = time.Parse(urldate, req.args[2]); err != nil {
59 s.badrequest(w, fmt.Errorf("Invalid start time: %v", err))
60 return
61 }
62 }
63 if len(req.args) > 3 {
64 if end, err = time.Parse(urldate, req.args[3]); err != nil {
65 s.badrequest(w, fmt.Errorf("Invalid start time: %v", err))
66 return
67 }
68 }
69 if start.Equal(end) || start.After(end) {
70 s.badrequest(w, fmt.Errorf("START(%v) is greater than or equal to END(%v)", start, end))
71 return
72 }
74 q, err := client.QueryString("TIMESERIES %s.%s START %s END %s", req.args[0], req.args[1], start, end)
75 if err != nil {
76 s.internal(w, fmt.Errorf("Failed to retrieve graph data: %v", err))
77 return
78 }
79 res, err := s.c.Query(q)
80 if err != nil {
81 s.internal(w, fmt.Errorf("Failed to retrieve graph data: %v", err))
82 return
83 }
85 ts, ok := res.(*sysdb.Timeseries)
86 if !ok {
87 s.internal(w, errors.New("TIMESERIES did not return a time-series"))
88 return
89 }
91 p, err := plot.New()
92 if err != nil {
93 s.internal(w, fmt.Errorf("Failed to create plot: %v", err))
94 return
95 }
96 p.Add(plotter.NewGrid())
97 p.X.Tick.Marker = dateTicks{}
99 var i int
100 for name, data := range ts.Data {
101 pts := make(plotter.XYs, len(data))
102 for i, p := range data {
103 pts[i].X = float64(time.Time(p.Timestamp).UnixNano())
104 pts[i].Y = p.Value
105 }
106 l, err := plotter.NewLine(pts)
107 if err != nil {
108 s.internal(w, fmt.Errorf("Failed to create line plotter: %v", err))
109 return
110 }
111 l.LineStyle.Color = plotutil.DarkColors[i%len(plotutil.DarkColors)]
112 p.Add(l)
113 p.Legend.Add(name, l)
114 i++
115 }
117 pw, err := p.WriterTo(vg.Length(500), vg.Length(200), "svg")
118 if err != nil {
119 s.internal(w, fmt.Errorf("Failed to write plot: %v", err))
120 return
121 }
123 var buf bytes.Buffer
124 if _, err := pw.WriteTo(&buf); err != nil {
125 s.internal(w, fmt.Errorf("Failed to write plot: %v", err))
126 return
127 }
128 w.Header().Set("Content-Type", "image/svg+xml")
129 w.WriteHeader(http.StatusOK)
130 io.Copy(w, &buf)
131 }
133 type dateTicks struct{}
135 func (dateTicks) Ticks(min, max float64) []plot.Tick {
136 // TODO: this is surely not the best we can do
137 // but it'll distribute ticks evenly.
138 ticks := plot.DefaultTicks{}.Ticks(min, max)
139 for i, t := range ticks {
140 if t.Label == "" {
141 // Skip minor ticks.
142 continue
143 }
144 ticks[i].Label = time.Unix(0, int64(t.Value)).Format(time.RFC822)
145 }
146 return ticks
147 }
149 // vim: set tw=78 sw=4 sw=4 noexpandtab :