gitlab-org--gitlab-foss/spec/lib/gitlab/metrics/system_spec.rb

262 lines
8.8 KiB
Ruby

# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Gitlab::Metrics::System do
context 'when /proc files exist' do
# Modified column 22 to be 1000 (starttime ticks)
let(:proc_stat) do
<<~SNIP
2095 (ruby) R 0 2095 2095 34818 2095 4194560 211267 7897 2 0 287 51 10 1 20 0 5 0 1000 566210560 80885 18446744073709551615 94736211292160 94736211292813 140720919612064 0 0 0 0 0 1107394127 0 0 0 17 3 0 0 0 0 0 94736211303768 94736211304544 94736226689024 140720919619473 140720919619513 140720919619513 140720919621604 0
SNIP
end
# Fixtures pulled from:
# Linux carbon 5.3.0-7648-generic #41~1586789791~19.10~9593806-Ubuntu SMP Mon Apr 13 17:50:40 UTC x86_64 x86_64 x86_64 GNU/Linux
let(:proc_status) do
# most rows omitted for brevity
<<~SNIP
Name: less
VmHWM: 2468 kB
VmRSS: 2468 kB
RssAnon: 260 kB
SNIP
end
let(:proc_smaps_rollup) do
# full snapshot
<<~SNIP
Rss: 2564 kB
Pss: 503 kB
Pss_Anon: 312 kB
Pss_File: 191 kB
Pss_Shmem: 0 kB
Shared_Clean: 2100 kB
Shared_Dirty: 0 kB
Private_Clean: 152 kB
Private_Dirty: 312 kB
Referenced: 2564 kB
Anonymous: 312 kB
LazyFree: 0 kB
AnonHugePages: 0 kB
ShmemPmdMapped: 0 kB
Shared_Hugetlb: 0 kB
Private_Hugetlb: 0 kB
Swap: 0 kB
SwapPss: 0 kB
Locked: 0 kB
SNIP
end
let(:proc_limits) do
# full snapshot
<<~SNIP
Limit Soft Limit Hard Limit Units
Max cpu time unlimited unlimited seconds
Max file size unlimited unlimited bytes
Max data size unlimited unlimited bytes
Max stack size 8388608 unlimited bytes
Max core file size 0 unlimited bytes
Max resident set unlimited unlimited bytes
Max processes 126519 126519 processes
Max open files 1024 1048576 files
Max locked memory 67108864 67108864 bytes
Max address space unlimited unlimited bytes
Max file locks unlimited unlimited locks
Max pending signals 126519 126519 signals
Max msgqueue size 819200 819200 bytes
Max nice priority 0 0
Max realtime priority 0 0
Max realtime timeout unlimited unlimited us
SNIP
end
describe '.memory_usage_rss' do
it "returns the process' resident set size (RSS) in bytes" do
mock_existing_proc_file('/proc/self/status', proc_status)
expect(described_class.memory_usage_rss).to eq(2527232)
end
end
describe '.file_descriptor_count' do
it 'returns the amount of open file descriptors' do
expect(Dir).to receive(:glob).and_return(['/some/path', '/some/other/path'])
expect(described_class.file_descriptor_count).to eq(2)
end
end
describe '.max_open_file_descriptors' do
it 'returns the max allowed open file descriptors' do
mock_existing_proc_file('/proc/self/limits', proc_limits)
expect(described_class.max_open_file_descriptors).to eq(1024)
end
end
describe '.memory_usage_uss_pss' do
it "returns the process' unique and porportional set size (USS/PSS) in bytes" do
mock_existing_proc_file('/proc/self/smaps_rollup', proc_smaps_rollup)
# (Private_Clean (152 kB) + Private_Dirty (312 kB) + Private_Hugetlb (0 kB)) * 1024
expect(described_class.memory_usage_uss_pss).to eq(uss: 475136, pss: 515072)
end
end
describe '.process_runtime_elapsed_seconds' do
it 'returns the seconds elapsed since the process was started' do
# sets process starttime ticks to 1000
mock_existing_proc_file('/proc/self/stat', proc_stat)
# system clock ticks/sec
expect(Etc).to receive(:sysconf).with(Etc::SC_CLK_TCK).and_return(100)
# system uptime in seconds
expect(::Process).to receive(:clock_gettime).and_return(15)
# uptime - (starttime_ticks / ticks_per_sec)
expect(described_class.process_runtime_elapsed_seconds).to eq(5)
end
context 'when inputs are not available' do
it 'returns 0' do
mock_missing_proc_file
expect(::Process).to receive(:clock_gettime).and_raise(NameError)
expect(described_class.process_runtime_elapsed_seconds).to eq(0)
end
end
end
describe '.summary' do
it 'contains a selection of the available fields' do
stub_const('RUBY_DESCRIPTION', 'ruby-3.0-patch1')
mock_existing_proc_file('/proc/self/status', proc_status)
mock_existing_proc_file('/proc/self/smaps_rollup', proc_smaps_rollup)
summary = described_class.summary
expect(summary[:version]).to eq('ruby-3.0-patch1')
expect(summary[:gc_stat].keys).to eq(GC.stat.keys)
expect(summary[:memory_rss]).to eq(2527232)
expect(summary[:memory_uss]).to eq(475136)
expect(summary[:memory_pss]).to eq(515072)
expect(summary[:time_cputime]).to be_a(Float)
expect(summary[:time_realtime]).to be_a(Float)
expect(summary[:time_monotonic]).to be_a(Float)
end
end
end
context 'when /proc files do not exist' do
before do
mock_missing_proc_file
end
describe '.memory_usage_rss' do
it 'returns 0' do
expect(described_class.memory_usage_rss).to eq(0)
end
end
describe '.memory_usage_uss_pss' do
it "returns 0 for all components" do
expect(described_class.memory_usage_uss_pss).to eq(uss: 0, pss: 0)
end
end
describe '.file_descriptor_count' do
it 'returns 0' do
expect(Dir).to receive(:glob).and_return([])
expect(described_class.file_descriptor_count).to eq(0)
end
end
describe '.max_open_file_descriptors' do
it 'returns 0' do
expect(described_class.max_open_file_descriptors).to eq(0)
end
end
describe '.summary' do
it 'returns only available fields' do
summary = described_class.summary
expect(summary[:version]).to be_a(String)
expect(summary[:gc_stat].keys).to eq(GC.stat.keys)
expect(summary[:memory_rss]).to eq(0)
expect(summary[:memory_uss]).to eq(0)
expect(summary[:memory_pss]).to eq(0)
expect(summary[:time_cputime]).to be_a(Float)
expect(summary[:time_realtime]).to be_a(Float)
expect(summary[:time_monotonic]).to be_a(Float)
end
end
end
describe '.cpu_time' do
it 'returns a Float' do
expect(described_class.cpu_time).to be_an(Float)
end
end
describe '.real_time' do
it 'returns a Float' do
expect(described_class.real_time).to be_an(Float)
end
end
describe '.monotonic_time' do
it 'returns a Float' do
expect(described_class.monotonic_time).to be_an(Float)
end
end
describe '.thread_cpu_time' do
it 'returns cpu_time on supported platform' do
stub_const("Process::CLOCK_THREAD_CPUTIME_ID", 16)
expect(Process).to receive(:clock_gettime)
.with(16, kind_of(Symbol)) { 0.111222333 }
expect(described_class.thread_cpu_time).to eq(0.111222333)
end
it 'returns nil on unsupported platform' do
hide_const("Process::CLOCK_THREAD_CPUTIME_ID")
expect(described_class.thread_cpu_time).to be_nil
end
end
describe '.thread_cpu_duration' do
let(:start_time) { described_class.thread_cpu_time }
it 'returns difference between start and current time' do
stub_const("Process::CLOCK_THREAD_CPUTIME_ID", 16)
expect(Process).to receive(:clock_gettime)
.with(16, kind_of(Symbol))
.and_return(
0.111222333,
0.222333833
)
expect(described_class.thread_cpu_duration(start_time)).to eq(0.1111115)
end
it 'returns nil on unsupported platform' do
hide_const("Process::CLOCK_THREAD_CPUTIME_ID")
expect(described_class.thread_cpu_duration(start_time)).to be_nil
end
end
def mock_existing_proc_file(path, content)
allow(File).to receive(:open).with(path) { |_path, &block| block.call(StringIO.new(content)) }
end
def mock_missing_proc_file
allow(File).to receive(:open).and_raise(Errno::ENOENT)
end
end