Skip to content
quarry Artifact [372ffc48a5]
Login

Artifact [372ffc48a5]

Artifact 372ffc48a527c329a47b12a596c3dbbbd8c6c92ac04eb161e337d657920a4d9d:


package api

import (
	"encoding/json"
	"strings"
	"testing"
	"time"

	"craftdesign.group/quarry/internal/obs"
)

// The memory verdict: quiet under the target, a finding over it that
// says by how much, for how long and how much is unexplained, and a
// warning for heavy swap alone.
func TestMemoryFindings(t *testing.T) {
	const gib = 1 << 30
	quiet := opsMemory{Memory: obs.Memory{Heap: 2 * gib, Limit: 7 * gib, Cap: 9 * gib}}
	if f := memoryFindings(quiet); len(f) != 0 {
		t.Fatalf("FAIL: a heap under its target is a finding: %+v", f)
	}
	over := opsMemory{Memory: obs.Memory{Heap: 10 * gib, Limit: 7 * gib, Cap: 9 * gib, Swap: 3 * gib,
		OverSince: time.Now().Add(-6 * time.Hour)}, Unaccounted: 9 * gib}
	f := memoryFindings(over)
	if len(f) != 1 || f[0].Severity != "bad" || f[0].About != "memory" {
		t.Fatalf("FAIL: a heap over its target: %+v", f)
	}
	for _, want := range []string{"1.4×", "6h", "3.0 GB swapped", "9.0 GB of it not accounted"} {
		if !strings.Contains(f[0].Text, want) {
			t.Errorf("FAIL: the finding does not say %q: %s", want, f[0].Text)
		}
	}
	nearCap := opsMemory{Memory: obs.Memory{Heap: 8_800 << 20, Cap: 9 * gib}}
	if f := memoryFindings(nearCap); len(f) != 1 || f[0].Severity != "bad" {
		t.Fatalf("FAIL: a heap at 95%% of the cap with no target set: %+v", f)
	}
	swapped := opsMemory{Memory: obs.Memory{Heap: 2 * gib, Limit: 7 * gib, Swap: 2 * gib}}
	if f := memoryFindings(swapped); len(f) != 1 || f[0].Severity != "warn" {
		t.Fatalf("FAIL: heavy swap under the target: %+v", f)
	}
	t.Log("ok: quiet, over target, near cap, swap")
}

// Each warning kind becomes its own finding, keyed by kind, and the
// payload carries the data fields.
func TestMemoryWarningsAreFindings(t *testing.T) {
	m := opsMemory{Memory: obs.Memory{Heap: 1 << 30, Warnings: []obs.MemWarning{
		{Kind: obs.WarnKindAnon, Value: 7 << 30, Threshold: obs.WarnAnon, Since: time.Now().Add(-20 * time.Minute)},
		{Kind: obs.WarnKindGrowth, Value: 400 << 20, Threshold: obs.WarnGrowthPerHour},
		{Kind: obs.WarnKindHost, Value: 1 << 30, Threshold: obs.WarnHostAvailable},
		{Kind: obs.WarnKindSwap, Value: 950, Threshold: 900},
		{Kind: obs.WarnKindCgroup, Value: 8 << 30, Threshold: 8 << 30},
	}}}
	got := map[string]string{}
	for _, f := range memoryFindings(m) {
		got[f.Key] = f.Severity
		if f.About != "memory" || f.Figure == "" {
			t.Errorf("FAIL: finding %s: about %q figure %q", f.Key, f.About, f.Figure)
		}
	}
	for _, k := range []string{obs.WarnKindAnon, obs.WarnKindGrowth, obs.WarnKindHost, obs.WarnKindSwap, obs.WarnKindCgroup} {
		if got["memory:"+k] == "" {
			t.Errorf("FAIL: no finding for kind %s: %v", k, got)
		}
	}
	if got["memory:"+obs.WarnKindGrowth] != "warn" {
		t.Errorf("FAIL: growth should be a warn: %v", got)
	}
	if f := memoryFindings(opsMemory{}); len(f) != 0 {
		t.Errorf("FAIL: no warnings, got findings %+v", f)
	}
	// Wire: the status payload has the literal keys.
	b, err := json.Marshal(opsMemoryNow())
	if err != nil {
		t.Fatal(err)
	}
	var raw map[string]json.RawMessage
	if err := json.Unmarshal(b, &raw); err != nil {
		t.Fatal(err)
	}
	if _, ok := raw["warnings"]; !ok {
		t.Errorf("FAIL: ops.status memory has no warnings key: %s", b)
	}
	if !t.Failed() {
		t.Log("ok")
	}
}