Skip to content

Commit 647f98e

Browse files
committed
Add Concurrent.cpu_shares that is cgroups aware.
1 parent cadc8de commit 647f98e

File tree

2 files changed

+57
-2
lines changed

2 files changed

+57
-2
lines changed

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

+35-2
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_shares = Delay.new { compute_cpu_shares }
1516
end
1617

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

45+
def cpu_shares
46+
@cpu_shares.value
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_shares
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) / 1024
128+
elsif cgroups_v1?
129+
File.read('/sys/fs/cgroup/cpu/cpu.shares').to_f / 1024
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_shares
221+
processor_counter.cpu_shares
222+
end
190223
end

spec/concurrent/utility/processor_count_spec.rb

+22
Original file line numberDiff line numberDiff line change
@@ -92,4 +92,26 @@ module Concurrent
9292
end
9393

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

0 commit comments

Comments
 (0)