summary | shortlog | log | commit | commitdiff | tree
raw | patch | inline | side by side (parent: e7d61e0)
raw | patch | inline | side by side (parent: e7d61e0)
author | Sebastian Harl <sh@tokkee.org> | |
Tue, 12 May 2015 21:13:01 +0000 (23:13 +0200) | ||
committer | Sebastian Harl <sh@tokkee.org> | |
Tue, 12 May 2015 21:13:01 +0000 (23:13 +0200) |
For now, the package is a simple wrapper around github.com/gonum/plot. It
supports graphing multiple metrics in the same graph (even though that's not
used by the server yet).
supports graphing multiple metrics in the same graph (even though that's not
used by the server yet).
graph/graph.go | [new file with mode: 0644] | patch | blob |
server/graph.go | patch | blob | history |
diff --git a/graph/graph.go b/graph/graph.go
--- /dev/null
+++ b/graph/graph.go
@@ -0,0 +1,128 @@
+//
+// Copyright (C) 2014-2015 Sebastian 'tokkee' Harl <sh@tokkee.org>
+// 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 graph handles time-series data provided by SysDB. It supports
+// querying and post-processing of the data.
+package graph
+
+import (
+ "fmt"
+ "time"
+
+ "github.com/gonum/plot"
+ "github.com/gonum/plot/plotter"
+ "github.com/gonum/plot/plotutil"
+ "github.com/sysdb/go/client"
+ "github.com/sysdb/go/sysdb"
+)
+
+// A Graph represents a single graph. It may reference multiple time-series.
+type Graph struct {
+ // Time range of the graph.
+ Start, End time.Time
+
+ // Metrics: {<hostname>, <identifier>}
+ Metrics [][2]string
+}
+
+type pl struct {
+ *plot.Plot
+
+ ts int // Index of the current time-series.
+}
+
+func (p *pl) addTimeseries(c *client.Client, metric [2]string, start, end time.Time) error {
+ q, err := client.QueryString("TIMESERIES %s.%s START %s END %s", metric[0], metric[1], start, end)
+ if err != nil {
+ return fmt.Errorf("Failed to retrieve graph data: %v", err)
+ }
+ res, err := c.Query(q)
+ if err != nil {
+ return fmt.Errorf("Failed to retrieve graph data: %v", err)
+ }
+
+ ts, ok := res.(*sysdb.Timeseries)
+ if !ok {
+ return fmt.Errorf("TIMESERIES did not return a time-series but %T", res)
+ }
+
+ 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 {
+ return fmt.Errorf("Failed to create line plotter: %v", err)
+ }
+ l.LineStyle.Color = plotutil.DarkColors[p.ts%len(plotutil.DarkColors)]
+
+ p.Add(l)
+ p.Legend.Add(name, l)
+ p.ts++
+ }
+ return nil
+}
+
+// Plot fetches a graph's time-series data using the specified client and
+// plots it.
+func (g *Graph) Plot(c *client.Client) (*plot.Plot, error) {
+ var err error
+
+ p := &pl{}
+ p.Plot, err = plot.New()
+ if err != nil {
+ return nil, fmt.Errorf("Failed to create plot: %v", err)
+ }
+ p.Add(plotter.NewGrid())
+ p.X.Tick.Marker = dateTicks{}
+
+ for _, m := range g.Metrics {
+ if err := p.addTimeseries(c, m, g.Start, g.End); err != nil {
+ return nil, err
+ }
+ }
+ return p.Plot, nil
+}
+
+type dateTicks struct{}
+
+func (dateTicks) Ticks(min, max float64) []plot.Tick {
+ // TODO: this is surely not the best we can do
+ // but it'll distribute ticks evenly.
+ ticks := plot.DefaultTicks{}.Ticks(min, max)
+ for i, t := range ticks {
+ if t.Label == "" {
+ // Skip minor ticks.
+ continue
+ }
+ ticks[i].Label = time.Unix(0, int64(t.Value)).Format(time.RFC822)
+ }
+ return ticks
+}
+
+// vim: set tw=78 sw=4 sw=4 noexpandtab :
diff --git a/server/graph.go b/server/graph.go
index ed5c7fd8338cedb694944674e0cdfe1c440cdca0..9c679bc5a0386a9ed48a4900a8a5c7d9a7f7af27 100644 (file)
--- a/server/graph.go
+++ b/server/graph.go
import (
"bytes"
- "errors"
"fmt"
"io"
"net/http"
"time"
- "github.com/gonum/plot"
- "github.com/gonum/plot/plotter"
- "github.com/gonum/plot/plotutil"
"github.com/gonum/plot/vg"
- "github.com/sysdb/go/client"
- "github.com/sysdb/go/sysdb"
+ "github.com/sysdb/webui/graph"
)
var urldate = "20060102150405"
return
}
}
- if start.Equal(end) || start.After(end) {
- s.badrequest(w, fmt.Errorf("START(%v) is greater than or equal to END(%v)", start, end))
- return
- }
-
- q, err := client.QueryString("TIMESERIES %s.%s START %s END %s", req.args[0], req.args[1], start, end)
- if err != nil {
- s.internal(w, fmt.Errorf("Failed to retrieve graph data: %v", err))
- return
- }
- res, err := s.c.Query(q)
- 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
+ g := &graph.Graph{
+ Start: start,
+ End: end,
+ Metrics: [][2]string{{req.args[0], req.args[1]}},
}
-
- p, err := plot.New()
+ p, err := g.Plot(s.c)
if err != nil {
- s.internal(w, fmt.Errorf("Failed to create plot: %v", err))
+ s.internal(w, err)
return
}
- p.Add(plotter.NewGrid())
- p.X.Tick.Marker = dateTicks{}
-
- 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++
- }
pw, err := p.WriterTo(vg.Length(500), vg.Length(200), "svg")
if err != nil {
io.Copy(w, &buf)
}
-type dateTicks struct{}
-
-func (dateTicks) Ticks(min, max float64) []plot.Tick {
- // TODO: this is surely not the best we can do
- // but it'll distribute ticks evenly.
- ticks := plot.DefaultTicks{}.Ticks(min, max)
- for i, t := range ticks {
- if t.Label == "" {
- // Skip minor ticks.
- continue
- }
- ticks[i].Label = time.Unix(0, int64(t.Value)).Format(time.RFC822)
- }
- return ticks
-}
-
// vim: set tw=78 sw=4 sw=4 noexpandtab :