From 0834a6d3b9daec1f460c3cc836136ace12c53df0 Mon Sep 17 00:00:00 2001 From: Alexander Shishkin Date: Sat, 28 Aug 2010 23:20:34 +0200 Subject: [PATCH] pmap: new applet. +1k. 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 Signed-off-by: Denys Vlasenko --- include/libbb.h | 31 +++++++--- libbb/procps.c | 160 ++++++++++++++++++++++++++++++++---------------- procps/pmap.c | 111 +++++++++++++++++++++++++++++++++ procps/top.c | 16 ++--- 4 files changed, 251 insertions(+), 67 deletions(-) create mode 100644 procps/pmap.c diff --git a/include/libbb.h b/include/libbb.h index 3fd754511..43e525cb9 100644 --- a/include/libbb.h +++ b/include/libbb.h @@ -1387,6 +1387,29 @@ 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 smap_pss, smap_swap; + unsigned long smap_size; + unsigned long smap_start; + char smap_mode[5]; + char *smap_name; +}; + +#if !ENABLE_PMAP +#define procps_read_smaps(pid, total, cb, data) \ + procps_read_smaps(pid, total) +#endif +int FAST_FUNC 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;) @@ -1415,13 +1438,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/libbb/procps.c b/libbb/procps.c index 16992b670..14d4481bd 100644 --- a/libbb/procps.c +++ b/libbb/procps.c @@ -137,12 +137,9 @@ static unsigned long fast_strtoul_16(char **endptr) *endptr = str; /* We skip trailing space! */ return n; } -/* TOPMEM uses fast_strtoul_10, so... */ -# undef ENABLE_FEATURE_FAST_TOP -# define ENABLE_FEATURE_FAST_TOP 1 #endif -#if ENABLE_FEATURE_FAST_TOP +#if ENABLE_FEATURE_FAST_TOP || ENABLE_FEATURE_TOPMEM || ENABLE_PMAP /* We cut a lot of corners here for speed */ static unsigned long fast_strtoul_10(char **endptr) { @@ -177,6 +174,111 @@ static char *skip_fields(char *str, int count) } #endif +#if ENABLE_FEATURE_TOPMEM || ENABLE_PMAP +int FAST_FUNC procps_read_smaps(pid_t pid, struct smaprec *total, + void (*cb)(struct smaprec *, void *), void *data) +{ + FILE *file; + struct smaprec currec; + char filename[sizeof("/proc/%u/smaps") + sizeof(int)*3]; + char buf[PROCPS_BUFSIZE]; +#if !ENABLE_PMAP + void (*cb)(struct smaprec *, void *) = NULL; + void *data = NULL; +#endif + + sprintf(filename, "/proc/%u/smaps", (int)pid); + + file = fopen_for_read(filename); + if (!file) + return 1; + + memset(&currec, 0, sizeof(currec)); + while (fgets(buf, PROCPS_BUFSIZE, file)) { + // Each mapping datum has this form: + // f7d29000-f7d39000 rw-s ADR M:m OFS FILE + // Size: nnn kB + // Rss: nnn kB + // ..... + + char *tp = buf, *p; + +#define SCAN(S, X) \ + if (strncmp(tp, S, sizeof(S)-1) == 0) { \ + tp = skip_whitespace(tp + sizeof(S)-1); \ + total->X += currec.X = fast_strtoul_10(&tp); \ + continue; \ + } + if (cb) { + SCAN("Pss:" , smap_pss ); + SCAN("Swap:" , smap_swap ); + } + SCAN("Private_Dirty:", private_dirty); + SCAN("Private_Clean:", private_clean); + SCAN("Shared_Dirty:" , shared_dirty ); + SCAN("Shared_Clean:" , shared_clean ); +#undef SCAN + tp = strchr(buf, '-'); + if (tp) { + // We reached next mapping - the line of this form: + // f7d29000-f7d39000 rw-s ADR M:m OFS FILE + + if (cb) { + /* If we have a previous record, there's nothing more + * for it, call the callback and clear currec + */ + if (currec.smap_size) + cb(&currec, data); + free(currec.smap_name); + } + memset(&currec, 0, sizeof(currec)); + + *tp = ' '; + tp = buf; + currec.smap_start = fast_strtoul_16(&tp); + currec.smap_size = (fast_strtoul_16(&tp) - currec.smap_start) >> 10; + + strncpy(currec.smap_mode, tp, sizeof(currec.smap_mode)-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 (currec.smap_mode[1] == 'w') { + currec.mapped_rw = currec.smap_size; + total->mapped_rw += currec.smap_size; + } else if (currec.smap_mode[1] == '-') { + currec.mapped_ro = currec.smap_size; + total->mapped_ro += currec.smap_size; + } + } + + if (strcmp(tp, "[stack]\n") == 0) + total->stack += currec.smap_size; + if (cb) { + p = skip_non_whitespace(tp); + if (p == tp) { + currec.smap_name = xstrdup(" [ anon ]"); + } else { + *p = '\0'; + currec.smap_name = xstrdup(tp); + } + } + total->smap_size += currec.smap_size; + } + } + fclose(file); + + if (cb) { + if (currec.smap_size) + cb(&currec, data); + free(currec.smap_name); + } + + return 0; +} +#endif + void BUG_comm_size(void); procps_status_t* FAST_FUNC procps_scan(procps_status_t* sp, int flags) { @@ -365,54 +467,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/pmap.c b/procps/pmap.c new file mode 100644 index 000000000..cfa94ed82 --- /dev/null +++ b/procps/pmap.c @@ -0,0 +1,111 @@ +/* + * pmap implementation for busybox + * + * Copyright (C) 2010 Nokia Corporation. All rights reserved. + * Written by Alexander Shishkin + * + * Licensed under GPLv2 or later, see the LICENSE file in this source tree + * for details. + */ + +//applet:IF_PMAP(APPLET(pmap, _BB_DIR_USR_BIN, _BB_SUID_DROP)) +//kbuild:lib-$(CONFIG_PMAP) += pmap.o + +//config:config PMAP +//config: bool "pmap" +//config: default y +//config: help +//config: Display processes' memory mappings. + +//usage:#define pmap_trivial_usage +//usage: "[-x][-q] PID" +//usage:#define pmap_full_usage "\n\n" +//usage: "Display detailed precesses' memory usage\n" +//usage: "\nOptions:" +//usage: "\n -x show details" +//usage: "\n -q quiet" + +#include "libbb.h" + +#if ULONG_MAX == 0xffffffff +# define TABS "\t" +# define AFMT "8" +# define DASHES "" +#else +# define TABS "\t\t" +# define AFMT "16" +# define DASHES "--------" +#endif + +enum { + OPT_x = 1 << 0, + OPT_q = 1 << 1, +}; + +static void print_smaprec(struct smaprec *currec, void *data) +{ + unsigned opt = (unsigned)data; + + printf("%0" AFMT "lx ", currec->smap_start); + + if (opt & OPT_x) + printf("%7lu %7lu %7lu %7lu ", + currec->smap_size, + currec->smap_pss, + currec->private_dirty, + currec->smap_swap); + else + printf("%7luK", currec->smap_size); + + printf(" %.4s %s\n", currec->smap_mode, currec->smap_name); +} + +static int procps_get_maps(pid_t pid, unsigned opt) +{ + struct smaprec total; + int ret; + char buf[256]; + + read_cmdline(buf, sizeof(buf), pid, "no such process"); + printf("%u: %s\n", (int)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, (void*)opt); + if (ret) + return ret; + + if (!(opt & OPT_q)) { + if (opt & OPT_x) + printf("--------" DASHES " ------ ------ ------ ------\n" + "total" TABS " %7lu %7lu %7lu %7lu\n", + total.smap_size, total.smap_pss, total.private_dirty, total.smap_swap); + else + printf("mapped: %luK\n", total.smap_size); + } + + return 0; +} + +int pmap_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; +int pmap_main(int argc UNUSED_PARAM, char **argv) +{ + unsigned opts; + int ret; + + opts = getopt32(argv, "xq"); + argv += optind; + + ret = 0; + while (*argv) { + pid_t pid = xatoi_positive(*argv++); + /* GNU pmap returns 42 if any of the pids failed */ + if (procps_get_maps(pid, opts) != 0) + ret = 42; + } + + return ret; +} diff --git a/procps/top.c b/procps/top.c index fd758099e..4f37878de 100644 --- a/procps/top.c +++ b/procps/top.c @@ -942,20 +942,20 @@ 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" */