Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add cgroups v2 support to resourcemanager #1744

Merged
merged 11 commits into from
Sep 22, 2023
Merged
26 changes: 16 additions & 10 deletions arbnode/resourcemanager/resource_management.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ var (
limitCheckDurationHistogram = metrics.NewRegisteredHistogram("arb/rpc/limitcheck/duration", nil, metrics.NewBoundedHistogramSample())
limitCheckSuccessCounter = metrics.NewRegisteredCounter("arb/rpc/limitcheck/success", nil)
limitCheckFailureCounter = metrics.NewRegisteredCounter("arb/rpc/limitcheck/failure", nil)
errNotSupported = errors.New("not supported")
)

// Init adds the resource manager's httpServer to a custom hook in geth.
Expand All @@ -33,7 +34,15 @@ var (
func Init(conf *Config) {
if conf.MemoryLimitPercent > 0 {
node.WrapHTTPHandler = func(srv http.Handler) (http.Handler, error) {
return newHttpServer(srv, newLimitChecker(conf)), nil
var c limitChecker
var err error
c, err = newCgroupsMemoryLimitCheckerIfSupported(conf)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: any reason why not just c,err := ... ; and drop declaration of those variables (two lines above this one) ?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wanted to have c be of the limitChecker interface type so I can assign the trivialLimitChecker to it. I moved the declaration err into the same line as the limit checker creation.

if errors.Is(err, errNotSupported) {
log.Error("No method for determining memory usage and limits was discovered, disabled memory limit RPC throttling")
c = &trivialLimitChecker{}
}

return newHttpServer(srv, c), nil
}
}
}
Expand Down Expand Up @@ -95,25 +104,22 @@ func isSupported(c limitChecker) bool {
return err == nil
}

// newLimitChecker attempts to auto-discover the mechanism by which it
// can check system limits. Currently Cgroups V1 and V2 are supported.
// If no supported mechanism is discovered, it logs an error and
// fails open, ie it creates a trivialLimitChecker that does no checks.
func newLimitChecker(conf *Config) limitChecker {
// newCgroupsMemoryLimitCheckerIfSupported attempts to auto-discover whether
// Cgroups V1 or V2 is supported for checking system memory limits.
func newCgroupsMemoryLimitCheckerIfSupported(conf *Config) (*cgroupsMemoryLimitChecker, error) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nit: go tends to encourage shorter names. You can drop "IfSupported" suffix and just add in the comment above the function that this returns errNotSupported if it's not supported.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what to rename it to since newCgroupsMemoryLimitChecker is already the factory method for the CgroupsMemoryLimitChecker and this version handles auto-detecting cgroups v1 or v2.

c := newCgroupsMemoryLimitChecker(cgroupsV1MemoryFiles, conf.MemoryLimitPercent)
if isSupported(c) {
log.Info("Cgroups v1 detected, enabling memory limit RPC throttling")
return c
return c, nil
}

c = newCgroupsMemoryLimitChecker(cgroupsV2MemoryFiles, conf.MemoryLimitPercent)
if isSupported(c) {
log.Info("Cgroups v2 detected, enabling memory limit RPC throttling")
return c
return c, nil
}

log.Error("No method for determining memory usage and limits was discovered, disabled memory limit RPC throttling")
return &trivialLimitChecker{}
return nil, errNotSupported
}

// trivialLimitChecker checks no limits, so its limits are never exceeded.
Expand Down
63 changes: 32 additions & 31 deletions arbnode/resourcemanager/resource_management_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,36 +57,37 @@ func TestCgroupsFailIfCantOpen(t *testing.T) {
}
}

func TestCgroupsLimitNotExceeded(t *testing.T) {
testFiles := makeCgroupsTestDir(t.TempDir())
c := newCgroupsMemoryLimitChecker(testFiles, 95)

var err error
if err = updateFakeCgroupFiles(c, 1000, 1000, 51); err != nil {
t.Fatal(err)
}
exceeded, err := c.isLimitExceeded()
if err != nil {
t.Fatal(err)
}
if exceeded {
t.Fatal("Expected under limit")
}
}

func TestCgroupsLimitExceeded(t *testing.T) {
testFiles := makeCgroupsTestDir(t.TempDir())
c := newCgroupsMemoryLimitChecker(testFiles, 95)

var err error
if err = updateFakeCgroupFiles(c, 1000, 1000, 50); err != nil {
t.Fatal(err)
}
exceeded, err := c.isLimitExceeded()
if err != nil {
t.Fatal(err)
}
if !exceeded {
t.Fatal("Expected over limit")
func TestCgroupsMemoryLimit(t *testing.T) {
for _, tc := range []struct {
desc string
inactive int
want bool
}{
{
desc: "limit should be exceeded",
inactive: 50,
want: true,
},
{
desc: "limit should not be exceeded",
inactive: 51,
want: false,
},
} {
t.Run(tc.desc, func(t *testing.T) {
testFiles := makeCgroupsTestDir(t.TempDir())
c := newCgroupsMemoryLimitChecker(testFiles, 95)
if err := updateFakeCgroupFiles(c, 1000, 1000, tc.inactive); err != nil {
t.Fatalf("Updating cgroup files: %v", err)
}
exceeded, err := c.isLimitExceeded()
if err != nil {
t.Fatalf("Checking if limit exceeded: %v", err)
}
if exceeded != tc.want {
t.Errorf("isLimitExceeded() = %t, want %t", exceeded, tc.want)
}
},
)
}
}
Loading