Files
nezha_domains/integration/agentcompat/internal/scenario/stress_artifact.go
T
2026-07-20 04:54:03 +00:00

189 lines
7.2 KiB
Go

//go:build linux && agentcompat
package scenario
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"github.com/nezhahq/nezha/integration/agentcompat/internal/contract"
"github.com/nezhahq/nezha/integration/agentcompat/internal/evidence"
)
var ErrStressArtifactInvalid = errors.New("stress artifact is invalid")
type stressArtifact struct {
Version int `json:"version"`
Profile string `json:"profile"`
Seed string `json:"seed"`
RoundCount int `json:"round_count"`
OperationCount int `json:"operation_count"`
SessionCount int `json:"session_count"`
WarmupCount int `json:"warmup_count"`
ResourceSummaryCount int `json:"resource_summary_count"`
ResourceSampleCount int `json:"resource_sample_count"`
ResourceIntervalMilliseconds int64 `json:"resource_interval_milliseconds"`
Quotas StressQuotaEvidence `json:"quotas"`
PathLockStripes int `json:"path_lock_stripes"`
DuplicateOperations int `json:"duplicate_operations"`
ResourceDrift int `json:"resource_drift"`
RSSBounded bool `json:"rss_bounded"`
Cleanup StressCleanupSummary `json:"cleanup"`
}
func stressPRFullProfile() (contract.Profile, error) {
return contract.ProfileByName(string(contract.ProfilePRFull))
}
func publishStressEvidence(root string, value StressEvidence) error {
profile, err := stressPRFullProfile()
if err != nil {
return err
}
if err := value.ValidateSuccess(profile); err != nil {
return err
}
artifact, err := newStressArtifact(value)
if err != nil {
return err
}
data, err := json.Marshal(artifact)
if err != nil {
return err
}
if evidence.Redact(string(data)) != string(data) {
return ErrStressArtifactInvalid
}
info, err := os.Lstat(root)
if errors.Is(err, os.ErrNotExist) {
if err := os.Mkdir(root, 0o700); err != nil {
return err
}
info, err = os.Lstat(root)
}
if err != nil || info.Mode()&os.ModeSymlink != 0 || !info.IsDir() || info.Mode().Perm() != 0o700 {
return ErrStressArtifactInvalid
}
path := filepath.Join(root, "stress.json")
if stale, statErr := os.Lstat(path); statErr == nil {
if stale.Mode()&os.ModeSymlink != 0 || !stale.Mode().IsRegular() {
return ErrStressArtifactInvalid
}
} else if !errors.Is(statErr, os.ErrNotExist) {
return statErr
}
temporary, err := os.CreateTemp(root, ".stress-*")
if err != nil {
return err
}
temporaryName := temporary.Name()
defer os.Remove(temporaryName)
if err := temporary.Chmod(0o600); err != nil {
_ = temporary.Close()
return err
}
if _, err := temporary.Write(data); err != nil {
_ = temporary.Close()
return err
}
if err := temporary.Close(); err != nil {
return err
}
return os.Rename(temporaryName, path)
}
func readStressEvidence(root string) (stressArtifact, error) {
var value stressArtifact
path := filepath.Join(root, "stress.json")
info, err := os.Lstat(path)
if err != nil {
return value, err
}
if info.Mode()&os.ModeSymlink != 0 || !info.Mode().IsRegular() || info.Mode().Perm() != 0o600 {
return value, ErrStressArtifactInvalid
}
data, err := os.ReadFile(path)
if err != nil {
return value, err
}
if evidence.Redact(string(data)) != string(data) {
return value, ErrStressArtifactInvalid
}
decoder := json.NewDecoder(bytes.NewReader(data))
decoder.DisallowUnknownFields()
if err := decoder.Decode(&value); err != nil {
return value, fmt.Errorf("decode stress artifact: %w", ErrStressArtifactInvalid)
}
var trailing struct{}
if err := decoder.Decode(&trailing); !errors.Is(err, io.EOF) {
return value, ErrStressArtifactInvalid
}
if err := value.validate(); err != nil {
return value, err
}
return value, nil
}
func newStressArtifact(value StressEvidence) (stressArtifact, error) {
profile, err := contract.ProfileByName(string(value.Profile))
if err != nil {
return stressArtifact{}, err
}
artifact := stressArtifact{Version: 1, Profile: string(value.Profile), Seed: fmt.Sprintf("%08x", uint64(value.Seed)), SessionCount: len(value.Sessions), WarmupCount: len(value.Warmups), ResourceSummaryCount: 1 + profile.AgentCount(), ResourceSampleCount: 5, ResourceIntervalMilliseconds: 250, Quotas: value.Quotas, PathLockStripes: value.Quotas.PathLockStripes, Cleanup: value.Cleanup}
operationIDs := make(map[StressOperationID]struct{})
resourceEvaluations := make([]StressResourceEvaluation, 0, artifact.ResourceSummaryCount)
for _, iteration := range value.Iterations {
artifact.RoundCount += len(iteration.Rounds)
for _, round := range iteration.Rounds {
artifact.OperationCount += len(round.Operations)
for _, operation := range round.Operations {
if _, duplicate := operationIDs[operation.ID]; duplicate {
artifact.DuplicateOperations++
}
operationIDs[operation.ID] = struct{}{}
}
}
for _, resource := range iteration.Resources {
evaluation, err := EvaluateStressResource(resource)
if err != nil {
return stressArtifact{}, err
}
resourceEvaluations = append(resourceEvaluations, evaluation)
}
}
resourceDrift, rssBounded, err := aggregateStressResourceEvaluations(resourceEvaluations, artifact.ResourceSummaryCount)
if err != nil {
return stressArtifact{}, err
}
artifact.ResourceDrift = resourceDrift
artifact.RSSBounded = rssBounded
return artifact, artifact.validate()
}
func aggregateStressResourceEvaluations(evaluations []StressResourceEvaluation, expectedCount int) (int, bool, error) {
if len(evaluations) != expectedCount {
return 0, false, fmt.Errorf("resource evaluations=%d want=%d: %w", len(evaluations), expectedCount, ErrStressArtifactInvalid)
}
drift := 0
rssBounded := true
for _, evaluation := range evaluations {
if evaluation.Baseline.Descendants != evaluation.End.Descendants || evaluation.Baseline.NonStdioFDs != evaluation.End.NonStdioFDs || evaluation.Baseline.TCPListeners != evaluation.End.TCPListeners || evaluation.Baseline.TCP6Listeners != evaluation.End.TCP6Listeners {
drift++
}
rssBounded = rssBounded && evaluation.RSSDeltaBytes <= evaluation.RSSLimitBytes
}
return drift, rssBounded, nil
}
func (artifact stressArtifact) validate() error {
if artifact.Version != 1 || artifact.Profile != string(contract.ProfilePRFull) || artifact.Seed != "4e5a4841" || artifact.RoundCount != 4 || artifact.OperationCount != 64 || artifact.SessionCount != 12 || artifact.WarmupCount != 8 || artifact.ResourceSummaryCount != 9 || artifact.ResourceSampleCount != 5 || artifact.ResourceIntervalMilliseconds != 250 || artifact.PathLockStripes != 1024 || artifact.DuplicateOperations != 0 || artifact.ResourceDrift != 0 || !artifact.RSSBounded || !artifact.Cleanup.Passed || artifact.Cleanup.ReceiptCount != 9 || artifact.Cleanup.FailedReceiptCount != 0 || artifact.Cleanup.ForcedCleanupCount != 0 || artifact.Cleanup.ProcessResidue != 0 || artifact.Cleanup.ProcessGroupResidue != 0 || artifact.Cleanup.WorkspaceResidue != 0 {
return ErrStressArtifactInvalid
}
return artifact.Quotas.Validate()
}