From d16bde623ced3cf113648e0bb977c1befed6d601 Mon Sep 17 00:00:00 2001 From: Denys Vlasenko Date: Tue, 5 Aug 2025 14:04:01 +0200 Subject: top,pmap: do not use common code for reading /proc/PID/smaps The logic is in fact quite far from common. While at it, stop accounting "---p" mappings as mapped (e.g. VSZ in top). Nothing is mapped there (why would kernel waste RAM to map pages which can't be accessed?). function old new delta read_smaps - 562 +562 read_cmdline 315 326 +11 print_smaprec 97 101 +4 procps_scan 1219 1211 -8 .rodata 115541 115533 -8 skip_whitespace_if_prefixed_with 25 - -25 procps_read_smaps 864 577 -287 ------------------------------------------------------------------------------ (add/remove: 1/1 grow/shrink: 2/3 up/down: 577/-328) Total: 249 bytes Signed-off-by: Denys Vlasenko --- libbb/procps.c | 130 +++++++++++++++++++++------------------------------------ 1 file changed, 48 insertions(+), 82 deletions(-) (limited to 'libbb') diff --git a/libbb/procps.c b/libbb/procps.c index 2cdfce42a..3256fafc5 100644 --- a/libbb/procps.c +++ b/libbb/procps.c @@ -109,7 +109,7 @@ void FAST_FUNC free_procps_scan(procps_status_t* sp) } #if ENABLE_FEATURE_TOPMEM || ENABLE_PMAP -static unsigned long long fast_strtoull_16(char **endptr) +unsigned long long FAST_FUNC fast_strtoull_16(char **endptr) { unsigned char c; char *str = *endptr; @@ -130,7 +130,7 @@ static unsigned long long fast_strtoull_16(char **endptr) #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) +unsigned long FAST_FUNC fast_strtoul_10(char **endptr) { unsigned char c; char *str = *endptr; @@ -159,7 +159,7 @@ static unsigned long long fast_strtoull_10(char **endptr) return n; } # else -# define fast_strtoull_10(endptr) fast_strtoul_10(endptr) +# define fast_strtoull_10(endptr) fast_strtoul_10(endptr) # endif # if ENABLE_FEATURE_FAST_TOP @@ -173,7 +173,7 @@ static long fast_strtol_10(char **endptr) } # endif -static char *skip_fields(char *str, int count) +char* FAST_FUNC skip_fields(char *str, int count) { do { while (*str++ != ' ') @@ -184,35 +184,25 @@ static char *skip_fields(char *str, int count) } #endif -#if ENABLE_FEATURE_TOPMEM || ENABLE_PMAP -static char* skip_whitespace_if_prefixed_with(char *buf, const char *prefix) +#if ENABLE_FEATURE_TOPMEM +static NOINLINE void procps_read_smaps(pid_t pid, procps_status_t *sp) { - char *tp = is_prefixed_with(buf, prefix); - if (tp) { - tp = skip_whitespace(tp); - } - return tp; -} + // There is A LOT of /proc/PID/smaps data on a big system. + // Optimize this for speed, makes "top -m" faster. +//TODO large speedup: +//read /proc/PID/smaps_rollup (cumulative stats of all mappings, much faster) +//and /proc/PID/maps to get mapped_ro and mapped_rw (IOW: VSZ,VSZRW) -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; + return; - memset(&currec, 0, sizeof(currec)); while (fgets(buf, PROCPS_BUFSIZE, file)) { // Each mapping datum has this form: // f7d29000-f7d39000 rw-s FILEOFS M:m INODE FILENAME @@ -220,80 +210,53 @@ int FAST_FUNC procps_read_smaps(pid_t pid, struct smaprec *total, // Rss: nnn kB // ..... - char *tp, *p; + char *tp; + if (buf[0] == 'S' || buf[0] == 'P') { #define SCAN(S, X) \ - if ((tp = skip_whitespace_if_prefixed_with(buf, S)) != NULL) { \ - 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 ); + if (memcmp(buf, S, sizeof(S)-1) == 0) { \ + tp = skip_whitespace(buf + sizeof(S)-1); \ + sp->X += fast_strtoul_10(&tp); \ + continue; \ + } + 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 FILEOFS M:m INODE FILENAME - 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)); + char *rwx; + unsigned long sz; *tp = ' '; tp = buf; - currec.smap_start = fast_strtoull_16(&tp); - currec.smap_size = (fast_strtoull_16(&tp) - currec.smap_start) >> 10; - - strncpy(currec.smap_mode, tp, sizeof(currec.smap_mode)-1); - + sz = fast_strtoull_16(&tp); // start + sz = (fast_strtoull_16(&tp) - sz) >> 10; // end - start + // tp -> "rw-s" string + rwx = tp; // skipping "rw-s FILEOFS M:m INODE " tp = skip_whitespace(skip_fields(tp, 4)); - // filter out /dev/something (something != zero) - if (!is_prefixed_with(tp, "/dev/") || 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 not a device memory mapped... + if (memcmp(tp, "/dev/", 5) != 0 // not "/dev/something" + || strcmp(tp + 5, "zero\n") == 0 // or is "/dev/zero" (which isn't a device) + ) { + if (rwx[1] == 'w') + sp->mapped_rw += sz; + else if (rwx[0] == 'r' || rwx[2] == 'x') + sp->mapped_ro += sz; + // else: seen "---p" mappings (mmap guard gaps?), + // do NOT account these as VSZ, they aren't really } - 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; + sp->stack += sz; } } fclose(file); - - if (cb) { - if (currec.smap_size) - cb(&currec, data); - free(currec.smap_name); - } - - return 0; } #endif @@ -502,7 +465,7 @@ procps_status_t* FAST_FUNC procps_scan(procps_status_t* sp, int flags) #if ENABLE_FEATURE_TOPMEM if (flags & PSSCAN_SMAPS) - procps_read_smaps(pid, &sp->smaps, NULL, NULL); + procps_read_smaps(pid, sp); #endif /* TOPMEM */ #if ENABLE_FEATURE_PS_ADDITIONAL_COLUMNS if (flags & PSSCAN_RUIDGID) { @@ -585,13 +548,15 @@ procps_status_t* FAST_FUNC procps_scan(procps_status_t* sp, int flags) return sp; } -void FAST_FUNC read_cmdline(char *buf, int col, unsigned pid, const char *comm) +int FAST_FUNC read_cmdline(char *buf, int col, unsigned pid, const char *comm) { int sz; char filename[sizeof("/proc/%u/cmdline") + sizeof(int)*3]; sprintf(filename, "/proc/%u/cmdline", pid); sz = open_read_close(filename, buf, col - 1); + if (sz < 0) + return sz; if (sz > 0) { const char *base; int comm_len; @@ -614,7 +579,7 @@ void FAST_FUNC read_cmdline(char *buf, int col, unsigned pid, const char *comm) * It allows to see thread names set by prctl(PR_SET_NAME). */ if (!comm) - return; + return 0; comm_len = strlen(comm); /* Why compare up to comm_len, not COMM_LEN-1? * Well, some processes rewrite argv, and use _spaces_ there @@ -628,13 +593,14 @@ void FAST_FUNC read_cmdline(char *buf, int col, unsigned pid, const char *comm) memmove(buf + comm_len, buf, col - comm_len); snprintf(buf, col, "{%s}", comm); if (col <= comm_len) - return; + return 0; buf[comm_len - 1] = ' '; buf[col - 1] = '\0'; } } else { snprintf(buf, col, "[%s]", comm ? comm : "?"); } + return 0; } /* from kernel: -- cgit v1.2.3-55-g6feb