@@ -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