-
Notifications
You must be signed in to change notification settings - Fork 470
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
Conversation
if err != nil { | ||
return err | ||
} | ||
_, err = fmt.Fprintf(statsFile, `total_cache 1029980160 | ||
if _, err = fmt.Fprintf(statsFile, `total_cache 1029980160 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit:
_, err = ....
return err
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed
inactiveRe: regexp.MustCompile(`total_inactive_file (\d+)`), | ||
} | ||
|
||
c := newCgroupsMemoryLimitChecker(testFiles, 95) | ||
_, err := c.isLimitExceeded() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: inline. Same below
if _, err := ... ; err == nil {
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Done.
_, err := c.isLimitExceeded() | ||
if err == nil { | ||
t.Error("Should fail open if can't read files") | ||
} | ||
|
||
err = updateFakeCgroupv1Files(c, 1000, 1000, 51) | ||
err = updateFakeCgroupFiles(c, 1000, 1000, 51) | ||
if err != nil { | ||
t.Error(err) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
When errors are not nil, there's probably no use of continuing the test, so this should be Fatal instead.
Same below/up.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I had used the wrong one by mistake.
return err | ||
} | ||
return nil | ||
} | ||
|
||
func TestCgroupsv1MemoryLimit(t *testing.T) { | ||
func TestCgroupsMemoryLimit(t *testing.T) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This test basically has duplicate code for 3 subtests. You can just describe each as a test-case and de-duplicate the code. (Sorry overlooked on the first review).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
// it creates a trivialLimitChecker that does no checks. | ||
// 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 { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
As I've mentioned in original review where this was introduced, please return concrete type here, as returning interfaces are not idiomatic in Go. You can change this function to:
func newLimitChecker(conf *Config) (cgroupsMemoryLimitChecker, error) {
if c := newCgroupsMemoryLimitChecker(cgroupsV1MemoryFiles, conf.MemoryLimitPercent); isSupported(c) {
return c, nil
}
if c := newCgroupsMemoryLimitChecker(cgroupsV2MemoryFiles, conf.MemoryLimitPercent); isSupported(c) {
return c, nil
}
return nil, errNotSupported
}
Then in the call site you can call this, check with error.Is(err, errNotSupported) to instantiate trivialLimitChecker{} when needed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Refactored it as suggested.
_, err := c.isLimitExceeded() | ||
if err == nil { | ||
t.Error("Should fail open if can't read files") | ||
func TestCgroupsFailIfCantOpen(t *testing.T) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
func TestCgroupsv1MemoryLimit(t *testing.T) {
for _, tc := range []struct {
desc string
inactive int
want bool
} {
{
desc: "limit should be exceeded",
inactive: 51,
want: true,
},
{
desc: "limit should not be exceeded",
inactive: 51,
want: false,
},
} {
t.Run(tc.desc, func(t *testing.T) {
dir := makeCgroupsTestDir(t.TempDir())
if err := updateFakeCgroupFiles(c, 1000, 1000, tc.inactive); err != nil {
t.Fatal("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)
}
}
})
}
}
You can either also have TestCgroupsFailIfCantOpen as another case in the test, or leave as is.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can see examples of table-driven tests at: https://github.com/OffchainLabs/nitro/blob/ac0a2de65e86b008ff10ded1f7b003ca01737315/arbnode/dataposter/storage_test.go
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, I'll use this pattern more often. Took your code largely as suggested.
} | ||
if exceeded { | ||
t.Error("Expected under limit") | ||
t.Fatal("Expected under limit") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Doesn't matter too much in this case, but once you change this to being table-driven tests, this should be Errorf.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks, that's interesting to know. I had always followed the opposite pattern (fail fast), but I'll try to use this where it makes sense.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for addressing the comments.
return newHttpServer(srv, newLimitChecker(conf)), nil | ||
var c limitChecker | ||
var err error | ||
c, err = newCgroupsMemoryLimitCheckerIfSupported(conf) |
There was a problem hiding this comment.
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) ?
There was a problem hiding this comment.
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.
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) { |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
testFiles := makeCgroupsTestDir(t.TempDir()) | ||
c := newCgroupsMemoryLimitChecker(testFiles, 95) | ||
var err error | ||
if _, err = c.isLimitExceeded(); err == nil { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nit: same here, you don't need to define err before, you can just do
if _, err := ... ; err == nil {
}
It's more concise and limits the scope of err variable (less error prone in general that way for consequent error checks)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed.
If the
--node.resource-mgmt.mem-limit-percent
command line option is set, now Nitro will auto-detect whether memory stats can be checked using cgroups v2 or v1, instead of only supporting v1.Testing done
Same testing was done as in #1738 after resetting my system to use cgroups v2:
Additionally confirmed that cgroups v2 was correctly detected by checking log messages: