diff options
-rw-r--r-- | README.md | 7 | ||||
-rw-r--r-- | http/http.go | 14 | ||||
-rw-r--r-- | http/http_test.go | 3 | ||||
-rw-r--r-- | sql/logger.go | 16 | ||||
-rw-r--r-- | sql/logger_test.go | 31 |
5 files changed, 65 insertions, 6 deletions
@@ -125,7 +125,7 @@ $ curl -s -XDELETE 'http://127.0.0.1:8053/cache/v1/' | jq . Metrics: ``` shell -$ curl 'http://127.0.0.1:8053/metric/v1/' | jq . +$ curl 'http://127.0.0.1:8053/metric/v1/?resolution=1m' | jq . { "summary": { "log": { @@ -151,6 +151,11 @@ Note that `log_mode = "hijacked"` or `log_mode = "all"` is required to make metrics available. Choosing `hijacked` will only produce metrics for hijacked requests. +The query parameter `resolution` controls the resolution of the data points in +`requests`. It accepts the same values as +[time.ParseDuration](https://golang.org/pkg/time/#ParseDuration) and defaults to +`1m`. + ## Port redirection Most operating systems expect to find their DNS resolver on UDP port 53. diff --git a/http/http.go b/http/http.go index 26e5b25..b7f1a86 100644 --- a/http/http.go +++ b/http/http.go @@ -113,6 +113,14 @@ func countFrom(r *http.Request) (int, error) { return n, nil } +func resolutionFrom(r *http.Request) (time.Duration, error) { + param := r.URL.Query().Get("resolution") + if param == "" { + return time.Minute, nil + } + return time.ParseDuration(param) +} + func (s *Server) cacheHandler(w http.ResponseWriter, r *http.Request) (interface{}, *httpError) { count, err := countFrom(r) if err != nil { @@ -165,7 +173,11 @@ func (s *Server) logHandler(w http.ResponseWriter, r *http.Request) (interface{} } func (s *Server) metricHandler(w http.ResponseWriter, r *http.Request) (interface{}, *httpError) { - lstats, err := s.logger.Stats() + resolution, err := resolutionFrom(r) + if err != nil { + return nil, newHTTPBadRequest(err) + } + lstats, err := s.logger.Stats(resolution) if err != nil { return nil, newHTTPError(err) } diff --git a/http/http_test.go b/http/http_test.go index a07a7c4..6fc53d8 100644 --- a/http/http_test.go +++ b/http/http_test.go @@ -105,6 +105,9 @@ func TestRequests(t *testing.T) { {http.MethodGet, "/cache/v1/?n=foo", `{"status":400,"message":"invalid value for parameter n: foo"}`, 400}, {http.MethodGet, "/cache/v1/?n=1", cr2, 200}, {http.MethodGet, "/metric/v1/", mr1, 200}, + {http.MethodGet, "/metric/v1/?resolution=1m", mr1, 200}, + {http.MethodGet, "/metric/v1/?resolution=0", mr1, 200}, + {http.MethodGet, "/metric/v1/?resolution=foo", `{"status":400,"message":"time: invalid duration foo"}`, 400}, {http.MethodDelete, "/cache/v1/", `{"message":"Cleared cache."}`, 200}, } diff --git a/sql/logger.go b/sql/logger.go index 85e9152..c27762a 100644 --- a/sql/logger.go +++ b/sql/logger.go @@ -117,18 +117,26 @@ func (l *Logger) Read(n int) ([]LogEntry, error) { return logEntries, nil } -// Stats returns logger statistics. -func (l *Logger) Stats() (LogStats, error) { +// Stats returns logger statistics. Events will be merged together according to resolution. A zero duration disables +// merging. +func (l *Logger) Stats(resolution time.Duration) (LogStats, error) { stats, err := l.client.readLogStats() if err != nil { return LogStats{}, err } events := make([]LogEvent, 0, len(stats.Events)) + var last *LogEvent for _, le := range stats.Events { - events = append(events, LogEvent{ + next := LogEvent{ Time: time.Unix(le.Time, 0).UTC(), Count: le.Count, - }) + } + if last != nil && next.Time.Before(last.Time.Add(resolution)) { + last.Count += next.Count + } else { + events = append(events, next) + last = &events[len(events)-1] + } } return LogStats{ Since: time.Unix(stats.Since, 0).UTC(), diff --git a/sql/logger_test.go b/sql/logger_test.go index 1d189c8..adc3774 100644 --- a/sql/logger_test.go +++ b/sql/logger_test.go @@ -131,3 +131,34 @@ func TestLogPruning(t *testing.T) { } } } + +func TestStats(t *testing.T) { + var tests = []struct { + interval time.Duration + resolution time.Duration + eventCount int + }{ + {time.Minute, 0, 3}, + {time.Minute, time.Second, 3}, + {time.Minute, time.Minute, 3}, + {time.Minute, time.Minute * 2, 2}, + {time.Minute, time.Minute * 3, 1}, + {time.Minute, time.Minute * 5, 1}, + } + for i, tt := range tests { + logger := NewLogger(testClient(), LogAll, time.Hour) + now := time.Now() + for i := 0; i < 3; i++ { + logger.now = func() time.Time { return now.Add(time.Duration(i) * tt.interval) } + logger.Record(net.IPv4(192, 0, 2, 100), false, 1, "example.com.", "192.0.2.1") + logger.Close() + } + stats, err := logger.Stats(tt.resolution) + if err != nil { + t.Fatal(err) + } + if got, want := len(stats.Events), tt.eventCount; got != want { + t.Errorf("#%d: len(Events) = %d, want %d", i, got, want) + } + } +} |