From 6e9697ef071fceeae3fc9d1fe024034717fd47a2 Mon Sep 17 00:00:00 2001 From: Martin Polden Date: Sat, 11 Jan 2020 23:31:16 +0100 Subject: Implement basic metrics --- http/http.go | 56 ++++++++++++++++++++++++++++++++++++++++++++++++------- http/http_test.go | 8 +++++--- 2 files changed, 54 insertions(+), 10 deletions(-) (limited to 'http') diff --git a/http/http.go b/http/http.go index 053435f..3bf047c 100644 --- a/http/http.go +++ b/http/http.go @@ -33,12 +33,35 @@ type entry struct { Rcode string `json:"rcode,omitempty"` } +type summary struct { + Since string `json:"since"` + Total int64 `json:"total"` + Hijacked int64 `json:"hijacked"` +} + +type request struct { + Time string `json:"time"` + Count int64 `json:"count"` +} + +type logStats struct { + Summary summary `json:"summary"` + Requests []request `json:"requests"` +} + type httpError struct { err error Status int `json:"status"` Message string `json:"message"` } +func newHTTPError(err error) *httpError { + return &httpError{ + err: err, + Status: http.StatusInternalServerError, + } +} + // NewServer creates a new HTTP server, serving logs from the given logger and listening on addr. func NewServer(cache *cache.Cache, logger *sql.Logger, addr string) *Server { server := &http.Server{Addr: addr} @@ -55,6 +78,7 @@ func (s *Server) handler() http.Handler { r := newRouter() r.route(http.MethodGet, "/cache/v1/", s.cacheHandler) r.route(http.MethodGet, "/log/v1/", s.logHandler) + r.route(http.MethodGet, "/metric/v1/", s.metricHandler) r.route(http.MethodDelete, "/cache/v1/", s.cacheResetHandler) return r.handler() } @@ -89,18 +113,13 @@ func (s *Server) cacheResetHandler(w http.ResponseWriter, r *http.Request) (inte s.cache.Reset() return struct { Message string `json:"message"` - }{ - "Cleared cache", - }, nil + }{"Cleared cache."}, nil } func (s *Server) logHandler(w http.ResponseWriter, r *http.Request) (interface{}, *httpError) { logEntries, err := s.logger.Read(listCountFrom(r)) if err != nil { - return nil, &httpError{ - err: err, - Status: http.StatusInternalServerError, - } + return nil, newHTTPError(err) } entries := make([]entry, 0, len(logEntries)) for _, le := range logEntries { @@ -117,6 +136,29 @@ func (s *Server) logHandler(w http.ResponseWriter, r *http.Request) (interface{} return entries, nil } +func (s *Server) metricHandler(w http.ResponseWriter, r *http.Request) (interface{}, *httpError) { + stats, err := s.logger.Stats() + if err != nil { + return nil, newHTTPError(err) + } + requests := make([]request, 0, len(stats.Events)) + for _, e := range stats.Events { + requests = append(requests, request{ + Time: e.Time.Format(time.RFC3339), + Count: e.Count, + }) + } + logStats := logStats{ + Summary: summary{ + Since: stats.Since.Format(time.RFC3339), + Total: stats.Total, + Hijacked: stats.Hijacked, + }, + Requests: requests, + } + return logStats, nil +} + // Close shuts down the HTTP server. func (s *Server) Close() error { return s.server.Shutdown(context.TODO()) } diff --git a/http/http_test.go b/http/http_test.go index 7cd5a9c..901903f 100644 --- a/http/http_test.go +++ b/http/http_test.go @@ -30,11 +30,11 @@ func newA(name string, ttl uint32, ipAddr ...net.IP) *dns.Msg { } func testServer() (*httptest.Server, *Server) { - db, err := sql.New(":memory:") + sqlClient, err := sql.New(":memory:") if err != nil { panic(err) } - logger := sql.NewLogger(db, sql.LogAll, 0) + logger := sql.NewLogger(sqlClient, sql.LogAll, 0) cache := cache.New(10, nil) server := Server{logger: logger, cache: cache} return httptest.NewServer(server.handler()), &server @@ -89,6 +89,7 @@ func TestRequests(t *testing.T) { lr1 := `[{"time":"RFC3339","remote_addr":"127.0.0.254","hijacked":true,"type":"AAAA","question":"example.com.","answers":["2001:db8::1"]},` + `{"time":"RFC3339","remote_addr":"127.0.0.42","hijacked":false,"type":"A","question":"example.com.","answers":["192.0.2.101","192.0.2.100"]}]` lr2 := `[{"time":"RFC3339","remote_addr":"127.0.0.254","hijacked":true,"type":"AAAA","question":"example.com.","answers":["2001:db8::1"]}]` + mr1 := `{"summary":{"since":"RFC3339","total":2,"hijacked":1},"requests":[{"time":"RFC3339","count":2}]}` var tests = []struct { method string @@ -103,7 +104,8 @@ func TestRequests(t *testing.T) { {http.MethodGet, "/cache/v1/", cr1, 200}, {http.MethodGet, "/cache/v1/?n=foo", cr1, 200}, {http.MethodGet, "/cache/v1/?n=1", cr2, 200}, - {http.MethodDelete, "/cache/v1/", `{"message":"Cleared cache"}`, 200}, + {http.MethodDelete, "/cache/v1/", `{"message":"Cleared cache."}`, 200}, + {http.MethodGet, "/metric/v1/", mr1, 200}, } for i, tt := range tests { -- cgit v1.2.3