Files
2026-07-20 04:54:03 +00:00

89 lines
2.9 KiB
Go

//go:build linux
package scenario
import (
"errors"
"fmt"
"github.com/nezhahq/nezha/integration/agentcompat/internal/contract"
)
var ErrStressSoakTrend = errors.New("stress soak RSS increases strictly across all iterations")
type StressRSSSeries struct {
Process StressProcessIdentity `json:"process"`
EndRSSBytes [3]uint64 `json:"end_rss_bytes"`
}
type StressSoakTrendEvidence struct {
Series []StressRSSSeries `json:"series"`
}
func ValidateStressSoakTrend(evidence StressSoakTrendEvidence) error {
if len(evidence.Series) == 0 {
return errors.New("stress soak RSS series are empty")
}
seen := make(map[string]struct{}, len(evidence.Series))
for _, series := range evidence.Series {
key := series.Process.key()
if series.Process.PID < 1 || key == ":0" || (series.Process.Kind == StressProcessAgent && series.Process.Agent.Int() < 1) {
return fmt.Errorf("process=%s: %w", key, ErrStressIdentity)
}
if _, duplicate := seen[key]; duplicate {
return fmt.Errorf("duplicate process=%s: %w", key, ErrStressIdentity)
}
seen[key] = struct{}{}
values := series.EndRSSBytes
if values[0] < values[1] && values[1] < values[2] {
return fmt.Errorf("process=%s RSS=%v: %w", key, values, ErrStressSoakTrend)
}
}
return nil
}
func ValidateStressSoakTrendForProfile(profile contract.Profile, evidence StressSoakTrendEvidence) error {
want := profile.AgentCount() + 1
if len(evidence.Series) != want {
return fmt.Errorf("soak series=%d want=%d: %w", len(evidence.Series), want, ErrStressIdentity)
}
seenDashboard := 0
seenAgents := make(map[int]struct{}, profile.AgentCount())
for _, series := range evidence.Series {
if err := validateStressSoakSeries(series); err != nil {
return err
}
switch series.Process.Kind {
case StressProcessDashboard:
seenDashboard++
case StressProcessAgent:
ordinal := series.Process.Agent.Int()
if ordinal < 1 || ordinal > profile.AgentCount() {
return fmt.Errorf("unknown agent ordinal=%d: %w", ordinal, ErrStressIdentity)
}
if _, duplicate := seenAgents[ordinal]; duplicate {
return fmt.Errorf("duplicate agent ordinal=%d: %w", ordinal, ErrStressIdentity)
}
seenAgents[ordinal] = struct{}{}
default:
return fmt.Errorf("unknown process kind=%q: %w", series.Process.Kind, ErrStressIdentity)
}
}
if seenDashboard != 1 || len(seenAgents) != profile.AgentCount() {
return fmt.Errorf("dashboard=%d agents=%d: %w", seenDashboard, len(seenAgents), ErrStressIdentity)
}
return nil
}
func validateStressSoakSeries(series StressRSSSeries) error {
key := series.Process.key()
if series.Process.PID < 1 || key == ":0" || (series.Process.Kind == StressProcessAgent && series.Process.Agent.Int() < 1) {
return fmt.Errorf("process=%s: %w", key, ErrStressIdentity)
}
values := series.EndRSSBytes
if values[0] < values[1] && values[1] < values[2] {
return fmt.Errorf("process=%s RSS=%v: %w", key, values, ErrStressSoakTrend)
}
return nil
}