Browse Source

Count EDNS queries per zone, pretty status page

Ask Bjørn Hansen 12 years ago
parent
commit
2748d26a09
10 changed files with 236 additions and 40 deletions
  1. 6 0
      Makefile
  2. 1 0
      README.md
  3. 75 35
      monitor.go
  4. 10 0
      monitor_test.go
  5. 3 1
      serve.go
  6. 51 0
      status.html
  7. 62 0
      status.html.go
  8. 17 0
      templates_devel.go
  9. 5 2
      zone.go
  10. 6 2
      zones.go

+ 6 - 0
Makefile

@@ -0,0 +1,6 @@
+
+all: status.html.go
+
+status.html.go: status.html
+	go-bindata  -i status.html -o status.html.go -p main -f status_html
+

+ 1 - 0
README.md

@@ -22,6 +22,7 @@ go get github.com/abh/geodns
 cd ~/go/src/github.com/abh/geodns
 go test
 go build
+./geodns -h
 ```
 
 ## Sample configuration

+ 75 - 35
monitor.go

@@ -6,8 +6,10 @@ import (
 	"expvar"
 	"fmt"
 	"github.com/abh/go-metrics"
+	"html/template"
 	"io"
 	"log"
+	"math"
 	"net/http"
 	"os"
 	"runtime"
@@ -191,9 +193,9 @@ func MainServer(w http.ResponseWriter, req *http.Request) {
 }
 
 type rate struct {
-	Name  string
-	Count int64
-	str   string
+	Name    string
+	Count   int64
+	Metrics ZoneMetrics
 }
 type Rates []*rate
 
@@ -211,49 +213,87 @@ func (s RatesByCount) Less(i, j int) bool {
 	return ic > jc
 }
 
-func StatusServer(w http.ResponseWriter, req *http.Request) {
+func metricHTML(name string, i interface{}) (string, int64) {
+	// https://github.com/rcrowley/go-metrics/blob/master/log.go
+	switch m := i.(type) {
+	case metrics.Meter:
+		str := fmt.Sprintf(
+			"<h4>meter %s</h4>\n"+
+				"count: %9d<br>"+
+				"  1-min rate:  %12.2f\n"+
+				"  5-min rate:  %12.2f\n"+
+				"15-min rate: %12.2f\n"+
+				"  mean rate:   %12.2f\n",
+			name,
+			m.Count(),
+			m.Rate1(),
+			m.Rate5(),
+			m.Rate15(),
+			m.RateMean(),
+		)
+		return str, m.Count()
+	}
+	return "", 0
+}
 
-	io.WriteString(w, `<html><head><title>GeoDNS `+
-		VERSION+`</title><body>`+
-		initialStatus())
+func round(val float64, prec int) float64 {
 
-	rates := make(Rates, 0)
+	var rounder float64
+	intermed := val * math.Pow(10, float64(prec))
 
-	// https://github.com/rcrowley/go-metrics/blob/master/log.go
-	metrics.Each(func(name string, i interface{}) {
-
-		switch m := i.(type) {
-		case metrics.Meter:
-			str := fmt.Sprintf(
-				"<h3>meter %s</h3>\n"+
-					"count: %9d<br>"+
-					"  1-min rate:  %12.2f\n"+
-					"  5-min rate:  %12.2f\n"+
-					"15-min rate: %12.2f\n"+
-					"  mean rate:   %12.2f\n",
-				name,
-				m.Count(),
-				m.Rate1(),
-				m.Rate5(),
-				m.Rate15(),
-				m.RateMean(),
-			)
-			rates = append(rates, &rate{Name: name, Count: m.Count(), str: str})
+	if val >= 0.5 {
+		rounder = math.Ceil(intermed)
+	} else {
+		rounder = math.Floor(intermed)
+	}
+
+	return rounder / math.Pow(10, float64(prec))
+
+}
+
+func StatusServer(zones Zones) func(http.ResponseWriter, *http.Request) {
+
+	return func(w http.ResponseWriter, req *http.Request) {
+
+		tmpl := template.New("status_html")
+		tmpl, err := tmpl.Parse(string(status_html()))
+
+		if err != nil {
+			str := fmt.Sprintf("Could not parse template: %s", err)
+			io.WriteString(w, str)
+			return
 		}
-	})
 
-	sort.Sort(RatesByCount{rates})
+		tmpl.Funcs(map[string]interface{}{
+			"round": round,
+		})
 
-	for _, rate := range rates {
-		io.WriteString(w, rate.str)
-	}
+		rates := make(Rates, 0)
+
+		for name, zone := range zones {
+			count := zone.Metrics.Queries.Count()
+			rates = append(rates, &rate{Name: name, Count: count, Metrics: zone.Metrics})
+		}
 
-	io.WriteString(w, `</body></html>`)
+		sort.Sort(RatesByCount{rates})
+
+		type statusData struct {
+			Version string
+			Zones   Rates
+		}
+
+		status := statusData{
+			Version: VERSION,
+			Zones:   rates,
+		}
+
+		tmpl.Execute(w, status)
+	}
 }
 
 func httpHandler(zones Zones) {
 	http.Handle("/monitor", websocket.Handler(wsHandler))
-	http.HandleFunc("/status", StatusServer)
+	http.HandleFunc("/status", StatusServer(zones))
 	http.HandleFunc("/", MainServer)
 
 	log.Println("Starting HTTP interface on", *flaghttp)

+ 10 - 0
monitor_test.go

@@ -5,6 +5,7 @@ import (
 	"io/ioutil"
 	. "launchpad.net/gocheck"
 	"net/http"
+	"strings"
 	"time"
 )
 
@@ -32,4 +33,13 @@ func (s *MonitorSuite) TestMonitorVersion(c *C) {
 	page, _ := ioutil.ReadAll(res.Body)
 	c.Check(string(page), Matches, ".*<title>GeoDNS [0-9].*")
 
+	res, err = http.Get("http://localhost:8053/status")
+	c.Assert(err, IsNil)
+	page, _ = ioutil.ReadAll(res.Body)
+	// just check that template basically works
+
+	isOk := strings.Contains(string(page), "<html>")
+	// page has <html>
+	c.Check(isOk, Equals, true)
+
 }

+ 3 - 1
serve.go

@@ -28,7 +28,8 @@ func serve(w dns.ResponseWriter, req *dns.Msg, z *Zone) {
 		dns.TypeToString[qtype], req.MsgHdr.Id, w.RemoteAddr())
 
 	qCounter.Add(1)
-	z.Metrics.Mark(1)
+
+	z.Metrics.Queries.Mark(1)
 
 	logPrintln("Got request", req)
 
@@ -48,6 +49,7 @@ func serve(w dns.ResponseWriter, req *dns.Msg, z *Zone) {
 				case *dns.EDNS0_NSID:
 					// do stuff with e.Nsid
 				case *dns.EDNS0_SUBNET:
+					z.Metrics.EdnsQueries.Mark(1)
 					logPrintln("Got edns", e.Address, e.Family, e.SourceNetmask, e.SourceScope)
 					if e.Address != nil {
 						edns = e

+ 51 - 0
status.html

@@ -0,0 +1,51 @@
+<html><head><title>GeoDNS {{ .Version }}</title>
+<link href="//netdna.bootstrapcdn.com/twitter-bootstrap/2.3.1/css/bootstrap-combined.min.css" rel="stylesheet">
+<link href="//netdna.bootstrapcdn.com/bootswatch/2.3.1/cerulean/bootstrap.min.css" rel="stylesheet">
+<style>
+	td.zonename { font-weight: bold;  }
+</style>
+<body>
+
+    <div class="container">
+
+
+<h1>Zones</h1>
+<table class="table table-bordered table-condensed">
+<tr>
+<th></th>
+<th>Mean QPS</th>
+<th>1 minute</th>
+<th>5 minutes</th>
+<th>15 minutes</th>
+</tr>
+
+{{range  .Zones }}
+
+	<tr class="info">
+		<td colspan=5 class="zonename"><b>{{.Name}}</b></td>
+	</tr>
+
+	<tr>
+		<td>Queries</td>
+		<td>{{printf "%.2f" .Metrics.Queries.RateMean}}</td>
+		<td>{{printf "%.2f" .Metrics.Queries.Rate1}}</td>
+		<td>{{printf "%.2f" .Metrics.Queries.Rate5}}</td>
+		<td>{{printf "%.2f" .Metrics.Queries.Rate15}}</td>
+	</tr>
+
+	<tr>
+		<td>EDNS Queries</td>
+		<td>{{printf "%.2f" .Metrics.EdnsQueries.RateMean}}</td>
+		<td>{{printf "%.2f" .Metrics.EdnsQueries.Rate1}}</td>
+		<td>{{printf "%.2f" .Metrics.EdnsQueries.Rate5}}</td>
+		<td>{{printf "%.2f" .Metrics.EdnsQueries.Rate15}}</td>
+	</tr>
+
+{{end}}
+
+</table>
+
+</div>
+
+</body>
+</html>

+ 62 - 0
status.html.go

@@ -0,0 +1,62 @@
+package main
+
+import (
+	"bytes"
+	"compress/gzip"
+	"io"
+)
+
+// status_html returns raw, uncompressed file data.
+func status_html() []byte {
+	gz, err := gzip.NewReader(bytes.NewBuffer([]byte{
+0x1f,0x8b,0x08,0x00,0x00,0x09,0x6e,0x88,0x00,0xff,0x9c,0x54,
+0xc1,0x6e,0x1b,0x21,0x10,0x3d,0x9b,0xaf,0x40,0x2b,0xf5,0x68,
+0x90,0x53,0xf9,0xd2,0x62,0x4e,0x8d,0x7a,0x4a,0xd4,0x34,0x52,
+0x0f,0xbd,0xb1,0xcb,0x38,0x8b,0x8a,0xc1,0x82,0x71,0xad,0x14,
+0xf9,0xdf,0x3b,0xac,0x77,0xe3,0xca,0xa9,0x22,0xc7,0x3e,0xac,
+0xe1,0x31,0x6f,0xde,0xbc,0x59,0x66,0x55,0x8f,0x1b,0xaf,0x55,
+0x0f,0xc6,0x6a,0x85,0x0e,0x3d,0xe8,0xaf,0x10,0xbf,0xdc,0x3f,
+0xf2,0x52,0xb8,0xf8,0x01,0x29,0xbb,0x18,0xf8,0xe1,0xa0,0xe4,
+0xf1,0x90,0x29,0xef,0xc2,0x2f,0xde,0x27,0x58,0xaf,0x1a,0x29,
+0x03,0xa0,0x0d,0x46,0xb4,0x31,0x62,0xc6,0x64,0xb6,0x9d,0x0d,
+0xa2,0x8b,0x1b,0x89,0x7b,0x87,0x08,0x69,0xfe,0x72,0x20,0x6f,
+0xc4,0x47,0xb1,0x90,0x5d,0xce,0xf2,0x05,0x9b,0x53,0x64,0xeb,
+0x02,0x58,0xb1,0x71,0x44,0xcb,0xb9,0xe1,0x09,0xfc,0xaa,0xc9,
+0xf8,0xec,0x21,0xf7,0x00,0xd8,0x5c,0xaa,0x37,0x00,0x7b,0x83,
+0x5d,0x3f,0x09,0x41,0xda,0x79,0x30,0xe1,0xa4,0xf6,0xa6,0xc8,
+0xb0,0xd3,0x6c,0x86,0x56,0xfc,0x89,0x01,0x82,0xd9,0x00,0x2f,
+0x7c,0x1d,0x03,0xce,0xf7,0xe0,0x9e,0x7a,0xfc,0xc4,0xdb,0xe8,
+0xed,0x67,0xce,0x0f,0x4c,0xc9,0x31,0x5a,0xb5,0xd1,0x3e,0x6b,
+0xc6,0x38,0xfd,0x94,0x75,0xbf,0x79,0xe7,0x4d,0xce,0xab,0xa6,
+0x23,0x9a,0x21,0x5f,0x89,0x32,0x33,0xa6,0xfa,0x85,0xfe,0x49,
+0x39,0xb3,0x92,0xb4,0x62,0x0a,0x4d,0xeb,0x61,0x0a,0x3d,0x6e,
+0x86,0x27,0xf5,0x2a,0x59,0x48,0x60,0xc7,0x2d,0x65,0xb1,0x10,
+0x32,0xd8,0x5a,0x1f,0xa6,0xfa,0xe8,0x35,0xbd,0x86,0xfe,0xb8,
+0xba,0x23,0x73,0xfc,0xe1,0xdb,0xe3,0x09,0x59,0x70,0x72,0xb8,
+0x43,0x38,0x21,0xcb,0x11,0xc9,0xff,0x04,0x9d,0x63,0xb2,0xa6,
+0x66,0xa5,0x24,0x13,0x9e,0x80,0x73,0x31,0x94,0x4a,0x2f,0x9c,
+0xb1,0x19,0xa9,0x4e,0x75,0xba,0xb0,0x8e,0x54,0xc7,0x8c,0x30,
+0xcb,0xbb,0xe8,0xf3,0xd6,0x84,0xd5,0x72,0x3a,0x9d,0x5a,0xd6,
+0x68,0xd5,0xea,0x52,0xc4,0x3d,0xad,0xeb,0x95,0x69,0x6b,0xbd,
+0x96,0x68,0xa3,0xca,0x6c,0xf0,0x31,0x24,0xd1,0x0f,0x3b,0x48,
+0x6e,0xa8,0xc2,0x4e,0x50,0x29,0xdb,0xe4,0x02,0xae,0x79,0xf3,
+0x41,0xdc,0xac,0x1b,0x2e,0xee,0x00,0x93,0xeb,0xb2,0x18,0x63,
+0xc5,0x77,0x83,0x50,0x7d,0x0f,0xd7,0xf1,0x7d,0xb4,0xc5,0x15,
+0x9c,0xe5,0x35,0x3a,0x27,0xd2,0x7f,0x4c,0xdf,0xd6,0xc1,0x7a,
+0x8f,0xf3,0x5b,0x1b,0xf2,0x95,0xee,0xcf,0xa9,0x97,0x76,0xe0,
+0x9c,0x77,0x69,0x17,0x5e,0xe9,0xbd,0xea,0x44,0x29,0x10,0x6c,
+0xbd,0x59,0xb4,0xaf,0x57,0x5c,0xd7,0x15,0xcd,0xcd,0xf0,0x7f,
+0x9c,0x25,0x9a,0x91,0xfa,0x3d,0x62,0x7f,0x03,0x00,0x00,0xff,
+0xff,0x0b,0x36,0x5f,0x2b,0x97,0x04,0x00,0x00,
+	}))
+
+	if err != nil {
+		panic("Decompression failed: " + err.Error())
+	}
+
+	var b bytes.Buffer
+	io.Copy(&b, gz)
+	gz.Close()
+
+	return b.Bytes()
+}

+ 17 - 0
templates_devel.go

@@ -0,0 +1,17 @@
+// +build devel
+
+package main
+
+import (
+	"io/ioutil"
+	"log"
+)
+
+func status_html() []byte {
+	data, err := ioutil.ReadFile("status.html")
+	if err != nil {
+		log.Println("Could not open status.html", err)
+		return []byte{}
+	}
+	return data
+}

+ 5 - 2
zone.go

@@ -39,7 +39,10 @@ type Label struct {
 
 type labels map[string]*Label
 
-type Zones map[string]*Zone
+type ZoneMetrics struct {
+	Queries     *metrics.StandardMeter
+	EdnsQueries *metrics.StandardMeter
+}
 
 type Zone struct {
 	Origin    string
@@ -47,7 +50,7 @@ type Zone struct {
 	LenLabels int
 	Options   Options
 	LastRead  time.Time
-	Metrics   *metrics.StandardMeter
+	Metrics   ZoneMetrics
 }
 
 type qTypes []uint16

+ 6 - 2
zones.go

@@ -18,6 +18,8 @@ import (
 	"time"
 )
 
+type Zones map[string]*Zone
+
 func zonesReader(dirName string, zones Zones) {
 	for {
 		zonesReadDir(dirName, zones)
@@ -30,8 +32,10 @@ func addHandler(zones Zones, name string, config *Zone) {
 	if ok {
 		config.Metrics = oldZone.Metrics
 	} else {
-		config.Metrics = metrics.NewMeter()
-		metrics.Register(config.Origin+" queries", config.Metrics)
+		config.Metrics.Queries = metrics.NewMeter()
+		config.Metrics.EdnsQueries = metrics.NewMeter()
+		metrics.Register(config.Origin+" queries", config.Metrics.Queries)
+		metrics.Register(config.Origin+" EDNS queries", config.Metrics.EdnsQueries)
 	}
 
 	zones[name] = config