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
         |