Operators coming from LibreNMS expect the same figures under a port graph. Add them: - Totals: bytes transferred in and out over the selected range (GraphResponse.totals), integrated from the average rate across the stitched raw and hourly tiers without double counting; partial coverage is flagged. - Summaries read Now / Avg / Max / 95th / Total per direction, plus an aggregate (in+out) 95th (p95.aggregate_bps, hot store only). - Utilisation as a percentage of port speed, and a "% of speed" axis. - "Compare previous period" overlay on the graph view and expanded row. - Errors and discards graphs (GET /api/v1/graph/counters), hot-store range only since they have no rollups. Packet and broadcast/multicast graphs stay disabled: those counters are not polled (DEVIATIONS 56). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_017TuKgyKve2XLFtrrXWMi2w
50 lines
1.9 KiB
Go
50 lines
1.9 KiB
Go
package api_test
|
|
|
|
import (
|
|
"net/url"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/v10networks/nmon/internal/api"
|
|
)
|
|
|
|
func countersURL(id, kind string, from, to time.Time) string {
|
|
return "/api/v1/graph/counters?iface=" + id + "&kind=" + kind + "&from=" + url.QueryEscape(iso(from)) + "&to=" + url.QueryEscape(iso(to))
|
|
}
|
|
|
|
func TestGraphCountersQueryTheErrorCountersAsRates(t *testing.T) {
|
|
e, i := newGraphEnv(t, &graphVM{hotVal: rising})
|
|
var g api.CounterGraphResponse
|
|
rec := e.getJSON(t, countersURL(i.ID, "errors", t0.Add(-time.Hour), t0), &g)
|
|
assertNoLeaks(t, "counters", rec.Body.Bytes())
|
|
if g.Kind != "errors" || g.Unit != "errors/s" || g.StepSeconds != 60 || g.Clamped || g.Iface.ID != i.ID {
|
|
t.Fatalf("response: %+v", g)
|
|
}
|
|
if len(g.Series.In) != 61 || len(g.Series.Out) != 61 || g.Series.In[60].TS != t0.Unix() {
|
|
t.Fatalf("series: %d in, %d out", len(g.Series.In), len(g.Series.Out))
|
|
}
|
|
qs := e.q.queries()
|
|
if len(qs) != 2 || qs[0].store != "hot" || qs[0].expr != `rate(ifInErrors{iface="`+i.ID+`"}[600s])` ||
|
|
qs[1].expr != `rate(ifOutErrors{iface="`+i.ID+`"}[600s])` {
|
|
t.Errorf("queries: %+v", qs)
|
|
}
|
|
}
|
|
|
|
func TestGraphCountersDiscardsAndHotOnlyClamp(t *testing.T) {
|
|
e, i := newGraphEnv(t, &graphVM{hotVal: rising})
|
|
var g api.CounterGraphResponse
|
|
e.getJSON(t, countersURL(i.ID, "discards", t0.Add(-90*24*time.Hour), t0), &g)
|
|
if g.Kind != "discards" || !g.Clamped || !g.From.After(t0.Add(-90*24*time.Hour)) {
|
|
t.Fatalf("a range older than hot retention must be clamped: %+v", g)
|
|
}
|
|
for _, q := range e.q.queries() {
|
|
if q.store != "hot" || !strings.Contains(q.expr, "Discards") {
|
|
t.Errorf("query: %+v", q)
|
|
}
|
|
}
|
|
isProblem(t, e.do("GET", countersURL(i.ID, "packets", t0.Add(-time.Hour), t0), ""), 422)
|
|
isProblem(t, e.do("GET", countersURL(i.ID, "errors", t0, t0.Add(-time.Hour)), ""), 422)
|
|
isProblem(t, e.do("GET", countersURL("01ARZ3NDEKTSV4RRFFQ69G5FAV", "errors", t0.Add(-time.Hour), t0), ""), 404)
|
|
}
|