From: Sebastian Harl Date: Fri, 21 Nov 2014 20:23:57 +0000 (+0100) Subject: Added support for simple SVG graphs for metrics. X-Git-Url: https://git.tokkee.org/?a=commitdiff_plain;h=16dddfceb2e1918a4db62f70d0aa7aef03045fc0;p=sysdb%2Fwebui.git Added support for simple SVG graphs for metrics. The graph will be displayed when querying a metric. The graph itself mostly uses default settings of the Plotinum package for now. --- diff --git a/server/graph.go b/server/graph.go new file mode 100644 index 0000000..054fe8e --- /dev/null +++ b/server/graph.go @@ -0,0 +1,105 @@ +// +// Copyright (C) 2014 Sebastian 'tokkee' Harl +// All rights reserved. +// +// Redistribution and use in source and binary forms, with or without +// modification, are permitted provided that the following conditions +// are met: +// 1. Redistributions of source code must retain the above copyright +// notice, this list of conditions and the following disclaimer. +// 2. Redistributions in binary form must reproduce the above copyright +// notice, this list of conditions and the following disclaimer in the +// documentation and/or other materials provided with the distribution. +// +// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS +// ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED +// TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +// PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDERS OR +// CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, +// EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +// PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; +// OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +// WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR +// OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF +// ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + +package server + +// Helper functions for handling and plotting graphs. + +import ( + "bytes" + "errors" + "fmt" + "io" + "net/http" + "strings" + "time" + + "code.google.com/p/plotinum/plot" + "code.google.com/p/plotinum/plotter" + "code.google.com/p/plotinum/plotutil" + "code.google.com/p/plotinum/vg" + "code.google.com/p/plotinum/vg/vgsvg" + "github.com/sysdb/go/proto" + "github.com/sysdb/go/sysdb" +) + +func (s *Server) graph(w http.ResponseWriter, req request) { + if len(req.args) < 2 { + s.internal(w, fmt.Errorf("Missing host/metric information")) + } + + host := proto.EscapeString(req.args[0]) + metric := proto.EscapeString(strings.Join(req.args[1:], "/")) + res, err := s.query(fmt.Sprintf("TIMESERIES %s.%s", host, metric)) + if err != nil { + s.internal(w, fmt.Errorf("Failed to retrieve graph data: %v", err)) + return + } + + ts, ok := res.(sysdb.Timeseries) + if !ok { + s.internal(w, errors.New("TIMESERIES did not return a time-series")) + return + } + + p, err := plot.New() + if err != nil { + s.internal(w, fmt.Errorf("Failed to create plot: %v", err)) + return + } + p.Add(plotter.NewGrid()) + + var i int + for name, data := range ts.Data { + pts := make(plotter.XYs, len(data)) + for i, p := range data { + pts[i].X = float64(time.Time(p.Timestamp).UnixNano()) + pts[i].Y = p.Value + } + l, err := plotter.NewLine(pts) + if err != nil { + s.internal(w, fmt.Errorf("Failed to create line plotter: %v", err)) + return + } + l.LineStyle.Color = plotutil.DarkColors[i%len(plotutil.DarkColors)] + p.Add(l) + p.Legend.Add(name, l) + i++ + } + + c := vgsvg.New(vg.Length(500), vg.Length(200)) + p.Draw(plot.MakeDrawArea(c)) + + var buf bytes.Buffer + if _, err := c.WriteTo(&buf); err != nil { + s.internal(w, fmt.Errorf("Failed to write plot: %v", err)) + return + } + w.Header().Set("Content-Type", "image/svg+xml") + w.WriteHeader(http.StatusOK) + io.Copy(w, &buf) +} + +// vim: set tw=78 sw=4 sw=4 noexpandtab : diff --git a/server/server.go b/server/server.go index 33a2536..59c1e42 100644 --- a/server/server.go +++ b/server/server.go @@ -104,6 +104,7 @@ func New(cfg Config) (*Server, error) { s.mux = map[string]handler{ "images": s.static, "style": s.static, + "graph": s.graph, } return s, nil } @@ -317,6 +318,10 @@ func (s *Server) query(cmd string) (interface{}, error) { var host sysdb.Host err = proto.Unmarshal(m, &host) res = host + case proto.Timeseries: + var ts sysdb.Timeseries + err = proto.Unmarshal(m, &ts) + res = ts default: return nil, fmt.Errorf("Unsupported data type %d", t) } diff --git a/templates/metric.tmpl b/templates/metric.tmpl index e4a0efb..a264801 100644 --- a/templates/metric.tmpl +++ b/templates/metric.tmpl @@ -1,5 +1,6 @@
{{$m := index .Metrics 0}}

Metric {{$.Name}} — {{$m.Name}}

+
Host{{$.Name}}
Last update{{$m.LastUpdate}}