From 5fd83771641d15c418f747bd343ba6738d3875f7 Mon Sep 17 00:00:00 2001 From: Cameron Katri Date: Sun, 9 May 2021 14:20:58 -0400 Subject: Import macOS userland adv_cmds-176 basic_cmds-55 bootstrap_cmds-116.100.1 developer_cmds-66 diskdev_cmds-667.40.1 doc_cmds-53.60.1 file_cmds-321.40.3 mail_cmds-35 misc_cmds-34 network_cmds-606.40.1 patch_cmds-17 remote_cmds-63 shell_cmds-216.60.1 system_cmds-880.60.2 text_cmds-106 --- system_cmds/zprint.tproj/zprint.lua | 281 ++++++++++++++++++++++++++++++++++++ 1 file changed, 281 insertions(+) create mode 100644 system_cmds/zprint.tproj/zprint.lua (limited to 'system_cmds/zprint.tproj/zprint.lua') diff --git a/system_cmds/zprint.tproj/zprint.lua b/system_cmds/zprint.tproj/zprint.lua new file mode 100644 index 0000000..a5ca245 --- /dev/null +++ b/system_cmds/zprint.tproj/zprint.lua @@ -0,0 +1,281 @@ +require 'strict' + +-- # zprint +-- +-- Parse the output of zprint into tables. + +local zprint = {} + +-- Return the lines inside "dashed" lines -- that is, lines that are entirely +-- made up of dashes (-) in the string `str`. The `skip_dashed` argument +-- controls how many dashed lines to skip before returning the lines between it +-- and the next dashed line. +local function lines_inside_dashes(str, skip_dashed) + local start_pos = 1 + for _ = 1, skip_dashed do + _, start_pos = str:find('\n[-]+\n', start_pos) + end + assert(start_pos, 'found dashed line in output') + local end_pos, _ = str:find('\n[-]+\n', start_pos + 1) + assert(end_pos, 'found ending dashed line in output') + + return str:sub(start_pos + 1, end_pos - 1) +end + +-- Iterate through the zones listed in the given zprint(1) output `zpout`. +-- +-- for zone in zprint_zones(io.stdin:read('*a')) do +-- print(zone.name, zone.size, zone.used_size) +-- end +function zprint.zones(zpout) + -- Get to the first section delimited by dashes. This is where the zones are + -- recorded. + local zones = lines_inside_dashes(zpout, 1) + + -- Create an iterator for each line, for use in our own iteration function. + local lines = zones:gmatch('([^\n]+)') + + return function () + -- Grab the next line. + local line = lines() + if not line then + return nil + end + + -- Match each column from zprint's output. + local name, eltsz, cursz_kb, maxsz_kb, nelts, nelts_max, nused, + allocsz_kb = line:match( + '([%S]+)%s+(%d+)%s+(%d+)K%s+(%d+)K%s+(%d+)%s+(%d+)%s+(%d+)%s+(%d+)') + -- Convert numeric fields to numbers, and into bytes if specified in KiB. + eltsz = tonumber(eltsz) + local cursz = tonumber(cursz_kb) * 1024 + local maxsz = tonumber(maxsz_kb) * 1024 + local usedsz = tonumber(nused) * eltsz + local allocsz = tonumber(allocsz_kb) * 1024 + + -- Return a table representing the zone. + return { + name = name, -- the name of the zone + size = cursz, -- the size of the zone + max_size = maxsz, -- the maximum size of the zone + used_size = usedsz, -- the size of all used elements in the zone + element_size = eltsz, -- the size of each element in the zone + allocation_size = allocsz, -- the size of allocations made for the zone + } + end +end + +-- Match the output of a vm_tag line +-- This line has a variable number of columns. +-- This function returns the name and a table containing each numeric column's +-- value. +local function match_tag(line, ncols) + -- First try to match names with C++ symbol names. + -- These can have whitespace in the argument list. + local name_pattern = '^(%S+%b()%S*)' + local name = line:match(name_pattern) + if not name then + name = line:match('(%S+)') + if not name then + return nil + end + end + local after_name = line:sub(#name) + local t = {} + for v in line:gmatch('%s+(%d+)K?') do + table.insert(t, v) + end + return name, t +end + +-- Iterate through the tags listed in the given zprint(1) output `zpout`. +function zprint.tags(zpout) + -- Get to the third zone delimited by dashes, where the tags are recorded. + local tags = lines_inside_dashes(zpout, 3) + + local lines = tags:gmatch('([^\n]+)') + + return function () + local line = lines() + if not line then + return nil + end + + -- Skip any unloaded kmod lines. + while line:match('(unloaded kmod)') do + line = lines() + end + -- End on the zone tags line, since it's not useful. + if line:match('zone tags') then + return nil + end + + local name, matches = match_tag(line) + if not name or #matches == 0 then + return nil + end + + local cursz_kb = matches[#matches] + -- If there are fewer than 3 numeric columns, there's no reported peak size + local maxsz_kb = nil + if #matches > 3 then + maxsz_kb = matches[#matches - 1] + end + + -- Convert numeric fields to numbers and then into bytes. + local cursz = tonumber(cursz_kb) * 1024 + local maxsz = maxsz_kb and (tonumber(maxsz_kb) * 1024) + + -- Return a table representing the region. + return { + name = name, + size = cursz, + max_size = maxsz, + } + end +end + +-- Iterate through the maps listed in the given zprint(1) output `zpout`. +function zprint.maps(zpout) + local maps = lines_inside_dashes(zpout, 5) + local lines = maps:gmatch('([^\n]+)') + + return function() + -- Grab the next line. + local line = lines() + if not line then + return nil + end + + -- The line can take on 3 different forms. Check for each of them + + -- Check for 3 columns + local name, free_kb, largest_free_kb, curr_size_kb = line:match( + '(%S+)%s+(%d+)K%s+(%d+)K%s+(%d+)K') + local free, largest_free, peak_size_kb, peak_size, size + if not name then + -- Check for 2 columns + name, peak_size_kb, curr_size_kb = line:match('(%S+)%s+(%d+)K%s+(%d+)K') + if not name then + -- Check for a single column + name, curr_size_kb = line:match('(%S+)%s+(%d+)K') + assert(name) + else + peak_size = tonumber(peak_size_kb) * 1024 + end + else + free = tonumber(free_kb) * 1024 + largest_free = tonumber(largest_free_kb) * 1024 + end + size = tonumber(curr_size_kb) * 1024 + + return { + name = name, + size = size, + max_size = peak_size, + free = free, + largest_free = largest_free + } + end +end + +-- Iterate through the zone views listed in the given zprint(1) output `zpout`. +function zprint.zone_views(zpout) + -- Skip to the zone views + local prev_pos = 1 + -- Look for a line that starts with "zone views" and is followed by a -- line. + while true do + local start_pos, end_pos = zpout:find('\n[-]+\n', prev_pos) + if start_pos == nil then + return nil + end + local before = zpout:sub(prev_pos, start_pos) + local zone_views_index = zpout:find('\n%s*zone views%s+[^\n]+\n', prev_pos + 1) + prev_pos = end_pos + if zone_views_index and zone_views_index < end_pos then + break + end + end + + local zone_views + local zone_totals_index = zpout:find("\nZONE TOTALS") + if zone_totals_index then + zone_views = zpout:sub(prev_pos + 1, zone_totals_index) + else + zone_views = zpout:sub(prev_pos+ 1) + end + + local lines = zone_views:gmatch('([^\n]+)') + + return function() + -- Grab the next line. + local line = lines() + if not line then + return nil + end + + local name, curr_size_kb = line:match('(%S+)%s+(%d+)') + local size = tonumber(curr_size_kb) * 1024 + + return { + name = name, + size = size, + } + end +end + +function zprint.total(zpout) + local total = zpout:match('total[^%d]+(%d+.%d+)M of') + local bytes = tonumber(total) * 1024 * 1024 + return bytes +end + +-- Return a library object, if called from require or dofile. +local calling_func = debug.getinfo(2).func +if calling_func == require or calling_func == dofile then + return zprint +end + +-- Otherwise, 'recon zprint.lua ...' runs as a script. + +local cjson = require 'cjson' + +if not arg[1] then + io.stderr:write('usage: ', arg[0], ' \n') + os.exit(1) +end + +local file +if arg[1] == '-' then + file = io.stdin +else + local err + file, err = io.open(arg[1]) + if not file then + io.stderr:write('zprint.lua: ', arg[1], ': open failed: ', err, '\n') + os.exit(1) + end +end + +local zpout = file:read('all') +file:close() + +local function collect(iter, arg) + local tbl = {} + for elt in iter(arg) do + tbl[#tbl + 1] = elt + end + return tbl +end + +local zones = collect(zprint.zones, zpout) +local tags = collect(zprint.tags, zpout) +local maps = collect(zprint.maps, zpout) +local zone_views = collect(zprint.zone_views, zpout) + +print(cjson.encode({ + zones = zones, + tags = tags, + maps = maps, + zone_views = zone_views, +})) -- cgit v1.2.3-56-ge451