aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--README.md7
-rw-r--r--http/http.go14
-rw-r--r--http/http_test.go3
-rw-r--r--sql/logger.go16
-rw-r--r--sql/logger_test.go31
5 files changed, 65 insertions, 6 deletions
diff --git a/README.md b/README.md
index 8874e5c..98bf055 100644
--- a/README.md
+++ b/README.md
@@ -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)
+ }
+ }
+}