Skip to content

Commit 98786b1

Browse files
israbbaniedoakes
authored andcommitted
[core] (cgroups 1/n) Adding a sys/fs filesystem driver to perform cgroup operations. (ray-project#54898)
Ray is going to use cgroups to implement resource isolation between system processes (raylet, dashboard_agent, gcs) and application processes (workers). See ray-project#54703 for more details. If you're interested in learning more about cgroups, here are a few resources: - https://docs.kernel.org/admin-guide/cgroup-v2.html (the docs) - https://www.youtube.com/watch?v=kcnFQgg9ToY (a video) The following classes are introduced: - CgroupDriver (interface that should be agnostic of the filesystem) - SysFsCgroupDriver (implements CgroupDriver by interacting with the /sys/fs/cgroup filesystem) Ray will currently only the following cgroup controllers with their respective constraints: - cpu (with cpu.weight) - memory (with memory.min) I've tried to introduce a few patterns and best practices that are worth pointing out: ### Class Design - Returning Status/StatusOr<T> from all public methods that can fail to facilitate proper error handling in the caller. - Separating the interface from the implementation (see CgroupDriverInterface.h) to facilitate dependency injection for tests. This also future-proofs the class for if/when we implement a Systemd based cgroup driver which is what K8S does. - Separating the interface also has the added benefit of improving compile times because the interface header files are lightweight. ### Documentation - I prefer to use the doxygen format for class and function documentation. These are comments that can be recognized by doxygen tooling and be used to produce API reference docs. - Writing documentation for the appropriate level thinking about the user/consumer of the API - CgroupDriver is an abstract class, but it's what the consumer of the API will import and use. It's documentation is API focused and outlines failure modes and return types. This is akin to man pages (see man 2 open). - SysFsCgroupDriver is a class that is aware of the filesystem. This is going to talk about which files on sys/fs/cgroup are used and how it interacts with them. The important thing here is to document the specific gotchas of those files. The documentation reflects this change in level of abstraction. - Most importantly, comments are written for the future reader and not for the writer. They need to be written with the goal of being understood. ### Testing - Unit tests use no mocking and are written strictly against the public API of the class under test. - Unit tests use the naming convention UnitOfWorkConditionUnderTestExpectedOutput. --------- Signed-off-by: irabbani <[email protected]> Signed-off-by: Ibrahim Rabbani <[email protected]> Co-authored-by: Edward Oakes <[email protected]> Signed-off-by: avigyabb <[email protected]>
1 parent 50122b1 commit 98786b1

File tree

12 files changed

+1278
-1
lines changed

12 files changed

+1278
-1
lines changed

.pre-commit-config.yaml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ repos:
7373
hooks:
7474
- id: cpplint
7575
args: ["--filter=-whitespace/braces,-whitespace/line_length,-build/c++11,-build/c++14,-build/c++17,-readability/braces,-whitespace/indent_namespace,-runtime/int,-runtime/references,-build/include_order"]
76-
files: ^src/ray/(common/ray_syncer|util|raylet_client|internal|scheduling|pubsub|object_manager|rpc(?:/.*)?|raylet|core_worker)/.*\.(h|cc)$
76+
files: ^src/ray/(common/cgroup2|common/ray_syncer|util|raylet_client|internal|scheduling|pubsub|object_manager|rpc(?:/.*)?|raylet|core_worker)/.*\.(h|cc)$
7777
exclude: |
7878
(?x)^(
7979
src/ray/raylet/scheduling/.*\.(h|cc)$ |

src/ray/common/cgroup2/BUILD

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
load("//bazel:ray.bzl", "ray_cc_library")
2+
3+
ray_cc_library(
4+
name = "cgroup_driver_interface",
5+
hdrs = [
6+
"cgroup_driver_interface.h",
7+
],
8+
deps = [
9+
"//src/ray/common:status",
10+
"//src/ray/common:status_or",
11+
],
12+
)
13+
14+
ray_cc_library(
15+
name = "sysfs_cgroup_driver",
16+
srcs = ["sysfs_cgroup_driver.cc"],
17+
hdrs = [
18+
"sysfs_cgroup_driver.h",
19+
],
20+
deps = [
21+
":cgroup_driver_interface",
22+
"//src/ray/common:status",
23+
"//src/ray/common:status_or",
24+
"//src/ray/util:logging",
25+
"@com_google_absl//absl/strings",
26+
],
27+
)
Lines changed: 206 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,206 @@
1+
// Copyright 2025 The Ray Authors.
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
#pragma once
15+
16+
#include <limits>
17+
#include <string>
18+
#include <unordered_map>
19+
#include <unordered_set>
20+
#include <utility>
21+
22+
#include "ray/common/status.h"
23+
#include "ray/common/status_or.h"
24+
25+
namespace ray {
26+
27+
/**
28+
A utility that can be used to check if cgroupv2 is mounted correctly
29+
and perform cgroup operations on the system. It supports the memory and cpu controllers
30+
with the memory.min and cpu.weight constraints respectively.
31+
32+
@see The cgroupv2 documentation for more details:
33+
https://docs.kernel.org/admin-guide/cgroup-v2.html
34+
*/
35+
class CgroupDriverInterface {
36+
public:
37+
virtual ~CgroupDriverInterface() = default;
38+
39+
/**
40+
Checks to see if only cgroupv2 is enabled (known as unified mode) on the system.
41+
If cgroupv2 is not enabled, or enabled along with cgroupv1, returns Invalid
42+
with the appropriate error message.
43+
44+
@see systemd's documentation for more information about unified mode:
45+
https://github.com/systemd/systemd/blob/main/docs/CGROUP_DELEGATION.md#hierarchy-and-controller-support
46+
47+
@see K8S documentation on how to enable cgroupv2 and check if it's enabled correctly:
48+
https://kubernetes.io/docs/concepts/architecture/cgroups/#linux-distribution-cgroup-v2-support
49+
50+
@return Status::OK if successful,
51+
@return Status::Invalid if cgroupv2 is not enabled correctly.
52+
*/
53+
virtual Status CheckCgroupv2Enabled() = 0;
54+
55+
/**
56+
Checks that the cgroup is valid. See return values for details of which
57+
invariants are checked.
58+
59+
@param cgroup the absolute path of the cgroup.
60+
61+
@return Status::OK if no errors are encounted. Otherwise, one of the following errors
62+
@return Status::NotFound if the cgroup does not exist.
63+
@return Status::PermissionDenied if current user doesn't have read, write, and execute
64+
permissions.
65+
@return Status::InvalidArgument if the cgroup is not using cgroupv2.
66+
*/
67+
virtual Status CheckCgroup(const std::string &cgroup) = 0;
68+
69+
/**
70+
Creates a new cgroup at the specified path.
71+
Expects all cgroups on the path from root -> the new cgroup to already exist.
72+
Expects the user to have read, write, and execute privileges to parent cgroup.
73+
74+
@param cgroup is an absolute path to the cgroup
75+
76+
@return Status::OK if no errors are encounted. Otherwise, one of the following errors
77+
@return Status::NotFound if an ancestor cgroup does not exist.
78+
@return Status::PermissionDenied if current user doesn't have read, write, and execute
79+
permissions.
80+
@return Status::AlreadyExists if the cgroup already exists.
81+
*/
82+
virtual Status CreateCgroup(const std::string &cgroup) = 0;
83+
84+
/**
85+
Move all processes from one cgroup to another. The process must have read, write, and
86+
execute permissions for both cgroups and their lowest common ancestor.
87+
88+
@see The relevant section of the cgroup documentation for more details:
89+
https://docs.kernel.org/admin-guide/cgroup-v2.html#delegation-containment
90+
91+
@param from the absolute path of the cgroup to migrate processes out of.
92+
@param to the absolute path of the cgroup to migrate processes into.
93+
94+
@return Status::OK if no errors are encounted. Otherwise, one of the following errors
95+
@return Status::NotFound if to or from don't exist.
96+
@return Status::PermissionDenied if current user doesn't have read, write, and execute
97+
permissions.
98+
@return Status::Invalid if any errors occur while reading from and writing to
99+
cgroups.
100+
*/
101+
virtual Status MoveAllProcesses(const std::string &from, const std::string &to) = 0;
102+
103+
/**
104+
Enables an available controller on a cgroup. A controller can be enabled if the
105+
1) controller is enabled in the parent of the cgroup.
106+
2) cgroup has no children i.e. it's a leaf node.
107+
108+
@param cgroup is an absolute path to the cgroup.
109+
@param controller is the name of the controller (e.g. "cpu" and not "+cpu")
110+
111+
@see No Internal Process Constraint for more details:
112+
https://docs.kernel.org/admin-guide/cgroup-v2.html#no-internal-process-constraint
113+
114+
@return Status::OK if successful, otherwise one of the following
115+
@return Status::NotFound if the cgroup does not exist.
116+
@return Status::PermissionDenied if current user doesn't have read, write, and execute
117+
permissions for the cgroup.
118+
@return Status::InvalidArgument if the controller is not available or if cgroup is not
119+
a cgroupv2.
120+
@return Status::Invalid for all other failures.
121+
*/
122+
virtual Status EnableController(const std::string &cgroup,
123+
const std::string &controller) = 0;
124+
125+
/**
126+
Disables an enabled controller in a cgroup. A controller can be disabled if the
127+
controller is not enabled on a child cgroup.
128+
129+
@param cgroup is an absolute path to the cgroup.
130+
@param controller is the name of the controller (e.g. "cpu" and not "-cpu")
131+
132+
@return Status::OK if successful, otherwise one of the following
133+
@return Status::NotFound if the cgroup does not exist.
134+
@return Status::PermissionDenied if current user doesn't have read, write, and execute
135+
permissions for the cgroup.
136+
@return Status::InvalidArgument if the controller is not enabled
137+
or if cgroup is not a cgroupv2. Status::Invalid for all other failures.
138+
*/
139+
virtual Status DisableController(const std::string &cgroup,
140+
const std::string &controller) = 0;
141+
/**
142+
Adds a resource constraint to the cgroup. To add a constraint
143+
1) the cgroup must have the relevant controller enabled e.g. memory.min cannot be
144+
enabled if the memory controller is not enabled.
145+
2) the constraint must be supported in Ray (@see supported_constraints_).
146+
3) the constraint value must be in the correct range (@see supported_constraints_).
147+
148+
@param cgroup is an absolute path to the cgroup.
149+
@param constraint the name of the constraint.
150+
@param value the value of the constraint.
151+
152+
@return Status::OK if successful, otherwise one of the following
153+
@return Status::NotFound if the cgroup does not exist.
154+
@return Status::PermissionDenied if current user doesn't have read, write, and execute
155+
permissions for the cgroup.
156+
@return Status::InvalidArgument if the cgroup is not valid or constraint is not
157+
supported or the value not correct.
158+
*/
159+
virtual Status AddConstraint(const std::string &cgroup,
160+
const std::string &constraint,
161+
const std::string &value) = 0;
162+
/**
163+
Returns a list of controllers that can be enabled on the given cgroup based on
164+
what is enabled on the parent cgroup.
165+
166+
@param cgroup absolute path of the cgroup.
167+
168+
@return Status::OK with a set of controllers if successful, otherwise one of
169+
following
170+
@return Status::NotFound if the cgroup does not exist.
171+
@return Status::PermissionDenied if current user doesn't have read, write, and execute
172+
permissions.
173+
@return Status::InvalidArgument if the cgroup is not using cgroupv2 or malformed
174+
controllers file.
175+
*/
176+
virtual StatusOr<std::unordered_set<std::string>> GetAvailableControllers(
177+
const std::string &cgroup) = 0;
178+
179+
/**
180+
Returns a list of controllers enabled on the cgroup.
181+
182+
@param cgroup absolute path of the cgroup.
183+
184+
@return Status::OK with a set of controllers if successful, otherwise one of following
185+
@return Status::NotFound if the cgroup does not exist.
186+
@return Status::PermissionDenied if current user doesn't have read, write, and execute
187+
permissions.
188+
@return Status::InvalidArgument if the cgroup is not using cgroupv2 or malformed
189+
controllers file.
190+
*/
191+
virtual StatusOr<std::unordered_set<std::string>> GetEnabledControllers(
192+
const std::string &cgroup) = 0;
193+
194+
struct Constraint {
195+
std::pair<size_t, size_t> range;
196+
std::string controller;
197+
};
198+
199+
protected:
200+
const std::unordered_map<std::string, Constraint> supported_constraints_ = {
201+
{"cpu.weight", {{1, 10000}, "cpu"}},
202+
{"memory.min", {{0, std::numeric_limits<size_t>::max()}, "memory"}},
203+
};
204+
const std::unordered_set<std::string> supported_controllers_ = {"cpu", "memory"};
205+
};
206+
} // namespace ray

0 commit comments

Comments
 (0)