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 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