diff options
Diffstat (limited to 'system_cmds/zprint.tproj')
-rw-r--r-- | system_cmds/zprint.tproj/entitlements.plist | 8 | ||||
-rw-r--r-- | system_cmds/zprint.tproj/test_zprint.lua | 89 | ||||
-rw-r--r-- | system_cmds/zprint.tproj/zprint.1 | 90 | ||||
-rw-r--r-- | system_cmds/zprint.tproj/zprint.c | 1092 | ||||
-rw-r--r-- | system_cmds/zprint.tproj/zprint.lua | 281 |
5 files changed, 1560 insertions, 0 deletions
diff --git a/system_cmds/zprint.tproj/entitlements.plist b/system_cmds/zprint.tproj/entitlements.plist new file mode 100644 index 0000000..600122d --- /dev/null +++ b/system_cmds/zprint.tproj/entitlements.plist @@ -0,0 +1,8 @@ +<?xml version="1.0" encoding="UTF-8"?> +<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> +<plist version="1.0"> +<dict> + <key>com.apple.private.kernel.get-kext-info</key> + <true/> +</dict> +</plist> diff --git a/system_cmds/zprint.tproj/test_zprint.lua b/system_cmds/zprint.tproj/test_zprint.lua new file mode 100644 index 0000000..b46d6ca --- /dev/null +++ b/system_cmds/zprint.tproj/test_zprint.lua @@ -0,0 +1,89 @@ +#!/usr/local/bin/recon + +local darwin = require 'darwin' +local proc = require 'proc' +local zprint = require 'zprint' + +if darwin.geteuid() ~= 0 then + io.stderr:write(arg[0], ': must be run as root (under sudo)\n') + os.exit(1) +end + +local function test_output(zpout) + -- These should be present in the output of zprint. + local expectations = { + { + region = 'zones', + name = 'vm.pages', + }, { + region = 'tags', + name = 'VM_KERN_MEMORY_DIAG', + }, { + region = 'maps', + name = 'VM_KERN_COUNT_WIRED_STATIC_KERNELCACHE', + }, { + region = 'zone_views', + -- This ties the kernel's hands when it comes to naming. + name = 'data.kalloc.16[raw]', + } + } + + local found_all = true + for i = 1, #expectations do + local region = expectations[i].region + local name = expectations[i].name + local iter = zprint[region] + if not iter then + io.stderr:write('zprint library has no iterator for ', region, '\n') + os.exit(4) + end + + local found = false + for elt in zprint[region](zpout) do + if elt.name == name then + found = true + break + end + end + if found then + io.stdout:write('PASS: found ', name, ' in ', region, '\n') + else + io.stdout:write('FAIL: could not find ', name, ' in ', region, '\n') + found_all = false + end + end + return found_all +end + +local function run_zprint(args) + if not args then + args = {} + end + + table.insert(args, 1, 'zprint') + local zpout, err, status, code = proc.run(args) + if not zpout then + io.stderr:write(arg[0], ': failed to run zprint: ', err, '\n') + os.exit(2) + end + + if code ~= 0 then + io.stderr:write(arg[0], ': zprint ', status, 'ed with code ', tostring(code), + ', stderr = ', err, '\n') + os.exit(3) + end + return zpout +end + +local function run_and_test(...) + local zpout = run_zprint(table.pack(...)) + local passed = test_output(zpout) + if not passed then + os.exit(5) + end +end + +print("TEST: zprint output") +run_and_test() +print("\nTEST: zprint -t output") +run_and_test("-t") diff --git a/system_cmds/zprint.tproj/zprint.1 b/system_cmds/zprint.tproj/zprint.1 new file mode 100644 index 0000000..aa7c918 --- /dev/null +++ b/system_cmds/zprint.tproj/zprint.1 @@ -0,0 +1,90 @@ +.\" Copyright (c) 2016, Apple Inc. All rights reserved. +.\" +.Dd 2 May 2016 +.Dt ZPRINT 1 +.Os "Mac OS X" +.Sh NAME +.Nm zprint +.Nd show information about kernel zones +.Sh SYNOPSIS +.Nm +.Op Fl cdhlLstw +.Op Ar name +.Sh DESCRIPTION +.Nm +displays data about Mach zones (allocation buckets). By default, +.Nm +will print out information about all Mach zones. If the optional +.Ar name +is specified, +.Nm +will print information about each zone for which +.Ar name +is a substring of the zone's name. +.Pp +.Nm +interprets the following options: +.Pp +.Bl -tag -width "disable -" +.\" -c +.It Fl c +(Default) +.Nm +prints zone info in columns. Long zone names are truncated with +.Ql \&$ , +and spaces are replaced with +.Ql \&. , +to allow for sorting by column. Pageable and collectible zones are shown with +.Ql \&P +and +.Ql \&C +on the far right, respectively. Zones with preposterously large maximum sizes +are shown with +.Ql ---- +in the max size and max num elts fields. +.\" -d +.It Fl d +Display deltas over time, showing any zones that have achieved a new maximum +current allocation size during the interval. If the total allocation sizes are +being displayed for the zones in question, it will also display the deltas if +the total allocations have doubled. +.\" -h +.It Fl h +(Default) Shows headings for the columns printed with the +.Fl c +option. It may be useful to override this option when sorting by column. +.\" -l +.It Fl l +(Default) Show all wired memory information after the zone information. +.\" -L +.It Fl L +Do not show all wired memory information after the zone information. +.\" -s +.It Fl s +.Nm +sorts the zones, showing the zone wasting the most memory first. +.\" -t +.It Fl t +For each zone, +.Nm +calculates the total size of allocations from the zone over the life of the +zone. +.\" -w +.It Fl w +For each zone, +.Nm +calculates how much space is allocated but not currently in use, the space +wasted by the zone. +.El +.Pp +Any option (including default options) can be overridden by specifying the +option in upper-case; for example, +.Fl C +overrides the default option +.Fl c . +.Sh DIAGNOSTICS +.Ex -std +.Sh SEE ALSO +.Xr ioclasscount 1 , +.Xr lsmp 1 , +.Xr lskq 1 , diff --git a/system_cmds/zprint.tproj/zprint.c b/system_cmds/zprint.tproj/zprint.c new file mode 100644 index 0000000..5c90f2d --- /dev/null +++ b/system_cmds/zprint.tproj/zprint.c @@ -0,0 +1,1092 @@ +/* + * Copyright (c) 2009-2016 Apple Inc. All rights reserved. + * + * @APPLE_OSREFERENCE_LICENSE_HEADER_START@ + * + * This file contains Original Code and/or Modifications of Original Code + * as defined in and that are subject to the Apple Public Source License + * Version 2.0 (the 'License'). You may not use this file except in + * compliance with the License. The rights granted to you under the License + * may not be used to create, or enable the creation or redistribution of, + * unlawful or unlicensed copies of an Apple operating system, or to + * circumvent, violate, or enable the circumvention or violation of, any + * terms of an Apple operating system software license agreement. + * + * Please obtain a copy of the License at + * http://www.opensource.apple.com/apsl/ and read it before using this file. + * + * The Original Code and all software distributed under the License are + * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER + * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES, + * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT. + * Please see the License for the specific language governing rights and + * limitations under the License. + * + * @APPLE_OSREFERENCE_LICENSE_HEADER_END@ + */ +/* + * @OSF_COPYRIGHT@ + */ +/* + * Mach Operating System + * Copyright (c) 1991,1990,1989 Carnegie Mellon University + * All Rights Reserved. + * + * Permission to use, copy, modify and distribute this software and its + * documentation is hereby granted, provided that both the copyright + * notice and this permission notice appear in all copies of the + * software, derivative works or modified versions, and any portions + * thereof, and that both notices appear in supporting documentation. + * + * CARNEGIE MELLON ALLOWS FREE USE OF THIS SOFTWARE IN ITS + * CONDITION. CARNEGIE MELLON DISCLAIMS ANY LIABILITY OF ANY KIND FOR + * ANY DAMAGES WHATSOEVER RESULTING FROM THE USE OF THIS SOFTWARE. + * + * Carnegie Mellon requests users of this software to return to + * + * Software Distribution Coordinator or Software.Distribution@CS.CMU.EDU + * School of Computer Science + * Carnegie Mellon University + * Pittsburgh PA 15213-3890 + * + * any improvements or extensions that they make and grant Carnegie the + * rights to redistribute these changes. + */ +/* + * zprint.c + * + * utility for printing out zone structures + * + * With no arguments, prints information on all zone structures. + * With an argument, prints information only on those zones for + * which the given name is a substring of the zone's name. + * With a "-w" flag, calculates how much much space is allocated + * to zones but not currently in use. + */ + +#include <vm_statistics.h> +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <mach/mach.h> +#include <mach_debug/mach_debug.h> +#include <mach/mach_error.h> +#include <libutil.h> +#include <errno.h> +#include <sysexits.h> +#include <getopt.h> +#include <malloc/malloc.h> +#include <Kernel/IOKit/IOKitDebug.h> +#include <Kernel/libkern/OSKextLibPrivate.h> +#include <IOKit/IOKitLib.h> +#include <IOKit/IOKitKeys.h> +#include <IOKit/kext/OSKext.h> +#include <CoreFoundation/CoreFoundation.h> +#include <CoreSymbolication/CoreSymbolication.h> + +#ifndef VM_KERN_SITE_ZONE_VIEW +#define VM_KERN_SITE_ZONE_VIEW 0x00001000 +#endif + +#define streql(a, b) (strcmp((a), (b)) == 0) +#define strneql(a, b, n) (strncmp((a), (b), (n)) == 0) +#define PRINTK(fmt, value) \ + printf(fmt "K", (value) / 1024 ) /* ick */ + +static void usage(FILE *stream); +static void printzone(mach_zone_name_t *, mach_zone_info_t *); +static void colprintzone(mach_zone_name_t *, mach_zone_info_t *); +static int find_deltas(mach_zone_name_t *, mach_zone_info_t *, mach_zone_info_t *, char *, int, int); +static void colprintzoneheader(void); +static boolean_t substr(const char *a, size_t alen, const char *b, size_t blen); + +static int SortName(void * thunk, const void * left, const void * right); +static int SortSize(void * thunk, const void * left, const void * right); +static void PrintLarge(mach_memory_info_t *wiredInfo, unsigned int wiredInfoCnt, + mach_zone_info_t *zoneInfo, mach_zone_name_t *zoneNames, + unsigned int zoneCnt, uint64_t zoneElements, + int (*func)(void *, const void *, const void *), boolean_t column); + +static char *program; + +static boolean_t ShowDeltas = FALSE; +static boolean_t ShowWasted = FALSE; +static boolean_t ShowTotal = FALSE; +static boolean_t ShowLarge = TRUE; +static boolean_t SortZones = FALSE; +static boolean_t ColFormat = TRUE; +static boolean_t PrintHeader = TRUE; + +static unsigned long long totalsize = 0; +static unsigned long long totalused = 0; +static unsigned long long totalsum = 0; +static unsigned long long totalfragmented = 0; +static unsigned long long totalcollectable = 0; + +static int last_time = 0; + +static char *zname = NULL; +static size_t znamelen = 0; + +#define LEFTALIGN -1 +#define RIGHTALIGN 1 + +typedef struct { + char *line1; + char *line2; + int colwidth; + int alignment; + bool visible; +} column_format; + +enum { + COL_ZONE_NAME, + COL_ELEM_SIZE, + COL_CUR_SIZE, + COL_MAX_SIZE, + COL_CUR_ELTS, + COL_MAX_ELTS, + COL_CUR_INUSE, + COL_ALLOC_SIZE, + COL_ALLOC_COUNT, + COL_ZONE_FLAGS, + COL_FRAG_SIZE, + COL_FREE_SIZE, + COL_TOTAL_ALLOCS, + COL_MAX +}; + +/* + * The order in which the columns appear below should match + * the order in which the values are printed in colprintzone(). + */ +static column_format columns[] = { + [COL_ZONE_NAME] = { "", "zone name", 25, LEFTALIGN, true }, + [COL_ELEM_SIZE] = { "elem", "size", 6, RIGHTALIGN, true }, + [COL_CUR_SIZE] = { "cur", "size", 11, RIGHTALIGN, true }, + [COL_MAX_SIZE] = { "max", "size", 11, RIGHTALIGN, true }, + [COL_CUR_ELTS] = { "cur", "#elts", 10, RIGHTALIGN, true }, + [COL_MAX_ELTS] = { "max", "#elts", 11, RIGHTALIGN, true }, + [COL_CUR_INUSE] = { "cur", "inuse", 11, RIGHTALIGN, true }, + [COL_ALLOC_SIZE] = { "alloc", "size", 6, RIGHTALIGN, true }, + [COL_ALLOC_COUNT] = { "alloc", "count", 6, RIGHTALIGN, true }, + [COL_ZONE_FLAGS] = { "", "", 2, RIGHTALIGN, true }, + /* additional columns for special flags, not visible by default */ + [COL_FRAG_SIZE] = { "frag", "size", 9, RIGHTALIGN, false }, + [COL_FREE_SIZE] = { "free", "size", 9, RIGHTALIGN, false }, + [COL_TOTAL_ALLOCS] = { "total", "allocs", 17, RIGHTALIGN, false } +}; + +static void +sigintr(__unused int signum) +{ + last_time = 1; +} + +static void +usage(FILE *stream) +{ + fprintf(stream, "usage: %s [-w] [-s] [-c] [-h] [-H] [-t] [-d] [-l] [-L] [name]\n\n", program); + fprintf(stream, "\t-w\tshow wasted memory for each zone\n"); + fprintf(stream, "\t-s\tsort zones by wasted memory\n"); + fprintf(stream, "\t-c\t(default) display output formatted in columns\n"); + fprintf(stream, "\t-h\tdisplay this help message\n"); + fprintf(stream, "\t-H\thide column names\n"); + fprintf(stream, "\t-t\tdisplay the total size of allocations over the life of the zone\n"); + fprintf(stream, "\t-d\tdisplay deltas over time\n"); + fprintf(stream, "\t-l\t(default) display wired memory info after zone info\n"); + fprintf(stream, "\t-L\tdo not show wired memory info, only show zone info\n"); + fprintf(stream, "\nAny option (including default options) can be overridden by specifying the option in upper-case.\n\n"); + exit(stream != stdout); +} + +int +main(int argc, char **argv) +{ + mach_zone_name_t *name = NULL; + unsigned int nameCnt = 0; + mach_zone_info_t *info = NULL; + unsigned int infoCnt = 0; + mach_memory_info_t *wiredInfo = NULL; + unsigned int wiredInfoCnt = 0; + mach_zone_info_t *max_info = NULL; + char *deltas = NULL; + uint64_t zoneElements; + + kern_return_t kr; + int i, j; + int first_time = 1; + int must_print = 1; + int interval = 1; + + signal(SIGINT, sigintr); + + program = strrchr(argv[0], '/'); + if (program == NULL) { + program = argv[0]; + } else { + program++; + } + + for (i = 1; i < argc; i++) { + if (streql(argv[i], "-d")) { + ShowDeltas = TRUE; + } else if (streql(argv[i], "-t")) { + ShowTotal = TRUE; + } else if (streql(argv[i], "-T")) { + ShowTotal = FALSE; + } else if (streql(argv[i], "-w")) { + ShowWasted = TRUE; + } else if (streql(argv[i], "-W")) { + ShowWasted = FALSE; + } else if (streql(argv[i], "-l")) { + ShowLarge = TRUE; + } else if (streql(argv[i], "-L")) { + ShowLarge = FALSE; + } else if (streql(argv[i], "-s")) { + SortZones = TRUE; + } else if (streql(argv[i], "-S")) { + SortZones = FALSE; + } else if (streql(argv[i], "-c")) { + ColFormat = TRUE; + } else if (streql(argv[i], "-C")) { + ColFormat = FALSE; + } else if (streql(argv[i], "-h")) { + usage(stdout); + } else if (streql(argv[i], "-H")) { + PrintHeader = FALSE; + } else if (streql(argv[i], "--")) { + i++; + break; + } else if (argv[i][0] == '-') { + usage(stderr); + } else { + break; + } + } + + switch (argc - i) { + case 0: + zname = ""; + znamelen = 0; + break; + + case 1: + zname = argv[i]; + znamelen = strlen(zname); + break; + + default: + usage(stderr); + } + + if (ShowDeltas) { + SortZones = FALSE; + ColFormat = TRUE; + PrintHeader = TRUE; + } + + if (ShowWasted) { + columns[COL_FRAG_SIZE].visible = true; + columns[COL_FREE_SIZE].visible = true; + } + if (ShowTotal) { + columns[COL_TOTAL_ALLOCS].visible = true; + } + + for (;;) { + kr = mach_memory_info(mach_host_self(), + &name, &nameCnt, &info, &infoCnt, + &wiredInfo, &wiredInfoCnt); + if (kr != KERN_SUCCESS) { + fprintf(stderr, "%s: mach_memory_info: %s (try running as root)\n", + program, mach_error_string(kr)); + exit(1); + } + + if (nameCnt != infoCnt) { + fprintf(stderr, "%s: mach_zone_name/ mach_zone_info: counts not equal?\n", + program); + exit(1); + } + + if (first_time) { + deltas = (char *)malloc(infoCnt); + max_info = (mach_zone_info_t *)malloc((infoCnt * sizeof *info)); + } + + if (SortZones) { + for (i = 0; i < nameCnt - 1; i++) { + for (j = i + 1; j < nameCnt; j++) { + unsigned long long wastei, wastej; + + wastei = (info[i].mzi_cur_size - + (info[i].mzi_elem_size * + info[i].mzi_count)); + wastej = (info[j].mzi_cur_size - + (info[j].mzi_elem_size * + info[j].mzi_count)); + + if (wastej > wastei) { + mach_zone_info_t tinfo; + mach_zone_name_t tname; + + tinfo = info[i]; + info[i] = info[j]; + info[j] = tinfo; + + tname = name[i]; + name[i] = name[j]; + name[j] = tname; + } + } + } + } + + must_print = find_deltas(name, info, max_info, deltas, infoCnt, first_time); + zoneElements = 0; + if (must_print) { + if (ColFormat) { + if (!first_time) { + printf("\n"); + } + colprintzoneheader(); + } + for (i = 0; i < nameCnt; i++) { + if (deltas[i]) { + if (ColFormat) { + colprintzone(&name[i], &info[i]); + } else { + printzone(&name[i], &info[i]); + } + zoneElements += info[i].mzi_count; + } + } + } + + if (ShowLarge && first_time) { + PrintLarge(wiredInfo, wiredInfoCnt, &info[0], &name[0], + nameCnt, zoneElements, + SortZones ? &SortSize : &SortName, ColFormat); + } + + first_time = 0; + + if ((name != NULL) && (nameCnt != 0)) { + kr = vm_deallocate(mach_task_self(), (vm_address_t) name, + (vm_size_t) (nameCnt * sizeof *name)); + if (kr != KERN_SUCCESS) { + fprintf(stderr, "%s: vm_deallocate: %s\n", + program, mach_error_string(kr)); + exit(1); + } + } + + if ((info != NULL) && (infoCnt != 0)) { + kr = vm_deallocate(mach_task_self(), (vm_address_t) info, + (vm_size_t) (infoCnt * sizeof *info)); + if (kr != KERN_SUCCESS) { + fprintf(stderr, "%s: vm_deallocate: %s\n", + program, mach_error_string(kr)); + exit(1); + } + } + + if ((wiredInfo != NULL) && (wiredInfoCnt != 0)) { + kr = vm_deallocate(mach_task_self(), (vm_address_t) wiredInfo, + (vm_size_t) (wiredInfoCnt * sizeof *wiredInfo)); + if (kr != KERN_SUCCESS) { + fprintf(stderr, "%s: vm_deallocate: %s\n", + program, mach_error_string(kr)); + exit(1); + } + } + + if ((ShowWasted || ShowTotal) && PrintHeader && !ShowDeltas) { + printf("\nZONE TOTALS\n"); + printf("---------------------------------------------\n"); + printf("TOTAL SIZE = %llu\n", totalsize); + printf("TOTAL USED = %llu\n", totalused); + if (ShowWasted) { + printf("TOTAL WASTED = %llu\n", totalsize - totalused); + printf("TOTAL FRAGMENTED = %llu\n", totalfragmented); + printf("TOTAL COLLECTABLE = %llu\n", totalcollectable); + } + if (ShowTotal) { + printf("TOTAL ALLOCS = %llu\n", totalsum); + } + } + + if (ShowDeltas == FALSE || last_time) { + break; + } + + sleep(interval); + } + exit(0); +} + +static boolean_t +substr(const char *a, size_t alen, const char *b, size_t blen) +{ + int i; + + if (alen > blen) { + return FALSE; + } + + for (i = 0; i <= blen - alen; i++) { + if (strneql(a, b + i, alen)) { + return TRUE; + } + } + + return FALSE; +} + +static void +printzone(mach_zone_name_t *name, mach_zone_info_t *info) +{ + unsigned long long used, size, fragmented, collectable; + + printf("%.*s zone:\n", (int)sizeof name->mzn_name, name->mzn_name); + printf("\tcur_size: %lluK bytes (%llu elements)\n", + info->mzi_cur_size / 1024, + (info->mzi_elem_size == 0) ? 0 : + info->mzi_cur_size / info->mzi_elem_size); + printf("\tmax_size: %lluK bytes (%llu elements)\n", + info->mzi_max_size / 1024, + (info->mzi_elem_size == 0) ? 0 : + info->mzi_max_size / info->mzi_elem_size); + printf("\telem_size: %llu bytes\n", + info->mzi_elem_size); + printf("\t# of elems: %llu\n", + info->mzi_count); + printf("\talloc_size: %lluK bytes (%llu elements)\n", + info->mzi_alloc_size / 1024, + (info->mzi_elem_size == 0) ? 0 : + info->mzi_alloc_size / info->mzi_elem_size); + if (info->mzi_exhaustible) { + printf("\tEXHAUSTIBLE\n"); + } + if (GET_MZI_COLLECTABLE_FLAG(info->mzi_collectable)) { + printf("\tCOLLECTABLE\n"); + } + if (ShowWasted) { + totalused += used = info->mzi_elem_size * info->mzi_count; + totalsize += size = info->mzi_cur_size; + totalcollectable += collectable = GET_MZI_COLLECTABLE_BYTES(info->mzi_collectable); + totalfragmented += fragmented = size - used - collectable; + printf("\t\t\t\t\tWASTED: %llu\n", size - used); + printf("\t\t\t\t\tFRAGMENTED: %llu\n", fragmented); + printf("\t\t\t\t\tCOLLECTABLE: %llu\n", collectable); + } + if (ShowTotal) { + totalsum += info->mzi_sum_size; + printf("\t\t\t\t\tTOTAL: %llu\n", totalsum); + } +} + +static void +colprintzone(mach_zone_name_t *zone_name, mach_zone_info_t *info) +{ + char *name = zone_name->mzn_name; + int j, namewidth; + unsigned long long used, size, fragmented, collectable; + + namewidth = columns[COL_ZONE_NAME].colwidth; + + for (j = 0; j < namewidth - 1 && name[j]; j++) { + if (name[j] == ' ') { + putchar('.'); + } else { + putchar(name[j]); + } + } + if (j == namewidth - 1) { + if (name[j]) { + putchar('$'); + } else { + putchar(' '); + } + } else { + for (; j < namewidth; j++) { + putchar(' '); + } + } + + +#define PRINTCOL(value, index) \ + if (columns[(index)].visible) { \ + printf(" %*llu", columns[(index)].colwidth * columns[(index)].alignment, (value)); \ + } +#define PRINTCOLSTR(value, index) \ + if (columns[(index)].visible) { \ + printf(" %*s", columns[(index)].colwidth * columns[(index)].alignment, (value)); \ + } +#define PRINTCOLK(value, index) \ + if (columns[(index)].visible) { \ + printf(" %*lluK", (columns[(index)].colwidth - 1) * columns[(index)].alignment, (value) / 1024 ); \ + } +#define PRINTCOLSZ(value, index) \ + if (columns[(index)].visible) { \ + if ((value) < 1024) { \ + printf(" %*lluB", (columns[(index)].colwidth - 1) * columns[(index)].alignment, (value)); \ + } else { \ + PRINTCOLK(value, index) \ + } \ + } + + + PRINTCOL(info->mzi_elem_size, COL_ELEM_SIZE); + PRINTCOLK(info->mzi_cur_size, COL_CUR_SIZE); + if (info->mzi_max_size / 1024 > 9999999) { + /* + * Zones with preposterously large maximum sizes are shown with `-------' + * in the max size and max num elts fields. + */ + PRINTCOLSTR("-------", COL_MAX_SIZE); + } else { + PRINTCOLK(info->mzi_max_size, COL_MAX_SIZE); + } + PRINTCOL(info->mzi_cur_size / info->mzi_elem_size, COL_CUR_ELTS); + if (info->mzi_max_size / 1024 > 9999999) { + PRINTCOLSTR("-------", COL_MAX_ELTS); + } else { + PRINTCOL(info->mzi_max_size / info->mzi_elem_size, COL_MAX_ELTS); + } + PRINTCOL(info->mzi_count, COL_CUR_INUSE); + PRINTCOLK(info->mzi_alloc_size, COL_ALLOC_SIZE); + PRINTCOL(info->mzi_alloc_size / info->mzi_elem_size, COL_ALLOC_COUNT); + + totalused += used = info->mzi_elem_size * info->mzi_count; + totalsize += size = info->mzi_cur_size; + totalsum += info->mzi_sum_size; + totalcollectable += collectable = GET_MZI_COLLECTABLE_BYTES(info->mzi_collectable); + totalfragmented += fragmented = size - used - collectable; + + printf(" %c%c", + (info->mzi_exhaustible ? 'X' : ' '), + (GET_MZI_COLLECTABLE_FLAG(info->mzi_collectable) ? 'C' : ' ')); + + PRINTCOLSZ(fragmented, COL_FRAG_SIZE); + PRINTCOLSZ(collectable, COL_FREE_SIZE); + PRINTCOLSZ(info->mzi_sum_size, COL_TOTAL_ALLOCS); + + printf("\n"); +} + + +static void +colprintzoneheader(void) +{ + int i, totalwidth = 0; + + if (!PrintHeader) { + return; + } + + for (i = 0; i < COL_MAX; i++) { + if (columns[i].visible) { + printf("%*s ", columns[i].colwidth * columns[i].alignment, columns[i].line1); + } + } + printf("\n"); + + for (i = 0; i < COL_MAX; i++) { + if (columns[i].visible) { + printf("%*s ", columns[i].colwidth * columns[i].alignment, columns[i].line2); + totalwidth += (columns[i].colwidth + 1); + } + } + + printf("\n"); + for (i = 0; i < totalwidth; i++) { + printf("-"); + } + printf("\n"); +} + +int +find_deltas(mach_zone_name_t *name, mach_zone_info_t *info, mach_zone_info_t *max_info, + char *deltas, int cnt, int first_time) +{ + int i; + int found_one = 0; + + for (i = 0; i < cnt; i++) { + deltas[i] = 0; + if (substr(zname, znamelen, name[i].mzn_name, + strnlen(name[i].mzn_name, sizeof name[i].mzn_name))) { + if (first_time || info->mzi_cur_size > max_info->mzi_cur_size || + (ShowTotal && ((info->mzi_sum_size >> 1) > max_info->mzi_sum_size))) { + max_info->mzi_cur_size = info->mzi_cur_size; + max_info->mzi_sum_size = info->mzi_sum_size; + deltas[i] = 1; + found_one = 1; + } + } + info++; + max_info++; + } + return found_one; +} + +/********************************************************************* +*********************************************************************/ + +static char * +kern_vm_tag_name(uint64_t tag) +{ + char * result; + const char * name; + switch (tag) { + case (VM_KERN_MEMORY_NONE): name = "VM_KERN_MEMORY_NONE"; break; + case (VM_KERN_MEMORY_OSFMK): name = "VM_KERN_MEMORY_OSFMK"; break; + case (VM_KERN_MEMORY_BSD): name = "VM_KERN_MEMORY_BSD"; break; + case (VM_KERN_MEMORY_IOKIT): name = "VM_KERN_MEMORY_IOKIT"; break; + case (VM_KERN_MEMORY_LIBKERN): name = "VM_KERN_MEMORY_LIBKERN"; break; + case (VM_KERN_MEMORY_OSKEXT): name = "VM_KERN_MEMORY_OSKEXT"; break; + case (VM_KERN_MEMORY_KEXT): name = "VM_KERN_MEMORY_KEXT"; break; + case (VM_KERN_MEMORY_IPC): name = "VM_KERN_MEMORY_IPC"; break; + case (VM_KERN_MEMORY_STACK): name = "VM_KERN_MEMORY_STACK"; break; + case (VM_KERN_MEMORY_CPU): name = "VM_KERN_MEMORY_CPU"; break; + case (VM_KERN_MEMORY_PMAP): name = "VM_KERN_MEMORY_PMAP"; break; + case (VM_KERN_MEMORY_PTE): name = "VM_KERN_MEMORY_PTE"; break; + case (VM_KERN_MEMORY_ZONE): name = "VM_KERN_MEMORY_ZONE"; break; + case (VM_KERN_MEMORY_KALLOC): name = "VM_KERN_MEMORY_KALLOC"; break; + case (VM_KERN_MEMORY_COMPRESSOR): name = "VM_KERN_MEMORY_COMPRESSOR"; break; + case (VM_KERN_MEMORY_COMPRESSED_DATA): name = "VM_KERN_MEMORY_COMPRESSED_DATA"; break; + case (VM_KERN_MEMORY_PHANTOM_CACHE): name = "VM_KERN_MEMORY_PHANTOM_CACHE"; break; + case (VM_KERN_MEMORY_WAITQ): name = "VM_KERN_MEMORY_WAITQ"; break; + case (VM_KERN_MEMORY_DIAG): name = "VM_KERN_MEMORY_DIAG"; break; + case (VM_KERN_MEMORY_LOG): name = "VM_KERN_MEMORY_LOG"; break; + case (VM_KERN_MEMORY_FILE): name = "VM_KERN_MEMORY_FILE"; break; + case (VM_KERN_MEMORY_MBUF): name = "VM_KERN_MEMORY_MBUF"; break; + case (VM_KERN_MEMORY_UBC): name = "VM_KERN_MEMORY_UBC"; break; + case (VM_KERN_MEMORY_SECURITY): name = "VM_KERN_MEMORY_SECURITY"; break; + case (VM_KERN_MEMORY_MLOCK): name = "VM_KERN_MEMORY_MLOCK"; break; + case (VM_KERN_MEMORY_REASON): name = "VM_KERN_MEMORY_REASON"; break; + case (VM_KERN_MEMORY_SKYWALK): name = "VM_KERN_MEMORY_SKYWALK"; break; + case (VM_KERN_MEMORY_LTABLE): name = "VM_KERN_MEMORY_LTABLE"; break; + case (VM_KERN_MEMORY_ANY): name = "VM_KERN_MEMORY_ANY"; break; + default: name = NULL; break; + } + if (name) { + asprintf(&result, "%s", name); + } else { + asprintf(&result, "VM_KERN_MEMORY_%lld", tag); + } + return result; +} + +static char * +kern_vm_counter_name(uint64_t tag) +{ + char * result; + const char * name; + switch (tag) { + case (VM_KERN_COUNT_MANAGED): name = "VM_KERN_COUNT_MANAGED"; break; + case (VM_KERN_COUNT_RESERVED): name = "VM_KERN_COUNT_RESERVED"; break; + case (VM_KERN_COUNT_WIRED): name = "VM_KERN_COUNT_WIRED"; break; + case (VM_KERN_COUNT_WIRED_BOOT): name = "VM_KERN_COUNT_WIRED_BOOT"; break; + case (VM_KERN_COUNT_WIRED_MANAGED): name = "VM_KERN_COUNT_WIRED_MANAGED"; break; + case (VM_KERN_COUNT_STOLEN): name = "VM_KERN_COUNT_STOLEN"; break; + case (VM_KERN_COUNT_BOOT_STOLEN): name = "VM_KERN_COUNT_BOOT_STOLEN"; break; + case (VM_KERN_COUNT_LOPAGE): name = "VM_KERN_COUNT_LOPAGE"; break; + case (VM_KERN_COUNT_MAP_KERNEL): name = "VM_KERN_COUNT_MAP_KERNEL"; break; + case (VM_KERN_COUNT_MAP_ZONE): name = "VM_KERN_COUNT_MAP_ZONE"; break; + case (VM_KERN_COUNT_MAP_KALLOC): name = "VM_KERN_COUNT_MAP_KALLOC"; break; + case (VM_KERN_COUNT_WIRED_STATIC_KERNELCACHE): + name = "VM_KERN_COUNT_WIRED_STATIC_KERNELCACHE"; + break; + default: name = NULL; break; + } + if (name) { + asprintf(&result, "%s", name); + } else { + asprintf(&result, "VM_KERN_COUNT_%lld", tag); + } + return result; +} + +static void +MakeLoadTagKeys(const void * key, const void * value, void * context) +{ + CFMutableDictionaryRef newDict = context; + CFDictionaryRef kextInfo = value; + CFNumberRef loadTag; + uint32_t loadTagValue; + + loadTag = (CFNumberRef)CFDictionaryGetValue(kextInfo, CFSTR(kOSBundleLoadTagKey)); + CFNumberGetValue(loadTag, kCFNumberSInt32Type, &loadTagValue); + key = (const void *)(uintptr_t) loadTagValue; + CFDictionarySetValue(newDict, key, value); +} + +static CSSymbolicatorRef gSym; +static CFMutableDictionaryRef gTagDict; +static mach_memory_info_t * gSites; + +static char * +GetSiteName(int siteIdx, mach_zone_name_t * zoneNames, unsigned int zoneNamesCnt) +{ + const char * name; + uintptr_t kmodid; + char * result; + char * append; + mach_vm_address_t addr; + CFDictionaryRef kextInfo; + CFStringRef bundleID; + uint32_t type; + + const mach_memory_info_t * site; + const char * fileName; + CSSymbolRef symbol; + const char * symbolName; + CSSourceInfoRef sourceInfo; + + name = NULL; + result = NULL; + site = &gSites[siteIdx]; + addr = site->site; + type = (VM_KERN_SITE_TYPE & site->flags); + kmodid = 0; + + if (VM_KERN_SITE_NAMED & site->flags) { + asprintf(&result, "%s", &site->name[0]); + } else { + switch (type) { + case VM_KERN_SITE_TAG: + result = kern_vm_tag_name(addr); + break; + + case VM_KERN_SITE_COUNTER: + result = kern_vm_counter_name(addr); + break; + + case VM_KERN_SITE_KMOD: + + kmodid = (uintptr_t) addr; + kextInfo = CFDictionaryGetValue(gTagDict, (const void *)kmodid); + if (kextInfo) { + bundleID = (CFStringRef)CFDictionaryGetValue(kextInfo, kCFBundleIdentifierKey); + name = CFStringGetCStringPtr(bundleID, kCFStringEncodingUTF8); + // wiredSize = (CFNumberRef)CFDictionaryGetValue(kextInfo, CFSTR(kOSBundleWiredSizeKey)); + } + + if (name) { + asprintf(&result, "%s", name); + } else { + asprintf(&result, "(unloaded kmod)"); + } + break; + + case VM_KERN_SITE_KERNEL: + symbolName = NULL; + if (addr) { + symbol = CSSymbolicatorGetSymbolWithAddressAtTime(gSym, addr, kCSNow); + symbolName = CSSymbolGetName(symbol); + } + if (symbolName) { + asprintf(&result, "%s", symbolName); + sourceInfo = CSSymbolicatorGetSourceInfoWithAddressAtTime(gSym, addr, kCSNow); + fileName = CSSourceInfoGetPath(sourceInfo); + if (fileName) { + printf(" (%s:%d)", fileName, CSSourceInfoGetLineNumber(sourceInfo)); + } + } else { + asprintf(&result, "site 0x%qx", addr); + } + break; + default: + asprintf(&result, ""); + break; + } + } + + if (result + && (VM_KERN_SITE_ZONE & site->flags) + && zoneNames + && (site->zone < zoneNamesCnt)) { + size_t namelen, zonelen; + namelen = strlen(result); + zonelen = strnlen(zoneNames[site->zone].mzn_name, sizeof(zoneNames[site->zone].mzn_name)); + if (((namelen + zonelen) > 61) && (zonelen < 61)) { + namelen = (61 - zonelen); + } + asprintf(&append, "%.*s[%.*s]", + (int)namelen, + result, + (int)zonelen, + zoneNames[site->zone].mzn_name); + free(result); + result = append; + } + if (result && kmodid) { + asprintf(&append, "%-64s%3ld", result, kmodid); + free(result); + result = append; + } + + return result; +} + +struct CompareThunk { + mach_zone_name_t *zoneNames; + unsigned int zoneNamesCnt; +}; + +static int +SortName(void * thunk, const void * left, const void * right) +{ + const struct CompareThunk * t = (typeof(t))thunk; + const int * idxL; + const int * idxR; + char * l; + char * r; + CFStringRef lcf; + CFStringRef rcf; + int result; + + idxL = (typeof(idxL))left; + idxR = (typeof(idxR))right; + l = GetSiteName(*idxL, t->zoneNames, t->zoneNamesCnt); + r = GetSiteName(*idxR, t->zoneNames, t->zoneNamesCnt); + + lcf = CFStringCreateWithCString(kCFAllocatorDefault, l, kCFStringEncodingUTF8); + rcf = CFStringCreateWithCString(kCFAllocatorDefault, r, kCFStringEncodingUTF8); + + result = (int) CFStringCompareWithOptionsAndLocale(lcf, rcf, CFRangeMake(0, CFStringGetLength(lcf)), kCFCompareNumerically, NULL); + + CFRelease(lcf); + CFRelease(rcf); + free(l); + free(r); + + return result; +} + +static int +SortSize(void * thunk, const void * left, const void * right) +{ + const mach_memory_info_t * siteL; + const mach_memory_info_t * siteR; + const int * idxL; + const int * idxR; + + idxL = (typeof(idxL))left; + idxR = (typeof(idxR))right; + siteL = &gSites[*idxL]; + siteR = &gSites[*idxR]; + + if (siteL->size > siteR->size) { + return -1; + } else if (siteL->size < siteR->size) { + return 1; + } + return 0; +} + + +static void +PrintLarge(mach_memory_info_t *wiredInfo, unsigned int wiredInfoCnt, + mach_zone_info_t *zoneInfo, mach_zone_name_t *zoneNames, + unsigned int zoneCnt, uint64_t zoneElements, + int (*func)(void *, const void *, const void *), boolean_t column) +{ + uint64_t zonetotal; + uint64_t top_wired; + uint64_t size; + uint64_t elemsTagged; + + CFDictionaryRef allKexts; + unsigned int idx, site, first; + int sorted[wiredInfoCnt]; + char totalstr[40]; + char * name; + bool headerPrinted; + + zonetotal = totalsize; + + gSites = wiredInfo; + + gSym = CSSymbolicatorCreateWithMachKernel(); + + allKexts = OSKextCopyLoadedKextInfo(NULL, NULL); + gTagDict = CFDictionaryCreateMutable( + kCFAllocatorDefault, (CFIndex) 0, + (CFDictionaryKeyCallBacks *) 0, + &kCFTypeDictionaryValueCallBacks); + + CFDictionaryApplyFunction(allKexts, &MakeLoadTagKeys, gTagDict); + CFRelease(allKexts); + + top_wired = 0; + + for (idx = 0; idx < wiredInfoCnt; idx++) { + sorted[idx] = idx; + } + first = 0; // VM_KERN_MEMORY_FIRST_DYNAMIC + struct CompareThunk thunk; + thunk.zoneNames = zoneNames; + thunk.zoneNamesCnt = zoneCnt; + qsort_r(&sorted[first], + wiredInfoCnt - first, + sizeof(sorted[0]), + &thunk, + func); + + elemsTagged = 0; + for (headerPrinted = false, idx = 0; idx < wiredInfoCnt; idx++) { + site = sorted[idx]; + if ((VM_KERN_SITE_COUNTER & gSites[site].flags) + && (VM_KERN_COUNT_WIRED == gSites[site].site)) { + top_wired = gSites[site].size; + } + if (VM_KERN_SITE_HIDE & gSites[site].flags) { + continue; + } + if (!((VM_KERN_SITE_WIRED | VM_KERN_SITE_ZONE) & gSites[site].flags)) { + continue; + } + + if ((VM_KERN_SITE_ZONE & gSites[site].flags) + && gSites[site].zone < zoneCnt) { + elemsTagged += gSites[site].size / zoneInfo[gSites[site].zone].mzi_elem_size; + } + + if ((gSites[site].size < 1024) && (gSites[site].peak < 1024)) { + continue; + } + + name = GetSiteName(site, zoneNames, zoneCnt); + if (!substr(zname, znamelen, name, strlen(name))) { + continue; + } + if (!headerPrinted) { + printf("-------------------------------------------------------------------------------------------------------------\n"); + printf(" kmod vm peak cur\n"); + printf("wired memory id tag size waste size\n"); + printf("-------------------------------------------------------------------------------------------------------------\n"); + headerPrinted = true; + } + printf("%-67s", name); + free(name); + printf("%12d", gSites[site].tag); + + if (gSites[site].peak) { + PRINTK(" %10llu", gSites[site].peak); + } else { + printf(" %11s", ""); + } + + if (gSites[site].collectable_bytes) { + PRINTK(" %5llu", gSites[site].collectable_bytes); + } else { + printf(" %6s", ""); + } + + PRINTK(" %9llu", gSites[site].size); + + if (!(VM_KERN_SITE_ZONE & gSites[site].flags)) { + totalsize += gSites[site].size; + } + + printf("\n"); + } + + if (!znamelen) { + printf("%-67s", "zones"); + printf("%12s", ""); + printf(" %11s", ""); + printf(" %6s", ""); + PRINTK(" %9llu", zonetotal); + printf("\n"); + } + if (headerPrinted) { + if (elemsTagged) { + snprintf(totalstr, sizeof(totalstr), "%lld of %lld", elemsTagged, zoneElements); + printf("zone tags%100s\n", totalstr); + } + snprintf(totalstr, sizeof(totalstr), "%6.2fM of %6.2fM", totalsize / 1024.0 / 1024.0, top_wired / 1024.0 / 1024.0); + printf("total%104s\n", totalstr); + } + for (headerPrinted = false, idx = 0; idx < wiredInfoCnt; idx++) { + site = sorted[idx]; + size = gSites[site].mapped; + if (!size) { + continue; + } + if (VM_KERN_SITE_HIDE & gSites[site].flags) { + continue; + } + if ((size == gSites[site].size) + && ((VM_KERN_SITE_WIRED | VM_KERN_SITE_ZONE) & gSites[site].flags)) { + continue; + } + + name = GetSiteName(site, NULL, 0); + if (!substr(zname, znamelen, name, strlen(name))) { + continue; + } + if (!headerPrinted) { + printf("-------------------------------------------------------------------------------------------------------------\n"); + printf(" largest peak cur\n"); + printf("maps free free size size\n"); + printf("-------------------------------------------------------------------------------------------------------------\n"); + headerPrinted = true; + } + printf("%-55s", name); + free(name); + + if (gSites[site].free) { + PRINTK(" %10llu", gSites[site].free); + } else { + printf(" %11s", ""); + } + if (gSites[site].largest) { + PRINTK(" %10llu", gSites[site].largest); + } else { + printf(" %11s", ""); + } + if (gSites[site].peak) { + PRINTK(" %10llu", gSites[site].peak); + } else { + printf(" %11s", ""); + } + PRINTK(" %16llu", size); + + printf("\n"); + } + for (headerPrinted = false, idx = 0; idx < wiredInfoCnt; idx++) { + site = sorted[idx]; + size = gSites[site].size; + if (!size || !(VM_KERN_SITE_ZONE_VIEW & gSites[site].flags)) { + continue; + } + + name = GetSiteName(site, NULL, 0); + if (!substr(zname, znamelen, name, strlen(name))) { + continue; + } + if (!headerPrinted) { + printf("-------------------------------------------------------------------------------------------------------------\n"); + printf(" cur\n"); + printf("zone views inuse\n"); + printf("-------------------------------------------------------------------------------------------------------------\n"); + headerPrinted = true; + } + printf("%-55s", name); + free(name); + + printf(" %11s", ""); + printf(" %11s", ""); + printf(" %11s", ""); + PRINTK(" %16llu", size); + + printf("\n"); + } + totalsize = zonetotal; +} 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], ' <zprint-output-path>\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, +})) |