-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
8298730: Refactor subsystem_file_line_contents and add docs and tests #11667
Conversation
No need for manual string handling
This avoids the callsites having to remember to skip the key in their scan_fmt
Some preliminary comments (due to @jerboaa):
There are a lot of other improvements that can be made, but I'm leaving all of these to a future RFE :-). |
👋 Welcome back jsjolen! A progress list of the required criteria for merging this PR into |
@jdksjolen The following label will be automatically applied to this pull request:
When this pull request is ready to be reviewed, an "RFR" email will be sent to the corresponding mailing list. If you would like to change these labels, use the /label pull request command. |
Webrevs
|
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.
Some initial comments. I'll run my container testing on it and will let you know.
char * CgroupV2Subsystem::cpu_cpuset_memory_nodes() { | ||
GET_CONTAINER_INFO_CPTR(cptr, _unified, "/cpuset.mems", | ||
"cpuset.mems is: %s", "%1023s", mems, 1024); | ||
return os::strdup(mems); | ||
} | ||
|
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 seems a no-op change, please remove from the patch.
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 puts cpu_period
and cpu_quota_val
next to each other, as they both read from cpu.max
in opposite order ("%*s %d"
and "%1023s %*d"
). I think it improves readability, as it's not obvious why %*d
is there otherwise. Still remove?
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.
Yes please, it's got nothing to do with this change. Feel free to move it in separate a RFE.
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.
Alright, reverted.
for (; line != nullptr; line = fgets(buf, buf_len, fp)) { | ||
char* key_substr = strstr(line, key); | ||
// key should be at beginning of line and next character space | ||
if (key_substr == line && line[key_len] == ' ') { |
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 now expects files to be space (
) separated. I'm not sure we can be sure of it. How about using isspace(line[key_len]) != 0
instead?
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.
We can't use isspace
, because it returns true for \n
. This was probably a bug in the previous implementation, as it'd match foo\nbar
as "%s %s"
.
I can only find an actual spec for the interface files for v2, which does state that space (
) is the expected separator:
https://www.kernel.org/doc/html/latest/admin-guide/cgroup-v2.html#interface-files
Even here it is specified as "whenever possible", so I guess tabs might be on the table. We could always do isspace(line[key_len]) != 0 && line[key_len] != '\n'
?
As an aside: Each of these types should ideally be implemented as separate functions, and not supported by a single monolith. It also seems like we only support 2 of these
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.
Even here it is specified as "whenever possible", so I guess tabs might be on the table. We could always do isspace(line[key_len]) != 0 && line[key_len] != '\n'?
That seems most appropriate here.
if (found_match) { | ||
return 0; | ||
} | ||
log_debug(os, container)("Type %s not found in file %s", scan_fmt, absolute_path); |
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.
Previously the log would include more information as the scan_fmt
now only includes the format of the value to read in. Perhaps we should changes this to the following for better diagnostics:
log_debug(os, container)("Type %s (key == %s) not found in file %s", scan_fmt, (key == nullptr ? "null" : key), absolute_path);
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.
Good idea!
@@ -286,7 +284,7 @@ int CgroupV1Subsystem::cpu_shares() { | |||
|
|||
char* CgroupV1Subsystem::pids_max_val() { | |||
GET_CONTAINER_INFO_CPTR(cptr, _pids, "/pids.max", | |||
"Maximum number of tasks is: %s", "%s %*d", pidsmax, 1024); | |||
"Maximum number of tasks is: %s", "%s", pidsmax, 1024); |
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.
To keep the format specifiers in sync, please use %1023s
over %s
here instead.
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.
fclose(fp); | ||
} | ||
|
||
TEST(CgroupTest, SubSystemFileLineContentsTests) { |
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.
test/hotspot/gtest/runtime/test_os_linux_cgroups.cpp
uses tag os_linux_cgroup
and underscore-separated (over camelCase) name. Could we make this consistent? Ideally, all cgroup related gtests should get selected with something like gtest:os_linux_cgroup
or gtest:cgroup
or a better name you come up with.
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.
Yeah, snake_case for Gtests is a Hotspot-ism, Gtest's documentation recommends PascalCase. I can always convert all to cgroupTest
?
Gtest seems to say this:
. Both names must be valid C++ identifiers, and they should not contain any underscores (_).
(The reason for this is that Gtest uses _ for name mangling, and so if we avoid _ then we also avoid accidental collisions)
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.
OK with cgroupTest
for all.
FWIW, container testing looks good on my end for cg v1 and cg v2 with this change. |
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've responded to and fixed the comments.
@@ -286,7 +284,7 @@ int CgroupV1Subsystem::cpu_shares() { | |||
|
|||
char* CgroupV1Subsystem::pids_max_val() { | |||
GET_CONTAINER_INFO_CPTR(cptr, _pids, "/pids.max", | |||
"Maximum number of tasks is: %s", "%s %*d", pidsmax, 1024); | |||
"Maximum number of tasks is: %s", "%s", pidsmax, 1024); |
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.
char * CgroupV2Subsystem::cpu_cpuset_memory_nodes() { | ||
GET_CONTAINER_INFO_CPTR(cptr, _unified, "/cpuset.mems", | ||
"cpuset.mems is: %s", "%1023s", mems, 1024); | ||
return os::strdup(mems); | ||
} | ||
|
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.
Alright, reverted.
fill_file(test_file, "foo\tbar"); | ||
err = subsystem_file_line_contents(&my_controller, test_file, "foo", "%s", &s); | ||
EXPECT_EQ(err, 0); | ||
EXPECT_STREQ(s, "bar") << "Incorrect!"; |
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 added your test here, @jerboaa
I missed the other two test cases you suggested, and have now added them. |
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.
Looks fine, thanks!
EXPECT_STREQ(s, "bar") << "Incorrect!"; | ||
|
||
s[0] = '\0'; | ||
fill_file(test_file, "foo\tbar"); |
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.
Since this is in the multi-line test (SubSystemFileLineContentsMultipleLines
), perhaps actually make it multi-line? Something like fill_file(test_file, "foo\tbar\nfoot car");
s[0] = '\0'; | ||
fill_file(test_file, "max 10000"); | ||
err = subsystem_file_line_contents(&my_controller, test_file, nullptr, "%s %*d", &s); | ||
EXPECT_EQ(err, 0); | ||
EXPECT_STREQ(s, "max"); | ||
|
||
x = -3; | ||
fill_file(test_file, "max 10001"); | ||
err = subsystem_file_line_contents(&my_controller, test_file, nullptr, "%*s %d", &x); | ||
EXPECT_EQ(err, 0); | ||
EXPECT_EQ(x, 10001); |
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.
These are single-line tests, perhaps move them to SubSystemFileLineContentsSingleLine
?
@jdksjolen This change now passes all automated pre-integration checks. ℹ️ This project also has non-automated pre-integration requirements. Please see the file CONTRIBUTING.md for details. After integration, the commit message for the final commit will be:
You can use pull request commands such as /summary, /contributor and /issue to adjust it as needed. At the time when this comment was updated there had been 71 new commits pushed to the
As there are no conflicts, your changes will automatically be rebased on top of these commits when integrating. If you prefer to avoid this automatic rebasing, please check the documentation for the /integrate command for further details. ➡️ To integrate this PR with the above commit message to the |
GET_CONTAINER_INFO_LINE(julong, _memory->controller(), "/memory.stat", matchline, | ||
"Hierarchical Memory Limit is: " JULONG_FORMAT, format, hier_memlimit) | ||
"Hierarchical Memory Limit is: " JULONG_FORMAT, JULONG_FORMAT, hier_memlimit) |
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.
For consistency, I think the "matchline" variable should be renamed to 'key', or just passed as a literal string in the GET_CONTAINER_INFO_LINE call.
int filelen = strlen(file); | ||
if ((filelen + strlen(filename)) > (MAXPATHLEN-1)) { | ||
log_debug(os, container)("File path too long %s, %s", file, filename); | ||
const int buf_len = MAXPATHLEN+1; |
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 is an existing bug. The contents of a line may theoretically be longer than MAXPATHLEN. It's possible to have a line like this:
somekey /some/file
Or it could just be some arbitrary values that have a very long string.
if (key_substr == line | ||
&& isspace(after_key) != 0 | ||
&& after_key != '\n') { | ||
// Skip key, skip space |
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 reading a string (scanning for "%s"
), I am not sure if cgroup allows empty values for keys. If the input is like this:
char line1[] = "key ";
char line2[] = "key";
Then this function will return OSCONTAINER_ERROR
for both cases. (This behavior is the same before and after this PR).
This might be OK (if cgroup will never have an empty value), but I think this should be documented to avoid surprises.
Also a test case is needed.
char discard[MAXPATHLEN+1]; | ||
bool found_match = false; | ||
if (c == nullptr) { | ||
log_debug(os, container)("subsystem_file_line_contents: CgroupController* is nullptr"); |
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.
The function name subsystem_file_line_contents
is a bit vague. Maybe it should be changed to something like parse_subsystem_file()
?
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'd like for a future RFE to split this function into two (with more reasonable names), separating the two parsing cases.
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 think the current changeset is good. The remaining issues are pre-existing and can be fixed in future RFEs.
Last test addition added a type issue with regards to signedness ( |
/integrate Time to integrate :-). |
Going to push as commit 500c3c1.
Your commit was automatically rebased without conflicts. |
@jdksjolen Pushed as commit 500c3c1. 💡 You may see a message that your pull request was closed with unmerged commits. This can be safely ignored. |
Hi!
Citing the ticket directly:
The function subsystem_file_line_contents does the simple job of parsing lines in a file, but does so in a fairly complex way. This function can be simplified. The function also has a surprising API, as you need to know that in one case the format string needs to take 2 specifiers, instead of 1.
Some more context:
subsystem_file_line_contents
either parses files that look like this:Called as:
subsystem_file_line_contents(ctrl, fname, nullptr, "%s")
Or like this:
Called as:
subsystem_file_line_contents(ctrl, fname, "key1", "%s %s")
The API for the key/value case is changed to:
subsystem_file_line_contents(ctrl, fname, "key1", "%s")
. Note:"%s"
, not"%s %s"
.Progress
Issue
Reviewers
Reviewing
Using
git
Checkout this PR locally:
$ git fetch https://git.openjdk.org/jdk pull/11667/head:pull/11667
$ git checkout pull/11667
Update a local copy of the PR:
$ git checkout pull/11667
$ git pull https://git.openjdk.org/jdk pull/11667/head
Using Skara CLI tools
Checkout this PR locally:
$ git pr checkout 11667
View PR using the GUI difftool:
$ git pr show -t 11667
Using diff file
Download this PR as a diff file:
https://git.openjdk.org/jdk/pull/11667.diff