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>
This commit is contained in:
parent
74c992af5c
commit
0834a6d3b9
@ -1387,6 +1387,29 @@ enum { COMM_LEN = TASK_COMM_LEN };
|
|||||||
enum { COMM_LEN = 16 };
|
enum { COMM_LEN = 16 };
|
||||||
# endif
|
# endif
|
||||||
#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 {
|
typedef struct procps_status_t {
|
||||||
DIR *dir;
|
DIR *dir;
|
||||||
IF_FEATURE_SHOW_THREADS(DIR *task_dir;)
|
IF_FEATURE_SHOW_THREADS(DIR *task_dir;)
|
||||||
@ -1415,13 +1438,7 @@ typedef struct procps_status_t {
|
|||||||
#endif
|
#endif
|
||||||
unsigned tty_major,tty_minor;
|
unsigned tty_major,tty_minor;
|
||||||
#if ENABLE_FEATURE_TOPMEM
|
#if ENABLE_FEATURE_TOPMEM
|
||||||
unsigned long mapped_rw;
|
struct smaprec smaps;
|
||||||
unsigned long mapped_ro;
|
|
||||||
unsigned long shared_clean;
|
|
||||||
unsigned long shared_dirty;
|
|
||||||
unsigned long private_clean;
|
|
||||||
unsigned long private_dirty;
|
|
||||||
unsigned long stack;
|
|
||||||
#endif
|
#endif
|
||||||
char state[4];
|
char state[4];
|
||||||
/* basename of executable in exec(2), read from /proc/N/stat
|
/* basename of executable in exec(2), read from /proc/N/stat
|
||||||
|
160
libbb/procps.c
160
libbb/procps.c
@ -137,12 +137,9 @@ static unsigned long fast_strtoul_16(char **endptr)
|
|||||||
*endptr = str; /* We skip trailing space! */
|
*endptr = str; /* We skip trailing space! */
|
||||||
return n;
|
return n;
|
||||||
}
|
}
|
||||||
/* TOPMEM uses fast_strtoul_10, so... */
|
|
||||||
# undef ENABLE_FEATURE_FAST_TOP
|
|
||||||
# define ENABLE_FEATURE_FAST_TOP 1
|
|
||||||
#endif
|
#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 */
|
/* We cut a lot of corners here for speed */
|
||||||
static unsigned long fast_strtoul_10(char **endptr)
|
static unsigned long fast_strtoul_10(char **endptr)
|
||||||
{
|
{
|
||||||
@ -177,6 +174,111 @@ static char *skip_fields(char *str, int count)
|
|||||||
}
|
}
|
||||||
#endif
|
#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);
|
void BUG_comm_size(void);
|
||||||
procps_status_t* FAST_FUNC procps_scan(procps_status_t* sp, int flags)
|
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 ENABLE_FEATURE_TOPMEM
|
||||||
if (flags & (PSSCAN_SMAPS)) {
|
if (flags & PSSCAN_SMAPS)
|
||||||
FILE *file;
|
procps_read_smaps(pid, &sp->smaps, NULL, NULL);
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#endif /* TOPMEM */
|
#endif /* TOPMEM */
|
||||||
#if ENABLE_FEATURE_PS_ADDITIONAL_COLUMNS
|
#if ENABLE_FEATURE_PS_ADDITIONAL_COLUMNS
|
||||||
if (flags & PSSCAN_RUIDGID) {
|
if (flags & PSSCAN_RUIDGID) {
|
||||||
|
111
procps/pmap.c
Normal file
111
procps/pmap.c
Normal file
@ -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;
|
||||||
|
}
|
16
procps/top.c
16
procps/top.c
@ -942,20 +942,20 @@ int top_main(int argc UNUSED_PARAM, char **argv)
|
|||||||
}
|
}
|
||||||
#if ENABLE_FEATURE_TOPMEM
|
#if ENABLE_FEATURE_TOPMEM
|
||||||
else { /* TOPMEM */
|
else { /* TOPMEM */
|
||||||
if (!(p->mapped_ro | p->mapped_rw))
|
if (!(p->smaps.mapped_ro | p->smaps.mapped_rw))
|
||||||
continue; /* kernel threads are ignored */
|
continue; /* kernel threads are ignored */
|
||||||
n = ntop;
|
n = ntop;
|
||||||
/* No bug here - top and topmem are the same */
|
/* No bug here - top and topmem are the same */
|
||||||
top = xrealloc_vector(topmem, 6, ntop++);
|
top = xrealloc_vector(topmem, 6, ntop++);
|
||||||
strcpy(topmem[n].comm, p->comm);
|
strcpy(topmem[n].comm, p->comm);
|
||||||
topmem[n].pid = p->pid;
|
topmem[n].pid = p->pid;
|
||||||
topmem[n].vsz = p->mapped_rw + p->mapped_ro;
|
topmem[n].vsz = p->smaps.mapped_rw + p->smaps.mapped_ro;
|
||||||
topmem[n].vszrw = p->mapped_rw;
|
topmem[n].vszrw = p->smaps.mapped_rw;
|
||||||
topmem[n].rss_sh = p->shared_clean + p->shared_dirty;
|
topmem[n].rss_sh = p->smaps.shared_clean + p->smaps.shared_dirty;
|
||||||
topmem[n].rss = p->private_clean + p->private_dirty + topmem[n].rss_sh;
|
topmem[n].rss = p->smaps.private_clean + p->smaps.private_dirty + topmem[n].rss_sh;
|
||||||
topmem[n].dirty = p->private_dirty + p->shared_dirty;
|
topmem[n].dirty = p->smaps.private_dirty + p->smaps.shared_dirty;
|
||||||
topmem[n].dirty_sh = p->shared_dirty;
|
topmem[n].dirty_sh = p->smaps.shared_dirty;
|
||||||
topmem[n].stack = p->stack;
|
topmem[n].stack = p->smaps.stack;
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
} /* end of "while we read /proc" */
|
} /* end of "while we read /proc" */
|
||||||
|
Loading…
Reference in New Issue
Block a user