process-metrics 0.2.1 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: f0917ad55068d053da8e6f58943114ab1cc7d2dd7b61969f022c7a373a7212d9
4
- data.tar.gz: cff5b81fdad7a03e4c2a843178e5c880c20c68580ef2dd93e9a29b529591ea87
3
+ metadata.gz: 42d11b0f2e725706c447c1b4cfa3f15a961f759fb8cacf0ef0d238338c3be6ab
4
+ data.tar.gz: 80468efcb1b3eab151f8fcf0d054d93d11dcd90127ec70a6ad63e5fc10b68f89
5
5
  SHA512:
6
- metadata.gz: 94617d513a1849da4c48d052e4aa92d7279d07deb0f0710439a19f5c87b17a3c9fd259ea260b9c774130c7a111ed7ff4d4870d58ece51c1d2bff6a5c65ccd939
7
- data.tar.gz: 2689b081c6aa85ce0901c97feec76ddb54ef9bd322872f1fc6a76a6bfd14f682a8a80499e094fae8c080f443f36ddfaea61ea90c888a63fdee6f027a0fd5a8ed
6
+ metadata.gz: 5c765d57991e1a15e8c966ea1497234dae6e8e98cdd8e2463cefe1caf046770b1930b01b68b202b7ace9d802ad6d0577dfbb3cfef48a1d23abc3013ea5a48580
7
+ data.tar.gz: 8c2bf4692a659037dc8167ad7f3349beb300e4bf021ee97b8ed3bf645cc83eaeefa19ae6ef5aee62855d781f7782ab9750d3999ca21982b865c24afbb65a0be5
checksums.yaml.gz.sig ADDED
Binary file
data/bin/process-metrics CHANGED
@@ -21,7 +21,7 @@
21
21
  # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
22
22
  # THE SOFTWARE.
23
23
 
24
- require_relative '../lib/process/metrics/command'
24
+ require_relative "../lib/process/metrics/command"
25
25
 
26
26
  begin
27
27
  Process::Metrics::Command.call
@@ -1,28 +1,13 @@
1
- # Copyright, 2020, by Samuel G. D. Williams. <http://www.codeotaku.com>
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
- require 'samovar'
3
+ # Released under the MIT License.
4
+ # Copyright, 2020-2025, by Samuel Williams.
22
5
 
23
- require_relative '../general'
6
+ require "samovar"
24
7
 
25
- require 'console/terminal'
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 '--pid <integer>', "Report on a single process id.", type: Integer, required: true
61
- option '-p/--ppid <integer>', "Report on all children of this process id.", type: Integer, required: true
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 format_pcpu(value, terminal)
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, terminal, scale: @options[:memory_scale])
108
- if value > (1024.0 * scale * 0.8)
90
+ def format_memory_usage(value, total, terminal)
91
+ if value > (total * 0.8)
109
92
  intensity = :high
110
- elsif value > (1024.0 * scale * 0.5)
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) + ' ').rjust(10)
99
+ formatted = (format_size(value) + " ").rjust(10)
117
100
 
118
- terminal.print(formatted, intensity, "[", Bar.format(value / (1024.0 * scale), 60), "]", :reset)
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
- memory_usage = 0
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
- format_pcpu(general.pcpu, terminal)
128
+ format_processor_utilization(general.processor_utilization, terminal)
135
129
  terminal.print_line
136
130
 
137
131
  if memory = general.memory
138
- memory_usage += memory.proportional_size
132
+ shared_memory_usage += memory.proportional_size
133
+ private_memory_usage += memory.unique_size
139
134
 
140
135
  terminal.print_line(
141
- :key, "Memory (PSS): ".rjust(20), :reset,
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 (USS): ".rjust(20), :reset,
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
- memory_usage += general.rss
145
+ shared_memory_usage += general.resident_size
151
146
  proportional = false
152
147
 
153
148
  terminal.print_line(
154
- :key, "Memory (RSS): ".rjust(20), :reset,
155
- format_memory_usage[general.rss]
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 (PSS): ".rjust(20), :reset,
165
- format_memory_usage[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 (RSS): ".rjust(20), :reset,
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
- # Copyright, 2020, by Samuel G. D. Williams. <http://www.codeotaku.com>
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
- require 'samovar'
3
+ # Released under the MIT License.
4
+ # Copyright, 2020-2025, by Samuel Williams.
22
5
 
23
- require_relative 'summary'
24
- require_relative '../version'
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 '-h/--help', "Print out help information."
34
- option '-v/--version', "Print out the application version."
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
- 'summary' => Summary,
39
- }, default: 'summary'
23
+ "summary" => Summary,
24
+ }, default: "summary"
40
25
 
41
26
  def call
42
27
  if @options[:version]
@@ -1,24 +1,9 @@
1
- # Copyright, 2017, by Samuel G. D. Williams. <http://www.codeotaku.com>
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
- require_relative 'command/top'
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
- # Copyright, 2019, by Samuel G. D. Williams. <https://www.codeotaku.com>
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 'memory'
24
- require 'set'
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 /((?<days>\d\d)\-)?((?<hours>\d\d):)?(?<minutes>\d\d):(?<seconds>\d\d)?/ =~ value
33
- (((days&.to_i || 0) * 24 + (hours&.to_i || 0)) * 60 + (minutes&.to_i || 0)) * 60 + seconds&.to_i
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
- # pid: Process Identifier
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
- time: self.method(:duration),
51
- vsz: ->(value){value.to_i},
52
- rss: ->(value){value.to_i},
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
- class General < Struct.new(:pid, :ppid, :pgid, :pcpu, :vsz, :rss, :time, :etime, :command, :memory)
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
- pid: self.pid,
61
- ppid: self.ppid,
62
- pgid: self.pgid,
63
- pcpu: self.pcpu,
64
- vsz: self.vsz,
65
- rss: self.rss,
66
- time: self.time,
67
- etime: self.etime,
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
- def memory_usage
78
- if self.memory
79
- self.memory.proportional_size
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.rss
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 ppid = process.ppid
106
- hierarchy[ppid] << process.pid
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
- def self.capture(pid: nil, ppid: nil, ps: PS, fields: FIELDS)
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", fields.keys.join(','))
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 = fields.
142
- zip(line.split(/\s+/, fields.size)).
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.pid] = 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 Memory.supported?
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