package obs
import (
"context"
"reflect"
"testing"
"time"
)
// The strip's lines are an hour of each signal, a bin a minute, kept as
// events happen rather than read back off a ring whose span depends on
// traffic. Quiet minutes are zero, not absent, and the hour is the
// hour: the sixty-first minute pushes the first one out.
func TestSignalsKeepAnHourOfMinutes(t *testing.T) {
var s signalRing
s.render(t0, false)
s.render(t0.Add(10*time.Second), true)
s.running(t0.Add(20*time.Second), 3)
s.running(t0.Add(30*time.Second), 1)
// Two quiet minutes pass; the third has one render.
s.render(t0.Add(3*time.Minute), false)
got := s.snapshot(t0.Add(3*time.Minute + 5*time.Second))
// Always the whole hour, aligned on End, so four series share an axis.
if got.Minutes != SignalMinutes || len(got.Renders) != SignalMinutes || len(got.Up) != SignalMinutes {
t.Fatalf("FAIL: %d minutes, series %d/%d, want %d: %+v", got.Minutes, len(got.Renders), len(got.Up), SignalMinutes, got)
}
if want := t0.Add(3 * time.Minute).UTC().Format(time.RFC3339); got.End != want {
t.Errorf("FAIL: the hour ends at %s, want %s", got.End, want)
}
tail := func(xs []int) []int { return xs[SignalMinutes-4:] }
if want := []int{2, 0, 0, 1}; !reflect.DeepEqual(tail(got.Renders), want) {
t.Errorf("FAIL: renders %v, want %v", tail(got.Renders), want)
}
if want := []int{1, 0, 0, 0}; !reflect.DeepEqual(tail(got.Slow), want) {
t.Errorf("FAIL: slow %v, want %v", tail(got.Slow), want)
}
// Busy is the most running at once in the minute, not a count.
if want := []int{3, 0, 0, 0}; !reflect.DeepEqual(tail(got.Busy), want) {
t.Errorf("FAIL: busy %v, want %v", tail(got.Busy), want)
}
// The quiet minutes of a running process are live, the hour before
// the first event is not: nothing was running to have been quiet.
for i, up := range got.Up {
if want := i >= SignalMinutes-4; up != want {
t.Errorf("FAIL: minute %d up=%v, want %v", i, up, want)
}
}
// A snapshot taken later fills the quiet minutes to now.
later := s.snapshot(t0.Add(70 * time.Minute))
if later.Minutes != SignalMinutes {
t.Errorf("FAIL: after 70 minutes the ring holds %d, want %d", later.Minutes, SignalMinutes)
}
if later.Renders[len(later.Renders)-1] != 0 || sumInts(later.Renders) != 0 {
t.Errorf("FAIL: the hour after the events should be empty: %v", later.Renders)
}
for i, up := range later.Up {
if !up {
t.Errorf("FAIL: minute %d of an idle but running hour is marked down", i)
}
}
}
// The default recorder feeds the ring; a private recorder in a test
// does not, so one test's renders cannot leak into another's hour.
func TestTheDefaultRecorderFeedsTheSignals(t *testing.T) {
resetSignals()
defer resetSignals()
now := time.Now()
Default.RecordRender(Render{At: now, Path: "GET /x", Elapsed: Budget + time.Millisecond})
NewRecorder(10, nil).RecordRender(Render{At: now, Path: "GET /y", Elapsed: time.Millisecond})
got := SignalsNow()
if got.Renders[len(got.Renders)-1] != 1 || got.Slow[len(got.Slow)-1] != 1 {
t.Errorf("FAIL: signals after one slow render on Default: %+v", got)
}
}
func sumInts(xs []int) int {
n := 0
for _, x := range xs {
n += x
}
return n
}
// A subprocess started under a request names the address it serves;
// one started under a background activity names that; one with neither
// names nothing rather than guessing.
func TestCauseIsTheAddressElseTheReason(t *testing.T) {
req := WithAddress(WithReason(context.Background(), ReasonWarm), "GET", "/r/fossil/timeline")
if got := Cause(req); got != "GET /r/fossil/timeline" {
t.Errorf("FAIL: a request's cause reads %q", got)
}
if got := Cause(WithReason(context.Background(), ReasonIngest)); got != ReasonIngest {
t.Errorf("FAIL: a background cause reads %q", got)
}
if got := Cause(context.Background()); got != "" {
t.Errorf("FAIL: no cause reads %q", got)
}
}