Skip to content

Commit 9e2f4ac

Browse files
aledbfroboquat
authored andcommitted
Fix check logic for I/O limits
1 parent 5ba78ec commit 9e2f4ac

File tree

4 files changed

+97
-69
lines changed

4 files changed

+97
-69
lines changed

components/ws-daemon/go.mod

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@ require (
3535
)
3636

3737
require (
38+
github.com/hashicorp/golang-lru v0.5.1
3839
github.com/heptiolabs/healthcheck v0.0.0-20211123025425-613501dd5deb
3940
github.com/opencontainers/runc v1.1.0
4041
)

components/ws-daemon/go.sum

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -501,6 +501,7 @@ github.com/hashicorp/go-uuid v1.0.0/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/b
501501
github.com/hashicorp/go-uuid v1.0.1/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro=
502502
github.com/hashicorp/go.net v0.0.1/go.mod h1:hjKkEWcCURg++eb33jQU7oqQcI9XDCnUzHA0oac0k90=
503503
github.com/hashicorp/golang-lru v0.5.0/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
504+
github.com/hashicorp/golang-lru v0.5.1 h1:0hERBMJE1eitiLkihrMvRVBYAkpHzc/J3QdDN+dAcgU=
504505
github.com/hashicorp/golang-lru v0.5.1/go.mod h1:/m3WP610KZHVQ1SGc6re/UDhFvYD7pJ4Ao+sR/qLZy8=
505506
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
506507
github.com/hashicorp/logutils v1.0.0/go.mod h1:QIAnNjmIWmVIIkWDTG1z5v++HQmx9WQRO+LraFDTW64=

components/ws-daemon/pkg/cgroup/plugin_iolimit_v1.go

Lines changed: 90 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -14,13 +14,17 @@ import (
1414
"sync"
1515

1616
"github.com/gitpod-io/gitpod/common-go/log"
17+
lru "github.com/hashicorp/golang-lru"
1718
"golang.org/x/xerrors"
1819
)
1920

2021
type IOLimiterV1 struct {
2122
limits limits
2223

23-
cond *sync.Cond
24+
cond *sync.Cond
25+
cache *lru.Cache
26+
27+
devices []string
2428
}
2529

2630
type limits struct {
@@ -30,16 +34,20 @@ type limits struct {
3034
ReadIOPS int64
3135
}
3236

33-
func NewIOLimiterV1(writeBytesPerSecond, readBytesPerSecond, writeIOPs, readIOPs int64) *IOLimiterV1 {
34-
return &IOLimiterV1{
35-
limits: limits{
36-
WriteBytesPerSecond: writeBytesPerSecond,
37-
ReadBytesPerSecond: readBytesPerSecond,
38-
WriteIOPS: writeIOPs,
39-
ReadIOPS: readIOPs,
40-
},
41-
cond: sync.NewCond(&sync.Mutex{}),
37+
func NewIOLimiterV1(writeBytesPerSecond, readBytesPerSecond, writeIOPs, readIOPs int64) (*IOLimiterV1, error) {
38+
cache, err := lru.New(10)
39+
if err != nil {
40+
return nil, xerrors.Errorf("cannot build I/O limit cache: %w", err)
41+
}
42+
43+
limiter := &IOLimiterV1{
44+
limits: buildLimits(writeBytesPerSecond, readBytesPerSecond, writeIOPs, readIOPs),
45+
cond: sync.NewCond(&sync.Mutex{}),
46+
cache: cache,
47+
devices: buildDevices(),
4248
}
49+
50+
return limiter, nil
4351
}
4452

4553
func (c *IOLimiterV1) Name() string { return "iolimiter-v1" }
@@ -55,80 +63,41 @@ const (
5563
// TODO: enable custom configuration
5664
var blockDevices = []string{"sd*", "md*", "nvme0n*"}
5765

58-
func (c *IOLimiterV1) Update(writeBytesPerSecond, readBytesPerSecond, writeIOPs, readIOPs int64) {
59-
c.cond.L.Lock()
60-
defer c.cond.L.Unlock()
61-
62-
c.limits = limits{
66+
func buildLimits(writeBytesPerSecond, readBytesPerSecond, writeIOPs, readIOPs int64) limits {
67+
return limits{
6368
WriteBytesPerSecond: writeBytesPerSecond,
6469
ReadBytesPerSecond: readBytesPerSecond,
6570
WriteIOPS: writeIOPs,
6671
ReadIOPS: readIOPs,
6772
}
68-
c.cond.Broadcast()
6973
}
7074

71-
func (c *IOLimiterV1) Apply(ctx context.Context, basePath, cgroupPath string) error {
72-
var devices []string
73-
for _, wc := range blockDevices {
74-
matches, err := filepath.Glob(filepath.Join("/sys/block", wc, "dev"))
75-
if err != nil {
76-
log.WithField("cgroupPath", cgroupPath).WithField("wc", wc).Warn("cannot glob devices")
77-
continue
78-
}
79-
80-
for _, dev := range matches {
81-
fc, err := os.ReadFile(dev)
82-
if err != nil {
83-
log.WithField("dev", dev).WithError(err).Error("cannot read device file")
84-
}
85-
devices = append(devices, strings.TrimSpace(string(fc)))
86-
}
87-
}
88-
log.WithField("devices", devices).Debug("found devices")
89-
90-
produceLimits := func(value int64) []string {
91-
lines := make([]string, 0, len(devices))
92-
for _, dev := range devices {
93-
lines = append(lines, fmt.Sprintf("%s %d", dev, value))
94-
}
95-
return lines
96-
}
75+
func (c *IOLimiterV1) Update(writeBytesPerSecond, readBytesPerSecond, writeIOPs, readIOPs int64) {
76+
c.cond.L.Lock()
77+
defer c.cond.L.Unlock()
9778

98-
writeLimit := func(limitPath string, content []string) error {
99-
for _, l := range content {
100-
_, err := os.Stat(limitPath)
101-
if errors.Is(err, os.ErrNotExist) {
102-
log.WithError(err).WithField("limitPath", limitPath).Debug("blkio file does not exist")
103-
continue
104-
}
79+
c.limits = buildLimits(writeBytesPerSecond, readBytesPerSecond, writeIOPs, readIOPs)
10580

106-
err = os.WriteFile(limitPath, []byte(l), 0644)
107-
if err != nil {
108-
log.WithError(err).WithField("limitPath", limitPath).WithField("line", l).Warn("cannot write limit")
109-
continue
110-
}
111-
}
112-
return nil
113-
}
81+
c.cond.Broadcast()
82+
}
11483

115-
writeLimits := func(l limits) error {
116-
base := filepath.Join(basePath, "blkio", cgroupPath)
84+
func (c *IOLimiterV1) Apply(ctx context.Context, basePath, cgroupPath string) error {
85+
baseCgroupPath := filepath.Join(basePath, "blkio", cgroupPath)
11786

118-
var err error
119-
err = writeLimit(filepath.Join(base, fnBlkioThrottleWriteBps), produceLimits(l.WriteBytesPerSecond))
87+
writeLimits := func(l limits, fromCache bool) error {
88+
err := writeLimit(filepath.Join(baseCgroupPath, fnBlkioThrottleWriteBps), c.produceLimits(fnBlkioThrottleWriteBps, l.WriteBytesPerSecond, fromCache))
12089
if err != nil {
12190
return xerrors.Errorf("cannot write %s: %w", fnBlkioThrottleWriteBps, err)
12291
}
123-
err = writeLimit(filepath.Join(base, fnBlkioThrottleReadBps), produceLimits(l.ReadBytesPerSecond))
92+
err = writeLimit(filepath.Join(baseCgroupPath, fnBlkioThrottleReadBps), c.produceLimits(fnBlkioThrottleReadBps, l.ReadBytesPerSecond, fromCache))
12493
if err != nil {
12594
return xerrors.Errorf("cannot write %s: %w", fnBlkioThrottleReadBps, err)
12695
}
127-
err = writeLimit(filepath.Join(base, fnBlkioThrottleWriteIOPS), produceLimits(l.WriteIOPS))
96+
err = writeLimit(filepath.Join(baseCgroupPath, fnBlkioThrottleWriteIOPS), c.produceLimits(fnBlkioThrottleWriteIOPS, l.WriteIOPS, fromCache))
12897
if err != nil {
12998
return xerrors.Errorf("cannot write %s: %w", fnBlkioThrottleWriteIOPS, err)
13099
}
131-
err = writeLimit(filepath.Join(base, fnBlkioThrottleReadIOPS), produceLimits(l.ReadIOPS))
100+
err = writeLimit(filepath.Join(baseCgroupPath, fnBlkioThrottleReadIOPS), c.produceLimits(fnBlkioThrottleReadIOPS, l.ReadIOPS, fromCache))
132101
if err != nil {
133102
return xerrors.Errorf("cannot write %s: %w", fnBlkioThrottleReadIOPS, err)
134103
}
@@ -151,9 +120,10 @@ func (c *IOLimiterV1) Apply(ctx context.Context, basePath, cgroupPath string) er
151120
update <- struct{}{}
152121
}
153122
}()
123+
154124
go func() {
155125
log.WithField("cgroupPath", cgroupPath).Debug("starting IO limiting")
156-
err := writeLimits(c.limits)
126+
err := writeLimits(c.limits, false)
157127
if err != nil {
158128
log.WithError(err).WithField("cgroupPath", cgroupPath).Error("cannot write IO limits")
159129
}
@@ -162,23 +132,75 @@ func (c *IOLimiterV1) Apply(ctx context.Context, basePath, cgroupPath string) er
162132
select {
163133
case <-update:
164134
log.WithField("cgroupPath", cgroupPath).WithField("l", c.limits).Debug("writing new IO limiting")
165-
err := writeLimits(c.limits)
135+
err := writeLimits(c.limits, false)
166136
if err != nil {
167137
log.WithError(err).WithField("cgroupPath", cgroupPath).Error("cannot write IO limits")
168138
}
169139
case <-ctx.Done():
170140
// Prior to shutting down though, we need to reset the IO limits to ensure we don't have
171141
// processes stuck in the uninterruptable "D" (disk sleep) state. This would prevent the
172142
// workspace pod from shutting down.
173-
err = writeLimits(limits{})
143+
err = writeLimits(limits{}, false)
174144
if err != nil {
175145
log.WithError(err).WithField("cgroupPath", cgroupPath).Error("cannot reset IO limits")
176146
}
177147
log.WithField("cgroupPath", cgroupPath).Debug("stopping IO limiting")
148+
return
178149
}
179150
}
180-
181151
}()
182152

183153
return nil
184154
}
155+
156+
func buildDevices() []string {
157+
var devices []string
158+
for _, wc := range blockDevices {
159+
matches, err := filepath.Glob(filepath.Join("/sys/block", wc, "dev"))
160+
if err != nil {
161+
log.WithField("wc", wc).Warn("cannot glob devices")
162+
continue
163+
}
164+
165+
for _, dev := range matches {
166+
fc, err := os.ReadFile(dev)
167+
if err != nil {
168+
log.WithField("dev", dev).WithError(err).Error("cannot read device file")
169+
}
170+
devices = append(devices, strings.TrimSpace(string(fc)))
171+
}
172+
}
173+
174+
return devices
175+
}
176+
177+
func (c *IOLimiterV1) produceLimits(kind string, value int64, useCache bool) []string {
178+
if val, exists := c.cache.Get(kind); exists && useCache {
179+
return val.([]string)
180+
}
181+
182+
lines := make([]string, len(c.devices))
183+
for _, dev := range c.devices {
184+
lines = append(lines, fmt.Sprintf("%s %d", dev, value))
185+
}
186+
187+
c.cache.Add(kind, lines)
188+
189+
return lines
190+
}
191+
192+
func writeLimit(limitPath string, content []string) error {
193+
_, err := os.Stat(limitPath)
194+
if errors.Is(err, os.ErrNotExist) {
195+
return nil
196+
}
197+
198+
for _, l := range content {
199+
err = os.WriteFile(limitPath, []byte(l), 0644)
200+
if err != nil {
201+
log.WithError(err).WithField("limitPath", limitPath).WithField("line", l).Warn("cannot write limit")
202+
continue
203+
}
204+
}
205+
return nil
206+
}

components/ws-daemon/pkg/daemon/daemon.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,11 @@ func NewDaemon(config Config, reg prometheus.Registerer) (*Daemon, error) {
5151
return nil, err
5252
}
5353

54-
cgroupV1IOLimiter := cgroup.NewIOLimiterV1(config.IOLimit.WriteBWPerSecond.Value(), config.IOLimit.ReadBWPerSecond.Value(), config.IOLimit.WriteIOPS, config.IOLimit.ReadIOPS)
54+
cgroupV1IOLimiter, err := cgroup.NewIOLimiterV1(config.IOLimit.WriteBWPerSecond.Value(), config.IOLimit.ReadBWPerSecond.Value(), config.IOLimit.WriteIOPS, config.IOLimit.ReadIOPS)
55+
if err != nil {
56+
return nil, err
57+
}
58+
5559
cgroupPlugins, err := cgroup.NewPluginHost(config.CPULimit.CGroupBasePath,
5660
&cgroup.CacheReclaim{},
5761
&cgroup.FuseDeviceEnablerV1{},

0 commit comments

Comments
 (0)