Skip to content

Commit f4ec7ca

Browse files
committed
Add Concurrent.cpu_requests that is cgroups aware.
1 parent cadc8de commit f4ec7ca

File tree

2 files changed

+56
-2
lines changed

2 files changed

+56
-2
lines changed

lib/concurrent-ruby/concurrent/utility/processor_counter.rb

Lines changed: 35 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ def initialize
1212
@processor_count = Delay.new { compute_processor_count }
1313
@physical_processor_count = Delay.new { compute_physical_processor_count }
1414
@cpu_quota = Delay.new { compute_cpu_quota }
15+
@cpu_requests = Delay.new { compute_cpu_requests }
1516
end
1617

1718
def processor_count
@@ -41,6 +42,10 @@ def cpu_quota
4142
@cpu_quota.value
4243
end
4344

45+
def cpu_requests
46+
@cpu_requests.value / 1024
47+
end
48+
4449
private
4550

4651
def compute_processor_count
@@ -98,13 +103,13 @@ def run(command)
98103

99104
def compute_cpu_quota
100105
if RbConfig::CONFIG["target_os"].include?("linux")
101-
if File.exist?("/sys/fs/cgroup/cpu.max")
106+
if cgroups_v2?
102107
# cgroups v2: https://docs.kernel.org/admin-guide/cgroup-v2.html#cpu-interface-files
103108
cpu_max = File.read("/sys/fs/cgroup/cpu.max")
104109
return nil if cpu_max.start_with?("max ") # no limit
105110
max, period = cpu_max.split.map(&:to_f)
106111
max / period
107-
elsif File.exist?("/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_quota_us")
112+
elsif cgroups_v1?
108113
# cgroups v1: https://kernel.googlesource.com/pub/scm/linux/kernel/git/glommer/memcg/+/cpu_stat/Documentation/cgroups/cpu.txt
109114
max = File.read("/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_quota_us").to_i
110115
return nil if max == 0
@@ -113,6 +118,26 @@ def compute_cpu_quota
113118
end
114119
end
115120
end
121+
122+
def compute_cpu_requests
123+
if RbConfig::CONFIG["target_os"].include?("linux")
124+
if cgroups_v2?
125+
# Ref: https://github.com/kubernetes/enhancements/tree/master/keps/sig-node/2254-cgroup-v2#phase-1-convert-from-cgroups-v1-settings-to-v2
126+
weight = File.read('/sys/fs/cgroup/cpu.weight').to_f
127+
(((weight - 1) * 262142) / 9999) + 2
128+
elsif cgroups_v1?
129+
File.read('/sys/fs/cgroup/cpu/cpu.shares').to_f
130+
end
131+
end
132+
end
133+
134+
def cgroups_v1?
135+
File.exist?("/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_quota_us")
136+
end
137+
138+
def cgroups_v2?
139+
File.exist?("/sys/fs/cgroup/cpu.max")
140+
end
116141
end
117142
end
118143

@@ -187,4 +212,12 @@ def self.available_processor_count
187212
def self.cpu_quota
188213
processor_counter.cpu_quota
189214
end
215+
216+
# The CPU shares requested by the process. For performance reasons the calculated
217+
# value will be memoized on the first call.
218+
#
219+
# @return [Float, nil] CPU shares requested by the process, or nil if not set
220+
def self.cpu_requests
221+
processor_counter.cpu_requests
222+
end
190223
end

spec/concurrent/utility/processor_count_spec.rb

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -92,4 +92,25 @@ module Concurrent
9292
end
9393

9494
end
95+
96+
RSpec.describe '#cpu_requests' do
97+
98+
it 'returns a float when cgroups v2 sets a cpu.weight' do
99+
expect(RbConfig::CONFIG).to receive(:[]).with("target_os").and_return("linux")
100+
expect(File).to receive(:exist?).with("/sys/fs/cgroup/cpu.max").and_return(true)
101+
102+
expect(File).to receive(:read).with("/sys/fs/cgroup/cpu.weight").and_return("10000\n")
103+
expect(Concurrent.cpu_requests).to be == 256.0
104+
end
105+
106+
it 'returns a float if cgroups v1 sets a cpu.shares' do
107+
expect(RbConfig::CONFIG).to receive(:[]).with("target_os").and_return("linux")
108+
expect(File).to receive(:exist?).with("/sys/fs/cgroup/cpu.max").and_return(false)
109+
expect(File).to receive(:exist?).with("/sys/fs/cgroup/cpu,cpuacct/cpu.cfs_quota_us").and_return(true)
110+
111+
expect(File).to receive(:read).with("/sys/fs/cgroup/cpu/cpu.shares").and_return("512\n")
112+
expect(Concurrent.cpu_requests).to be == 0.5
113+
end
114+
115+
end
95116
end

0 commit comments

Comments
 (0)