process-metrics 0.2.1 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/bin/process-metrics +1 -1
- data/lib/process/metrics/command/summary.rb +51 -46
- data/lib/process/metrics/command/top.rb +11 -26
- data/lib/process/metrics/command.rb +5 -20
- data/lib/process/metrics/general.rb +68 -67
- data/lib/process/metrics/memory/darwin.rb +100 -0
- data/lib/process/metrics/memory/linux.rb +98 -0
- data/lib/process/metrics/memory.rb +27 -70
- data/lib/process/metrics/version.rb +3 -20
- data/lib/process/metrics.rb +4 -19
- data/license.md +22 -0
- data/readme.md +31 -0
- data.tar.gz.sig +0 -0
- metadata +48 -68
- metadata.gz.sig +2 -0
- data/.gitignore +0 -14
- data/.rspec +0 -3
- data/.travis.yml +0 -22
- data/Gemfile +0 -4
- data/README.md +0 -184
- data/Rakefile +0 -6
- data/command-line.png +0 -0
- data/process-metrics.gemspec +0 -33
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 42d11b0f2e725706c447c1b4cfa3f15a961f759fb8cacf0ef0d238338c3be6ab
|
4
|
+
data.tar.gz: 80468efcb1b3eab151f8fcf0d054d93d11dcd90127ec70a6ad63e5fc10b68f89
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 5c765d57991e1a15e8c966ea1497234dae6e8e98cdd8e2463cefe1caf046770b1930b01b68b202b7ace9d802ad6d0577dfbb3cfef48a1d23abc3013ea5a48580
|
7
|
+
data.tar.gz: 8c2bf4692a659037dc8167ad7f3349beb300e4bf021ee97b8ed3bf645cc83eaeefa19ae6ef5aee62855d781f7782ab9750d3999ca21982b865c24afbb65a0be5
|
checksums.yaml.gz.sig
ADDED
Binary file
|
data/bin/process-metrics
CHANGED
@@ -1,28 +1,13 @@
|
|
1
|
-
#
|
2
|
-
#
|
3
|
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
-
# of this software and associated documentation files (the "Software"), to deal
|
5
|
-
# in the Software without restriction, including without limitation the rights
|
6
|
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
-
# copies of the Software, and to permit persons to whom the Software is
|
8
|
-
# furnished to do so, subject to the following conditions:
|
9
|
-
#
|
10
|
-
# The above copyright notice and this permission notice shall be included in
|
11
|
-
# all copies or substantial portions of the Software.
|
12
|
-
#
|
13
|
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
-
# THE SOFTWARE.
|
1
|
+
# frozen_string_literal: true
|
20
2
|
|
21
|
-
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2020-2025, by Samuel Williams.
|
22
5
|
|
23
|
-
|
6
|
+
require "samovar"
|
24
7
|
|
25
|
-
|
8
|
+
require_relative "../general"
|
9
|
+
|
10
|
+
require "console/terminal"
|
26
11
|
|
27
12
|
module Process
|
28
13
|
module Metrics
|
@@ -57,10 +42,8 @@ module Process
|
|
57
42
|
self.description = "Display a summary of memory usage statistics."
|
58
43
|
|
59
44
|
options do
|
60
|
-
option
|
61
|
-
option
|
62
|
-
|
63
|
-
option '--memory-scale <integer>', "Scale maximum memory usage to the specified amount (MiB).", type: Integer, default: 512
|
45
|
+
option "--pid <integer>", "Report on a single process id.", type: Integer, required: true
|
46
|
+
option "-p/--ppid <integer>", "Report on all children of this process id.", type: Integer, required: true
|
64
47
|
end
|
65
48
|
|
66
49
|
def terminal
|
@@ -77,7 +60,7 @@ module Process
|
|
77
60
|
return terminal
|
78
61
|
end
|
79
62
|
|
80
|
-
def
|
63
|
+
def format_processor_utilization(value, terminal)
|
81
64
|
if value > 80.0
|
82
65
|
intensity = :high
|
83
66
|
elsif value > 50.0
|
@@ -104,18 +87,18 @@ module Process
|
|
104
87
|
return "#{value.round(unit)}#{units[unit]}"
|
105
88
|
end
|
106
89
|
|
107
|
-
def format_memory_usage(value,
|
108
|
-
if value > (
|
90
|
+
def format_memory_usage(value, total, terminal)
|
91
|
+
if value > (total * 0.8)
|
109
92
|
intensity = :high
|
110
|
-
elsif value > (
|
93
|
+
elsif value > (total * 0.5)
|
111
94
|
intensity = :medium
|
112
95
|
else
|
113
96
|
intensity = :low
|
114
97
|
end
|
115
98
|
|
116
|
-
formatted = (format_size(value) +
|
99
|
+
formatted = (format_size(value) + " ").rjust(10)
|
117
100
|
|
118
|
-
terminal.print(formatted, intensity, "[", Bar.format(value /
|
101
|
+
terminal.print(formatted, intensity, "[", Bar.format(value / total.to_f, 60), "]", :reset)
|
119
102
|
end
|
120
103
|
|
121
104
|
def call
|
@@ -124,35 +107,47 @@ module Process
|
|
124
107
|
summary = Process::Metrics::General.capture(pid: @options[:pid], ppid: @options[:ppid])
|
125
108
|
|
126
109
|
format_memory_usage = self.method(:format_memory_usage).curry
|
127
|
-
|
110
|
+
shared_memory_usage = 0
|
111
|
+
private_memory_usage = 0
|
112
|
+
total_memory_usage = 0
|
113
|
+
|
114
|
+
summary.each do |pid, general|
|
115
|
+
if memory = general.memory
|
116
|
+
total_memory_usage += memory.proportional_size + memory.unique_size
|
117
|
+
else
|
118
|
+
total_memory_usage += general.resident_size
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
128
122
|
proportional = true
|
129
123
|
|
130
124
|
summary.each do |pid, general|
|
131
125
|
terminal.print_line(:pid, pid, :reset, " ", :command, general[:command])
|
132
126
|
|
133
127
|
terminal.print(:key, "Processor Usage: ".rjust(20), :reset)
|
134
|
-
|
128
|
+
format_processor_utilization(general.processor_utilization, terminal)
|
135
129
|
terminal.print_line
|
136
130
|
|
137
131
|
if memory = general.memory
|
138
|
-
|
132
|
+
shared_memory_usage += memory.proportional_size
|
133
|
+
private_memory_usage += memory.unique_size
|
139
134
|
|
140
135
|
terminal.print_line(
|
141
|
-
:key, "Memory
|
142
|
-
format_memory_usage[memory.proportional_size]
|
136
|
+
:key, "Shared Memory: ".rjust(20), :reset,
|
137
|
+
format_memory_usage[memory.proportional_size, total_memory_usage]
|
143
138
|
)
|
144
139
|
|
145
140
|
terminal.print_line(
|
146
|
-
:key, "Private
|
147
|
-
format_memory_usage[memory.unique_size]
|
141
|
+
:key, "Private Memory: ".rjust(20), :reset,
|
142
|
+
format_memory_usage[memory.unique_size, total_memory_usage]
|
148
143
|
)
|
149
144
|
else
|
150
|
-
|
145
|
+
shared_memory_usage += general.resident_size
|
151
146
|
proportional = false
|
152
147
|
|
153
148
|
terminal.print_line(
|
154
|
-
:key, "Memory
|
155
|
-
format_memory_usage[general.
|
149
|
+
:key, "Memory: ".rjust(20), :reset,
|
150
|
+
format_memory_usage[general.resident_size, total_memory_usage]
|
156
151
|
)
|
157
152
|
end
|
158
153
|
end
|
@@ -161,15 +156,25 @@ module Process
|
|
161
156
|
|
162
157
|
if proportional
|
163
158
|
terminal.print_line(
|
164
|
-
:key, "Memory
|
165
|
-
format_memory_usage[
|
159
|
+
:key, "Shared Memory: ".rjust(20), :reset,
|
160
|
+
format_memory_usage[shared_memory_usage, total_memory_usage]
|
161
|
+
)
|
162
|
+
|
163
|
+
terminal.print_line(
|
164
|
+
:key, "Private Memory: ".rjust(20), :reset,
|
165
|
+
format_memory_usage[private_memory_usage, total_memory_usage]
|
166
166
|
)
|
167
167
|
else
|
168
168
|
terminal.print_line(
|
169
|
-
:key, "Memory
|
170
|
-
format_memory_usage[memory_usage]
|
169
|
+
:key, "Memory: ".rjust(20), :reset,
|
170
|
+
format_memory_usage[memory_usage, total_memory_usage]
|
171
171
|
)
|
172
172
|
end
|
173
|
+
|
174
|
+
terminal.print_line(
|
175
|
+
:key, "Memory (Total): ".rjust(20), :reset,
|
176
|
+
format_memory_usage[shared_memory_usage + private_memory_usage, total_memory_usage]
|
177
|
+
)
|
173
178
|
end
|
174
179
|
end
|
175
180
|
end
|
@@ -1,27 +1,12 @@
|
|
1
|
-
#
|
2
|
-
#
|
3
|
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
-
# of this software and associated documentation files (the "Software"), to deal
|
5
|
-
# in the Software without restriction, including without limitation the rights
|
6
|
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
-
# copies of the Software, and to permit persons to whom the Software is
|
8
|
-
# furnished to do so, subject to the following conditions:
|
9
|
-
#
|
10
|
-
# The above copyright notice and this permission notice shall be included in
|
11
|
-
# all copies or substantial portions of the Software.
|
12
|
-
#
|
13
|
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
-
# THE SOFTWARE.
|
1
|
+
# frozen_string_literal: true
|
20
2
|
|
21
|
-
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2020-2025, by Samuel Williams.
|
22
5
|
|
23
|
-
|
24
|
-
|
6
|
+
require "samovar"
|
7
|
+
|
8
|
+
require_relative "summary"
|
9
|
+
require_relative "../version"
|
25
10
|
|
26
11
|
module Process
|
27
12
|
module Metrics
|
@@ -30,13 +15,13 @@ module Process
|
|
30
15
|
self.description = "Collect memory usage statistics."
|
31
16
|
|
32
17
|
options do
|
33
|
-
option
|
34
|
-
option
|
18
|
+
option "-h/--help", "Print out help information."
|
19
|
+
option "-v/--version", "Print out the application version."
|
35
20
|
end
|
36
21
|
|
37
22
|
nested :command, {
|
38
|
-
|
39
|
-
}, default:
|
23
|
+
"summary" => Summary,
|
24
|
+
}, default: "summary"
|
40
25
|
|
41
26
|
def call
|
42
27
|
if @options[:version]
|
@@ -1,24 +1,9 @@
|
|
1
|
-
#
|
2
|
-
#
|
3
|
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
-
# of this software and associated documentation files (the "Software"), to deal
|
5
|
-
# in the Software without restriction, including without limitation the rights
|
6
|
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
-
# copies of the Software, and to permit persons to whom the Software is
|
8
|
-
# furnished to do so, subject to the following conditions:
|
9
|
-
#
|
10
|
-
# The above copyright notice and this permission notice shall be included in
|
11
|
-
# all copies or substantial portions of the Software.
|
12
|
-
#
|
13
|
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
-
# THE SOFTWARE.
|
1
|
+
# frozen_string_literal: true
|
20
2
|
|
21
|
-
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2020-2025, by Samuel Williams.
|
5
|
+
|
6
|
+
require_relative "command/top"
|
22
7
|
|
23
8
|
module Process
|
24
9
|
module Metrics
|
@@ -1,87 +1,89 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
#
|
4
|
-
#
|
5
|
-
# Permission is hereby granted, free of charge, to any person obtaining a copy
|
6
|
-
# of this software and associated documentation files (the "Software"), to deal
|
7
|
-
# in the Software without restriction, including without limitation the rights
|
8
|
-
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
9
|
-
# copies of the Software, and to permit persons to whom the Software is
|
10
|
-
# furnished to do so, subject to the following conditions:
|
11
|
-
#
|
12
|
-
# The above copyright notice and this permission notice shall be included in
|
13
|
-
# all copies or substantial portions of the Software.
|
14
|
-
#
|
15
|
-
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
16
|
-
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
17
|
-
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
18
|
-
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
19
|
-
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
20
|
-
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
21
|
-
# THE SOFTWARE.
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2019-2025, by Samuel Williams.
|
22
5
|
|
23
|
-
require_relative
|
24
|
-
require
|
6
|
+
require_relative "memory"
|
7
|
+
require "set"
|
8
|
+
require "json"
|
25
9
|
|
26
10
|
module Process
|
27
11
|
module Metrics
|
28
12
|
PS = "ps"
|
29
13
|
|
14
|
+
DURATION = /\A
|
15
|
+
(?:(?<days>\d+)-)? # Optional days (e.g., '2-')
|
16
|
+
(?:(?<hours>\d+):)? # Optional hours (e.g., '1:')
|
17
|
+
(?<minutes>\d{1,2}): # Minutes (always present, 1 or 2 digits)
|
18
|
+
(?<seconds>\d{2}) # Seconds (exactly 2 digits)
|
19
|
+
(?:\.(?<fraction>\d{1,2}))? # Optional fraction of a second (e.g., '.27')
|
20
|
+
\z/x
|
21
|
+
|
22
|
+
|
23
|
+
# Parse a duration string into seconds.
|
30
24
|
# According to the linux manual page specifications.
|
31
25
|
def self.duration(value)
|
32
|
-
if
|
33
|
-
|
26
|
+
if match = DURATION.match(value)
|
27
|
+
days = match[:days].to_i
|
28
|
+
hours = match[:hours].to_i
|
29
|
+
minutes = match[:minutes].to_i
|
30
|
+
seconds = match[:seconds].to_i
|
31
|
+
fraction = match[:fraction].to_i
|
32
|
+
|
33
|
+
return days * 24 * 60 * 60 + hours * 60 * 60 + minutes * 60 + seconds + fraction / 100.0
|
34
|
+
else
|
35
|
+
return 0.0
|
34
36
|
end
|
35
37
|
end
|
36
38
|
|
37
|
-
#
|
38
|
-
# pmem: Percentage Memory used.
|
39
|
-
# pcpu: Percentage Processor used.
|
40
|
-
# time: The process time used (executing on CPU).
|
41
|
-
# vsz: Virtual Size in kilobytes
|
42
|
-
# rss: Resident Set Size in kilobytes
|
43
|
-
# etime: The process elapsed time.
|
44
|
-
# command: The name of the process.
|
39
|
+
# The fields that will be extracted from the `ps` command.
|
45
40
|
FIELDS = {
|
46
|
-
pid: ->(value){value.to_i},
|
47
|
-
ppid: ->(value){value.to_i},
|
48
|
-
pgid: ->(value){value.to_i},
|
49
|
-
pcpu: ->(value){value.to_f},
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
etime: self.method(:duration),
|
54
|
-
command: ->(value){value},
|
41
|
+
pid: ->(value){value.to_i}, # Process ID
|
42
|
+
ppid: ->(value){value.to_i}, # Parent Process ID
|
43
|
+
pgid: ->(value){value.to_i}, # Process Group ID
|
44
|
+
pcpu: ->(value){value.to_f}, # Percentage CPU
|
45
|
+
vsz: ->(value){value.to_i}, # Virtual Size (KiB)
|
46
|
+
rss: ->(value){value.to_i}, # Resident Size (KiB)
|
47
|
+
time: self.method(:duration), # CPU Time (seconds)
|
48
|
+
etime: self.method(:duration), # Elapsed Time (seconds)
|
49
|
+
command: ->(value){value}, # Command (name of the process)
|
55
50
|
}
|
56
51
|
|
57
|
-
|
52
|
+
# General process information.
|
53
|
+
class General < Struct.new(:process_id, :parent_process_id, :process_group_id, :processor_utilization, :virtual_size, :resident_size, :processor_time, :elapsed_time, :command, :memory)
|
54
|
+
# Convert the object to a JSON serializable hash.
|
58
55
|
def as_json
|
59
56
|
{
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
57
|
+
process_id: self.process_id,
|
58
|
+
parent_process_id: self.parent_process_id,
|
59
|
+
process_group_id: self.process_group_id,
|
60
|
+
processor_utilization: self.processor_utilization,
|
61
|
+
total_size: self.total_size,
|
62
|
+
virtual_size: self.virtual_size,
|
63
|
+
resident_size: self.resident_size,
|
64
|
+
processor_time: self.processor_time,
|
65
|
+
elapsed_time: self.elapsed_time,
|
68
66
|
command: self.command,
|
69
67
|
memory: self.memory&.as_json,
|
70
68
|
}
|
71
69
|
end
|
72
70
|
|
71
|
+
# Convert the object to a JSON string.
|
73
72
|
def to_json(*arguments)
|
74
73
|
as_json.to_json(*arguments)
|
75
74
|
end
|
76
75
|
|
77
|
-
|
78
|
-
|
79
|
-
|
76
|
+
# The total size of the process in memory, in kilobytes.
|
77
|
+
def total_size
|
78
|
+
if memory = self.memory
|
79
|
+
memory.proportional_size
|
80
80
|
else
|
81
|
-
self.
|
81
|
+
self.resident_size
|
82
82
|
end
|
83
83
|
end
|
84
84
|
|
85
|
+
alias memory_usage total_size
|
86
|
+
|
85
87
|
def self.expand_children(children, hierarchy, pids)
|
86
88
|
children.each do |pid|
|
87
89
|
self.expand(pid, hierarchy, pids)
|
@@ -102,8 +104,8 @@ module Process
|
|
102
104
|
hierarchy = Hash.new{|h,k| h[k] = []}
|
103
105
|
|
104
106
|
processes.each_value do |process|
|
105
|
-
if
|
106
|
-
hierarchy[
|
107
|
+
if parent_process_id = process.parent_process_id
|
108
|
+
hierarchy[parent_process_id] << process.process_id
|
107
109
|
end
|
108
110
|
end
|
109
111
|
|
@@ -116,18 +118,22 @@ module Process
|
|
116
118
|
end
|
117
119
|
end
|
118
120
|
|
119
|
-
|
121
|
+
# Capture process information. If given a `pid`, it will capture the details of that process. If given a `ppid`, it will capture the details of all child processes. Specify both `pid` and `ppid` if you want to capture a process and all its children.
|
122
|
+
#
|
123
|
+
# @parameter pid [Integer] The process ID to capture.
|
124
|
+
# @parameter ppid [Integer] The parent process ID to capture.
|
125
|
+
def self.capture(pid: nil, ppid: nil, ps: PS, memory: Memory.supported?)
|
120
126
|
input, output = IO.pipe
|
121
127
|
|
122
128
|
arguments = [ps]
|
123
129
|
|
124
130
|
if pid && ppid.nil?
|
125
|
-
arguments.push("-p", Array(pid).join(
|
131
|
+
arguments.push("-p", Array(pid).join(","))
|
126
132
|
else
|
127
133
|
arguments.push("ax")
|
128
134
|
end
|
129
135
|
|
130
|
-
arguments.push("-o",
|
136
|
+
arguments.push("-o", FIELDS.keys.join(","))
|
131
137
|
|
132
138
|
ps_pid = Process.spawn(*arguments, out: output, pgroup: true)
|
133
139
|
|
@@ -138,13 +144,12 @@ module Process
|
|
138
144
|
processes = {}
|
139
145
|
|
140
146
|
lines.map do |line|
|
141
|
-
record =
|
142
|
-
zip(line.split(/\s+/,
|
147
|
+
record = FIELDS.
|
148
|
+
zip(line.split(/\s+/, FIELDS.size)).
|
143
149
|
map{|(key, type), value| type.call(value)}
|
144
|
-
|
145
150
|
instance = self.new(*record)
|
146
151
|
|
147
|
-
processes[instance.
|
152
|
+
processes[instance.process_id] = instance
|
148
153
|
end
|
149
154
|
|
150
155
|
if ppid
|
@@ -162,12 +167,8 @@ module Process
|
|
162
167
|
end
|
163
168
|
end
|
164
169
|
|
165
|
-
if
|
170
|
+
if memory
|
166
171
|
self.capture_memory(processes)
|
167
|
-
|
168
|
-
# if pid
|
169
|
-
# self.compute_summary(pid, processes)
|
170
|
-
# end
|
171
172
|
end
|
172
173
|
|
173
174
|
return processes
|
@@ -0,0 +1,100 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
# Released under the MIT License.
|
4
|
+
# Copyright, 2025, by Samuel Williams.
|
5
|
+
|
6
|
+
module Process
|
7
|
+
module Metrics
|
8
|
+
class Memory::Darwin
|
9
|
+
VMMAP = "/usr/bin/vmmap"
|
10
|
+
|
11
|
+
# Whether the memory usage can be captured on this system.
|
12
|
+
def self.supported?
|
13
|
+
File.executable?(VMMAP)
|
14
|
+
end
|
15
|
+
|
16
|
+
# Parse a size string into kilobytes.
|
17
|
+
def self.parse_size(string)
|
18
|
+
return 0 unless string
|
19
|
+
|
20
|
+
case string.strip
|
21
|
+
when /([\d\.]+)K/i then ($1.to_f).round
|
22
|
+
when /([\d\.]+)M/i then ($1.to_f * 1024).round
|
23
|
+
when /([\d\.]+)G/i then ($1.to_f * 1024 * 1024).round
|
24
|
+
else (string.to_f / 1024).ceil
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
LINE = /\A
|
29
|
+
\s*
|
30
|
+
(?<region_name>.+?)\s+
|
31
|
+
(?<start_address>[0-9a-fA-F]+)-(?<end_address>[0-9a-fA-F]+)\s+
|
32
|
+
\[\s*(?<virtual_size>[\d\.]+[KMG]?)\s+(?<resident_size>[\d\.]+[KMG]?)\s+(?<dirty_size>[\d\.]+[KMG]?)\s+(?<swap_size>[\d\.]+[KMG]?)\s*\]\s+
|
33
|
+
(?<permissions>[rwx\-\/]+)\s+
|
34
|
+
SM=(?<sharing_mode>\w+)
|
35
|
+
/x
|
36
|
+
|
37
|
+
# Capture memory usage for the given process IDs.
|
38
|
+
def self.capture(pids)
|
39
|
+
usage = Memory.zero
|
40
|
+
|
41
|
+
pids.each do |pid|
|
42
|
+
IO.popen(["vmmap", pid.to_s], "r") do |io|
|
43
|
+
io.each_line do |line|
|
44
|
+
if match = LINE.match(line)
|
45
|
+
virtual_size = parse_size(match[:virtual_size])
|
46
|
+
resident_size = parse_size(match[:resident_size])
|
47
|
+
dirty_size = parse_size(match[:dirty_size])
|
48
|
+
swap_size = parse_size(match[:swap_size])
|
49
|
+
|
50
|
+
# puts [match[:region_name], virtual_size, resident_size, dirty_size, swap_size, match[:permissions], match[:sharing_mode]].join(",")
|
51
|
+
|
52
|
+
# Update counts
|
53
|
+
usage.map_count += 1
|
54
|
+
usage.resident_size += resident_size
|
55
|
+
usage.swap_size += swap_size
|
56
|
+
|
57
|
+
# Private vs. Shared memory
|
58
|
+
# COW=copy_on_write PRV=private NUL=empty ALI=aliased
|
59
|
+
# SHM=shared ZER=zero_filled S/A=shared_alias
|
60
|
+
case match[:sharing_mode]
|
61
|
+
when "PRV"
|
62
|
+
usage.private_clean_size += resident_size - dirty_size
|
63
|
+
usage.private_dirty_size += dirty_size
|
64
|
+
when "COW", "SHM"
|
65
|
+
usage.shared_clean_size += resident_size - dirty_size
|
66
|
+
usage.shared_dirty_size += dirty_size
|
67
|
+
end
|
68
|
+
|
69
|
+
# Anonymous memory: no region detail path or special names
|
70
|
+
if match[:region_name] =~ /MALLOC|VM_ALLOCATE|Stack|STACK|anonymous/
|
71
|
+
usage.anonymous_size += resident_size
|
72
|
+
end
|
73
|
+
# else
|
74
|
+
# puts "Failed to match line: #{line}"
|
75
|
+
end
|
76
|
+
end
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# On Darwin, we cannot compute the proportional size, so we just set it to the resident size.
|
81
|
+
usage.proportional_size = usage.resident_size
|
82
|
+
usage.proportional_swap_size = usage.swap_size
|
83
|
+
|
84
|
+
return usage
|
85
|
+
end
|
86
|
+
end
|
87
|
+
|
88
|
+
if Memory::Darwin.supported?
|
89
|
+
class << Memory
|
90
|
+
def supported?
|
91
|
+
return true
|
92
|
+
end
|
93
|
+
|
94
|
+
def capture(pids)
|
95
|
+
return Memory::Darwin.capture(pids)
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|