Skip to content

Commit fc33582

Browse files
committed
add support for cgroupv2
1 parent 58487f2 commit fc33582

File tree

3 files changed

+476
-2
lines changed

3 files changed

+476
-2
lines changed

logstash-core/lib/logstash/instrument/periodic_poller/cgroup.rb

Lines changed: 117 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -52,7 +52,10 @@ def override(other)
5252
CGROUP_FILE = "/proc/self/cgroup"
5353
CPUACCT_DIR = "/sys/fs/cgroup/cpuacct"
5454
CPU_DIR = "/sys/fs/cgroup/cpu"
55-
CRITICAL_PATHS = [CGROUP_FILE, CPUACCT_DIR, CPU_DIR]
55+
CGROUP2_DIR = "/sys/fs/cgroup"
56+
CRITICAL_PATHS_V1 = [CGROUP_FILE, CPUACCT_DIR, CPU_DIR]
57+
CRITICAL_PATHS = CRITICAL_PATHS_V1 # backward compat for logging
58+
CONTROL_GROUP_V2_RE = Regexp.compile("^0::(/.*)")
5659

5760
CONTROLLER_CPUACCT_LABEL = "cpuacct"
5861
CONTROLLER_CPU_LABEL = "cpu"
@@ -63,12 +66,28 @@ class CGroupResources
6366

6467
def cgroup_available?
6568
# don't cache to ivar, in case the files are mounted after logstash starts??
66-
CRITICAL_PATHS.all? {|path| ::File.exist?(path)}
69+
cgroup_v1_available? || cgroup_v2_available?
70+
end
71+
72+
def cgroup_v1_available?
73+
CRITICAL_PATHS_V1.all? { |path| ::File.exist?(path) }
74+
end
75+
76+
def cgroup_v2_available?
77+
::File.exist?(CGROUP_FILE) &&
78+
::File.exist?(::File.join(CGROUP2_DIR, "cgroup.controllers"))
6779
end
6880

6981
def controller_groups
7082
response = {}
83+
v2_path = nil
7184
IO.readlines(CGROUP_FILE).each do |line|
85+
# capture v2 unified hierarchy path (0::/path)
86+
v2_match = CONTROL_GROUP_V2_RE.match(line)
87+
if v2_match
88+
v2_path = v2_match[1]
89+
end
90+
7291
matches = CONTROL_GROUP_RE.match(line)
7392
next if matches.nil?
7493
# multiples controls, same hierarchy
@@ -84,6 +103,15 @@ def controller_groups
84103
end
85104
end
86105
end
106+
107+
# If cpu/cpuacct not found via v1, try v2 unified hierarchy
108+
if v2_path && !response.key?(CONTROLLER_CPU_LABEL) && !response.key?(CONTROLLER_CPUACCT_LABEL)
109+
if cgroup_v2_available?
110+
response[CONTROLLER_CPU_LABEL] = CpuResourceV2.new(v2_path)
111+
response[CONTROLLER_CPUACCT_LABEL] = CpuAcctResourceV2.new(v2_path)
112+
end
113+
end
114+
87115
response
88116
end
89117
end
@@ -210,6 +238,93 @@ def to_hash
210238
end
211239
end
212240

241+
class CpuAcctResourceV2
242+
include LogStash::Util::Loggable
243+
include ControllerResource
244+
245+
def initialize(original_path)
246+
common_initialize(CGROUP2_DIR, "ls.cgroup.cpuacct.path.override", original_path)
247+
end
248+
249+
def to_hash
250+
{:control_group => offset_path, :usage_nanos => cpu_usage_nanos}
251+
end
252+
253+
private
254+
def cpu_usage_nanos
255+
lines = call_if_file_exists(:read_lines, "cpu.stat", [])
256+
lines.each do |line|
257+
fields = line.split(/\s+/)
258+
return fields[1].to_i * 1000 if fields.first == "usage_usec"
259+
end
260+
-1
261+
end
262+
end
263+
264+
class CpuResourceV2
265+
include LogStash::Util::Loggable
266+
include ControllerResource
267+
268+
def initialize(original_path)
269+
common_initialize(CGROUP2_DIR, "ls.cgroup.cpu.path.override", original_path)
270+
end
271+
272+
def to_hash
273+
quota, period = read_cpu_max
274+
{
275+
:control_group => offset_path,
276+
:cfs_period_micros => period,
277+
:cfs_quota_micros => quota,
278+
:stat => build_cpu_stats_hash
279+
}
280+
end
281+
282+
private
283+
def read_cpu_max
284+
content = call_if_file_exists(:read_lines, "cpu.max", [])
285+
return [-1, -1] if content.empty?
286+
parts = content.first.split(/\s+/)
287+
quota = parts[0] == "max" ? -1 : parts[0].to_i
288+
period = parts[1].to_i
289+
[quota, period]
290+
end
291+
292+
def build_cpu_stats_hash
293+
stats = CpuStatsV2.new
294+
lines = call_if_file_exists(:read_lines, "cpu.stat", [])
295+
stats.update(lines)
296+
stats.to_hash
297+
end
298+
end
299+
300+
class CpuStatsV2
301+
def initialize
302+
@number_of_elapsed_periods = -1
303+
@number_of_times_throttled = -1
304+
@time_throttled_nanos = -1
305+
end
306+
307+
def update(lines)
308+
lines.each do |line|
309+
fields = line.split(/\s+/)
310+
next unless fields.size > 1
311+
case fields.first
312+
when "nr_periods" then @number_of_elapsed_periods = fields[1].to_i
313+
when "nr_throttled" then @number_of_times_throttled = fields[1].to_i
314+
when "throttled_usec" then @time_throttled_nanos = fields[1].to_i * 1000
315+
end
316+
end
317+
end
318+
319+
def to_hash
320+
{
321+
:number_of_elapsed_periods => @number_of_elapsed_periods,
322+
:number_of_times_throttled => @number_of_times_throttled,
323+
:time_throttled_nanos => @time_throttled_nanos
324+
}
325+
end
326+
end
327+
213328
CGROUP_RESOURCES = CGroupResources.new
214329

215330
class << self

0 commit comments

Comments
 (0)