mirror of
https://github.com/rcourtman/Pulse.git
synced 2026-10-27 05:57:27 +00:00
1052f21419
Additively integrate exact upstream1a094646d7while retaining starting reviewed19a8358a49. Keep reviewed UUID incident/event IDs and upstream non-wrapping atomic sequences for other memory IDs. Compose upstream shared SMART/Unraid temperatures with reviewed pending context and observation-gap interruption; pin Unraid-only continuity and both grace paths. Retain all browser, completion and security validation limits until evidenced. Change-source: pulse-maintainer
127 lines
3.8 KiB
Go
127 lines
3.8 KiB
Go
package memory
|
|
|
|
import (
|
|
"fmt"
|
|
"regexp"
|
|
"strings"
|
|
"sync"
|
|
"testing"
|
|
"time"
|
|
|
|
"github.com/rcourtman/pulse-go-rewrite/internal/alerts"
|
|
)
|
|
|
|
// TestSequencedIDsStayUniqueUnderConcurrentMinting mints from every
|
|
// package-level ID sequence on many goroutines at once. Under -race it proves
|
|
// the sequences are synchronized. The uniqueness check proves the suffix does
|
|
// not wrap: more than a thousand IDs from one generator land in the same
|
|
// wall-clock second, which the old counter%1000 suffix turned into duplicates.
|
|
func TestSequencedIDsStayUniqueUnderConcurrentMinting(t *testing.T) {
|
|
generators := []struct {
|
|
prefix string
|
|
mint func() string
|
|
uuid bool
|
|
}{
|
|
{"inc-", generateIncidentID, true},
|
|
{"inc-evt-", generateIncidentEventID, true},
|
|
{"rem-", generateRecordID, false},
|
|
{"mem-", generateMemoryID, false},
|
|
{"inc-mem-", generateIncidentMemoryID, false},
|
|
{"pat-mem-", generatePatternMemoryID, false},
|
|
}
|
|
const goroutines, perGoroutine = 16, 150
|
|
|
|
for _, gen := range generators {
|
|
t.Run(strings.TrimSuffix(gen.prefix, "-"), func(t *testing.T) {
|
|
shape := regexp.MustCompile("^" + regexp.QuoteMeta(gen.prefix) + `\d{14}-[1-9]\d*$`)
|
|
if gen.uuid {
|
|
shape = regexp.MustCompile("^" + regexp.QuoteMeta(gen.prefix) + `[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$`)
|
|
}
|
|
minted := make([][]string, goroutines)
|
|
start := make(chan struct{})
|
|
var wg sync.WaitGroup
|
|
for g := range goroutines {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
<-start
|
|
batch := make([]string, perGoroutine)
|
|
for i := range batch {
|
|
batch[i] = gen.mint()
|
|
}
|
|
minted[g] = batch
|
|
}()
|
|
}
|
|
close(start)
|
|
wg.Wait()
|
|
|
|
seen := make(map[string]bool, goroutines*perGoroutine)
|
|
for _, batch := range minted {
|
|
for _, id := range batch {
|
|
if !shape.MatchString(id) {
|
|
t.Fatalf("ID %q does not match %s", id, shape)
|
|
}
|
|
if seen[id] {
|
|
t.Fatalf("duplicate ID %q among %d concurrent mints", id, goroutines*perGoroutine)
|
|
}
|
|
seen[id] = true
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
// TestIncidentStoresMintIDsConcurrentlyAcrossTenants mirrors multi-tenant
|
|
// monitoring: each tenant monitor owns its own IncidentStore, and their alert
|
|
// checks call RecordAlertFired from separate goroutines. Each store's mutex
|
|
// covers only its own state, so the shared ID sequence must be synchronized on
|
|
// its own. Run under -race.
|
|
func TestIncidentStoresMintIDsConcurrentlyAcrossTenants(t *testing.T) {
|
|
const tenants, checkersPerTenant, alertsPerChecker = 2, 4, 50
|
|
stores := make([]*IncidentStore, tenants)
|
|
for i := range stores {
|
|
stores[i] = NewIncidentStore(IncidentStoreConfig{MaxIncidents: checkersPerTenant * alertsPerChecker})
|
|
}
|
|
|
|
startedAt := time.Now().Add(-time.Minute)
|
|
start := make(chan struct{})
|
|
var wg sync.WaitGroup
|
|
for tenant, store := range stores {
|
|
for checker := range checkersPerTenant {
|
|
wg.Add(1)
|
|
go func() {
|
|
defer wg.Done()
|
|
<-start
|
|
for n := range alertsPerChecker {
|
|
store.RecordAlertFired(&alerts.Alert{
|
|
ID: fmt.Sprintf("tenant-%d-checker-%d-alert-%d", tenant, checker, n),
|
|
Type: "cpu",
|
|
Level: alerts.AlertLevelWarning,
|
|
ResourceID: fmt.Sprintf("tenant-%d-vm-%d-%d", tenant, checker, n),
|
|
ResourceName: "vm",
|
|
StartTime: startedAt,
|
|
})
|
|
}
|
|
}()
|
|
}
|
|
}
|
|
close(start)
|
|
wg.Wait()
|
|
|
|
seen := make(map[string]bool, tenants*checkersPerTenant*alertsPerChecker)
|
|
for tenant, store := range stores {
|
|
store.mu.RLock()
|
|
incidents := len(store.incidents)
|
|
for _, shell := range store.incidents {
|
|
if seen[shell.ID] {
|
|
t.Errorf("tenant %d incident ID %q was already minted", tenant, shell.ID)
|
|
}
|
|
seen[shell.ID] = true
|
|
}
|
|
store.mu.RUnlock()
|
|
if incidents != checkersPerTenant*alertsPerChecker {
|
|
t.Errorf("tenant %d recorded %d incidents, want %d", tenant, incidents, checkersPerTenant*alertsPerChecker)
|
|
}
|
|
}
|
|
}
|