From 6349061f840fe8d0976a488fbffd21ea7c5bfe4a Mon Sep 17 00:00:00 2001 From: johha Date: Fri, 31 Jul 2026 16:12:23 +0200 Subject: [PATCH] Replace vmstat gem with /proc reads The vmstat gem is a native C extension with no Windows support (its build fails there), and it surfaced in an outdated-dependency audit. On the Linux ccng runtime all three values it provided are available directly from /proc, so the gem can be dropped from lib/vcap/stats.rb. Behaviour is preserved. vmstat's Linux backend reads /proc/meminfo, so: - memory_free_bytes = (Inactive + MemFree) * 1024 - memory_used_bytes = (MemTotal - Inactive - MemFree) * 1024 (equals vmstat's active+wired by conservation - the buckets sum to MemTotal) - cpu_load_average = first field of /proc/loadavg Verified against the live vmstat gem on a production CF API VM: 0.0% diff on both memory metrics, load average identical to rounding. Non-Linux dev machines (Mac/Windows) return 0, where these metrics have no consumer. Add a stats spec covering all three methods on both the Linux and non-Linux paths, which previously had no direct coverage. --- Gemfile | 1 - Gemfile.lock | 2 -- lib/vcap/stats.rb | 25 ++++++++++---- spec/unit/lib/vcap/stats_spec.rb | 58 ++++++++++++++++++++++++++++++++ 4 files changed, 77 insertions(+), 9 deletions(-) diff --git a/Gemfile b/Gemfile index 03cc493bd84..86fb80c006f 100644 --- a/Gemfile +++ b/Gemfile @@ -31,7 +31,6 @@ gem 'sinatra-contrib' gem 'statsd-instrument', '~> 3.11' gem 'talentbox-delayed_job_sequel', '~> 4.4.0' gem 'uri', '~> 1.1' -gem 'vmstat', '~> 2.3' # Rails Components gem 'actionpack', '~> 8.1.2' diff --git a/Gemfile.lock b/Gemfile.lock index f447789d438..b1cfd26edbc 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -419,7 +419,6 @@ GEM unicode-emoji (4.2.0) uri (1.1.1) useragent (0.16.11) - vmstat (2.3.1) webmock (3.26.2) addressable (>= 2.8.0) crack (>= 0.3.2) @@ -505,7 +504,6 @@ DEPENDENCIES talentbox-delayed_job_sequel (~> 4.4.0) timecop uri (~> 1.1) - vmstat (~> 2.3) webmock (> 2.3.1) webrick (~> 1.9.2) diff --git a/lib/vcap/stats.rb b/lib/vcap/stats.rb index 0963b679c73..401b5be2d58 100644 --- a/lib/vcap/stats.rb +++ b/lib/vcap/stats.rb @@ -1,5 +1,4 @@ require 'vcap/pid_file' -require 'vmstat' module VCAP class Stats @@ -16,21 +15,35 @@ def process_memory_bytes_and_cpu end def memory_used_bytes - mem = Vmstat.memory - mem.active_bytes + mem.wired_bytes + return 0 unless linux? + + (meminfo_kb('MemTotal') - meminfo_kb('Inactive') - meminfo_kb('MemFree')) * 1024 end def memory_free_bytes - mem = Vmstat.memory - mem.inactive_bytes + mem.free_bytes + return 0 unless linux? + + (meminfo_kb('Inactive') + meminfo_kb('MemFree')) * 1024 end def cpu_load_average - Vmstat.load_average.one_minute + return 0 unless linux? + + File.read('/proc/loadavg').split.first.to_f end private + def linux? + RUBY_PLATFORM.match?(/linux/) + end + + # Reads a kB-valued field from /proc/meminfo (e.g. "MemTotal: 16793990 kB"). + def meminfo_kb(key) + line = File.read('/proc/meminfo').lines.find { |l| l.start_with?("#{key}:") } + line ? line.split[1].to_i : 0 + end + def ps_pid `ps -o rss=,pcpu= -p #{Process.pid}` end diff --git a/spec/unit/lib/vcap/stats_spec.rb b/spec/unit/lib/vcap/stats_spec.rb index c397bf14c3d..92d9cad8ba7 100644 --- a/spec/unit/lib/vcap/stats_spec.rb +++ b/spec/unit/lib/vcap/stats_spec.rb @@ -13,4 +13,62 @@ expect(pcpu).to eq(17) end end + + describe 'system metrics on Linux' do + let(:meminfo) do + <<~MEMINFO + MemTotal: 16793990 kB + MemFree: 10810368 kB + MemAvailable: 13636608 kB + Buffers: 455475 kB + Cached: 2707660 kB + Active: 920268 kB + Inactive: 4609024 kB + MEMINFO + end + let(:loadavg) { "1.37 1.21 1.05 2/512 12345\n" } + + before do + allow(VCAP::Stats).to receive(:linux?).and_return(true) + allow(File).to receive(:read).and_call_original + allow(File).to receive(:read).with('/proc/meminfo').and_return(meminfo) + allow(File).to receive(:read).with('/proc/loadavg').and_return(loadavg) + end + + describe '#memory_free_bytes' do + it 'returns (Inactive + MemFree) in bytes' do + expect(VCAP::Stats.memory_free_bytes).to eq((4_609_024 + 10_810_368) * 1024) + end + end + + describe '#memory_used_bytes' do + it 'returns (MemTotal - Inactive - MemFree) in bytes' do + expect(VCAP::Stats.memory_used_bytes).to eq((16_793_990 - 4_609_024 - 10_810_368) * 1024) + end + end + + describe '#cpu_load_average' do + it 'returns the one-minute load as a float' do + expect(VCAP::Stats.cpu_load_average).to eq(1.37) + end + end + end + + describe 'system metrics on non-Linux' do + before do + allow(VCAP::Stats).to receive(:linux?).and_return(false) + end + + it 'returns 0 for memory_used_bytes' do + expect(VCAP::Stats.memory_used_bytes).to eq(0) + end + + it 'returns 0 for memory_free_bytes' do + expect(VCAP::Stats.memory_free_bytes).to eq(0) + end + + it 'returns 0 for cpu_load_average' do + expect(VCAP::Stats.cpu_load_average).to eq(0) + end + end end