Skip to content

Commit 16d0c97

Browse files
committed
Support cpusets when they are controlled by cgroup v2
1 parent ecfa743 commit 16d0c97

1 file changed

Lines changed: 64 additions & 11 deletions

File tree

misc/benchmark_mode.rb

Lines changed: 64 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -72,15 +72,16 @@ def disengage!
7272

7373
# Get thread id's of all running processes.
7474
def all_tasks
75-
(CpuSet.read(:tasks) || `ps -eo tid=`).lines.map(&:strip).map(&:to_i)
75+
# In cgroup v2 the task list doesn't necessarily include all thread id's.
76+
(CpuSet.tasks + "\n" + `ps -eo tid=`).lines.map(&:strip).map(&:to_i).uniq
7677
end
7778

7879
# Get list of all cpu numbers.
7980
def all_cpus
8081
return @all_cpus if defined?(@all_cpus)
8182

8283
# Read from root cpuset so our view is not limited by any current cpuset.
83-
@all_cpus = Helpers.list_to_ints(CpuSet.read(:cpus))
84+
@all_cpus = Helpers.list_to_ints(CpuSet.cpus)
8485

8586
# If that didn't work just guess at it.
8687
@all_cpus ||= (0 ... Etc.nprocessors).to_a
@@ -196,6 +197,15 @@ def disable
196197
class CpuSet
197198
include Sudo
198199

200+
# If cgroup v2 is mounted (systemd unified hierarchy) but not managing
201+
# cpusets we can mount them and use them in the old fashion.
202+
# If cgroup v2 is managing cpusets we can integrate with the already mounted fs.
203+
# If cpusets aren't being managed by v2 you can enable it with:
204+
# echo +cpuset | sudo tee /sys/fs/cgroup/cgroup.subtree_control
205+
# (provided you don't have a cpusets mount somewhere else).
206+
207+
CGROUP_V2_ROOT = "/sys/fs/cgroup"
208+
199209
# Man cpuset(7) says "/dev/cpuset" but mounting there may fail when /dev is a devtmpfs.
200210
DEFAULT_ROOT = "/cpusets"
201211

@@ -213,24 +223,64 @@ def self.find_root
213223
end
214224
end
215225

226+
if read("cgroup.subtree_control", CGROUP_V2_ROOT)&.split(' ')&.include?('cpuset')
227+
@cgroup_v2 = true
228+
return @root = CGROUP_V2_ROOT
229+
end
230+
216231
@root = DEFAULT_ROOT.then do |dir|
217232
Sudo.sudo("mkdir", "-p", dir) unless File.exist?(dir)
218233

219234
Sudo.sudo("mount", "-t", "cpuset", "none", dir)
220235

221-
# If the fs isn't mounted, return nil.
222-
dir if File.exist?("#{dir}/tasks")
236+
# If the fs failed to mount, return nil.
237+
dir if File.exist?(self.file_path(:tasks, dir))
223238
end
224239
end
225240

226-
def self.read(key)
227-
path = "#{find_root}/#{key}"
241+
V2_PATHS = {
242+
cpu_exclusive: "cpuset.cpus.exclusive",
243+
cpus: "cpuset.cpus",
244+
effective_cpus: "cpuset.cpus.effective",
245+
effective_mems: "cpuset.mems.effective",
246+
mems: "cpuset.mems",
247+
sched_load_balance: nil, # not available in the v2 fs
248+
tasks: "cgroup.procs",
249+
}
250+
251+
def self.file_path(name, dir = nil)
252+
dir ||= find_root
253+
name = V2_PATHS.fetch(name) if cgroup_v2?
254+
255+
return unless name
256+
257+
File.join(dir, name.to_s)
258+
end
259+
260+
def self.read(name, dir = nil)
261+
path = file_path(name, dir)
228262

229-
return unless File.exist?(path)
263+
return unless path && File.exist?(path)
230264

231265
File.read(path)
232266
end
233267

268+
def self.cgroup_v2?
269+
@cgroup_v2
270+
end
271+
272+
def self.cpus
273+
read(:effective_cpus)&.strip
274+
end
275+
276+
def self.mems
277+
read(:effective_mems)&.strip
278+
end
279+
280+
def self.tasks
281+
read(:tasks)
282+
end
283+
234284
def initialize(name, cpus: nil, tasks: [], **kwargs)
235285
@name = name
236286
@cpus = cpus
@@ -255,7 +305,7 @@ def create
255305

256306
# Both cpus and mems must be set in order to add tasks.
257307
# Default mems to the root value.
258-
@settings[:mems] ||= File.read("#{self.class.find_root}/mems").strip
308+
@settings[:mems] ||= self.class.mems
259309

260310
# Settings must be written before tasks can be added.
261311
@settings.each_pair do |key, value|
@@ -267,9 +317,12 @@ def create
267317
end
268318

269319
def write_setting(key, value, verbose: true)
320+
key_path = self.class.file_path(key, path)
321+
return unless key_path
322+
270323
# If an array is provided (for "tasks") write each item individually.
271324
Array(value).each do |item|
272-
write("#{path}/#{key}", item, verbose:)
325+
write(key_path, item, verbose:)
273326
end
274327
end
275328

@@ -278,8 +331,8 @@ def destroy
278331
return unless path && File.directory?(path)
279332

280333
# Move tasks to parent cpuset as we cannot remove a set that still has tasks.
281-
root_tasks = "#{self.class.find_root}/tasks"
282-
File.read("#{path}/tasks").lines.each do |tid|
334+
root_tasks = self.class.file_path(:tasks)
335+
File.read(self.class.file_path(:tasks, path)).lines.each do |tid|
283336
write(root_tasks, tid, verbose: false)
284337
end
285338

0 commit comments

Comments
 (0)