/* Copyright 2014-2020 Samsung Electronics Co., Ltd All Rights Reserved * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define STR_SGX_PATH "/dev/pvrsrvkm" #define STR_3D_PATH1 "/dev/mali" #define STR_3D_PATH2 "/dev/kgsl-3d0" #define STR_3D_UNIFIED_PATH "/usr/bin/gpu_mem_info" #define STR_DRM_PATH1 "/drm mm object (deleted)" #define STR_DRM_PATH2 "/dev/dri/card" #define STR_DRM_DBG_DIR "/sys/kernel/debug/dri/" #define STR_DRM_RENDER_PATH "/dev/dri/renderD" #define MEMCG_PATH "/sys/fs/cgroup/memory" #define ZRAM_USED_PATH "/sys/block/zram0/mem_used_total" #define ZRAM_MM_STAT_PATH "/sys/block/zram0/mm_stat" #define OOM_SCORE_ADJ_STR "oom_score_adj" #define OOM_SCORE_STR "oom_score" #define BUF_MAX (BUFSIZ) /* most optimal for libc::stdio */ #define BUF_INC_SIZE (512 * 1024) /* maximal SMAPS I saw 2 MB */ #define KB(bytes) ((bytes)/1024) #define BYTE_TO_KBYTE(b) ((b) >> 10) #define BYTE_TO_MBYTE(b) ((b) >> 20) #define BYTE_TO_GBYTE(b) ((b) >> 30) #define KBYTE_TO_BYTE(k) ((k) << 10) #define KBYTE_TO_MBYTE(k) ((k) >> 10) #define KBYTE_TO_GBYTE(k) ((k) >> 20) #define GBYTE_TO_BYTE(g) ((g) << 30) #define GBYTE_TO_KBYTE(g) ((g) << 20) #define GBYTE_TO_MBYTE(g) ((g) << 10) typedef struct geminfo geminfo; typedef struct mapinfo mapinfo; typedef struct trib_mapinfo trib_mapinfo; enum { OUTPUT_UART, OUTPUT_FILE, NUM_OUTPUT_TYPE }; struct mapinfo { mapinfo *next; unsigned long start; unsigned long end; unsigned size; unsigned swap; unsigned rss; unsigned pss; unsigned shared_clean; unsigned shared_dirty; unsigned private_clean; unsigned private_dirty; char *perm; char *name; }; /* classify normal, graphic and other devices memory */ struct trib_mapinfo { unsigned shared_clean; unsigned shared_dirty; unsigned private_clean; unsigned private_dirty; unsigned shared_clean_pss; unsigned shared_dirty_pss; unsigned swap; unsigned rss; unsigned pss; unsigned size; unsigned graphic_3d; unsigned gem_rss; unsigned gem_pss; unsigned peak_rss; unsigned other_devices; unsigned gem_mmap; unsigned render_gem_mmap; }; struct geminfo { geminfo *next; unsigned int tgid; unsigned rss_size; unsigned pss_size; unsigned hcount; unsigned int imported; }; static int sum; static int verbos; static int use_gpu_mem_info = 0; static int pid_max = 0; static int dri_card = 0; /* reads file contents into memory */ static char* cread(const char* path) { /* once allocated area for reads */ static char* text = NULL; static size_t size = 0; ssize_t ret; char* ptr = text; size_t cap = size; int fd = open(path, O_RDONLY); if (fd < 0) return NULL; do { /* ensure we have enough space */ if (cap == 0) { ptr = (char*)realloc(text, size + BUF_INC_SIZE); if (ptr == NULL) { ret = -1; break; } text = ptr; ptr = text + size; cap = BUF_INC_SIZE; size += BUF_INC_SIZE; } ret = read(fd, ptr, cap); if (ret == 0) { *ptr = 0; } else if (ret > 0) { cap -= ret; ptr += ret; } } while (ret > 0); close(fd); return (ret < 0 ? NULL : text); } /* cread */ /* like fgets/gets but adjusting contents pointer */ static inline char* cgets(char** contents) { if (contents && *contents && **contents) { char* bos = *contents; /* begin of string */ char* eos = strchr(bos, '\n'); /* end of string */ if (eos) { *contents = eos + 1; *eos = 0; } else { *contents = NULL; } return bos; } return NULL; } /* cgets */ /* get pid_max value */ static inline int get_pid_max(void) { static const char pid_max_path[] = "/proc/sys/kernel/pid_max"; char* line; line = cread(pid_max_path); if (line == NULL) { fprintf(stderr, "cannot open %s\n", pid_max_path); return 0; } return strtoul(line, NULL, 10); } static unsigned get_peak_rss(unsigned int pid) { static const char field[] = "VmHWM:"; char tmp[128]; char* line; char* value; snprintf(tmp, sizeof(tmp), "/proc/%d/status", pid); line = cread(tmp); if (line == NULL) { fprintf(stderr, "cannot open %s\n", tmp); return 0; } value = strstr(line, field); if (value) { value += sizeof(field); return strtoul(value, NULL, 10); } return 0; } #define NUM_GEM_FIELD 7 static geminfo *read_geminfo(FILE *fp) { geminfo *tgeminfo; char line[BUF_MAX]; unsigned int pid, tgid, handle, refcount, hcount; unsigned gem_size; unsigned int imported; if (fgets(line, BUF_MAX, fp) != NULL) { if (sscanf(line, "%d %d %d %d %d 0x%x 0x%*x %*d %*d %d", &pid, &tgid, &handle, &refcount, &hcount, &gem_size, &imported) != NUM_GEM_FIELD) return NULL; if (hcount == 0) return NULL; tgeminfo = malloc(sizeof(geminfo)); if (tgeminfo == NULL) return NULL; tgeminfo->tgid = tgid; tgeminfo->hcount = hcount; tgeminfo->rss_size = KB(gem_size); tgeminfo->pss_size = KB(gem_size/tgeminfo->hcount); tgeminfo->imported = imported; } else return NULL; return tgeminfo; } static geminfo *find_geminfo(unsigned int tgid, geminfo *gilist) { geminfo *gi; for (gi = gilist; gi; ) { if (gi->tgid == tgid) return gi; gi = gi->next; } return NULL; } static geminfo *get_geminfo(FILE *drm_fp) { geminfo *ginfo; geminfo *gilist = NULL; geminfo *exist_ginfo = NULL; char line[BUF_MAX]; if (fgets(line, BUF_MAX, drm_fp) == NULL) { return NULL; } else { /* we should count a number of whitespace separated fields */ int in_field = (line[0] && !isblank(line[0])); unsigned int size = (unsigned)in_field; const char* ptr = &line[1]; /* sscanf() was used in original code, so number of fields */ /* in string is expected to be at least NUM_GEM_FIELD */ while (*ptr && size < NUM_GEM_FIELD) { if (isblank(*ptr++)) { if (in_field) { /* end of field */ in_field = 0; } } else { if (!in_field) { /* next field started */ in_field = 1; size++; } } } /* while */ if (size != NUM_GEM_FIELD) { return NULL; } } while ((ginfo = read_geminfo(drm_fp)) != NULL) { if (gilist && ginfo->tgid == gilist->tgid) { gilist->pss_size += ginfo->pss_size; gilist->rss_size += ginfo->rss_size; free(ginfo); continue; } else if (gilist && ((exist_ginfo = find_geminfo(ginfo->tgid, gilist)) != NULL)) { exist_ginfo->pss_size += ginfo->pss_size; exist_ginfo->rss_size += ginfo->rss_size; free(ginfo); continue; } ginfo->next = gilist; gilist = ginfo; } return gilist; } static geminfo *load_geminfo(void) { geminfo *gilist; FILE *drm_fp; drm_fp = fopen(STR_DRM_DBG_DIR "0/gem_info", "r"); if (drm_fp == NULL) { drm_fp = fopen(STR_DRM_DBG_DIR "1/gem_info", "r"); dri_card = 1; if (drm_fp == NULL) { fprintf(stderr, "cannot open " STR_DRM_DBG_DIR "%d/gem_info\n", dri_card); return NULL; } } gilist = get_geminfo(drm_fp); fclose(drm_fp); return gilist; } static geminfo *load_gpu_geminfo(void) { geminfo *gilist; FILE *drm_fp; /* Check the render dri card having gpu_gem_info */ drm_fp = fopen(STR_DRM_DBG_DIR "128/gpu_gem_info", "r"); if (drm_fp == NULL) { fprintf(stderr, "cannot open " STR_DRM_DBG_DIR "128/gpu_gem_info\n"); return NULL; } gilist = get_geminfo(drm_fp); fclose(drm_fp); return gilist; } static void clear_geminfo(geminfo *glist) { while (glist) { geminfo *gelement = glist; glist = glist->next; free(gelement); } } /* b6e82000-b6e83000 rw-p 00020000 b3:19 714 /usr/lib/ld-2.20-2014.11.so : TM1 * 7f9389d000-7f9389e000 rw-p 0001f000 b3:12 618 /usr/lib64/ld-2.20-2014.11.so : TM2 * 7fae2e4b2000-7fae2e4b3000 r--p 00021000 fe:01 603 /usr/lib64/ld-2.20-2014.11.so : x86-64 Emulator * 01234567890123456789012345678901234567890123456789012345678901234567890123456789 * 0 1 2 3 4 5 6 7 */ mapinfo *read_mapinfo(char** smaps) { char* line; mapinfo *mi; int len; int n; if ((line = cgets(smaps)) == 0) return 0; len = strlen(line); if (len < 1) return 0; mi = malloc(sizeof(mapinfo)); if (mi == 0) return 0; n = sscanf(line, "%lx-%lx %ms %*s %*s %*s %m[^\n]", &mi->start, &mi->end, &mi->perm, &mi->name); if (n == 3 && !mi->name) mi->name = strndup("[anon]", strlen("[anon]")); else if (n <= 3) { fprintf(stderr, "Fail to parse smaps\n"); free(mi); return 0; } while ((line = cgets(smaps))) { if (sscanf(line, "Size: %d kB", &mi->size) == 1) ; else if (sscanf(line, "Rss: %d kB", &mi->rss) == 1) ; else if (sscanf(line, "Pss: %d kB", &mi->pss) == 1) ; else if (sscanf(line, "Shared_Clean: %d kB", &mi->shared_clean) == 1) ; else if (sscanf(line, "Shared_Dirty: %d kB", &mi->shared_dirty) == 1) ; else if (sscanf(line, "Private_Clean: %d kB", &mi->private_clean) == 1) ; else if (sscanf(line, "Private_Dirty: %d kB", &mi->private_dirty) == 1) ; else if (sscanf(line, "Swap: %d kB", &mi->swap) == 1) ; if (*smaps) { /* Drain lines until it meets next VMA address */ char next = **smaps; if ((next >= '0' && next <= '9') || (next >= 'a' && next <= 'f')) break; } } return mi; } static unsigned total_gem_memory(void) { FILE *gem_fp; unsigned total_gem_mem = 0; unsigned name, size, handles, refcount; char line[BUF_MAX]; if (!dri_card) gem_fp = fopen(STR_DRM_DBG_DIR "0/gem_names", "r"); else gem_fp = fopen(STR_DRM_DBG_DIR "1/gem_names", "r"); if (gem_fp == NULL) { fprintf(stderr, "cannot open " STR_DRM_DBG_DIR "%d/gem_names\n", dri_card); return 0; } if (fgets(line, BUF_MAX, gem_fp) == NULL) { fclose(gem_fp); return 0; } while (fgets(line, BUF_MAX, gem_fp) != NULL) { if (sscanf(line, "%d %d %d %d\n", &name, &size, &handles, &refcount) == 4) { if (total_gem_mem <= UINT_MAX - size) { total_gem_mem += size; } } } fclose(gem_fp); return total_gem_mem; } int get_zram_used(u_int32_t *zram_used) { FILE *f = NULL; /* only read 3rd value */ char *fscanf_format = "%*d %*d %d %*d %*d %*d %*d %*d"; int ret; f = fopen(ZRAM_MM_STAT_PATH, "r"); if (!f) { /* * ZRAM_USED_PATH is deprecated on latest kernel, but to support * old kernel try with that if fails new node 'mm_stat'. */ f = fopen(ZRAM_USED_PATH, "r"); if (!f) { fprintf(stderr, "Fail to open zram file.\n"); return -1; } fscanf_format = "%u"; } ret = fscanf(f, fscanf_format, zram_used); if (ret == EOF) { fprintf(stderr, "Fail to read file\n"); fclose(f); return -1; } fclose(f); return 0; } int fread_uint(const char *path, u_int32_t *number) { FILE *f = NULL; int ret; f = fopen(path, "r"); if (!f) { fprintf(stderr, "Fail to open %s file.\n", path); return -1; } ret = fscanf(f, "%u", number); if (ret == EOF) { fprintf(stderr, "Fail to read file\n"); fclose(f); return -1; } fclose(f); return 0; } static int cgroup_read_node(const char *cgroup_name, const char *file_name, unsigned int *value) { char buf[PATH_MAX + NAME_MAX]; int ret; snprintf(buf, sizeof(buf), "%s%s", cgroup_name, file_name); ret = fread_uint(buf, value); return ret; } /** * @desc Provides usage in bytes for provided memory cgroup. Works * with/without swap accounting. * * @param memcg_path[in] Full path to memory cgroup * @param swap[in] Boolean value for deciding if account usage with swap * @return current cgroup usage in bytes or 0 on error */ static unsigned int get_memcg_usage(const char *memcg_path, bool swap) { int ret; unsigned int usage; if (swap) { ret = cgroup_read_node(memcg_path, "/memory.memsw.usage_in_bytes", &usage); } else { ret = cgroup_read_node(memcg_path, "/memory.usage_in_bytes", &usage); } if (ret != 0) usage = 0; return usage; } static void get_memcg_info(FILE *output_fp) { char buf[PATH_MAX]; DIR *pdir = NULL; struct dirent *entry; struct stat path_stat; long usage_swap; unsigned long usage, usage_with_swap; fprintf(output_fp, "====================================================================\n"); fprintf(output_fp, "MEMORY CGROUPS USAGE INFO\n"); pdir = opendir(MEMCG_PATH); if (pdir == NULL) { fprintf(stderr, "cannot read directory %s", MEMCG_PATH); return; } errno = 0; while ((entry = readdir(pdir)) != NULL && !errno) { snprintf(buf, sizeof(buf), "%s/%s", MEMCG_PATH, entry->d_name); /* If can't stat then ignore */ if (stat(buf, &path_stat) != 0) continue; /* If it's not directory or it's parent path then ignore */ if (!(S_ISDIR(path_stat.st_mode) && strncmp(entry->d_name, "..", 3))) continue; usage = get_memcg_usage(buf, false); usage_with_swap = get_memcg_usage(buf, true); /* It is posible by rounding errors to get negative value */ usage_swap = usage_with_swap - usage; if (usage_swap < 0) usage_swap = 0; /* Case of root cgroup in hierarchy */ if (!strncmp(entry->d_name, ".", 2)) fprintf(output_fp, "%13s Mem %3ld MB (%6ld kB), Mem+Swap %3ld MB (%6ld kB), Swap %3ld MB (%6ld kB) \n\n", MEMCG_PATH, BYTE_TO_MBYTE(usage), BYTE_TO_KBYTE(usage), BYTE_TO_MBYTE(usage_with_swap), BYTE_TO_KBYTE(usage_with_swap), BYTE_TO_MBYTE(usage_swap), BYTE_TO_KBYTE(usage_swap)); else fprintf(output_fp, "memcg: %13s Mem %3ld MB (%6ld kB), Mem+Swap %3ld MB (%6ld kB), Swap %3ld MB (%6ld kB)\n", entry->d_name, BYTE_TO_MBYTE(usage), BYTE_TO_KBYTE(usage), BYTE_TO_MBYTE(usage_with_swap), BYTE_TO_KBYTE(usage_with_swap), BYTE_TO_MBYTE(usage_swap), BYTE_TO_KBYTE(usage_swap)); } closedir(pdir); } static void get_mem_info(FILE *output_fp) { char buf[PATH_MAX]; FILE *fp; char *idx; unsigned int free = 0, cached = 0; unsigned int total_mem = 0, available = 0, used; unsigned int swap_total = 0, swap_free = 0, zram_used, swap_used; unsigned int used_ratio; if (output_fp == NULL) return; fp = fopen("/proc/meminfo", "r"); if (!fp) { fprintf(stderr, "/proc/meminfo open failed, %p", fp); return; } while (fgets(buf, PATH_MAX, fp) != NULL) { if ((idx = strstr(buf, "MemTotal:"))) { idx += strlen("Memtotal:"); while ((idx < buf + PATH_MAX) && (*idx < '0' || *idx > '9')) idx++; total_mem = atoi(idx); } else if ((idx = strstr(buf, "MemFree:"))) { idx += strlen("MemFree:"); while ((idx < buf + PATH_MAX) && (*idx < '0' || *idx > '9')) idx++; free = atoi(idx); } else if ((idx = strstr(buf, "MemAvailable:"))) { idx += strlen("MemAvailable:"); while ((idx < buf + PATH_MAX) && (*idx < '0' || *idx > '9')) idx++; available = atoi(idx); } else if ((idx = strstr(buf, "Cached:")) && !strstr(buf, "Swap")) { idx += strlen("Cached:"); while ((idx < buf + PATH_MAX) && (*idx < '0' || *idx > '9')) idx++; cached = atoi(idx); } else if ((idx = strstr(buf, "SwapTotal:"))) { idx += strlen("SwapTotal:"); while ((idx < buf + PATH_MAX) && (*idx < '0' || *idx > '9')) idx++; swap_total = atoi(idx); } else if ((idx = strstr(buf, "SwapFree:"))) { idx += strlen("SwapFree"); while ((idx < buf + PATH_MAX) && (*idx < '0' || *idx > '9')) idx++; swap_free = atoi(idx); break; } } if (total_mem == 0) { fprintf(stderr, "cannot get total memory size\n"); fclose(fp); return; } if (available == 0) available = free + cached; used = total_mem - available; used_ratio = used * 100 / total_mem; swap_used = swap_total - swap_free; if (get_zram_used(&zram_used) < 0) zram_used = 0; fprintf(output_fp, "====================================================================\n"); fprintf(output_fp, "Total RAM size: \t%15d MB( %6d kB)\n", total_mem >> 10, total_mem); fprintf(output_fp, "Used (Mem+Reclaimable): %15d MB( %6d kB)\n", (total_mem - free) >> 10, total_mem - free); fprintf(output_fp, "Used (Mem+Swap): \t%15d MB( %6d kB)\n", used >> 10, used); fprintf(output_fp, "Used (Mem): \t\t%15d MB( %6d kB)\n", used >> 10, used); fprintf(output_fp, "Used (Swap): \t\t%15d MB( %6d kB)\n", swap_used >> 10, swap_used); fprintf(output_fp, "Used (Zram block device): %13d MB( %6d kB)\n", BYTE_TO_MBYTE(zram_used), BYTE_TO_KBYTE(zram_used)); fprintf(output_fp, "Used Ratio: \t\t%15d %%\n", used_ratio); fprintf(output_fp, "Mem Free:\t\t%15d MB( %6d kB)\n", free >> 10, free); fprintf(output_fp, "Available (Free+Reclaimable):%10d MB( %6d kB)\n", available >> 10, available); fclose(fp); } static int get_tmpfs_info(FILE *output_fp) { FILE *fp; char line[BUF_MAX]; char *tmpfs_mp; /* tmpfs mount point */ struct statfs tmpfs_info; if (output_fp == NULL) return -1; fp = fopen("/etc/mtab", "r"); if (fp == NULL) return -1; fprintf(output_fp, "====================================================================\n"); fprintf(output_fp, "TMPFS INFO\n"); while (fgets(line, BUF_MAX, fp) != NULL) { if (sscanf(line, "tmpfs %ms tmpfs", &tmpfs_mp) == 1) { if (statfs(tmpfs_mp, &tmpfs_info) == 0) { fprintf(output_fp, #ifndef __USE_FILE_OFFSET64 "tmpfs %16s Total %8ld KB, Used %8ld, Avail %8ld\n", #else "tmpfs %16s Total %8"PRId64" KB, Used %8"PRId64", Avail %8"PRId64"\n", #endif tmpfs_mp, /* 1 block is 4 KB */ tmpfs_info.f_blocks * 4, (tmpfs_info.f_blocks - tmpfs_info.f_bfree) * 4, tmpfs_info.f_bfree * 4); } free(tmpfs_mp); tmpfs_mp = NULL; } } fclose(fp); return 0; } static inline mapinfo *__load_maps(const char *path) { char* smaps; mapinfo *milist = 0; mapinfo *mi; if (!path) return 0; smaps = cread(path); if (smaps == NULL) return 0; while ((mi = read_mapinfo(&smaps)) != 0) { if (milist) { if ((!strcmp(mi->name, milist->name) && (mi->name[0] != '['))) { milist->size += mi->size; milist->swap += mi->swap; milist->rss += mi->rss; milist->pss += mi->pss; milist->shared_clean += mi->shared_clean; milist->shared_dirty += mi->shared_dirty; milist->private_clean += mi->private_clean; milist->private_dirty += mi->private_dirty; milist->end = mi->end; strncpy(milist->perm, mi->perm, 4); free(mi->perm); free(mi->name); free(mi); continue; } } mi->next = milist; milist = mi; } return milist; } mapinfo *load_maps_rollup(int pid) { char tmp[128]; /* * use smaps_rollup instead of traversing smaps * for getting values quickly */ snprintf(tmp, sizeof(tmp), "/proc/%d/smaps_rollup", pid); tmp[sizeof(tmp) - 1] = '\0'; if (access(tmp, F_OK) < 0) return 0; return __load_maps(tmp); } mapinfo *load_maps(int pid) { char tmp[128]; snprintf(tmp, sizeof(tmp), "/proc/%d/smaps", pid); tmp[sizeof(tmp) - 1] = '\0'; return __load_maps(tmp); } static void init_trib_mapinfo(trib_mapinfo *tmi) { if (!tmi) return; tmi->shared_clean = 0; tmi->shared_dirty = 0; tmi->private_clean = 0; tmi->private_dirty = 0; tmi->swap = 0; tmi->shared_clean_pss = 0; tmi->shared_dirty_pss = 0; tmi->rss = 0; tmi->pss = 0; tmi->size = 0; tmi->graphic_3d = 0; tmi->gem_rss = 0; tmi->gem_pss = 0; tmi->peak_rss = 0; tmi->other_devices = 0; tmi->gem_mmap = 0; tmi->render_gem_mmap = 0; } unsigned int get_graphic_3d_meminfo(unsigned int tgid) { char command[256], buf[256]; char *tmp[2]; unsigned int size = 0; int tid, ret; FILE *p_gpu; snprintf(command, sizeof(command), "%s %d", STR_3D_UNIFIED_PATH, tgid); p_gpu = popen(command, "r"); if (p_gpu) { if (fgets(buf, 256, p_gpu)) { ret = sscanf(buf, "%d %ms %d %ms", &tid, &tmp[0], &size, &tmp[1]); if (ret == 4) { free(tmp[0]); free(tmp[1]); } } pclose(p_gpu); } return size; } static int get_trib_mapinfo(unsigned int tgid, mapinfo *milist, geminfo *gilist, geminfo *gpu_glist, trib_mapinfo *result) { mapinfo *mi; mapinfo *temp = NULL; geminfo *gi; if (!result) return -EINVAL; init_trib_mapinfo(result); if (use_gpu_mem_info) result->graphic_3d = get_graphic_3d_meminfo(tgid); for (mi = milist; mi;) { if (!use_gpu_mem_info && strstr(mi->name, STR_SGX_PATH)) { result->graphic_3d += mi->pss; } else if (!use_gpu_mem_info && (strstr(mi->name, STR_3D_PATH1) || strstr(mi->name, STR_3D_PATH2))) { result->graphic_3d += mi->size; } else if (mi->rss != 0 && mi->pss == 0 && mi->shared_clean == 0 && mi->shared_dirty == 0 && mi->private_clean == 0 && mi->private_dirty == 0 && mi->swap == 0) { result->other_devices += mi->size; } else if (!strncmp(mi->name, STR_DRM_PATH1, sizeof(STR_DRM_PATH1)) || !strncmp(mi->name, STR_DRM_PATH2, sizeof(STR_DRM_PATH2))) { result->gem_mmap += mi->rss; } else if (!strncmp(mi->name, STR_DRM_RENDER_PATH, sizeof(STR_DRM_RENDER_PATH))) { result->render_gem_mmap += mi->rss; } else { result->shared_clean += mi->shared_clean; result->shared_dirty += mi->shared_dirty; result->private_clean += mi->private_clean; result->private_dirty += mi->private_dirty; result->swap += mi->swap; result->rss += mi->rss; result->pss += mi->pss; result->size += mi->size; if (mi->shared_clean != 0) result->shared_clean_pss += mi->pss; else if (mi->shared_dirty != 0) result->shared_dirty_pss += mi->pss; } temp = mi; mi = mi->next; free(temp->perm); free(temp->name); free(temp); temp = NULL; } result->peak_rss = get_peak_rss(tgid); if (result->peak_rss < result->rss) result->peak_rss = result->rss; if (result->gem_mmap > 0) result->peak_rss -= result->gem_mmap; if (result->render_gem_mmap > 0) result->peak_rss -= result->render_gem_mmap; gi = find_geminfo(tgid, gilist); if (gi != NULL) { result->gem_rss = gi->rss_size; result->gem_pss = gi->pss_size; } if (gpu_glist) { /* * drm render has gpu_gem_info, then use gpua gem pss * as graphic 3d memory */ gi = find_geminfo(tgid, gpu_glist); if (gi != NULL) result->graphic_3d += gi->pss_size; } return 0; } static int get_cmdline(unsigned int pid, char *cmdline) { FILE *fp; char buf[NAME_MAX] = {0, }; int ret = -1; snprintf(buf, sizeof(buf), "/proc/%d/cmdline", pid); fp = fopen(buf, "r"); if (fp == 0) { if (errno == ENOENT) errno = 0; fprintf(stderr, "cannot file open %s\n", buf); return ret; } if ((ret = fscanf(fp, "%4095s", cmdline)) < 1) { fclose(fp); return ret; } fclose(fp); return ret; } static int get_oomscoreadj(unsigned int pid, const char *oom_string) { FILE *fp; char tmp[256]; int oomadj_val; snprintf(tmp, sizeof(tmp), "/proc/%d/%s", pid, oom_string); fp = fopen(tmp, "r"); if (fp == NULL) { oomadj_val = -50; return oomadj_val; } if (fgets(tmp, sizeof(tmp), fp) == NULL) { oomadj_val = -100; fclose(fp); return oomadj_val; } oomadj_val = atoi(tmp); fclose(fp); return oomadj_val; } static void get_rss(pid_t pid, unsigned long *result) { FILE *fp; char proc_path[PATH_MAX]; unsigned long rss = 0; *result = 0; snprintf(proc_path, sizeof(proc_path), "/proc/%d/statm", pid); fp = fopen(proc_path, "r"); if (fp == NULL) return; if (fscanf(fp, "%*s %ld", &rss) < 1) { fclose(fp); return; } fclose(fp); /* convert page to Kb */ *result = rss << 2; return; } static const char* get_readable_oom_path(void) { FILE *fp = NULL; char tmp[256]; snprintf(tmp, sizeof(tmp), "/proc/1/%s", OOM_SCORE_ADJ_STR); fp = fopen(tmp, "r"); if (fp) { fclose(fp); return OOM_SCORE_ADJ_STR; } snprintf(tmp, sizeof(tmp), "/proc/1/%s", OOM_SCORE_STR); fp = fopen(tmp, "r"); if (fp) { fclose(fp); return OOM_SCORE_STR; } return NULL; } static void show_rss(int output_type, char *output_path) { DIR *pDir = NULL; struct dirent *curdir; pid_t pid; char cmdline[PATH_MAX]; FILE *output_file = NULL; int oom_score_adj; unsigned long rss; const char *oom_path = NULL; oom_path = get_readable_oom_path(); if (!oom_path) { fprintf(stderr, "there is no readable oom path\n"); return; } pDir = opendir("/proc"); if (pDir == NULL) { fprintf(stderr, "cannot read directory /proc.\n"); return; } if (output_type == OUTPUT_FILE && output_path) { output_file = fopen(output_path, "w+"); if (!output_file) { fprintf(stderr, "cannot open output file(%s)\n", output_path); closedir(pDir); exit(1); } } else output_file = stdout; fprintf(output_file, " PID RSS %14s COMMAND\n", oom_path); errno = 0; while ((curdir = readdir(pDir)) != NULL && !errno) { pid = atoi(curdir->d_name); if (pid < 1 || pid > pid_max || pid == getpid()) continue; if (get_cmdline(pid, cmdline) < 0) continue; get_rss(pid, &rss); oom_score_adj = get_oomscoreadj(pid, oom_path); fprintf(output_file, "%8d %8lu %8d %s\n", pid, rss, oom_score_adj, cmdline); } /* end of while */ get_tmpfs_info(output_file); get_mem_info(output_file); fclose(output_file); closedir(pDir); return; } static int show_map_all_new(int output_type, char *output_path, bool use_rollup) { DIR *pDir = NULL; struct dirent *curdir; unsigned int pid; geminfo *glist; geminfo *gpu_glist; unsigned total_pss = 0; unsigned total_private = 0; unsigned total_private_code = 0; unsigned total_private_data = 0; unsigned total_shared_code = 0; unsigned total_shared_data = 0; unsigned total_shared_code_pss = 0; unsigned total_shared_data_pss = 0; unsigned total_swap = 0; unsigned total_rss = 0; unsigned total_graphic_3d = 0; unsigned total_gem_rss = 0; unsigned total_gem_pss = 0; unsigned total_peak_rss = 0; unsigned total_allocated_gem = 0; trib_mapinfo tmi; char cmdline[PATH_MAX]; FILE *output_file = NULL; int oom_score_adj; const char *oom_path = NULL; oom_path = get_readable_oom_path(); if (!oom_path) { fprintf(stderr, "there is no readable oom path\n"); return 0; } use_gpu_mem_info = (!access(STR_3D_UNIFIED_PATH, F_OK | X_OK)) ? 1 : 0; errno = 0; pDir = opendir("/proc"); if (pDir == NULL) { fprintf(stderr, "cannot read directory /proc.\n"); return 0; } if (output_type == OUTPUT_FILE && output_path) { output_file = fopen(output_path, "w+"); if (!output_file) { fprintf(stderr, "cannot open output file(%s)\n", output_path); closedir(pDir); exit(1); } } else output_file = stdout; glist = load_geminfo(); gpu_glist = load_gpu_geminfo(); if (!sum) { if (verbos) fprintf(output_file, " PID S(CODE) S(DATA) P(CODE) P(DATA) " " PEAK PSS 3D GEM(PSS) GEM(RSS) " " SWAP %14s COMMAND\n", oom_path); else fprintf(output_file, " PID CODE DATA PEAK PSS " " 3D GEM(PSS) SWAP COMMAND\n"); } errno = 0; while ((curdir = readdir(pDir)) != NULL && !errno) { mapinfo *milist = 0; pid = atoi(curdir->d_name); if (pid < 1 || pid > pid_max || pid == getpid()) continue; if (get_cmdline(pid, cmdline) < 0) continue; if (use_rollup) milist = load_maps_rollup(pid); if (milist == 0) { /* fall back to the default lookup path */ milist = load_maps(pid); if (milist == 0) continue; } /* get classified map info, milist will be freed */ get_trib_mapinfo(pid, milist, glist, gpu_glist, &tmi); oom_score_adj = get_oomscoreadj(pid, oom_path); if (!sum) { if (verbos) fprintf(output_file, "%8d %8d %8d %8d %8d " "%8d %8d %8d %8d %8d " "%8d %8d %s\n", pid, tmi.shared_clean, tmi.shared_dirty, tmi.private_clean, tmi.private_dirty, tmi.peak_rss, tmi.pss, tmi.graphic_3d, tmi.gem_pss, tmi.gem_rss, tmi.swap, oom_score_adj, cmdline); else fprintf(output_file, "%8d %8d %8d %8d %8d " "%8d %8d %8d %s\n", pid, tmi.shared_clean + tmi.private_clean, tmi.shared_dirty + tmi.private_dirty, tmi.peak_rss, tmi.pss, tmi.graphic_3d, tmi.gem_pss, tmi.swap, cmdline); if (tmi.other_devices != 0) fprintf(output_file, "%s(%d) %d KB may mapped by device(s).\n", cmdline, pid, tmi.other_devices); } total_private += (tmi.private_clean + tmi.private_dirty); total_pss += tmi.pss; total_rss += tmi.rss; total_graphic_3d += tmi.graphic_3d; total_gem_rss += tmi.gem_rss; total_gem_pss += tmi.gem_pss; total_private_code += tmi.private_clean; total_private_data += tmi.private_dirty; total_swap += tmi.swap; total_shared_code += tmi.shared_clean; total_shared_data += tmi.shared_dirty; total_peak_rss += tmi.peak_rss; total_shared_code_pss += tmi.shared_clean_pss; total_shared_data_pss += tmi.shared_dirty_pss; } /* end of while */ total_allocated_gem = KB(total_gem_memory()); fprintf(output_file, "===============================================" "===============================================\n"); if (verbos) { fprintf(output_file, "TOTAL: S(CODE) S(DATA) P(CODE) P(DATA) " " PEAK PSS 3D GEM(PSS) GEM(RSS) " "GEM(ALLOC) SWAP TOTAL(KB)\n"); fprintf(output_file, " %8d %8d %8d %8d " "%8d %8d %8d %8d %8d " "%10d %8d %10d\n", total_shared_code, total_shared_data, total_private_code, total_private_data, total_peak_rss, total_pss, total_graphic_3d, total_gem_pss, total_gem_rss, total_allocated_gem, total_swap, total_pss + total_graphic_3d + total_allocated_gem); } else { fprintf(output_file, "TOTAL: CODE DATA PEAK PSS " " 3D GEM(PSS) GEM(ALLOC) SWAP TOTAL(KB)\n"); fprintf(output_file, " %8d %8d %8d %8d " "%8d %8d %10d %8d %10d\n", total_shared_code + total_private_code, total_shared_data + total_private_data, total_peak_rss, total_pss, total_graphic_3d, total_gem_pss, total_allocated_gem, total_swap, total_pss + total_graphic_3d + total_allocated_gem); } if (verbos) fprintf(output_file, "* S(CODE): shared clean memory, it includes" " duplicated memory\n" "* S(DATA): shared dirty memory, it includes" " duplicated memory\n" "* P(CODE): private clean memory\n" "* P(DATA): private dirty memory\n" "* PEAK: peak memory usage of S(CODE) + S(DATA)" " + P(CODE) + P(DATA)\n" "* PSS: Proportional Set Size\n" "* 3D: memory allocated by GPU driver\n" "* GEM(PSS): GEM memory divided by # of sharers\n" "* GEM(RSS): GEM memory including duplicated memory\n" "* GEM(ALLOC): sum of unique gem memory in the system\n" "* TOTAL: PSS + 3D + GEM(ALLOC)\n"); else fprintf(output_file, "* CODE: shared and private clean memory\n" "* DATA: shared and private dirty memory\n" "* PEAK: peak memory usage of CODE + DATA\n" "* PSS: Proportional Set Size\n" "* 3D: memory allocated by GPU driver\n" "* GEM(PSS): GEM memory divided by # of sharers\n" "* GEM(ALLOC): sum of unique GEM memory in the system\n" "* TOTAL: PSS + 3D + GEM(ALLOC)\n"); get_tmpfs_info(output_file); get_memcg_info(output_file); get_mem_info(output_file); fclose(output_file); clear_geminfo(glist); clear_geminfo(gpu_glist); closedir(pDir); return 1; } static int show_map_new(int pid) { mapinfo *milist; mapinfo *mi; mapinfo *temp = NULL; unsigned shared_dirty = 0; unsigned shared_clean = 0; unsigned private_dirty = 0; unsigned private_clean = 0; unsigned pss = 0; unsigned long start = 0; unsigned long end = 0; unsigned private_clean_total = 0; unsigned private_dirty_total = 0; unsigned shared_clean_total = 0; unsigned shared_dirty_total = 0; int duplication = 0; milist = load_maps(pid); if (milist == 0) { fprintf(stderr, "cannot get /proc/smaps for pid %d\n", pid); return 1; } if (!sum) { if (sizeof(unsigned long) == 4) { /* for 32-bit address */ printf(" S(CODE) S(DATA) P(CODE) P(DATA) PSS " "ADDR(start-end) " "OBJECT NAME\n"); printf("-------- -------- -------- -------- -------- " "----------------- " "------------------------------\n"); } else { /* for 64-bit address */ printf(" S(CODE) S(DATA) P(CODE) P(DATA) PSS " "ADDR(start-end) " "OBJECT NAME\n"); printf("-------- -------- -------- -------- -------- " "--------------------------------- " "------------------------------\n"); } } else { printf(" S(CODE) S(DATA) P(CODE) P(DATA) PSS\n"); printf("-------- -------- -------- -------- --------\n"); } for (mi = milist; mi;) { shared_clean += mi->shared_clean; shared_dirty += mi->shared_dirty; private_clean += mi->private_clean; private_dirty += mi->private_dirty; pss += mi->pss; shared_clean_total += mi->shared_clean; shared_dirty_total += mi->shared_dirty; private_clean_total += mi->private_clean; private_dirty_total += mi->private_dirty; if (!duplication) start = mi->start; if ((mi->next && !strcmp(mi->next->name, mi->name)) && (mi->next->start == mi->end)) { duplication = 1; temp = mi; mi = mi->next; free(temp->perm); free(temp->name); free(temp); temp = NULL; continue; } end = mi->end; duplication = 0; if (!sum) { if (sizeof(unsigned long) == 4) { /* for 32-bit address */ printf("%8d %8d %8d %8d %8d %08lx-%08lx %s\n", shared_clean, shared_dirty, private_clean, private_dirty, mi->pss, start, end, mi->name); } else { /* for 64-bit address */ printf("%8d %8d %8d %8d %8d %016lx-%016lx %s\n", shared_clean, shared_dirty, private_clean, private_dirty, mi->pss, start, end, mi->name); } } shared_clean = 0; shared_dirty = 0; private_clean = 0; private_dirty = 0; temp = mi; mi = mi->next; free(temp->perm); free(temp->name); free(temp); temp = NULL; } if (sum) { printf("%8d %8d %8d %8d %8d\n", shared_clean_total, shared_dirty_total, private_clean_total, private_dirty_total, pss); } return 1; } int main(int argc, char *argv[]) { int usage = 1; sum = 0; pid_max = get_pid_max(); if (argc > 1) { if (!strncmp(argv[1], "-r", strlen("-r")+1)) { if (argc >= 3) show_rss(OUTPUT_FILE, argv[2]); else show_rss(OUTPUT_UART, NULL); usage = 0; } else if (!strncmp(argv[1], "-s", strlen("-s")+1)) { sum = 1; if (argc == 3 && atoi(argv[2]) > 0) { show_map_new(atoi(argv[2])); usage = 0; } } else if (!strncmp(argv[1], "-a", strlen("-a")+1)) { verbos = 0; show_map_all_new(OUTPUT_UART, NULL, false); usage = 0; } else if (!strncmp(argv[1], "-v", strlen("-v")+1)) { verbos = 1; show_map_all_new(OUTPUT_UART, NULL, false); usage = 0; } else if (!strncmp(argv[1], "-f", strlen("-f")+1)) { if (argc >= 3) { verbos = 1; show_map_all_new(OUTPUT_FILE, argv[2], false); usage = 0; } } else if (!strncmp(argv[1], "-q", strlen("-q")+1)) { verbos = 1; show_map_all_new(OUTPUT_UART, NULL, true); usage = 0; } else if (argc == 2 && atoi(argv[1]) > 0) { show_map_new(atoi(argv[1])); usage = 0; } } if (usage) { fprintf(stderr, "memps [-a] | [-v] | [-q] | [-r] | [-s] | | [-f] \n" " -s = sum (show only sum of each)\n" " -f = all (show all processes via output file)\n" " -a = all (show all processes)\n" " -r = all (show rss of all processes)\n" " -v = verbose (show all processes in detail)\n" " -q = verbose (experimental - quickly show all processes in detail)\n"); } return 0; }