summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAlexander Shishkin <virtuoso@slind.org>2010-08-28 21:20:34 (GMT)
committer Denys Vlasenko <vda.linux@googlemail.com>2010-08-28 21:20:34 (GMT)
commit0834a6d3b9daec1f460c3cc836136ace12c53df0 (patch)
tree5a1ff91bf23113224ee2a54334c020ff97e0f19a
parent74c992af5c6b8cfe6214e705bc04f8c2f9d8a304 (diff)
downloadbusybox-0834a6d3b9daec1f460c3cc836136ace12c53df0.tar.gz
busybox-0834a6d3b9daec1f460c3cc836136ace12c53df0.tar.bz2
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 <virtuoso@slind.org> Signed-off-by: Denys Vlasenko <vda.linux@googlemail.com>
-rw-r--r--include/libbb.h31
-rw-r--r--libbb/procps.c160
-rw-r--r--procps/pmap.c111
-rw-r--r--procps/top.c16
4 files changed, 251 insertions, 67 deletions
diff --git a/include/libbb.h b/include/libbb.h
index 3fd7545..43e525c 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 16992b6..14d4481 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 0000000..cfa94ed
--- /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 <virtuoso@slind.org>
+ *
+ * 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 fd75809..4f37878 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" */