class Darwin
Darwin (macOS) implementation of per-process memory metrics using vmmap(1). Parses vmmap output for virtual/resident/dirty/swap per region and maps sharing mode (PRV, COW, SHM) to private/shared fields.
Definitions
def self.supported?
Whether the memory usage can be captured on this system.
Implementation
def self.supported?
File.executable?(VMMAP)
end
def self.parse_size(size_string)
Parse a size string from vmmap (e.g. "4K", "1.5M", "2G") into bytes.
Signature
-
parameter
size_stringString | Nil -
returns
Integer
Implementation
def self.parse_size(size_string)
return 0 unless size_string
case size_string.strip
when /([\d\.]+)K/i then ($1.to_f * 1024).round
when /([\d\.]+)M/i then ($1.to_f * 1024 * 1024).round
when /([\d\.]+)G/i then ($1.to_f * 1024 * 1024 * 1024).round
else (size_string.to_f).ceil
end
end
LINE
Regex for vmmap region lines: region name, address range, [virtual resident dirty swap], permissions, SM=sharing_mode.
Implementation
LINE = /\A
\s*
(?<region_name>.+?)\s+
(?<start_address>[0-9a-fA-F]+)-(?<end_address>[0-9a-fA-F]+)\s+
\[\s*(?<virtual_size>[\d\.]+[KMG]?)\s+(?<resident_size>[\d\.]+[KMG]?)\s+(?<dirty_size>[\d\.]+[KMG]?)\s+(?<swap_size>[\d\.]+[KMG]?)\s*\]\s+
(?<permissions>[rwx\-\/]+)\s+
SM=(?<sharing_mode>\w+)
/x
def self.capture(pid, count: 1, **options)
Capture memory usage by running vmmap for the given pid and summing region sizes. Proportional size is estimated as resident_size / count (Darwin has no PSS).
Signature
-
parameter
pidInteger Process ID.
-
parameter
countInteger Number of processes for proportional estimate (default: 1).
-
returns
Memory | Nil
Implementation
def self.capture(pid, count: 1, **options)
IO.popen(["vmmap", pid.to_s], "r") do |io|
usage = Memory.zero
io.each_line do |line|
if match = LINE.match(line)
usage.map_count += 1
virtual_size = parse_size(match[:virtual_size])
resident_size = parse_size(match[:resident_size])
dirty_size = parse_size(match[:dirty_size])
swap_size = parse_size(match[:swap_size])
usage.resident_size += resident_size
usage.swap_size += swap_size
# Private vs. Shared memory: COW=copy_on_write PRV=private NUL=empty ALI=aliased SHM=shared ZER=zero_filled S/A=shared_alias
case match[:sharing_mode]
when "PRV"
usage.private_clean_size += resident_size - dirty_size
usage.private_dirty_size += dirty_size
when "COW", "SHM"
usage.shared_clean_size += resident_size - dirty_size
usage.shared_dirty_size += dirty_size
end
# Anonymous memory: no region detail path or special names
if match[:region_name] =~ /MALLOC|VM_ALLOCATE|Stack|STACK|anonymous/
usage.anonymous_size += resident_size
end
end
end
# vmmap might not fail, but also might not return any data.
return nil if usage.map_count.zero?
# Darwin does not expose proportional memory usage, so we guess based on the number of processes. Yes, this is a terrible hack, but it's the most reasonable thing to do given the constraints:
usage.proportional_size = usage.resident_size / count
usage.proportional_swap_size = usage.swap_size / count
return usage
end
rescue Errno::ESRCH
# Process doesn't exist.
return nil
end