diff options
author | Alexander Shishkin <virtuoso@slind.org> | 2010-08-28 23:20:34 +0200 |
---|---|---|
committer | Denys Vlasenko <vda.linux@googlemail.com> | 2010-08-28 23:20:34 +0200 |
commit | 0834a6d3b9daec1f460c3cc836136ace12c53df0 (patch) | |
tree | 5a1ff91bf23113224ee2a54334c020ff97e0f19a | |
parent | 74c992af5c6b8cfe6214e705bc04f8c2f9d8a304 (diff) | |
download | busybox-w32-0834a6d3b9daec1f460c3cc836136ace12c53df0.tar.gz busybox-w32-0834a6d3b9daec1f460c3cc836136ace12c53df0.tar.bz2 busybox-w32-0834a6d3b9daec1f460c3cc836136ace12c53df0.zip |
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.h | 31 | ||||
-rw-r--r-- | libbb/procps.c | 160 | ||||
-rw-r--r-- | procps/pmap.c | 111 | ||||
-rw-r--r-- | procps/top.c | 16 |
4 files changed, 251 insertions, 67 deletions
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 }; | |||
1387 | enum { COMM_LEN = 16 }; | 1387 | enum { COMM_LEN = 16 }; |
1388 | # endif | 1388 | # endif |
1389 | #endif | 1389 | #endif |
1390 | |||
1391 | struct smaprec { | ||
1392 | unsigned long mapped_rw; | ||
1393 | unsigned long mapped_ro; | ||
1394 | unsigned long shared_clean; | ||
1395 | unsigned long shared_dirty; | ||
1396 | unsigned long private_clean; | ||
1397 | unsigned long private_dirty; | ||
1398 | unsigned long stack; | ||
1399 | unsigned long smap_pss, smap_swap; | ||
1400 | unsigned long smap_size; | ||
1401 | unsigned long smap_start; | ||
1402 | char smap_mode[5]; | ||
1403 | char *smap_name; | ||
1404 | }; | ||
1405 | |||
1406 | #if !ENABLE_PMAP | ||
1407 | #define procps_read_smaps(pid, total, cb, data) \ | ||
1408 | procps_read_smaps(pid, total) | ||
1409 | #endif | ||
1410 | int FAST_FUNC procps_read_smaps(pid_t pid, struct smaprec *total, | ||
1411 | void (*cb)(struct smaprec *, void *), void *data); | ||
1412 | |||
1390 | typedef struct procps_status_t { | 1413 | typedef struct procps_status_t { |
1391 | DIR *dir; | 1414 | DIR *dir; |
1392 | IF_FEATURE_SHOW_THREADS(DIR *task_dir;) | 1415 | IF_FEATURE_SHOW_THREADS(DIR *task_dir;) |
@@ -1415,13 +1438,7 @@ typedef struct procps_status_t { | |||
1415 | #endif | 1438 | #endif |
1416 | unsigned tty_major,tty_minor; | 1439 | unsigned tty_major,tty_minor; |
1417 | #if ENABLE_FEATURE_TOPMEM | 1440 | #if ENABLE_FEATURE_TOPMEM |
1418 | unsigned long mapped_rw; | 1441 | struct smaprec smaps; |
1419 | unsigned long mapped_ro; | ||
1420 | unsigned long shared_clean; | ||
1421 | unsigned long shared_dirty; | ||
1422 | unsigned long private_clean; | ||
1423 | unsigned long private_dirty; | ||
1424 | unsigned long stack; | ||
1425 | #endif | 1442 | #endif |
1426 | char state[4]; | 1443 | char state[4]; |
1427 | /* basename of executable in exec(2), read from /proc/N/stat | 1444 | /* 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) | |||
137 | *endptr = str; /* We skip trailing space! */ | 137 | *endptr = str; /* We skip trailing space! */ |
138 | return n; | 138 | return n; |
139 | } | 139 | } |
140 | /* TOPMEM uses fast_strtoul_10, so... */ | ||
141 | # undef ENABLE_FEATURE_FAST_TOP | ||
142 | # define ENABLE_FEATURE_FAST_TOP 1 | ||
143 | #endif | 140 | #endif |
144 | 141 | ||
145 | #if ENABLE_FEATURE_FAST_TOP | 142 | #if ENABLE_FEATURE_FAST_TOP || ENABLE_FEATURE_TOPMEM || ENABLE_PMAP |
146 | /* We cut a lot of corners here for speed */ | 143 | /* We cut a lot of corners here for speed */ |
147 | static unsigned long fast_strtoul_10(char **endptr) | 144 | static unsigned long fast_strtoul_10(char **endptr) |
148 | { | 145 | { |
@@ -177,6 +174,111 @@ static char *skip_fields(char *str, int count) | |||
177 | } | 174 | } |
178 | #endif | 175 | #endif |
179 | 176 | ||
177 | #if ENABLE_FEATURE_TOPMEM || ENABLE_PMAP | ||
178 | int FAST_FUNC procps_read_smaps(pid_t pid, struct smaprec *total, | ||
179 | void (*cb)(struct smaprec *, void *), void *data) | ||
180 | { | ||
181 | FILE *file; | ||
182 | struct smaprec currec; | ||
183 | char filename[sizeof("/proc/%u/smaps") + sizeof(int)*3]; | ||
184 | char buf[PROCPS_BUFSIZE]; | ||
185 | #if !ENABLE_PMAP | ||
186 | void (*cb)(struct smaprec *, void *) = NULL; | ||
187 | void *data = NULL; | ||
188 | #endif | ||
189 | |||
190 | sprintf(filename, "/proc/%u/smaps", (int)pid); | ||
191 | |||
192 | file = fopen_for_read(filename); | ||
193 | if (!file) | ||
194 | return 1; | ||
195 | |||
196 | memset(&currec, 0, sizeof(currec)); | ||
197 | while (fgets(buf, PROCPS_BUFSIZE, file)) { | ||
198 | // Each mapping datum has this form: | ||
199 | // f7d29000-f7d39000 rw-s ADR M:m OFS FILE | ||
200 | // Size: nnn kB | ||
201 | // Rss: nnn kB | ||
202 | // ..... | ||
203 | |||
204 | char *tp = buf, *p; | ||
205 | |||
206 | #define SCAN(S, X) \ | ||
207 | if (strncmp(tp, S, sizeof(S)-1) == 0) { \ | ||
208 | tp = skip_whitespace(tp + sizeof(S)-1); \ | ||
209 | total->X += currec.X = fast_strtoul_10(&tp); \ | ||
210 | continue; \ | ||
211 | } | ||
212 | if (cb) { | ||
213 | SCAN("Pss:" , smap_pss ); | ||
214 | SCAN("Swap:" , smap_swap ); | ||
215 | } | ||
216 | SCAN("Private_Dirty:", private_dirty); | ||
217 | SCAN("Private_Clean:", private_clean); | ||
218 | SCAN("Shared_Dirty:" , shared_dirty ); | ||
219 | SCAN("Shared_Clean:" , shared_clean ); | ||
220 | #undef SCAN | ||
221 | tp = strchr(buf, '-'); | ||
222 | if (tp) { | ||
223 | // We reached next mapping - the line of this form: | ||
224 | // f7d29000-f7d39000 rw-s ADR M:m OFS FILE | ||
225 | |||
226 | if (cb) { | ||
227 | /* If we have a previous record, there's nothing more | ||
228 | * for it, call the callback and clear currec | ||
229 | */ | ||
230 | if (currec.smap_size) | ||
231 | cb(&currec, data); | ||
232 | free(currec.smap_name); | ||
233 | } | ||
234 | memset(&currec, 0, sizeof(currec)); | ||
235 | |||
236 | *tp = ' '; | ||
237 | tp = buf; | ||
238 | currec.smap_start = fast_strtoul_16(&tp); | ||
239 | currec.smap_size = (fast_strtoul_16(&tp) - currec.smap_start) >> 10; | ||
240 | |||
241 | strncpy(currec.smap_mode, tp, sizeof(currec.smap_mode)-1); | ||
242 | |||
243 | // skipping "rw-s ADR M:m OFS " | ||
244 | tp = skip_whitespace(skip_fields(tp, 4)); | ||
245 | // filter out /dev/something (something != zero) | ||
246 | if (strncmp(tp, "/dev/", 5) != 0 || strcmp(tp, "/dev/zero\n") == 0) { | ||
247 | if (currec.smap_mode[1] == 'w') { | ||
248 | currec.mapped_rw = currec.smap_size; | ||
249 | total->mapped_rw += currec.smap_size; | ||
250 | } else if (currec.smap_mode[1] == '-') { | ||
251 | currec.mapped_ro = currec.smap_size; | ||
252 | total->mapped_ro += currec.smap_size; | ||
253 | } | ||
254 | } | ||
255 | |||
256 | if (strcmp(tp, "[stack]\n") == 0) | ||
257 | total->stack += currec.smap_size; | ||
258 | if (cb) { | ||
259 | p = skip_non_whitespace(tp); | ||
260 | if (p == tp) { | ||
261 | currec.smap_name = xstrdup(" [ anon ]"); | ||
262 | } else { | ||
263 | *p = '\0'; | ||
264 | currec.smap_name = xstrdup(tp); | ||
265 | } | ||
266 | } | ||
267 | total->smap_size += currec.smap_size; | ||
268 | } | ||
269 | } | ||
270 | fclose(file); | ||
271 | |||
272 | if (cb) { | ||
273 | if (currec.smap_size) | ||
274 | cb(&currec, data); | ||
275 | free(currec.smap_name); | ||
276 | } | ||
277 | |||
278 | return 0; | ||
279 | } | ||
280 | #endif | ||
281 | |||
180 | void BUG_comm_size(void); | 282 | void BUG_comm_size(void); |
181 | procps_status_t* FAST_FUNC procps_scan(procps_status_t* sp, int flags) | 283 | procps_status_t* FAST_FUNC procps_scan(procps_status_t* sp, int flags) |
182 | { | 284 | { |
@@ -365,54 +467,8 @@ procps_status_t* FAST_FUNC procps_scan(procps_status_t* sp, int flags) | |||
365 | } | 467 | } |
366 | 468 | ||
367 | #if ENABLE_FEATURE_TOPMEM | 469 | #if ENABLE_FEATURE_TOPMEM |
368 | if (flags & (PSSCAN_SMAPS)) { | 470 | if (flags & PSSCAN_SMAPS) |
369 | FILE *file; | 471 | procps_read_smaps(pid, &sp->smaps, NULL, NULL); |
370 | |||
371 | strcpy(filename_tail, "smaps"); | ||
372 | file = fopen_for_read(filename); | ||
373 | if (file) { | ||
374 | while (fgets(buf, sizeof(buf), file)) { | ||
375 | unsigned long sz; | ||
376 | char *tp; | ||
377 | char w; | ||
378 | #define SCAN(str, name) \ | ||
379 | if (strncmp(buf, str, sizeof(str)-1) == 0) { \ | ||
380 | tp = skip_whitespace(buf + sizeof(str)-1); \ | ||
381 | sp->name += fast_strtoul_10(&tp); \ | ||
382 | continue; \ | ||
383 | } | ||
384 | SCAN("Shared_Clean:" , shared_clean ); | ||
385 | SCAN("Shared_Dirty:" , shared_dirty ); | ||
386 | SCAN("Private_Clean:", private_clean); | ||
387 | SCAN("Private_Dirty:", private_dirty); | ||
388 | #undef SCAN | ||
389 | // f7d29000-f7d39000 rw-s ADR M:m OFS FILE | ||
390 | tp = strchr(buf, '-'); | ||
391 | if (tp) { | ||
392 | *tp = ' '; | ||
393 | tp = buf; | ||
394 | sz = fast_strtoul_16(&tp); /* start */ | ||
395 | sz = (fast_strtoul_16(&tp) - sz) >> 10; /* end - start */ | ||
396 | // tp -> "rw-s" string | ||
397 | w = tp[1]; | ||
398 | // skipping "rw-s ADR M:m OFS " | ||
399 | tp = skip_whitespace(skip_fields(tp, 4)); | ||
400 | // filter out /dev/something (something != zero) | ||
401 | if (strncmp(tp, "/dev/", 5) != 0 || strcmp(tp, "/dev/zero\n") == 0) { | ||
402 | if (w == 'w') { | ||
403 | sp->mapped_rw += sz; | ||
404 | } else if (w == '-') { | ||
405 | sp->mapped_ro += sz; | ||
406 | } | ||
407 | } | ||
408 | //else printf("DROPPING %s (%s)\n", buf, tp); | ||
409 | if (strcmp(tp, "[stack]\n") == 0) | ||
410 | sp->stack += sz; | ||
411 | } | ||
412 | } | ||
413 | fclose(file); | ||
414 | } | ||
415 | } | ||
416 | #endif /* TOPMEM */ | 472 | #endif /* TOPMEM */ |
417 | #if ENABLE_FEATURE_PS_ADDITIONAL_COLUMNS | 473 | #if ENABLE_FEATURE_PS_ADDITIONAL_COLUMNS |
418 | if (flags & PSSCAN_RUIDGID) { | 474 | 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 @@ | |||
1 | /* | ||
2 | * pmap implementation for busybox | ||
3 | * | ||
4 | * Copyright (C) 2010 Nokia Corporation. All rights reserved. | ||
5 | * Written by Alexander Shishkin <virtuoso@slind.org> | ||
6 | * | ||
7 | * Licensed under GPLv2 or later, see the LICENSE file in this source tree | ||
8 | * for details. | ||
9 | */ | ||
10 | |||
11 | //applet:IF_PMAP(APPLET(pmap, _BB_DIR_USR_BIN, _BB_SUID_DROP)) | ||
12 | //kbuild:lib-$(CONFIG_PMAP) += pmap.o | ||
13 | |||
14 | //config:config PMAP | ||
15 | //config: bool "pmap" | ||
16 | //config: default y | ||
17 | //config: help | ||
18 | //config: Display processes' memory mappings. | ||
19 | |||
20 | //usage:#define pmap_trivial_usage | ||
21 | //usage: "[-x][-q] PID" | ||
22 | //usage:#define pmap_full_usage "\n\n" | ||
23 | //usage: "Display detailed precesses' memory usage\n" | ||
24 | //usage: "\nOptions:" | ||
25 | //usage: "\n -x show details" | ||
26 | //usage: "\n -q quiet" | ||
27 | |||
28 | #include "libbb.h" | ||
29 | |||
30 | #if ULONG_MAX == 0xffffffff | ||
31 | # define TABS "\t" | ||
32 | # define AFMT "8" | ||
33 | # define DASHES "" | ||
34 | #else | ||
35 | # define TABS "\t\t" | ||
36 | # define AFMT "16" | ||
37 | # define DASHES "--------" | ||
38 | #endif | ||
39 | |||
40 | enum { | ||
41 | OPT_x = 1 << 0, | ||
42 | OPT_q = 1 << 1, | ||
43 | }; | ||
44 | |||
45 | static void print_smaprec(struct smaprec *currec, void *data) | ||
46 | { | ||
47 | unsigned opt = (unsigned)data; | ||
48 | |||
49 | printf("%0" AFMT "lx ", currec->smap_start); | ||
50 | |||
51 | if (opt & OPT_x) | ||
52 | printf("%7lu %7lu %7lu %7lu ", | ||
53 | currec->smap_size, | ||
54 | currec->smap_pss, | ||
55 | currec->private_dirty, | ||
56 | currec->smap_swap); | ||
57 | else | ||
58 | printf("%7luK", currec->smap_size); | ||
59 | |||
60 | printf(" %.4s %s\n", currec->smap_mode, currec->smap_name); | ||
61 | } | ||
62 | |||
63 | static int procps_get_maps(pid_t pid, unsigned opt) | ||
64 | { | ||
65 | struct smaprec total; | ||
66 | int ret; | ||
67 | char buf[256]; | ||
68 | |||
69 | read_cmdline(buf, sizeof(buf), pid, "no such process"); | ||
70 | printf("%u: %s\n", (int)pid, buf); | ||
71 | |||
72 | if (!(opt & OPT_q) && (opt & OPT_x)) | ||
73 | puts("Address" TABS " Kbytes PSS Dirty Swap Mode Mapping"); | ||
74 | |||
75 | memset(&total, 0, sizeof(total)); | ||
76 | |||
77 | ret = procps_read_smaps(pid, &total, print_smaprec, (void*)opt); | ||
78 | if (ret) | ||
79 | return ret; | ||
80 | |||
81 | if (!(opt & OPT_q)) { | ||
82 | if (opt & OPT_x) | ||
83 | printf("--------" DASHES " ------ ------ ------ ------\n" | ||
84 | "total" TABS " %7lu %7lu %7lu %7lu\n", | ||
85 | total.smap_size, total.smap_pss, total.private_dirty, total.smap_swap); | ||
86 | else | ||
87 | printf("mapped: %luK\n", total.smap_size); | ||
88 | } | ||
89 | |||
90 | return 0; | ||
91 | } | ||
92 | |||
93 | int pmap_main(int argc, char **argv) MAIN_EXTERNALLY_VISIBLE; | ||
94 | int pmap_main(int argc UNUSED_PARAM, char **argv) | ||
95 | { | ||
96 | unsigned opts; | ||
97 | int ret; | ||
98 | |||
99 | opts = getopt32(argv, "xq"); | ||
100 | argv += optind; | ||
101 | |||
102 | ret = 0; | ||
103 | while (*argv) { | ||
104 | pid_t pid = xatoi_positive(*argv++); | ||
105 | /* GNU pmap returns 42 if any of the pids failed */ | ||
106 | if (procps_get_maps(pid, opts) != 0) | ||
107 | ret = 42; | ||
108 | } | ||
109 | |||
110 | return ret; | ||
111 | } | ||
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) | |||
942 | } | 942 | } |
943 | #if ENABLE_FEATURE_TOPMEM | 943 | #if ENABLE_FEATURE_TOPMEM |
944 | else { /* TOPMEM */ | 944 | else { /* TOPMEM */ |
945 | if (!(p->mapped_ro | p->mapped_rw)) | 945 | if (!(p->smaps.mapped_ro | p->smaps.mapped_rw)) |
946 | continue; /* kernel threads are ignored */ | 946 | continue; /* kernel threads are ignored */ |
947 | n = ntop; | 947 | n = ntop; |
948 | /* No bug here - top and topmem are the same */ | 948 | /* No bug here - top and topmem are the same */ |
949 | top = xrealloc_vector(topmem, 6, ntop++); | 949 | top = xrealloc_vector(topmem, 6, ntop++); |
950 | strcpy(topmem[n].comm, p->comm); | 950 | strcpy(topmem[n].comm, p->comm); |
951 | topmem[n].pid = p->pid; | 951 | topmem[n].pid = p->pid; |
952 | topmem[n].vsz = p->mapped_rw + p->mapped_ro; | 952 | topmem[n].vsz = p->smaps.mapped_rw + p->smaps.mapped_ro; |
953 | topmem[n].vszrw = p->mapped_rw; | 953 | topmem[n].vszrw = p->smaps.mapped_rw; |
954 | topmem[n].rss_sh = p->shared_clean + p->shared_dirty; | 954 | topmem[n].rss_sh = p->smaps.shared_clean + p->smaps.shared_dirty; |
955 | topmem[n].rss = p->private_clean + p->private_dirty + topmem[n].rss_sh; | 955 | topmem[n].rss = p->smaps.private_clean + p->smaps.private_dirty + topmem[n].rss_sh; |
956 | topmem[n].dirty = p->private_dirty + p->shared_dirty; | 956 | topmem[n].dirty = p->smaps.private_dirty + p->smaps.shared_dirty; |
957 | topmem[n].dirty_sh = p->shared_dirty; | 957 | topmem[n].dirty_sh = p->smaps.shared_dirty; |
958 | topmem[n].stack = p->stack; | 958 | topmem[n].stack = p->smaps.stack; |
959 | } | 959 | } |
960 | #endif | 960 | #endif |
961 | } /* end of "while we read /proc" */ | 961 | } /* end of "while we read /proc" */ |