Repository navigation
Expand file tree
/
Copy pathsample_batch_wiring_test.go
More file actions
118 lines (114 loc) · 5.05 KB
/
Copy pathsample_batch_wiring_test.go
File metadata and controls
118 lines (114 loc) · 5.05 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
package main
import (
"os"
"path/filepath"
"regexp"
"sort"
"strings"
"testing"
"github.com/pingular/pingularity/internal/stats"
"github.com/pingular/pingularity/internal/store"
)
// A store that was never given a save interval writes every round at once,
// and everything about batched saves passes its tests on such a store. So
// whether the daemon batches at all hangs on a few lines of run(), which no
// unit test reaches. They are guarded literally, the way the restore drain is
// (TestMainWiresTheRestoreDrain).
func TestMainWiresBatchedSaving(t *testing.T) {
b, err := os.ReadFile("main.go")
if err != nil {
t.Fatal(err)
}
src := string(b)
for _, want := range []string{
"p.store.SetSaveEveryFn(set.SaveEvery)", // without it no round is ever held
"p.store.SetSaveLogFn(", // a failed save has no other way into the log the dashboard shows
"p.store.SaveBuffered(store.SaveStop)", // the save at shutdown that does not wait for the workers
"defer p.store.Close()", // and the close, which saves what that one could not
"SaveEvery: 5 * time.Minute", // on by default
"2*store.FinalSaveBudget", // Stop waits for both saves of a shutdown
`"save_every", set.SaveEvery()`, // the startup line says what is in force
"range store.SaveReasons()", // the counters are seeded from the list they are booked from
} {
if !strings.Contains(src, want) {
t.Errorf("main.go no longer has %q", want)
}
}
// The interval is wired before the load's outcome is looked at. A load
// that failed still returned a controller, and the daemon runs on it.
load := strings.Index(src, "set, err := settings.New(ctx, p.store, def, setOpts...)")
wire := strings.Index(src, "p.store.SetSaveEveryFn(set.SaveEvery)")
judge := strings.Index(src, "if errors.Is(err, settings.ErrLegacyReseal) {")
if load < 0 || judge < 0 || wire < load || wire > judge {
t.Error("the save interval is no longer wired between the settings load and the first look at its error: " +
"one of the ways out of the load would leave the daemon writing every round")
}
// The save at shutdown comes once the monitor has returned, which is
// when no more readings can arrive, and before run returns into the wait
// for the workers.
ran := strings.Index(src, "m.Run(ctx)")
saved := strings.Index(src, "p.store.SaveBuffered(store.SaveStop)")
stopped := strings.Index(src, `p.log.Info("pingularity stopped")`)
if ran < 0 || stopped < 0 || saved < ran || saved > stopped {
t.Error("the save at shutdown is no longer between the monitor's return and the end of run")
}
}
// TestSampleSaveCountersAreSeeded holds the counters the store books for
// batched saves and seedKnownCounters together, like
// TestWALTrimCountersAreSeeded and for its reason: a counter that first
// appears at 1 hides its first step from rate() and increase(), and the first
// failed save is the one to alert on.
//
// The gauge beside them is the other way round. Seeding creates counters, so
// a seeded db.sample_rows_waiting would put one name in both families of a
// scrape.
func TestSampleSaveCountersAreSeeded(t *testing.T) {
_, thisFile, ok := callerFile()
if !ok {
t.Fatal("runtime.Caller failed; cannot locate the module root")
}
root := filepath.Dir(thisFile) // this file lives at the module root
storeSrc, err := os.ReadFile(filepath.Join(root, "internal", "store", "store.go"))
if err != nil {
t.Fatalf("read store.go: %v", err)
}
recorded := regexp.MustCompile(`stats\.(?:Inc|Add)\("(db\.sample_[a-z_.]+)"`).FindAllStringSubmatch(string(storeSrc), -1)
seen := map[string]bool{}
var keys []string
for _, m := range recorded {
if k := m[1]; !seen[k] && !strings.HasSuffix(k, ".") {
seen[k] = true
keys = append(keys, k)
}
}
// The saves that went through are booked under a name built from the
// reason, which the scan above cannot read.
if !strings.Contains(string(storeSrc), `stats.Inc("db.sample_saves." + string(reason))`) {
t.Fatal("internal/store no longer books a save as db.sample_saves.<reason>: this guard is reading the wrong text")
}
for _, r := range store.SaveReasons() {
keys = append(keys, "db.sample_saves."+string(r))
}
sort.Strings(keys)
if len(keys) < 4+len(store.SaveReasons()) {
t.Fatalf("found %v in internal/store/store.go, want the four fixed counters and one for every reason: "+
"either they were removed, or the call shape changed and this guard now passes on anything", keys)
}
stats.ResetForTest()
seedKnownCounters()
snap := stats.Lifetime()
for _, k := range keys {
if v, ok := snap.Counters[k]; !ok || v != 0 {
t.Errorf("%s is %d, present %v after seeding; want it present at 0", k, v, ok)
}
}
gauges := regexp.MustCompile(`stats\.Set\("(db\.sample_[a-z_.]+)"`).FindAllStringSubmatch(string(storeSrc), -1)
if len(gauges) == 0 {
t.Fatal("found no db.sample_* gauge in internal/store/store.go")
}
for _, m := range gauges {
if _, ok := snap.Counters[m[1]]; ok {
t.Errorf("%s is a gauge and seedKnownCounters seeds it as a counter: a scrape would carry the name in both families", m[1])
}
}
}