2019-02-15 15:39:39 +05:30
|
|
|
# frozen_string_literal: true
|
|
|
|
|
2016-01-14 18:37:52 +05:30
|
|
|
module Gitlab
|
|
|
|
module Metrics
|
|
|
|
# Module for gathering system/process statistics such as the memory usage.
|
|
|
|
#
|
|
|
|
# This module relies on the /proc filesystem being available. If /proc is
|
|
|
|
# not available the methods of this module will be stubbed.
|
|
|
|
module System
|
2020-05-24 23:13:21 +05:30
|
|
|
PROC_STATUS_PATH = '/proc/self/status'
|
|
|
|
PROC_SMAPS_ROLLUP_PATH = '/proc/self/smaps_rollup'
|
|
|
|
PROC_LIMITS_PATH = '/proc/self/limits'
|
|
|
|
PROC_FD_GLOB = '/proc/self/fd/*'
|
|
|
|
|
|
|
|
PRIVATE_PAGES_PATTERN = /^(Private_Clean|Private_Dirty|Private_Hugetlb):\s+(?<value>\d+)/.freeze
|
|
|
|
PSS_PATTERN = /^Pss:\s+(?<value>\d+)/.freeze
|
|
|
|
RSS_PATTERN = /VmRSS:\s+(?<value>\d+)/.freeze
|
|
|
|
MAX_OPEN_FILES_PATTERN = /Max open files\s*(?<value>\d+)/.freeze
|
|
|
|
|
2021-03-08 18:12:59 +05:30
|
|
|
def self.summary
|
|
|
|
proportional_mem = memory_usage_uss_pss
|
|
|
|
{
|
|
|
|
version: RUBY_DESCRIPTION,
|
|
|
|
gc_stat: GC.stat,
|
|
|
|
memory_rss: memory_usage_rss,
|
|
|
|
memory_uss: proportional_mem[:uss],
|
|
|
|
memory_pss: proportional_mem[:pss],
|
|
|
|
time_cputime: cpu_time,
|
|
|
|
time_realtime: real_time,
|
|
|
|
time_monotonic: monotonic_time
|
|
|
|
}
|
|
|
|
end
|
|
|
|
|
2020-05-24 23:13:21 +05:30
|
|
|
# Returns the current process' RSS (resident set size) in bytes.
|
|
|
|
def self.memory_usage_rss
|
|
|
|
sum_matches(PROC_STATUS_PATH, rss: RSS_PATTERN)[:rss].kilobytes
|
|
|
|
end
|
2019-09-04 21:01:54 +05:30
|
|
|
|
2020-05-24 23:13:21 +05:30
|
|
|
# Returns the current process' USS/PSS (unique/proportional set size) in bytes.
|
|
|
|
def self.memory_usage_uss_pss
|
|
|
|
sum_matches(PROC_SMAPS_ROLLUP_PATH, uss: PRIVATE_PAGES_PATTERN, pss: PSS_PATTERN)
|
|
|
|
.transform_values(&:kilobytes)
|
|
|
|
end
|
2016-01-14 18:37:52 +05:30
|
|
|
|
2020-05-24 23:13:21 +05:30
|
|
|
def self.file_descriptor_count
|
|
|
|
Dir.glob(PROC_FD_GLOB).length
|
|
|
|
end
|
2019-09-04 21:01:54 +05:30
|
|
|
|
2020-05-24 23:13:21 +05:30
|
|
|
def self.max_open_file_descriptors
|
|
|
|
sum_matches(PROC_LIMITS_PATH, max_fds: MAX_OPEN_FILES_PATTERN)[:max_fds]
|
2016-01-14 18:37:52 +05:30
|
|
|
end
|
2016-06-02 11:05:42 +05:30
|
|
|
|
2019-09-30 21:07:59 +05:30
|
|
|
def self.cpu_time
|
2020-05-24 23:13:21 +05:30
|
|
|
Process.clock_gettime(Process::CLOCK_PROCESS_CPUTIME_ID, :float_second)
|
2016-06-02 11:05:42 +05:30
|
|
|
end
|
2016-08-24 12:49:21 +05:30
|
|
|
|
|
|
|
# Returns the current real time in a given precision.
|
|
|
|
#
|
2018-03-17 18:26:18 +05:30
|
|
|
# Returns the time as a Float for precision = :float_second.
|
|
|
|
def self.real_time(precision = :float_second)
|
2016-09-13 17:45:13 +05:30
|
|
|
Process.clock_gettime(Process::CLOCK_REALTIME, precision)
|
2016-08-24 12:49:21 +05:30
|
|
|
end
|
|
|
|
|
2018-03-17 18:26:18 +05:30
|
|
|
# Returns the current monotonic clock time as seconds with microseconds precision.
|
2016-08-24 12:49:21 +05:30
|
|
|
#
|
|
|
|
# Returns the time as a Float.
|
2018-03-17 18:26:18 +05:30
|
|
|
def self.monotonic_time
|
|
|
|
Process.clock_gettime(Process::CLOCK_MONOTONIC, :float_second)
|
2016-08-24 12:49:21 +05:30
|
|
|
end
|
2019-12-21 20:55:43 +05:30
|
|
|
|
|
|
|
def self.thread_cpu_time
|
|
|
|
# Not all OS kernels are supporting `Process::CLOCK_THREAD_CPUTIME_ID`
|
|
|
|
# Refer: https://gitlab.com/gitlab-org/gitlab/issues/30567#note_221765627
|
|
|
|
return unless defined?(Process::CLOCK_THREAD_CPUTIME_ID)
|
|
|
|
|
|
|
|
Process.clock_gettime(Process::CLOCK_THREAD_CPUTIME_ID, :float_second)
|
|
|
|
end
|
|
|
|
|
|
|
|
def self.thread_cpu_duration(start_time)
|
|
|
|
end_time = thread_cpu_time
|
|
|
|
return unless start_time && end_time
|
|
|
|
|
|
|
|
end_time - start_time
|
|
|
|
end
|
2020-05-24 23:13:21 +05:30
|
|
|
|
|
|
|
# Given a path to a file in /proc and a hash of (metric, pattern) pairs,
|
|
|
|
# sums up all values found for those patterns under the respective metric.
|
|
|
|
def self.sum_matches(proc_file, **patterns)
|
|
|
|
results = patterns.transform_values { 0 }
|
|
|
|
|
|
|
|
begin
|
|
|
|
File.foreach(proc_file) do |line|
|
|
|
|
patterns.each do |metric, pattern|
|
|
|
|
match = line.match(pattern)
|
|
|
|
value = match&.named_captures&.fetch('value', 0)
|
|
|
|
results[metric] += value.to_i
|
|
|
|
end
|
|
|
|
end
|
|
|
|
rescue Errno::ENOENT
|
|
|
|
# This means the procfile we're reading from did not exist;
|
|
|
|
# this is safe to ignore, since we initialize each metric to 0
|
|
|
|
end
|
|
|
|
|
|
|
|
results
|
|
|
|
end
|
2016-01-14 18:37:52 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|