Skip to content

Commit 1d48395

Browse files
samuel-williams-shopifyioquatix
authored andcommitted
Linux specific tests.
1 parent 086a334 commit 1d48395

3 files changed

Lines changed: 93 additions & 36 deletions

File tree

lib/process/metrics/general/linux.rb

Lines changed: 28 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -51,38 +51,38 @@ def self.capture(pid: nil, ppid: nil, memory: Memory.supported?)
5151

5252
comm = stat[1...rparen]
5353
fields = stat[(rparen + 2)..].split(/\s+/)
54-
# After comm: state(3), ppid(4), pgrp(5), ... utime(14), stime(15), ... starttime(22), vsz(23), rss(24). 0-based: ppid=1, pgrp=2, utime=11, stime=12, starttime=20, vsz=21, rss=22
54+
# After comm: state(3), ppid(4), pgrp(5), ... utime(14), stime(15), ... starttime(22), vsz(23), rss(24). 0-based: ppid=1, pgrp=2, utime=11, stime=12, starttime=19, vsz=20, rss=21.
5555
ppid_val = fields[1].to_i
5656
pgrp = fields[2].to_i
5757
utime = fields[11].to_i
5858
stime = fields[12].to_i
59-
starttime = fields[20].to_i
60-
vsz = fields[21].to_i
61-
rss_pages = fields[22].to_i
62-
63-
# Read /proc/uptime once per capture and reuse for every process (starttime is in jiffies since boot).
64-
uptime_jiffies ||= begin
65-
uptime_seconds = File.read("/proc/uptime").split(/\s+/).first.to_f
66-
(uptime_seconds * CLK_TCK).to_i
67-
end
68-
69-
processor_time = (utime + stime).to_f / CLK_TCK
70-
elapsed_time = [(uptime_jiffies - starttime).to_f / CLK_TCK, 0.0].max
71-
72-
command = read_command(p, comm)
73-
74-
processes[p] = General.new(
75-
p,
76-
ppid_val,
77-
pgrp,
78-
0.0, # processor_utilization: would need two samples; not available from single stat read
79-
vsz,
80-
rss_pages * PAGE_SIZE,
81-
processor_time,
82-
elapsed_time,
83-
command,
84-
nil
85-
)
59+
starttime = fields[19].to_i
60+
vsz = fields[20].to_i
61+
rss_pages = fields[21].to_i
62+
63+
# Read /proc/uptime once per capture and reuse for every process (starttime is in jiffies since boot).
64+
uptime_jiffies ||= begin
65+
uptime_seconds = File.read("/proc/uptime").split(/\s+/).first.to_f
66+
(uptime_seconds * CLK_TCK).to_i
67+
end
68+
69+
processor_time = (utime + stime).to_f / CLK_TCK
70+
elapsed_time = [(uptime_jiffies - starttime).to_f / CLK_TCK, 0.0].max
71+
72+
command = read_command(p, comm)
73+
74+
processes[p] = General.new(
75+
p,
76+
ppid_val,
77+
pgrp,
78+
0.0, # processor_utilization: would need two samples; not available from single stat read
79+
vsz,
80+
rss_pages * PAGE_SIZE,
81+
processor_time,
82+
elapsed_time,
83+
command,
84+
nil
85+
)
8686
rescue Errno::ENOENT, Errno::ESRCH, Errno::EACCES
8787
# Process disappeared or we can't read it.
8888
next

test/process/general.rb

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -28,29 +28,29 @@
2828
it "can extract memory usage" do
2929
expect(capture[pid].memory_usage).to be > 0.0
3030
end
31-
31+
3232
it "sets parent_process_id and process_group_id" do
3333
process = capture[pid]
3434
expect(process.process_id).to be == pid
3535
expect(process.parent_process_id).to be_a(Integer)
3636
expect(process.process_group_id).to be_a(Integer)
3737
end
3838
end
39-
39+
4040
with ".capture with ppid only" do
4141
def before
4242
super
4343
@child_pid = Process.spawn("sleep 10")
4444
end
45-
45+
4646
def after(error = nil)
4747
super
4848
Process.kill(:TERM, @child_pid) if @child_pid
4949
Process.wait(@child_pid) if @child_pid
5050
end
51-
52-
let(:capture) { Process::Metrics::General.capture(ppid: Process.pid) }
53-
51+
52+
let(:capture) {Process::Metrics::General.capture(ppid: Process.pid)}
53+
5454
it "includes descendants of the given ppid" do
5555
expect(capture).to be(:include?, @child_pid)
5656
child = capture[@child_pid]
@@ -59,7 +59,7 @@ def after(error = nil)
5959
expect(child.parent_process_id).to be == Process.pid
6060
end
6161
end
62-
62+
6363
with ".capture with parent pid" do
6464
def before
6565
super
@@ -92,7 +92,7 @@ def after(error = nil)
9292
expect(command[:processor_time]).to be >= 0.0
9393
expect(command[:processor_utilization]).to be >= 0.0
9494
end
95-
95+
9696
it "sets parent_process_id and process_group_id on child" do
9797
child = capture[@pid]
9898
expect(child).not.to be_nil

test/process/general/linux.rb

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
# frozen_string_literal: true
2+
3+
# Released under the MIT License.
4+
# Copyright, 2024-2025, by Samuel Williams.
5+
6+
require "process/metrics"
7+
# Load ProcessStatus backend so we can compare General (Linux) vs General::ProcessStatus.
8+
require "process/metrics/general/process_status"
9+
10+
describe Process::Metrics::General do
11+
with "Linux backend matches ProcessStatus backend" do
12+
def assert_backends_match(linux, ps)
13+
expect(linux.keys.sort).to be == ps.keys.sort
14+
15+
linux.each_key do |pid|
16+
linux_process = linux[pid]
17+
ps_process = ps[pid]
18+
19+
expect(ps_process).not.to be_nil
20+
expect(linux_process.process_id).to be == ps_process.process_id
21+
expect(linux_process.parent_process_id).to be == ps_process.parent_process_id
22+
expect(linux_process.process_group_id).to be == ps_process.process_group_id
23+
24+
# VSZ and RSS differ because ps excludes device mappings while /proc/stat includes them.
25+
expect(linux_process.virtual_size).to be_within(10.0).percent_of(ps_process.virtual_size)
26+
expect(linux_process.resident_size).to be_within(10.0).percent_of(ps_process.resident_size)
27+
28+
expect(linux_process.command).to be == ps_process.command
29+
expect((linux_process.processor_time - ps_process.processor_time).abs).to be < 1.0
30+
expect((linux_process.elapsed_time - ps_process.elapsed_time).abs).to be < 1.0
31+
end
32+
end
33+
34+
it "single pid capture matches" do
35+
skip "Linux with ps required" unless RUBY_PLATFORM.include?("linux") && Process::Metrics::General::ProcessStatus.supported?
36+
37+
pid = Process.pid
38+
linux = Process::Metrics::General.capture(pid: pid, memory: false)
39+
ps = Process::Metrics::General::ProcessStatus.capture(pid: pid, memory: false)
40+
assert_backends_match(linux, ps)
41+
end
42+
43+
it "pid and ppid capture matches" do
44+
skip "Linux with ps required" unless RUBY_PLATFORM.include?("linux") && Process::Metrics::General::ProcessStatus.supported?
45+
46+
child_pid = Process.spawn("sleep 10")
47+
begin
48+
linux = Process::Metrics::General.capture(pid: child_pid, ppid: child_pid, memory: false)
49+
ps = Process::Metrics::General::ProcessStatus.capture(pid: child_pid, ppid: child_pid, memory: false)
50+
assert_backends_match(linux, ps)
51+
ensure
52+
Process.kill(:TERM, child_pid)
53+
Process.wait(child_pid)
54+
end
55+
end
56+
end
57+
end

0 commit comments

Comments
 (0)