mirror of
https://github.com/Buriburizaem0n/nezha_domains.git
synced 2026-09-21 18:50:13 +00:00
test(agentcompat): add exact stress scenario
Co-authored-by: naiba/CloudCode <hi+cloudcode@nai.ba>
This commit is contained in:
@@ -0,0 +1,188 @@
|
||||
//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()
|
||||
}
|
||||
Reference in New Issue
Block a user