internal/waste/meters_test.go
Rama main · réplica de lectura del repositorio original.
package waste
import (
"context"
"errors"
"strings"
"sync"
"testing"
"time"
"github.com/sophotechlabs/spinoza/internal/api"
)
type fakeSampler struct {
metrics api.Metrics
}
func (fs fakeSampler) Metrics(context.Context) api.Metrics {
return fs.metrics
}
type fakeHistorian struct {
mu sync.Mutex
byPod map[string]api.MetricHistory
err error
spans []time.Duration
}
func (fh *fakeHistorian) asked() []time.Duration {
fh.mu.Lock()
defer fh.mu.Unlock()
return append([]time.Duration{}, fh.spans...)
}
func (fh *fakeHistorian) MetricHistory(
_ context.Context,
namespace, pod string,
span time.Duration,
) (api.MetricHistory, error) {
fh.mu.Lock()
fh.spans = append(fh.spans, span)
fh.mu.Unlock()
if fh.err != nil {
return api.MetricHistory{}, fh.err
}
history, ok := fh.byPod[namespace+"/"+pod]
if !ok {
return api.MetricHistory{}, errors.New("no history for " + namespace + "/" + pod)
}
return history, nil
}
func pointsOf(values ...float64) []api.MetricPoint {
out := make([]api.MetricPoint, 0, len(values))
for at, value := range values {
out = append(out, api.MetricPoint{At: int64(at), Value: value})
}
return out
}
func TestTheLiveMeterNamesMetricsServerAndTheMomentItRead(t *testing.T) {
meter := RightNow(fakeSampler{metrics: api.Metrics{Pods: map[string]api.ResourceUsage{
"shop/web-1": {CPUMilli: 120, MemoryMi: 256},
}}})
reading, err := meter.Usage(t.Context(), []Ref{{Namespace: "shop", Name: "web-1"}})
if err != nil {
t.Fatalf("usage: %v", err)
}
if reading.Source != liveSource || reading.Window != liveWindow {
t.Errorf("source = %q, window = %q, want the live reading named", reading.Source, reading.Window)
}
if reading.Pods["shop/web-1"].CPUMilli != 120 {
t.Errorf("usage = %+v, want what metrics-server said", reading.Pods["shop/web-1"])
}
}
func TestTheLiveMeterRefusesRatherThanAnswerFromAFailedRead(t *testing.T) {
for _, one := range []struct {
name string
metrics api.Metrics
want string
}{
{
name: "metrics-server reported an error",
metrics: api.Metrics{Error: "pods.metrics.k8s.io: the server could not find the requested resource"},
want: "could not find",
},
{
name: "metrics-server knew none of the pods",
metrics: api.Metrics{Pods: map[string]api.ResourceUsage{"other/thing": {CPUMilli: 1}}},
want: "named none of the pods",
},
} {
t.Run(one.name, func(t *testing.T) {
meter := RightNow(fakeSampler{metrics: one.metrics})
_, err := meter.Usage(t.Context(), []Ref{{Namespace: "shop", Name: "web-1"}})
if err == nil {
t.Fatal("err = nil, want the read refused so another source is tried")
}
if !strings.Contains(err.Error(), one.want) {
t.Errorf("err = %q, want it to contain %q", err, one.want)
}
})
}
}
func TestTheWindowMeterAveragesTheHistoryOfEachPod(t *testing.T) {
historian := &fakeHistorian{byPod: map[string]api.MetricHistory{
"shop/web-1": {
Source: "monitoring/prometheus:9090 (https)",
CPU: pointsOf(0.1, 0.3),
Memory: pointsOf(100*bytesPerMi, 300*bytesPerMi),
},
}}
meter := OverAWindow(historian, 30*time.Minute)
reading, err := meter.Usage(t.Context(), []Ref{{Namespace: "shop", Name: "web-1"}})
if err != nil {
t.Fatalf("usage: %v", err)
}
use := reading.Pods["shop/web-1"]
if use.CPUMilli != 200 {
t.Errorf("cpu = %d millicores, want the mean of the window in millicores", use.CPUMilli)
}
if use.MemoryMi != 200 {
t.Errorf("memory = %d mebibytes, want the mean of the window in mebibytes", use.MemoryMi)
}
if reading.Window != "the last 30 minutes" {
t.Errorf("window = %q, want it said in words", reading.Window)
}
if reading.Source != "monitoring/prometheus:9090 (https)" {
t.Errorf("source = %q, want where the history came from", reading.Source)
}
asked := historian.asked()
if len(asked) != 1 || asked[0] != 30*time.Minute {
t.Errorf("spans = %v, want the span it was built with", asked)
}
}
func TestTheWindowMeterNamesWhereTheHistoryCameFrom(t *testing.T) {
for _, one := range []struct {
name string
history api.MetricHistory
want string
}{
{name: "spinoza recorded it", history: api.MetricHistory{Sampled: true}, want: sampledSource},
{name: "prometheus named a target", history: api.MetricHistory{Source: "monitoring/prom:9090"}, want: "monitoring/prom:9090"},
{name: "nothing named itself", history: api.MetricHistory{}, want: promSource},
} {
t.Run(one.name, func(t *testing.T) {
one.history.CPU = pointsOf(0.5)
historian := &fakeHistorian{byPod: map[string]api.MetricHistory{"shop/web-1": one.history}}
reading, err := OverAWindow(historian, time.Hour).Usage(t.Context(), []Ref{{Namespace: "shop", Name: "web-1"}})
if err != nil {
t.Fatalf("usage: %v", err)
}
if reading.Source != one.want {
t.Errorf("source = %q, want %q", reading.Source, one.want)
}
})
}
}
func TestTheWindowMeterDeclinesMorePodsThanItWillRead(t *testing.T) {
historian := &fakeHistorian{byPod: map[string]api.MetricHistory{}}
pods := make([]Ref, 0, windowPodBudget+1)
for at := range windowPodBudget + 1 {
pods = append(pods, Ref{Namespace: "shop", Name: "web-" + string(rune('a'+at%26))})
}
_, err := OverAWindow(historian, time.Hour).Usage(t.Context(), pods)
if err == nil {
t.Fatal("err = nil, want the window to decline rather than read one pod at a time")
}
if reads := len(historian.asked()); reads != 0 {
t.Errorf("reads = %d, want none attempted", reads)
}
if !strings.Contains(err.Error(), "ask about one namespace") {
t.Errorf("err = %q, want it to say what to do instead", err)
}
}
func TestAPodWithNoHistoryIsLeftUnmeasuredRatherThanReadAsZero(t *testing.T) {
historian := &fakeHistorian{byPod: map[string]api.MetricHistory{
"shop/web-1": {CPU: pointsOf(0.5)},
"shop/web-2": {},
}}
reading, err := OverAWindow(historian, time.Hour).Usage(t.Context(), []Ref{
{Namespace: "shop", Name: "web-1"},
{Namespace: "shop", Name: "web-2"},
})
if err != nil {
t.Fatalf("usage: %v", err)
}
if _, seen := reading.Pods["shop/web-2"]; seen {
t.Errorf("pods = %+v, want the pod with no history left out rather than read as zero", reading.Pods)
}
if reading.Pods["shop/web-1"].CPUMilli != 500 {
t.Errorf("web-1 = %+v, want the pod that had a history measured", reading.Pods["shop/web-1"])
}
}
func TestAWindowNoPodAnsweredIsRefusedSoAnotherSourceIsTried(t *testing.T) {
historian := &fakeHistorian{err: errors.New("prometheus is unavailable")}
_, err := OverAWindow(historian, time.Hour).Usage(t.Context(), []Ref{{Namespace: "shop", Name: "web-1"}})
if err == nil {
t.Fatal("err = nil, want the window refused so another source is tried")
}
if !strings.Contains(err.Error(), "prometheus is unavailable") {
t.Errorf("err = %q, want the reason prometheus gave", err)
}
}
func TestTheWindowIsSaidInWords(t *testing.T) {
for _, one := range []struct {
span time.Duration
want string
}{
{span: 0, want: "right now"},
{span: 45 * time.Second, want: "the last 45 seconds"},
{span: time.Minute, want: "the last minute"},
{span: 30 * time.Minute, want: "the last 30 minutes"},
{span: time.Hour, want: "the last hour"},
{span: 6 * time.Hour, want: "the last 6 hours"},
{span: 24 * time.Hour, want: "the last day"},
{span: 72 * time.Hour, want: "the last 3 days"},
} {
t.Run(one.want, func(t *testing.T) {
got := windowWords(one.span)
if got != one.want {
t.Errorf("windowWords(%s) = %q, want %q", one.span, got, one.want)
}
})
}
}