Skip to content
quarry Artifact [d4f7599f7f]
Login

Artifact [d4f7599f7f]

Artifact d4f7599f7f182c7c94ac2fc39f12617ed0c199d592deada8ff56e0d9e69acbbc:


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)
	}
}