11b55b84fef483d16ff545bf50894444fdd58576
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 "strings"
37 "time"
39 "code.google.com/p/plotinum/plot"
40 "code.google.com/p/plotinum/plotter"
41 "code.google.com/p/plotinum/plotutil"
42 "code.google.com/p/plotinum/vg"
43 "code.google.com/p/plotinum/vg/vgsvg"
44 "github.com/sysdb/go/proto"
45 "github.com/sysdb/go/sysdb"
46 )
48 func (s *Server) graph(w http.ResponseWriter, req request) {
49 if len(req.args) < 2 {
50 s.internal(w, fmt.Errorf("Missing host/metric information"))
51 }
53 host := proto.EscapeString(req.args[0])
54 metric := proto.EscapeString(strings.Join(req.args[1:], "/"))
55 res, err := s.query(fmt.Sprintf("TIMESERIES %s.%s", host, metric))
56 if err != nil {
57 s.internal(w, fmt.Errorf("Failed to retrieve graph data: %v", err))
58 return
59 }
61 ts, ok := res.(sysdb.Timeseries)
62 if !ok {
63 s.internal(w, errors.New("TIMESERIES did not return a time-series"))
64 return
65 }
67 p, err := plot.New()
68 if err != nil {
69 s.internal(w, fmt.Errorf("Failed to create plot: %v", err))
70 return
71 }
72 p.Add(plotter.NewGrid())
73 p.X.Tick.Marker = dateTicks
75 var i int
76 for name, data := range ts.Data {
77 pts := make(plotter.XYs, len(data))
78 for i, p := range data {
79 pts[i].X = float64(time.Time(p.Timestamp).UnixNano())
80 pts[i].Y = p.Value
81 }
82 l, err := plotter.NewLine(pts)
83 if err != nil {
84 s.internal(w, fmt.Errorf("Failed to create line plotter: %v", err))
85 return
86 }
87 l.LineStyle.Color = plotutil.DarkColors[i%len(plotutil.DarkColors)]
88 p.Add(l)
89 p.Legend.Add(name, l)
90 i++
91 }
93 c := vgsvg.New(vg.Length(500), vg.Length(200))
94 p.Draw(plot.MakeDrawArea(c))
96 var buf bytes.Buffer
97 if _, err := c.WriteTo(&buf); err != nil {
98 s.internal(w, fmt.Errorf("Failed to write plot: %v", err))
99 return
100 }
101 w.Header().Set("Content-Type", "image/svg+xml")
102 w.WriteHeader(http.StatusOK)
103 io.Copy(w, &buf)
104 }
106 func dateTicks(min, max float64) []plot.Tick {
107 // TODO: this is surely not the best we can do
108 // but it'll distribute ticks evenly.
109 ticks := plot.DefaultTicks(min, max)
110 for i, t := range ticks {
111 if t.Label == "" {
112 // Skip minor ticks.
113 continue
114 }
115 ticks[i].Label = time.Unix(0, int64(t.Value)).Format(time.RFC822)
116 }
117 return ticks
118 }
120 // vim: set tw=78 sw=4 sw=4 noexpandtab :