pmap is a tool used to look at processes' memory maps, normally found in procps package. It provides more readable and easily sortable output (one line per mapping) from maps/smaps files in /proc/PID/. This would help in debugging memory usage issues, especially on devices where lots of typing is not a viable option.
This patch does'n implement -d and -A command line options of GNU pmap, since those are not that must have features and I was afraid of going blind from looking at its code. The implementation takes smaps scanning part out of procps_scan() function and moves it into procps_read_smaps(), which does more detailed processing of a single PID's smaps data. Signed-off-by: Alexander Shishkin <[email protected]> --- include/applets.src.h | 1 + include/libbb.h | 27 +++++++-- include/usage.src.h | 8 +++ libbb/procps.c | 146 ++++++++++++++++++++++++++++++++----------------- procps/Config.src | 6 ++ procps/Kbuild.src | 1 + procps/pmap.c | 90 ++++++++++++++++++++++++++++++ procps/top.c | 20 ++++--- 8 files changed, 234 insertions(+), 65 deletions(-) create mode 100644 procps/pmap.c diff --git a/include/applets.src.h b/include/applets.src.h index 195598f..4227ca7 100644 --- a/include/applets.src.h +++ b/include/applets.src.h @@ -289,6 +289,7 @@ IF_PING6(APPLET(ping6, _BB_DIR_BIN, _BB_SUID_MAYBE)) IF_PIPE_PROGRESS(APPLET(pipe_progress, _BB_DIR_BIN, _BB_SUID_DROP)) IF_PIVOT_ROOT(APPLET(pivot_root, _BB_DIR_SBIN, _BB_SUID_DROP)) IF_PKILL(APPLET_ODDNAME(pkill, pgrep, _BB_DIR_USR_BIN, _BB_SUID_DROP, pkill)) +IF_PMAP(APPLET(pmap, _BB_DIR_USR_BIN, _BB_SUID_DROP)) IF_POPMAILDIR(APPLET(popmaildir, _BB_DIR_USR_SBIN, _BB_SUID_DROP)) IF_HALT(APPLET_ODDNAME(poweroff, halt, _BB_DIR_SBIN, _BB_SUID_DROP, poweroff)) IF_PRINTENV(APPLET(printenv, _BB_DIR_BIN, _BB_SUID_DROP)) diff --git a/include/libbb.h b/include/libbb.h index c043506..85fcbb0 100644 --- a/include/libbb.h +++ b/include/libbb.h @@ -1381,6 +1381,25 @@ enum { COMM_LEN = TASK_COMM_LEN }; enum { COMM_LEN = 16 }; # endif #endif + +struct smaprec { + unsigned long mapped_rw; + unsigned long mapped_ro; + unsigned long shared_clean; + unsigned long shared_dirty; + unsigned long private_clean; + unsigned long private_dirty; + unsigned long stack; + unsigned long pss, swap; + unsigned long size; + unsigned long start; + char mode[5]; + char *name; +}; + +int procps_read_smaps(pid_t pid, struct smaprec *total, + void (*cb)(struct smaprec *, void *), void *data); + typedef struct procps_status_t { DIR *dir; IF_FEATURE_SHOW_THREADS(DIR *task_dir;) @@ -1409,13 +1428,7 @@ typedef struct procps_status_t { #endif unsigned tty_major,tty_minor; #if ENABLE_FEATURE_TOPMEM - unsigned long mapped_rw; - unsigned long mapped_ro; - unsigned long shared_clean; - unsigned long shared_dirty; - unsigned long private_clean; - unsigned long private_dirty; - unsigned long stack; + struct smaprec smaps; #endif char state[4]; /* basename of executable in exec(2), read from /proc/N/stat diff --git a/include/usage.src.h b/include/usage.src.h index 9b326ee..fd19b42 100644 --- a/include/usage.src.h +++ b/include/usage.src.h @@ -3208,6 +3208,14 @@ INSERT "\n -s Match session ID (0 for current)" \ "\n -P Match parent process ID" \ +#define pmap_trivial_usage \ + "[-x][-q] PID" +#define pmap_full_usage "\n\n" \ + "Display detailed precesses' memory usage\n" \ + "\nOptions:" \ + "\n -x show details" \ + "\n -q quiet" \ + #define popmaildir_trivial_usage \ "[OPTIONS] MAILDIR [CONN_HELPER ARGS]" #define popmaildir_full_usage "\n\n" \ diff --git a/libbb/procps.c b/libbb/procps.c index 48e60a7..3494002 100644 --- a/libbb/procps.c +++ b/libbb/procps.c @@ -170,13 +170,105 @@ static char *skip_fields(char *str, int count) { do { while (*str++ != ' ') - continue; - /* we found a space char, str points after it */ + ; } while (--count); return str; } #endif +#ifdef ENABLE_FEATURE_TOPMEM +int procps_read_smaps(pid_t pid, struct smaprec *total, + void (*cb)(struct smaprec *, void *), void *data) +{ + char filename[FILENAME_MAX]; + FILE *file; + char buf[PROCPS_BUFSIZE]; + struct smaprec currec = { .size = 0, .name = NULL }; + + snprintf(filename, FILENAME_MAX, "/proc/%d/smaps", pid); + + file = fopen_for_read(filename); + if (!file) + return EXIT_FAILURE; + + while (fgets(buf, sizeof buf , file)) { + char *tp = buf, *p; + +#define SCAN(X) \ + if (!strncasecmp(tp, #X ":", sizeof #X)) { \ + tp = skip_whitespace(tp + sizeof #X); \ + total->X += currec.X += fast_strtoul_10(&tp); \ + continue; \ + } + + SCAN(pss); + SCAN(swap); + SCAN(private_dirty); + SCAN(private_clean); + SCAN(shared_dirty); + SCAN(shared_clean); +#undef SCAN + + // f7d29000-f7d39000 rw-s ADR M:m OFS FILE + tp = strchr(buf, '-'); + if (tp) { + /* + * if we have a previous record, there's nothing more + * for it, call the callback and clear currec + */ + if (currec.size) { + if (cb) + cb(&currec, data); + free(currec.name); + memset(&currec, 0, sizeof currec); + } + + *tp = ' '; + tp = buf; + currec.start = fast_strtoul_16(&tp); + currec.size = (fast_strtoul_16(&tp) - + currec.start) >> 10; /* end - start */ + + strncpy(currec.mode, tp, 4); + + // skipping "rw-s ADR M:m OFS " + tp = skip_whitespace(skip_fields(tp, 4)); + // filter out /dev/something (something != zero) + if (strncmp(tp, "/dev/", 5) != 0 || strcmp(tp, "/dev/zero\n") == 0) { + if (currec.mode[1] == 'w') { + total->mapped_rw += + currec.mapped_rw += currec.size; + } else if (currec.mode[1] == '-') { + total->mapped_ro += + currec.mapped_ro += currec.size; + } + } + + if (strcmp(tp, "[stack]\n") == 0) + total->stack += currec.size; + p = skip_non_whitespace(tp); + if (p == tp) + currec.name = xstrdup(" [ anon ]"); + else { + *p = '\0'; + currec.name = xstrdup(tp); + } + + + total->size += currec.size; + } + } + + if (cb && currec.size) + cb(&currec, data); + free(currec.name); + + fclose(file); + + return EXIT_SUCCESS; +} +#endif + void BUG_comm_size(void); procps_status_t* FAST_FUNC procps_scan(procps_status_t* sp, int flags) { @@ -365,54 +457,8 @@ procps_status_t* FAST_FUNC procps_scan(procps_status_t* sp, int flags) } #if ENABLE_FEATURE_TOPMEM - if (flags & (PSSCAN_SMAPS)) { - FILE *file; - - strcpy(filename_tail, "smaps"); - file = fopen_for_read(filename); - if (file) { - while (fgets(buf, sizeof(buf), file)) { - unsigned long sz; - char *tp; - char w; -#define SCAN(str, name) \ - if (strncmp(buf, str, sizeof(str)-1) == 0) { \ - tp = skip_whitespace(buf + sizeof(str)-1); \ - sp->name += fast_strtoul_10(&tp); \ - continue; \ - } - SCAN("Shared_Clean:" , shared_clean ); - SCAN("Shared_Dirty:" , shared_dirty ); - SCAN("Private_Clean:", private_clean); - SCAN("Private_Dirty:", private_dirty); -#undef SCAN - // f7d29000-f7d39000 rw-s ADR M:m OFS FILE - tp = strchr(buf, '-'); - if (tp) { - *tp = ' '; - tp = buf; - sz = fast_strtoul_16(&tp); /* start */ - sz = (fast_strtoul_16(&tp) - sz) >> 10; /* end - start */ - // tp -> "rw-s" string - w = tp[1]; - // skipping "rw-s ADR M:m OFS " - tp = skip_whitespace(skip_fields(tp, 4)); - // filter out /dev/something (something != zero) - if (strncmp(tp, "/dev/", 5) != 0 || strcmp(tp, "/dev/zero\n") == 0) { - if (w == 'w') { - sp->mapped_rw += sz; - } else if (w == '-') { - sp->mapped_ro += sz; - } - } -//else printf("DROPPING %s (%s)\n", buf, tp); - if (strcmp(tp, "[stack]\n") == 0) - sp->stack += sz; - } - } - fclose(file); - } - } + if (flags & (PSSCAN_SMAPS)) + procps_read_smaps(pid, &sp->smaps, NULL, NULL); #endif /* TOPMEM */ #if ENABLE_FEATURE_PS_ADDITIONAL_COLUMNS if (flags & PSSCAN_RUIDGID) { diff --git a/procps/Config.src b/procps/Config.src index 1ff6dfd..99222e7 100644 --- a/procps/Config.src +++ b/procps/Config.src @@ -81,6 +81,12 @@ config FEATURE_PIDOF_OMIT The special pid %PPID can be used to name the parent process of the pidof, in other words the calling shell or shell script. +config PMAP + bool "pmap" + default y + help + Display processes' memory mappings. + config PKILL bool "pkill" default y diff --git a/procps/Kbuild.src b/procps/Kbuild.src index c41f12b..f2b1b34 100644 --- a/procps/Kbuild.src +++ b/procps/Kbuild.src @@ -15,6 +15,7 @@ lib-$(CONFIG_NMETER) += nmeter.o lib-$(CONFIG_PGREP) += pgrep.o lib-$(CONFIG_PKILL) += pgrep.o lib-$(CONFIG_PIDOF) += pidof.o +lib-$(CONFIG_PMAP) += pmap.o lib-$(CONFIG_PS) += ps.o lib-$(CONFIG_RENICE) += renice.o lib-$(CONFIG_BB_SYSCTL) += sysctl.o diff --git a/procps/pmap.c b/procps/pmap.c new file mode 100644 index 0000000..8610dd4 --- /dev/null +++ b/procps/pmap.c @@ -0,0 +1,90 @@ +/* + * pmap implementation for busybox + * + * Copyright (C) 2010 Nokia Corporation. All rights reserved. + * Written by Alexander Shishkin <[email protected]> + * + * Licensed under GPLv2 or later, see the LICENSE file in this source tree + * for details. + */ + +#include "libbb.h" + +#define OPT_X 1 +#define OPT_Q 2 + +#if ULONG_MAX == 0xffffffff +#define TABS "\t" +#define AFMT "8" +#define DASHES "" +#else +#define TABS "\t\t" +#define AFMT "16" +#define DASHES "--------" +#endif + +static void print_smaprec(struct smaprec *currec, void *data) +{ + unsigned int opt = *(unsigned int *)data; + + printf("%0" AFMT "lx ", currec->start); + + if (opt & OPT_X) + printf("%7lu %7lu %7lu %7lu ", + currec->size, currec->pss, + currec->shared_dirty + currec->private_dirty, + currec->swap); + else + printf("%7luK", currec->size); + + printf(" %.4s %s\n", currec->mode, currec->name); +} + +static int procps_get_maps(pid_t pid, unsigned int opt) +{ + char buf[BUFSIZ]; + struct smaprec total; + int ret; + + read_cmdline(buf, BUFSIZ, pid, "no such process"); + printf("%d: %s\n", pid, buf); + + if (!(opt & OPT_Q) && (opt & OPT_X)) + puts("Address" TABS " Kbytes PSS Dirty Swap Mode " + "Mapping"); + + memset(&total, 0, sizeof total); + + ret = procps_read_smaps(pid, &total, print_smaprec, &opt); + if (ret) + return EXIT_FAILURE; + + if (!(opt & OPT_Q) && (opt & OPT_X)) + printf("--------" DASHES " ------ ------ ------ ------\n" + "total" TABS " %7lu %7lu %7lu %7lu\n", + total.size, total.pss, + total.shared_dirty + total.private_dirty, total.swap); + else if (!(opt & OPT_Q)) + printf(" total" TABS "%7luK\n", total.size); + + return EXIT_SUCCESS; +} + +int pmap_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int pmap_main(int argc, char **argv) +{ + unsigned int opt; + pid_t pid; + int ret = 0; + + opt = getopt32(argv, "xq"); + + for (argv += optind; argv && *argv; argv++) { + pid = bb_strtou(*argv, NULL, 10); + ret += !procps_get_maps(pid, opt); + } + + /* GNU pmap returns 42 if any of the pids failed */ + return (ret == argc - optind) ? EXIT_SUCCESS : 42; +} + diff --git a/procps/top.c b/procps/top.c index ec84374..040e156 100644 --- a/procps/top.c +++ b/procps/top.c @@ -942,20 +942,24 @@ int top_main(int argc UNUSED_PARAM, char **argv) } #if ENABLE_FEATURE_TOPMEM else { /* TOPMEM */ - if (!(p->mapped_ro | p->mapped_rw)) + if (!(p->smaps.mapped_ro | p->smaps.mapped_rw)) continue; /* kernel threads are ignored */ n = ntop; /* No bug here - top and topmem are the same */ top = xrealloc_vector(topmem, 6, ntop++); strcpy(topmem[n].comm, p->comm); topmem[n].pid = p->pid; - topmem[n].vsz = p->mapped_rw + p->mapped_ro; - topmem[n].vszrw = p->mapped_rw; - topmem[n].rss_sh = p->shared_clean + p->shared_dirty; - topmem[n].rss = p->private_clean + p->private_dirty + topmem[n].rss_sh; - topmem[n].dirty = p->private_dirty + p->shared_dirty; - topmem[n].dirty_sh = p->shared_dirty; - topmem[n].stack = p->stack; + topmem[n].vsz = p->smaps.mapped_rw + + p->smaps.mapped_ro; + topmem[n].vszrw = p->smaps.mapped_rw; + topmem[n].rss_sh = p->smaps.shared_clean + + p->smaps.shared_dirty; + topmem[n].rss = p->smaps.private_clean + + p->smaps.private_dirty + topmem[n].rss_sh; + topmem[n].dirty = p->smaps.private_dirty + + p->smaps.shared_dirty; + topmem[n].dirty_sh = p->smaps.shared_dirty; + topmem[n].stack = p->smaps.stack; } #endif } /* end of "while we read /proc" */ -- 1.7.1 _______________________________________________ busybox mailing list [email protected] http://lists.busybox.net/mailman/listinfo/busybox
