|
1 | 1 | # frozen_string_literal: true |
2 | 2 |
|
3 | 3 | # Released under the MIT License. |
4 | | -# Copyright, 2025, by Samuel Williams. |
| 4 | +# Copyright, 2025-2026, by Samuel Williams. |
5 | 5 |
|
6 | 6 | def capture(pid: nil, ppid: nil) |
7 | 7 | require "process/metrics/general" |
8 | 8 |
|
9 | 9 | Process::Metrics::General.capture(pid: pid, ppid: ppid) |
10 | 10 | end |
11 | 11 |
|
| 12 | +# Print metrics for a process or processes. |
| 13 | +# |
| 14 | +# @parameter pid [Integer] The process ID to capture. |
| 15 | +# @parameter ppid [Integer] The parent process ID to capture. |
| 16 | +def metrics(pid: nil, ppid: nil) |
| 17 | + require "process/metrics/general" |
| 18 | + require "process/metrics/host" |
| 19 | + |
| 20 | + terminal = self.terminal |
| 21 | + format_memory = self.method(:format_memory).curry |
| 22 | + |
| 23 | + # Host name (uname -a) as first line |
| 24 | + if host_name = Process::Metrics::Host.name |
| 25 | + terminal.print_line(host_name, :reset) |
| 26 | + end |
| 27 | + |
| 28 | + host = Process::Metrics::Host::Memory.capture |
| 29 | + terminal.print_line( |
| 30 | + :key, "Total Memory: ".rjust(20), :reset, |
| 31 | + format_size(host.total_size).rjust(9) |
| 32 | + ) |
| 33 | + |
| 34 | + terminal.print_line( |
| 35 | + :key, "Used Memory: ".rjust(20), :reset, |
| 36 | + format_memory[host.used_size, host.total_size] |
| 37 | + ) |
| 38 | + |
| 39 | + summary = Process::Metrics::General.capture(pid: pid, ppid: ppid) |
| 40 | + |
| 41 | + shared_memory = 0 |
| 42 | + private_memory = 0 |
| 43 | + total_memory = host.total_size |
| 44 | + |
| 45 | + proportional = true |
| 46 | + |
| 47 | + summary.each do |pid, general| |
| 48 | + terminal.print_line(:pid, pid, :reset, " ", :command, general[:command]) |
| 49 | + |
| 50 | + terminal.print(:key, "Processor Usage: ".rjust(20), :reset) |
| 51 | + format_processor_utilization(general.processor_utilization, terminal) |
| 52 | + terminal.print_line |
| 53 | + |
| 54 | + if memory = general.memory |
| 55 | + shared_memory += memory.proportional_size |
| 56 | + private_memory += memory.unique_size |
| 57 | + |
| 58 | + terminal.print_line( |
| 59 | + :key, "Memory: ".rjust(20), :reset, |
| 60 | + format_memory[memory.proportional_size, total_memory] |
| 61 | + ) |
| 62 | + |
| 63 | + terminal.print_line( |
| 64 | + :key, "Private Memory: ".rjust(20), :reset, |
| 65 | + format_memory[memory.unique_size, total_memory] |
| 66 | + ) |
| 67 | + else |
| 68 | + shared_memory += general.resident_size |
| 69 | + proportional = false |
| 70 | + |
| 71 | + terminal.print_line( |
| 72 | + :key, "Memory: ".rjust(20), :reset, |
| 73 | + format_memory[general.resident_size, total_memory] |
| 74 | + ) |
| 75 | + end |
| 76 | + end |
| 77 | + |
| 78 | + terminal.print_line("Summary") |
| 79 | + |
| 80 | + if proportional |
| 81 | + terminal.print_line( |
| 82 | + :key, "Memory: ".rjust(20), :reset, |
| 83 | + format_memory[shared_memory, total_memory] |
| 84 | + ) |
| 85 | + |
| 86 | + terminal.print_line( |
| 87 | + :key, "Private Memory: ".rjust(20), :reset, |
| 88 | + format_memory[private_memory, total_memory] |
| 89 | + ) |
| 90 | + else |
| 91 | + terminal.print_line( |
| 92 | + :key, "Memory: ".rjust(20), :reset, |
| 93 | + format_memory[shared_memory, total_memory] |
| 94 | + ) |
| 95 | + end |
| 96 | + |
| 97 | + terminal.print_line( |
| 98 | + :key, "Memory (Total): ".rjust(20), :reset, |
| 99 | + format_memory[shared_memory + private_memory, total_memory] |
| 100 | + ) |
| 101 | +end |
| 102 | + |
| 103 | +protected |
| 104 | + |
| 105 | +# Helper module for rendering horizontal progress bars using Unicode block characters. |
| 106 | +module Bar |
| 107 | + BLOCK = [ |
| 108 | + " ", |
| 109 | + "▏", |
| 110 | + "▎", |
| 111 | + "▍", |
| 112 | + "▌", |
| 113 | + "▋", |
| 114 | + "▊", |
| 115 | + "▉", |
| 116 | + "█", |
| 117 | + ] |
| 118 | + |
| 119 | + # Format a fractional value as a horizontal bar. |
| 120 | + # @parameter value [Float] A value between 0.0 and 1.0 representing the fill level. |
| 121 | + # @parameter width [Integer] The width of the bar in characters. |
| 122 | + # @returns [String] A string of Unicode block characters representing the filled bar. |
| 123 | + def self.format(value, width) |
| 124 | + blocks = width * value |
| 125 | + full_blocks = blocks.floor |
| 126 | + partial_block = ((blocks - full_blocks) * BLOCK.size).floor |
| 127 | + |
| 128 | + if partial_block.zero? |
| 129 | + BLOCK.last * full_blocks |
| 130 | + else |
| 131 | + "#{BLOCK.last * full_blocks}#{BLOCK[partial_block]}" |
| 132 | + end.ljust(width) |
| 133 | + end |
| 134 | +end |
| 135 | + |
| 136 | +# Get the configured terminal for styled output. |
| 137 | +# @returns [Console::Terminal] A terminal object with color/style definitions. |
| 138 | +def terminal |
| 139 | + terminal = Console::Terminal.for($stdout) |
| 140 | + |
| 141 | + # terminal[:pid] = terminal.style(:blue) |
| 142 | + terminal[:command] = terminal.style(nil, nil, :bold) |
| 143 | + terminal[:key] = terminal.style(:cyan) |
| 144 | + |
| 145 | + terminal[:low] = terminal.style(:green) |
| 146 | + terminal[:medium] = terminal.style(:yellow) |
| 147 | + terminal[:high] = terminal.style(:red) |
| 148 | + |
| 149 | + return terminal |
| 150 | +end |
| 151 | + |
| 152 | +# Width in characters for progress bars (label + value + " []" ≈ 33 chars). |
| 153 | +DEFAULT_BAR_WIDTH = 60 |
| 154 | + |
| 155 | +def bar_width(terminal, prefix_width: 33) |
| 156 | + if width = terminal.width |
| 157 | + return width - prefix_width if width > prefix_width |
| 158 | + end |
| 159 | + |
| 160 | + return DEFAULT_BAR_WIDTH |
| 161 | +end |
| 162 | + |
| 163 | +# Format a processor utilization percentage with color-coded bar. |
| 164 | +# @parameter value [Float] The CPU utilization percentage (0.0-100.0). |
| 165 | +# @parameter terminal [Console::Terminal] The terminal to output styled text. |
| 166 | +def format_processor_utilization(value, terminal) |
| 167 | + if value > 80.0 |
| 168 | + intensity = :high |
| 169 | + elsif value > 50.0 |
| 170 | + intensity = :medium |
| 171 | + else |
| 172 | + intensity = :low |
| 173 | + end |
| 174 | + |
| 175 | + formatted = "%5.1f%% " % value |
| 176 | + |
| 177 | + terminal.print(formatted.rjust(10), intensity, "[", Bar.format(value / 100.0, bar_width(terminal)), "]", :reset) |
| 178 | +end |
| 179 | + |
| 180 | +UNITS = ["KiB", "MiB", "GiB"] |
| 181 | + |
| 182 | +# Format a memory size value in human-readable units. |
| 183 | +# @parameter value [Numeric] The size value in bytes. |
| 184 | +# @parameter units [Array(String)] The unit labels to use for scaling. |
| 185 | +# @returns [String] A formatted string with value and unit (e.g., "512KiB", "1.5MiB"). |
| 186 | +def format_size(value, units: UNITS) |
| 187 | + unit = -1 |
| 188 | + |
| 189 | + while value >= 1024.0 && unit < units.size - 1 |
| 190 | + value /= 1024.0 |
| 191 | + unit += 1 |
| 192 | + end |
| 193 | + |
| 194 | + if unit < 0 |
| 195 | + # Value is less than 1 KiB, show in bytes |
| 196 | + return "#{value.round(0)}B" |
| 197 | + else |
| 198 | + return "#{value.round(unit)}#{units[unit]}" |
| 199 | + end |
| 200 | +end |
| 201 | + |
| 202 | +# Format a memory value with a horizontal bar showing utilization relative to total. |
| 203 | +# @parameter value [Numeric] The memory value in bytes. |
| 204 | +# @parameter total [Numeric] The total memory available in bytes. |
| 205 | +# @parameter terminal [Console::Terminal] The terminal to output styled text. |
| 206 | +def format_memory(value, total, terminal) |
| 207 | + if value > (total * 0.8) |
| 208 | + intensity = :high |
| 209 | + elsif value > (total * 0.5) |
| 210 | + intensity = :medium |
| 211 | + else |
| 212 | + intensity = :low |
| 213 | + end |
| 214 | + |
| 215 | + formatted = (format_size(value) + " ").rjust(10) |
| 216 | + |
| 217 | + terminal.print(formatted, intensity, "[", Bar.format(value / total.to_f, bar_width(terminal)), "]", :reset) |
| 218 | +end |
0 commit comments