X Tutup
Skip to content

Commit a2ef695

Browse files
committed
Add density stress test
Running the density tool will report Pss and Rss total and per container values for shim memory usage. Values are reported in KB. ```bash containerd-stress density --count 500 INFO[0000] pulling docker.io/library/alpine:latest INFO[0000] generating spec from image {"pss":421188,"rss":2439688,"pssPerContainer":842,"rssPerContainer":4879} ``` Signed-off-by: Michael Crosby <crosbymichael@gmail.com>
1 parent 5bd99af commit a2ef695

File tree

4 files changed

+303
-0
lines changed

4 files changed

+303
-0
lines changed

cmd/containerd-stress/density.go

Lines changed: 232 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,232 @@
1+
/*
2+
Copyright The containerd Authors.
3+
4+
Licensed under the Apache License, Version 2.0 (the "License");
5+
you may not use this file except in compliance with the License.
6+
You may obtain a copy of the License at
7+
8+
http://www.apache.org/licenses/LICENSE-2.0
9+
10+
Unless required by applicable law or agreed to in writing, software
11+
distributed under the License is distributed on an "AS IS" BASIS,
12+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
See the License for the specific language governing permissions and
14+
limitations under the License.
15+
*/
16+
17+
package main
18+
19+
import (
20+
"bufio"
21+
"context"
22+
"encoding/json"
23+
"fmt"
24+
"io/ioutil"
25+
"os"
26+
"os/signal"
27+
"path/filepath"
28+
"strconv"
29+
"strings"
30+
"syscall"
31+
32+
"github.com/containerd/containerd"
33+
"github.com/containerd/containerd/cio"
34+
"github.com/containerd/containerd/containers"
35+
"github.com/containerd/containerd/namespaces"
36+
"github.com/containerd/containerd/oci"
37+
"github.com/sirupsen/logrus"
38+
"github.com/urfave/cli"
39+
)
40+
41+
var densityCommand = cli.Command{
42+
Name: "density",
43+
Usage: "stress tests density of containers running on a system",
44+
Flags: []cli.Flag{
45+
cli.IntFlag{
46+
Name: "count",
47+
Usage: "number of containers to run",
48+
Value: 10,
49+
},
50+
},
51+
Action: func(cliContext *cli.Context) error {
52+
config := config{
53+
Address: cliContext.GlobalString("address"),
54+
Duration: cliContext.GlobalDuration("duration"),
55+
Concurrency: cliContext.GlobalInt("concurrent"),
56+
Exec: cliContext.GlobalBool("exec"),
57+
JSON: cliContext.GlobalBool("json"),
58+
Metrics: cliContext.GlobalString("metrics"),
59+
}
60+
client, err := config.newClient()
61+
if err != nil {
62+
return err
63+
}
64+
defer client.Close()
65+
ctx := namespaces.WithNamespace(context.Background(), "density")
66+
if err := cleanup(ctx, client); err != nil {
67+
return err
68+
}
69+
logrus.Infof("pulling %s", imageName)
70+
image, err := client.Pull(ctx, imageName, containerd.WithPullUnpack)
71+
if err != nil {
72+
return err
73+
}
74+
logrus.Info("generating spec from image")
75+
76+
s := make(chan os.Signal, 1)
77+
signal.Notify(s, syscall.SIGTERM, syscall.SIGINT)
78+
79+
spec, err := oci.GenerateSpec(ctx, client,
80+
&containers.Container{},
81+
oci.WithImageConfig(image),
82+
oci.WithProcessArgs("sleep", "120m"),
83+
)
84+
if err != nil {
85+
return err
86+
}
87+
var (
88+
pids []uint32
89+
count = cliContext.Int("count")
90+
)
91+
loop:
92+
for i := 0; i < count+1; i++ {
93+
select {
94+
case <-s:
95+
break loop
96+
default:
97+
id := fmt.Sprintf("density-%d", i)
98+
spec.Linux.CgroupsPath = filepath.Join("/", "density", id)
99+
100+
c, err := client.NewContainer(ctx, id,
101+
containerd.WithNewSnapshot(id, image),
102+
containerd.WithSpec(spec, oci.WithUsername("games")),
103+
)
104+
if err != nil {
105+
return err
106+
}
107+
defer c.Delete(ctx, containerd.WithSnapshotCleanup)
108+
109+
t, err := c.NewTask(ctx, cio.NullIO)
110+
if err != nil {
111+
return err
112+
}
113+
defer t.Delete(ctx, containerd.WithProcessKill)
114+
if err := t.Start(ctx); err != nil {
115+
return err
116+
}
117+
pids = append(pids, t.Pid())
118+
}
119+
}
120+
var results struct {
121+
PSS int `json:"pss"`
122+
RSS int `json:"rss"`
123+
PSSPerContainer int `json:"pssPerContainer"`
124+
RSSPerContainer int `json:"rssPerContainer"`
125+
}
126+
127+
for _, pid := range pids {
128+
shimPid, err := getppid(int(pid))
129+
if err != nil {
130+
return err
131+
}
132+
smaps, err := getMaps(shimPid)
133+
if err != nil {
134+
return err
135+
}
136+
results.RSS += smaps["Rss:"]
137+
results.PSS += smaps["Pss:"]
138+
}
139+
results.PSSPerContainer = results.PSS / count
140+
results.RSSPerContainer = results.RSS / count
141+
142+
return json.NewEncoder(os.Stdout).Encode(results)
143+
},
144+
}
145+
146+
func getMaps(pid int) (map[string]int, error) {
147+
f, err := os.Open(fmt.Sprintf("/proc/%d/smaps", pid))
148+
if err != nil {
149+
return nil, err
150+
}
151+
defer f.Close()
152+
var (
153+
smaps = make(map[string]int)
154+
s = bufio.NewScanner(f)
155+
)
156+
for s.Scan() {
157+
var (
158+
fields = strings.Fields(s.Text())
159+
name = fields[0]
160+
)
161+
if len(fields) < 2 {
162+
continue
163+
}
164+
n, err := strconv.Atoi(fields[1])
165+
if err != nil {
166+
continue
167+
}
168+
smaps[name] += n
169+
}
170+
if err := s.Err(); err != nil {
171+
return nil, err
172+
}
173+
return smaps, nil
174+
}
175+
176+
func getppid(pid int) (int, error) {
177+
bytes, err := ioutil.ReadFile(filepath.Join("/proc", strconv.Itoa(pid), "stat"))
178+
if err != nil {
179+
return 0, err
180+
}
181+
s, err := parseStat(string(bytes))
182+
if err != nil {
183+
return 0, err
184+
}
185+
return int(s.PPID), nil
186+
}
187+
188+
// Stat represents the information from /proc/[pid]/stat, as
189+
// described in proc(5) with names based on the /proc/[pid]/status
190+
// fields.
191+
type Stat struct {
192+
// PID is the process ID.
193+
PID uint
194+
195+
// Name is the command run by the process.
196+
Name string
197+
198+
// StartTime is the number of clock ticks after system boot (since
199+
// Linux 2.6).
200+
StartTime uint64
201+
// Parent process ID.
202+
PPID uint
203+
}
204+
205+
func parseStat(data string) (stat Stat, err error) {
206+
// From proc(5), field 2 could contain space and is inside `(` and `)`.
207+
// The following is an example:
208+
// 89653 (gunicorn: maste) S 89630 89653 89653 0 -1 4194560 29689 28896 0 3 146 32 76 19 20 0 1 0 2971844 52965376 3920 18446744073709551615 1 1 0 0 0 0 0 16781312 137447943 0 0 0 17 1 0 0 0 0 0 0 0 0 0 0 0 0 0
209+
i := strings.LastIndex(data, ")")
210+
if i <= 2 || i >= len(data)-1 {
211+
return stat, fmt.Errorf("invalid stat data: %q", data)
212+
}
213+
214+
parts := strings.SplitN(data[:i], "(", 2)
215+
if len(parts) != 2 {
216+
return stat, fmt.Errorf("invalid stat data: %q", data)
217+
}
218+
219+
stat.Name = parts[1]
220+
_, err = fmt.Sscanf(parts[0], "%d", &stat.PID)
221+
if err != nil {
222+
return stat, err
223+
}
224+
225+
// parts indexes should be offset by 3 from the field number given
226+
// proc(5), because parts is zero-indexed and we've removed fields
227+
// one (PID) and two (Name) in the paren-split.
228+
parts = strings.Split(data[i+2:], " ")
229+
fmt.Sscanf(parts[22-3], "%d", &stat.StartTime)
230+
fmt.Sscanf(parts[4-3], "%d", &stat.PPID)
231+
return stat, nil
232+
}

cmd/containerd-stress/main.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,11 @@ func init() {
5555
binarySizeGauge = ns.NewLabeledGauge("binary_size", "Binary size of compiled binaries", metrics.Bytes, "name")
5656
errCounter = ns.NewLabeledCounter("errors", "Errors encountered running the stress tests", "err")
5757
metrics.Register(ns)
58+
59+
// set higher ulimits
60+
if err := setRlimit(); err != nil {
61+
panic(err)
62+
}
5863
}
5964

6065
type run struct {
@@ -150,6 +155,9 @@ func main() {
150155
}
151156
return nil
152157
}
158+
app.Commands = []cli.Command{
159+
densityCommand,
160+
}
153161
app.Action = func(context *cli.Context) error {
154162
config := config{
155163
Address: context.GlobalString("address"),
Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
// +build !windows
2+
3+
/*
4+
Copyright The containerd Authors.
5+
6+
Licensed under the Apache License, Version 2.0 (the "License");
7+
you may not use this file except in compliance with the License.
8+
You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing, software
13+
distributed under the License is distributed on an "AS IS" BASIS,
14+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
See the License for the specific language governing permissions and
16+
limitations under the License.
17+
*/
18+
19+
package main
20+
21+
import (
22+
"syscall"
23+
)
24+
25+
func setRlimit() error {
26+
rlimit := uint64(100000)
27+
if rlimit > 0 {
28+
var limit syscall.Rlimit
29+
if err := syscall.Getrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil {
30+
return err
31+
}
32+
if limit.Cur < rlimit {
33+
limit.Cur = rlimit
34+
if err := syscall.Setrlimit(syscall.RLIMIT_NOFILE, &limit); err != nil {
35+
return err
36+
}
37+
}
38+
}
39+
return nil
40+
}
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
// +build windows
2+
3+
/*
4+
Copyright The containerd Authors.
5+
6+
Licensed under the Apache License, Version 2.0 (the "License");
7+
you may not use this file except in compliance with the License.
8+
You may obtain a copy of the License at
9+
10+
http://www.apache.org/licenses/LICENSE-2.0
11+
12+
Unless required by applicable law or agreed to in writing, software
13+
distributed under the License is distributed on an "AS IS" BASIS,
14+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
15+
See the License for the specific language governing permissions and
16+
limitations under the License.
17+
*/
18+
19+
package main
20+
21+
func setRlimit() error {
22+
return nil
23+
}

0 commit comments

Comments
 (0)
X Tutup