Skip to content

Commit 7e0031f

Browse files
ZenitharaymericDD
andauthored
feat(disruption): Memory pressure. (#1040)
* feat(disruption): Memory pressure. * chore(test): add E2E tests, and fix CPU stress race condition. * chore(go): update vendor. * chore(ci): update licenses * chore(git): pr reviews. * chore(license): update banners. * fix(injector): fix data race in memoryStressInjector.Clean() Reading len(m.allocations) in the log message before synchronizing with the background stress goroutine caused a data race detected by -race. The goroutine could still be appending to m.allocations while Clean() read it concurrently. Move the allocation count log to after the exitCh/exitCompleted synchronization point, where the goroutine is guaranteed to have stopped. --------- Co-authored-by: aymericDD <8859832+aymericDD@users.noreply.github.com>
1 parent 7dd0bf0 commit 7e0031f

File tree

89 files changed

+3155
-9704
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

89 files changed

+3155
-9704
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,5 +56,6 @@ ebpf/builds/
5656

5757
# DS_Store
5858
.DS_Store
59+
.claude
5960

6061
.envrc

CLAUDE.md

Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
# CLAUDE.md
2+
3+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4+
5+
## Project Overview
6+
7+
Kubernetes operator for chaos engineering (Datadog). Injects systemic failures (network, CPU, disk, DNS, gRPC, container/node failure) into Kubernetes clusters at scale. Built with Kubebuilder v3 and controller-runtime.
8+
9+
## Build Commands
10+
11+
```bash
12+
make docker-build-all # Build all Docker images (manager, injector, handler)
13+
make docker-build-injector # Build injector Docker image
14+
make docker-build-handler # Build handler Docker image
15+
make docker-build-manager # Build manager Docker image
16+
make docker-build-only-all # Build all images without saving tars
17+
make manifests # Generate CRDs and RBAC manifests
18+
make generate # Generate Go code (deepcopy, etc.)
19+
make generate-mocks # Regenerate mocks (mockery v2.53.5)
20+
make clean-mocks # Remove all generated mocks
21+
make generate-disruptionlistener-protobuf # Generate disruptionlistener protobuf
22+
make generate-chaosdogfood-protobuf # Generate chaosdogfood protobuf
23+
make chaosli # Build CLI helper tool
24+
make chaosli-test # Test chaosli API portability (Docker)
25+
make godeps # go mod tidy + vendor
26+
make deps # godeps + license check
27+
make header # Check/fix license headers
28+
make header-fix # Fix missing license headers
29+
make license # Check licenses
30+
make release # Run release script (VERSION required)
31+
make update-deps # Update Python dependencies (tasks/requirements.txt)
32+
```
33+
34+
## Testing
35+
36+
```bash
37+
make test # Run all unit tests (Ginkgo v2)
38+
make test TEST_ARGS="injector" # Filter tests by package name
39+
make test TEST_ARGS="--until-it-fails" # Detect flaky tests
40+
make test GINKGO_PROCS=4 # Control parallelism
41+
make e2e-test # End-to-end tests (requires cluster)
42+
make e2e-test SKIP_DEPLOY=true # E2E tests without redeploying controller
43+
```
44+
45+
Tests use **Ginkgo v2** (BDD) with **Gomega** matchers. Coverage output: `cover.profile`.
46+
47+
## Linting and Formatting
48+
49+
```bash
50+
make lint # golangci-lint (v2.8.0)
51+
make fmt # Format Go code
52+
make vet # Go vet
53+
make spellcheck # Spell check markdown docs
54+
make spellcheck-report # Spell check with report output
55+
make spellcheck-docker # Spell check via Docker (platform-agnostic)
56+
make spellcheck-format-spelling # Sort and deduplicate .spelling file
57+
```
58+
59+
## Local Development
60+
61+
```bash
62+
make lima-all # Start local k3s cluster with controller
63+
make lima-start # Start lima cluster
64+
make lima-stop # Stop and delete lima cluster
65+
make lima-redeploy # Rebuild and redeploy to local cluster
66+
make lima-install # Install CRDs and controller into lima cluster
67+
make lima-uninstall # Uninstall CRDs and controller from lima cluster
68+
make lima-restart # Restart chaos-controller pod
69+
make lima-push-all # Push all images to lima cluster
70+
make lima-push-injector # Build and push injector image to lima
71+
make lima-push-handler # Build and push handler image to lima
72+
make lima-push-manager # Build and push manager image to lima
73+
make lima-install-cert-manager # Install cert-manager into cluster
74+
make lima-install-datadog-agent # Install Datadog agent into cluster
75+
make lima-install-demo # Install demo workloads (curl + nginx)
76+
make lima-install-longhorn # Install Longhorn StorageClass for disk throttling
77+
make lima-kubectx # Configure kubectl context for lima
78+
make lima-kubectx-clean # Remove lima references from kubectl config
79+
make minikube-load-all # Load all images into minikube
80+
make watch # Auto-rebuild on file changes
81+
make debug # Prepare for IDE debugging
82+
make run # Run controller locally
83+
```
84+
85+
## CI
86+
87+
```bash
88+
make ci-install-minikube # Install and start minikube for CI
89+
make venv # Create Python virtual environment
90+
make install-datadog-ci # Install datadog-ci binary
91+
```
92+
93+
## Tool Installation
94+
95+
```bash
96+
make install-golangci-lint # Install golangci-lint
97+
make install-controller-gen # Install controller-gen
98+
make install-mockery # Install mockery
99+
make install-helm # Install Helm
100+
make install-protobuf # Install protoc
101+
make install-kubebuilder # Install kubebuilder + setup-envtest
102+
make install-yamlfmt # Install yamlfmt
103+
make install-watchexec # Install watchexec (via brew)
104+
make install-go # Install Go (version from Makefile)
105+
```
106+
107+
## Architecture
108+
109+
Three main components, each with its own Dockerfile in `bin/`:
110+
111+
- **Manager** (`main.go`, `controllers/`): Long-running controller pod. Watches Disruption CRDs, selects targets via label selectors, creates chaos pods, manages lifecycle with finalizers. Reconciliation flow: add finalizer → compute spec hash → select targets → create chaos pods → track injection status.
112+
- **Injector** (`injector/`, `cli/injector/`): Runs as ephemeral chaos pods on target nodes. Performs actual disruption using Linux primitives (cgroups, tc, iptables, eBPF). One chaos pod per target per disruption kind.
113+
- **Handler** (`webhook/`, `cli/handler/`): Admission webhook for pod initialization-time network disruptions.
114+
115+
### CRDs (api/v1beta1/)
116+
117+
- **Disruption**: Main resource defining what failure to inject and targeting criteria
118+
- **DisruptionCron**: Scheduled/recurring disruptions
119+
- **DisruptionRollout**: Progressive disruption rollout
120+
121+
### Key Packages
122+
123+
- `controllers/` — Reconciliation controllers for Disruption, DisruptionCron, and DisruptionRollout CRDs
124+
- `targetselector/` — Target selection logic (labels, count, filters, safety nets)
125+
- `safemode/` — Safety mechanisms to prevent dangerous disruptions
126+
- `eventnotifier/` — Notifications (Slack, Datadog, HTTP)
127+
- `o11y/` — Observability (metrics, tracing, profiling for Datadog and Prometheus)
128+
- `cloudservice/` — Cloud provider integrations
129+
- `ebpf/` — eBPF programs for network disruption
130+
- `grpc/disruptionlistener/` — gRPC service for disruption events
131+
- `chart/` — Helm chart for deployment
132+
133+
### Code Generation
134+
135+
CRDs are defined in `api/v1beta1/` with kubebuilder markers. After modifying types, run `make manifests generate`. Mocks are generated with mockery into `mocks/`. Protobuf definitions live in `grpc/` and `dogfood/`.
136+
137+
## Requirements
138+
139+
- Kubernetes >= 1.16 (not 1.20.0-1.20.4)
140+
- Go 1.25.6
141+
- Docker with buildx (multi-arch: amd64, arm64)

LICENSE-3rdparty.csv

Lines changed: 0 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -414,8 +414,6 @@ github.com/miekg/dns,github.com/miekg/dns,BSD-3-Clause
414414
github.com/mitchellh/go-homedir,github.com/mitchellh/go-homedir,MIT
415415
github.com/moby/docker-image-spec,github.com/moby/docker-image-spec/specs-go/v1,Apache-2.0
416416
github.com/moby/locker,github.com/moby/locker,Apache-2.0
417-
github.com/moby/spdystream,github.com/moby/spdystream,Apache-2.0
418-
github.com/moby/spdystream,github.com/moby/spdystream/spdy,Apache-2.0
419417
github.com/moby/sys/mountinfo,github.com/moby/sys/mountinfo,Apache-2.0
420418
github.com/moby/sys/sequential,github.com/moby/sys/sequential,Apache-2.0
421419
github.com/moby/sys/signal,github.com/moby/sys/signal,Apache-2.0
@@ -426,7 +424,6 @@ github.com/moby/term,github.com/moby/term/windows,Apache-2.0
426424
github.com/modern-go/concurrent,github.com/modern-go/concurrent,Apache-2.0
427425
github.com/modern-go/reflect2,github.com/modern-go/reflect2,Apache-2.0
428426
github.com/munnerz/goautoneg,github.com/munnerz/goautoneg,BSD-3-Clause
429-
github.com/mxk/go-flowrate,github.com/mxk/go-flowrate/flowrate,BSD-3-Clause
430427
github.com/onsi/ginkgo/v2,github.com/onsi/ginkgo/v2,MIT
431428
github.com/onsi/ginkgo/v2,github.com/onsi/ginkgo/v2/config,MIT
432429
github.com/onsi/ginkgo/v2,github.com/onsi/ginkgo/v2/formatter,MIT
@@ -662,7 +659,6 @@ golang.org/x/net,golang.org/x/net/ipv4,BSD-3-Clause
662659
golang.org/x/net,golang.org/x/net/ipv6,BSD-3-Clause
663660
golang.org/x/net,golang.org/x/net/proxy,BSD-3-Clause
664661
golang.org/x/net,golang.org/x/net/trace,BSD-3-Clause
665-
golang.org/x/net,golang.org/x/net/websocket,BSD-3-Clause
666662
golang.org/x/oauth2,golang.org/x/oauth2,BSD-3-Clause
667663
golang.org/x/oauth2,golang.org/x/oauth2/internal,BSD-3-Clause
668664
golang.org/x/sync,golang.org/x/sync/errgroup,BSD-3-Clause
@@ -942,20 +938,14 @@ k8s.io/apimachinery,k8s.io/apimachinery/pkg/util/dump,Apache-2.0
942938
k8s.io/apimachinery,k8s.io/apimachinery/pkg/util/duration,Apache-2.0
943939
k8s.io/apimachinery,k8s.io/apimachinery/pkg/util/errors,Apache-2.0
944940
k8s.io/apimachinery,k8s.io/apimachinery/pkg/util/framer,Apache-2.0
945-
k8s.io/apimachinery,k8s.io/apimachinery/pkg/util/httpstream,Apache-2.0
946-
k8s.io/apimachinery,k8s.io/apimachinery/pkg/util/httpstream/spdy,Apache-2.0
947-
k8s.io/apimachinery,k8s.io/apimachinery/pkg/util/httpstream/wsstream,Apache-2.0
948941
k8s.io/apimachinery,k8s.io/apimachinery/pkg/util/intstr,Apache-2.0
949942
k8s.io/apimachinery,k8s.io/apimachinery/pkg/util/json,Apache-2.0
950943
k8s.io/apimachinery,k8s.io/apimachinery/pkg/util/managedfields,Apache-2.0
951944
k8s.io/apimachinery,k8s.io/apimachinery/pkg/util/managedfields/internal,Apache-2.0
952945
k8s.io/apimachinery,k8s.io/apimachinery/pkg/util/mergepatch,Apache-2.0
953946
k8s.io/apimachinery,k8s.io/apimachinery/pkg/util/naming,Apache-2.0
954947
k8s.io/apimachinery,k8s.io/apimachinery/pkg/util/net,Apache-2.0
955-
k8s.io/apimachinery,k8s.io/apimachinery/pkg/util/portforward,Apache-2.0
956-
k8s.io/apimachinery,k8s.io/apimachinery/pkg/util/proxy,Apache-2.0
957948
k8s.io/apimachinery,k8s.io/apimachinery/pkg/util/rand,Apache-2.0
958-
k8s.io/apimachinery,k8s.io/apimachinery/pkg/util/remotecommand,Apache-2.0
959949
k8s.io/apimachinery,k8s.io/apimachinery/pkg/util/runtime,Apache-2.0
960950
k8s.io/apimachinery,k8s.io/apimachinery/pkg/util/sets,Apache-2.0
961951
k8s.io/apimachinery,k8s.io/apimachinery/pkg/util/strategicpatch,Apache-2.0
@@ -967,7 +957,6 @@ k8s.io/apimachinery,k8s.io/apimachinery/pkg/util/yaml,Apache-2.0
967957
k8s.io/apimachinery,k8s.io/apimachinery/pkg/version,Apache-2.0
968958
k8s.io/apimachinery,k8s.io/apimachinery/pkg/watch,Apache-2.0
969959
k8s.io/apimachinery,k8s.io/apimachinery/third_party/forked/golang/json,Apache-2.0
970-
k8s.io/apimachinery,k8s.io/apimachinery/third_party/forked/golang/netutil,Apache-2.0
971960
k8s.io/apimachinery,k8s.io/apimachinery/third_party/forked/golang/reflect,Apache-2.0
972961
k8s.io/cli-runtime,k8s.io/cli-runtime/pkg/printers,Apache-2.0
973962
k8s.io/client-go,k8s.io/client-go/applyconfigurations,Apache-2.0
@@ -1295,15 +1284,11 @@ k8s.io/client-go,k8s.io/client-go/tools/pager,Apache-2.0
12951284
k8s.io/client-go,k8s.io/client-go/tools/record,Apache-2.0
12961285
k8s.io/client-go,k8s.io/client-go/tools/record/util,Apache-2.0
12971286
k8s.io/client-go,k8s.io/client-go/tools/reference,Apache-2.0
1298-
k8s.io/client-go,k8s.io/client-go/tools/remotecommand,Apache-2.0
12991287
k8s.io/client-go,k8s.io/client-go/transport,Apache-2.0
1300-
k8s.io/client-go,k8s.io/client-go/transport/spdy,Apache-2.0
1301-
k8s.io/client-go,k8s.io/client-go/transport/websocket,Apache-2.0
13021288
k8s.io/client-go,k8s.io/client-go/util/apply,Apache-2.0
13031289
k8s.io/client-go,k8s.io/client-go/util/cert,Apache-2.0
13041290
k8s.io/client-go,k8s.io/client-go/util/connrotation,Apache-2.0
13051291
k8s.io/client-go,k8s.io/client-go/util/consistencydetector,Apache-2.0
1306-
k8s.io/client-go,k8s.io/client-go/util/exec,Apache-2.0
13071292
k8s.io/client-go,k8s.io/client-go/util/flowcontrol,Apache-2.0
13081293
k8s.io/client-go,k8s.io/client-go/util/homedir,Apache-2.0
13091294
k8s.io/client-go,k8s.io/client-go/util/jsonpath,Apache-2.0

api/v1beta1/disruption_types.go

Lines changed: 23 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -76,6 +76,8 @@ type DisruptionSpec struct {
7676
// +nullable
7777
CPUPressure *CPUPressureSpec `json:"cpuPressure,omitempty"`
7878
// +nullable
79+
MemoryPressure *MemoryPressureSpec `json:"memoryPressure,omitempty"`
80+
// +nullable
7981
DiskPressure *DiskPressureSpec `json:"diskPressure,omitempty"`
8082
// +nullable
8183
DiskFailure *DiskFailureSpec `json:"diskFailure,omitempty"`
@@ -694,25 +696,25 @@ func (s DisruptionSpec) validateGlobalDisruptionScope(requireSelectors bool) (re
694696
}
695697

696698
// Rule: At least one disruption kind must be applied
697-
if s.CPUPressure == nil && s.DiskPressure == nil && s.DiskFailure == nil && s.Network == nil && s.GRPC == nil && s.DNS == nil && s.ContainerFailure == nil && s.NodeFailure == nil && s.PodReplacement == nil {
699+
if s.CPUPressure == nil && s.MemoryPressure == nil && s.DiskPressure == nil && s.DiskFailure == nil && s.Network == nil && s.GRPC == nil && s.DNS == nil && s.ContainerFailure == nil && s.NodeFailure == nil && s.PodReplacement == nil {
698700
retErr = multierror.Append(retErr, errors.New("at least one disruption kind must be specified, please read the docs to see your options"))
699701
}
700702

701703
// Rule: ContainerFailure, NodeFailure, and PodReplacement disruptions are not compatible with other failure types
702704
if s.ContainerFailure != nil {
703-
if s.CPUPressure != nil || s.DiskPressure != nil || s.DiskFailure != nil || s.Network != nil || s.GRPC != nil || s.DNS != nil || s.NodeFailure != nil || s.PodReplacement != nil {
705+
if s.CPUPressure != nil || s.MemoryPressure != nil || s.DiskPressure != nil || s.DiskFailure != nil || s.Network != nil || s.GRPC != nil || s.DNS != nil || s.NodeFailure != nil || s.PodReplacement != nil {
704706
retErr = multierror.Append(retErr, errors.New("container failure disruptions are not compatible with other disruption kinds. The container failure will remove the impact of the other disruption types"))
705707
}
706708
}
707709

708710
if s.NodeFailure != nil {
709-
if s.CPUPressure != nil || s.DiskPressure != nil || s.DiskFailure != nil || s.Network != nil || s.GRPC != nil || s.DNS != nil || s.ContainerFailure != nil || s.PodReplacement != nil {
711+
if s.CPUPressure != nil || s.MemoryPressure != nil || s.DiskPressure != nil || s.DiskFailure != nil || s.Network != nil || s.GRPC != nil || s.DNS != nil || s.ContainerFailure != nil || s.PodReplacement != nil {
710712
retErr = multierror.Append(retErr, errors.New("node failure disruptions are not compatible with other disruption kinds. The node failure will remove the impact of the other disruption types"))
711713
}
712714
}
713715

714716
if s.PodReplacement != nil {
715-
if s.CPUPressure != nil || s.DiskPressure != nil || s.DiskFailure != nil || s.Network != nil || s.GRPC != nil || s.DNS != nil || s.ContainerFailure != nil || s.NodeFailure != nil {
717+
if s.CPUPressure != nil || s.MemoryPressure != nil || s.DiskPressure != nil || s.DiskFailure != nil || s.Network != nil || s.GRPC != nil || s.DNS != nil || s.ContainerFailure != nil || s.NodeFailure != nil {
716718
retErr = multierror.Append(retErr, errors.New("pod replacement disruptions are not compatible with other disruption kinds. The pod replacement will remove the impact of the other disruption types"))
717719
}
718720
// Rule: container failure not possible if disruption is node-level
@@ -724,6 +726,7 @@ func (s DisruptionSpec) validateGlobalDisruptionScope(requireSelectors bool) (re
724726
// Rule: on init compatibility
725727
if s.OnInit {
726728
if s.CPUPressure != nil ||
729+
s.MemoryPressure != nil ||
727730
s.NodeFailure != nil ||
728731
s.PodReplacement != nil ||
729732
s.ContainerFailure != nil ||
@@ -747,6 +750,11 @@ func (s DisruptionSpec) validateGlobalDisruptionScope(requireSelectors bool) (re
747750
retErr = multierror.Append(retErr, errors.New("disk pressure disruptions apply to all containers, specifying certain containers does not isolate the disruption"))
748751
}
749752

753+
// Rule: No specificity of containers on a memory disruption
754+
if len(s.Containers) != 0 && s.MemoryPressure != nil {
755+
retErr = multierror.Append(retErr, errors.New("memory pressure disruptions apply to all containers, specifying certain containers does not isolate the disruption"))
756+
}
757+
750758
// Rule: DisruptionTrigger
751759
if s.Triggers != nil && !s.Triggers.IsZero() {
752760
if !s.Triggers.Inject.IsZero() && !s.Triggers.CreatePods.IsZero() {
@@ -772,7 +780,7 @@ func (s DisruptionSpec) validateGlobalDisruptionScope(requireSelectors bool) (re
772780
if s.Pulse != nil {
773781
if s.Pulse.ActiveDuration.Duration() > 0 || s.Pulse.DormantDuration.Duration() > 0 {
774782
if s.NodeFailure != nil || s.PodReplacement != nil || s.ContainerFailure != nil {
775-
retErr = multierror.Append(retErr, errors.New("pulse is only compatible with network, cpu pressure, disk pressure, dns, and grpc disruptions"))
783+
retErr = multierror.Append(retErr, errors.New("pulse is only compatible with network, cpu pressure, memory pressure, disk pressure, dns, and grpc disruptions"))
776784
}
777785
}
778786

@@ -824,6 +832,8 @@ func (s DisruptionSpec) DisruptionKindPicker(kind chaostypes.DisruptionKindName)
824832
disruptionKind = s.Network
825833
case chaostypes.DisruptionKindCPUPressure:
826834
disruptionKind = s.CPUPressure
835+
case chaostypes.DisruptionKindMemoryPressure:
836+
disruptionKind = s.MemoryPressure
827837
case chaostypes.DisruptionKindDiskPressure:
828838
disruptionKind = s.DiskPressure
829839
case chaostypes.DisruptionKindGRPCDisruption:
@@ -888,6 +898,10 @@ func (s DisruptionSpec) DisruptionCount() int {
888898
count++
889899
}
890900

901+
if s.MemoryPressure != nil {
902+
count++
903+
}
904+
891905
if s.ContainerFailure != nil {
892906
count++
893907
}
@@ -1060,6 +1074,10 @@ func (s DisruptionSpec) Explain() []string {
10601074
explanation = append(explanation, s.CPUPressure.Explain()...)
10611075
}
10621076

1077+
if s.MemoryPressure != nil {
1078+
explanation = append(explanation, s.MemoryPressure.Explain()...)
1079+
}
1080+
10631081
if s.DiskPressure != nil {
10641082
explanation = append(explanation, s.DiskPressure.Explain()...)
10651083
}

api/v1beta1/memory_pressure.go

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,78 @@
1+
// Unless explicitly stated otherwise all files in this repository are licensed
2+
// under the Apache License Version 2.0.
3+
// This product includes software developed at Datadog (https://www.datadoghq.com/).
4+
// Copyright 2026 Datadog, Inc.
5+
6+
package v1beta1
7+
8+
import (
9+
"fmt"
10+
"strconv"
11+
"strings"
12+
13+
"github.com/hashicorp/go-multierror"
14+
)
15+
16+
// MemoryPressureSpec represents a memory pressure disruption
17+
type MemoryPressureSpec struct {
18+
// Target memory utilization as a percentage (e.g., "76%")
19+
// +kubebuilder:validation:Required
20+
TargetPercent string `json:"targetPercent" chaos_validate:"required"`
21+
// Duration over which memory is gradually consumed (e.g., "10m")
22+
// If empty, memory is consumed immediately
23+
RampDuration DisruptionDuration `json:"rampDuration,omitempty"`
24+
}
25+
26+
// Validate validates args for the given disruption
27+
func (s *MemoryPressureSpec) Validate() (retErr error) {
28+
// Rule: targetPercent must be a valid percentage between 1 and 100
29+
pct, err := ParseTargetPercent(s.TargetPercent)
30+
if err != nil {
31+
retErr = multierror.Append(retErr, fmt.Errorf("invalid targetPercent %q: %w", s.TargetPercent, err))
32+
} else if pct < 1 || pct > 100 {
33+
retErr = multierror.Append(retErr, fmt.Errorf("targetPercent must be between 1 and 100, got %d", pct))
34+
}
35+
36+
// Rule: rampDuration must be non-negative
37+
if s.RampDuration.Duration() < 0 {
38+
retErr = multierror.Append(retErr, fmt.Errorf("rampDuration must be non-negative, got %s", s.RampDuration))
39+
}
40+
41+
return retErr
42+
}
43+
44+
// GenerateArgs generates injection or cleanup pod arguments for the given spec
45+
func (s *MemoryPressureSpec) GenerateArgs() []string {
46+
args := []string{
47+
"memory-pressure",
48+
"--target-percent", s.TargetPercent,
49+
}
50+
51+
if s.RampDuration.Duration() > 0 {
52+
args = append(args, "--ramp-duration", s.RampDuration.Duration().String())
53+
}
54+
55+
return args
56+
}
57+
58+
func (s *MemoryPressureSpec) Explain() []string {
59+
pct, _ := ParseTargetPercent(s.TargetPercent)
60+
61+
explanation := fmt.Sprintf("spec.memoryPressure will cause memory pressure on the target, by joining its cgroup and allocating memory to reach %d%% of the target's memory limit", pct)
62+
63+
if s.RampDuration.Duration() > 0 {
64+
explanation += fmt.Sprintf(", ramping up over %s.", s.RampDuration.Duration())
65+
} else {
66+
explanation += " immediately."
67+
}
68+
69+
return []string{"", explanation}
70+
}
71+
72+
// ParseTargetPercent parses a percentage string like "76%" or "76" and returns the integer value
73+
func ParseTargetPercent(s string) (int, error) {
74+
s = strings.TrimSpace(s)
75+
s = strings.TrimSuffix(s, "%")
76+
77+
return strconv.Atoi(s)
78+
}

0 commit comments

Comments
 (0)