/* * Copyright (C) 2010 Timo Kokkonen * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA. */ #include #include #include "analyze.h" #include "utils.h" struct kpageflag_str kpageflag_str[] = { { .flag = LOCKED, .str = "locked", }, { .flag = ERROR, .str = "error", }, { .flag = REFERENCED, .str = "referenced", }, { .flag = UPTODATE, .str = "uptodate", }, { .flag = DIRTY, .str = "dirty", }, { .flag = LRU, .str = "lru", }, { .flag = ACTIVE, .str = "active", }, { .flag = SLAB, .str = "slab", }, { .flag = WRITEBACK, .str = "writeback", }, { .flag = RECLAIM, .str = "reclaim", }, { .flag = BUDDY, .str = "buddy", }, { .flag = MMAP, .str = "mmap", }, { .flag = ANON, .str = "anon", }, { .flag = SWAPCACHE, .str = "swapcache", }, { .flag = SWAPBACKED, .str = "swapbacked", }, { .flag = COMPOUND_HEAD, .str = "compound_head", }, { .flag = COMPOUND_TAIL, .str = "compound_tail", }, { .flag = HUGE, .str = "huge", }, { .flag = UNEVICTABLE, .str = "unevictable", }, { .flag = HWPOISON, .str = "hwpoison", }, { .flag = NOPAGE, .str = "nopage", }, { .flag = KSM, .str = "ksm", }, { .flag = THP, .str = "thp", }, }; #define SI_k 1024ll #define SI_M (SI_k * SI_k) #define SI_G (SI_M * SI_k) #define PRETTY_THRESH 100 #define NICE_DIV(a) \ ((a) < SI_k * 4 ? (a) : \ (a < SI_k * PRETTY_THRESH * 1000 ? ((a) / SI_k) : \ (a < SI_M * PRETTY_THRESH * 1000 ? ((a) / SI_M) : \ ((a) / SI_G)))) #define NICE_UNIT(a) \ ((a) < (SI_k * 4) ? " " : \ ((a) < (SI_k * PRETTY_THRESH * 1000) ? "k" : \ ((a) < (SI_M * PRETTY_THRESH * 1000) ? "M" : "G"))) #define PAGE_TO_NICE(a) NICE_DIV((long long)a * PAGE_SIZE) #define PAGE_TO_NICE_UNIT(a) NICE_UNIT((long long)a * PAGE_SIZE) struct analyze_frames { int pid; struct list_head *pidlist; struct maps *map; int pids; /* General counters */ long int pages_present; long int pages_swapped; long int pages_unique; /* kpageflag stats counters */ long int kpageflag[KPAGEFLAGS_NUM]; }; #define bintree_ops_to_af(bintree_ops) \ container_of((bintree_ops), struct analyze_frames, ops) static void count_page(struct pageframe *pf, struct analyze_frames *af) { struct maps_list *ml; int i; if (af->pid) { /* Find pages which reference at least once a pid */ list_for_each_entry(ml, &pf->ml, list) { if (ml->map->pid == af->pid) goto get_stats; } return; } else if (af->pidlist && af->map) { /* * Find pages that reference at least once all of the * given pids and a given mapping */ struct pidlist *pid; int matches = 0; /* * Check that we reference the given mapping at least * once */ list_for_each_entry(ml, &pf->ml, list) { if (ml->map == af->map) { matches++; break; } } if (!matches) return; matches = 0; /* * Check that we reference all of the given pids * too. The order of the loops is important here. We * must scan through all the references and test for a * given pid. If we would iterate through the * references in the outer loop, we might get * duplicate matches for a pid since it is possible * that a page is mapped multiple times in a process's * addrses space. */ list_for_each_entry(pid, af->pidlist, list) { list_for_each_entry(ml, &pf->ml, list) { if (ml->map->pid == pid->pid) { matches++; break; } } /* * If we have found as many matches as ther * are pids, we will count the stats */ if (matches == af->pids) goto get_stats; } return; } get_stats: if (page_present(pf)) af->pages_present++; else if (page_swapped(pf)) af->pages_swapped++; if (pf->kpagecount == 1) af->pages_unique++; for (i = 0; i < KPAGEFLAGS_NUM; i++) if (kpageflag_is_set(pf, i)) af->kpageflag[i]++; } static int count_pages(struct rb_root *root, struct analyze_frames *af) { struct pageframe *pf; int pages = 0; pf = rb_to_pageframe(rb_first(root)); while (pf) { pages++; count_page(pf, af); pf = rb_to_pageframe(rb_next(&pf->tree)); } return pages; } /* * print_page_stats - Prints system wide page stats */ void print_page_stats(struct rb_root *root) { struct analyze_frames af; long count; int i; memset(&af, 0, sizeof(af)); count = count_pages(root, &af); printf("\n"); for (i = 0; i < KPAGEFLAGS_NUM; i++) { if (!af.kpageflag[i]) continue; printf("%13s pages: %7ld, %5lld %sB\n", kpageflag_to_str(i), af.kpageflag[i], PAGE_TO_NICE(af.kpageflag[i]), PAGE_TO_NICE_UNIT(af.kpageflag[i])); } printf(" present pages: %7ld, %5lld %sB\n" " swapped pages: %7ld, %5lld %sB\n" " unique pages: %7ld, %5lld %sB\n" " total pages: %7ld, %5lld %sB\n", af.pages_present, PAGE_TO_NICE(af.pages_present), PAGE_TO_NICE_UNIT(af.pages_present), af.pages_swapped, PAGE_TO_NICE(af.pages_swapped), PAGE_TO_NICE_UNIT(af.pages_swapped), af.pages_unique, PAGE_TO_NICE(af.pages_unique), PAGE_TO_NICE_UNIT(af.pages_unique), count, PAGE_TO_NICE(count), PAGE_TO_NICE_UNIT(count)); } void print_pid_stats(struct rb_root *root, struct process *process_list, struct parse_opts *opts) { struct analyze_frames af; struct process *ps; struct pageframe *pf; long int swapped, present, unique, total; long int biggest = 0, smallest = 0, second_smallest; int count, processes = 0; /* * walk through all processes, find the one with most present * pages */ pf = rb_to_pageframe(rb_first(root)); while(pf) { list_for_each_entry(ps, &process_list->list, list) { memset(&af, 0, sizeof(af)); af.pid = ps->pid; count_page(pf, &af); ps->pages_present += af.pages_present; ps->pages_swapped += af.pages_swapped; ps->pages_unique += af.pages_unique; biggest = MAX(biggest, ps->pages_present + ps->pages_swapped); } pf = rb_to_pageframe(rb_next(&pf->tree)); } printf(" RSS swapped USS total pid"); if (opts->with_threads) printf(" tid"); printf(" name\n"); restart: second_smallest = biggest; count = 0; list_for_each_entry(ps, &process_list->list, list) { present = ps->pages_present; swapped = ps->pages_swapped; unique = ps->pages_unique; total = present + swapped; if (total > smallest) second_smallest = MIN(second_smallest, total); if (total != smallest) continue; if (total == 0) continue; printf("%5lld %sB %5lld %sB %5lld %sB %5lld %sB %5d ", PAGE_TO_NICE(present), PAGE_TO_NICE_UNIT(present), PAGE_TO_NICE(swapped), PAGE_TO_NICE_UNIT(swapped), PAGE_TO_NICE(unique), PAGE_TO_NICE_UNIT(unique), PAGE_TO_NICE(total), PAGE_TO_NICE_UNIT(total), ps->pid); if (opts->with_threads) printf("%5d ", ps->tid); printf("%c %s\n", ps->is_initial_pid ? '*' : ' ', ps->name); count++; processes++; } if (smallest != second_smallest) { smallest = second_smallest; goto restart; } printf(" RSS swapped USS total pid"); if (opts->with_threads) printf(" tid"); printf(" name\n"); printf("Total %d processes\n", processes); } static void _dump_process_maps(struct rb_root *root, struct process *ps, struct parse_opts *opts) { struct maps *map; long int swapped, present, total; long int biggest = 0, smallest = 0, second_smallest; int count, processes = 0, pids = 0; if (is_parse_option(opts, PARSE_SHARED_MAPPING)) { struct pidlist *pid; list_for_each_entry(pid, &opts->pidlist, list) pids++; } list_for_each_entry(map, &ps->maps->list, list) { struct analyze_frames af; if (is_parse_option(opts, PARSE_SHARED_MAPPING)) { memset(&af, 0, sizeof(af)); af.pidlist = &opts->pidlist; af.pids = pids; af.map = map; count_pages(root, &af); map->pages_present = af.pages_present; map->pages_swapped = af.pages_swapped; } biggest = MAX(biggest, map->pages_present + map->pages_swapped); } printf("process: [%d] %s\n", ps->pid, ps->name); printf(" size RSS swapped total name\n"); restart: second_smallest = biggest; count = 0; list_for_each_entry(map, &ps->maps->list, list) { present = map->pages_present; swapped = map->pages_swapped; total = present + swapped; if (total > smallest) second_smallest = MIN(second_smallest, total); if (total != smallest) continue; /* * Do not print zero sized mappings if * --shared-mappings is enabled */ if (is_parse_option(opts, PARSE_SHARED_MAPPING) && total == 0) continue; printf("%5lld %sB %5lld %sB %5lld %sB %5lld %sB %s\n", NICE_DIV(map->size), NICE_UNIT(map->size), PAGE_TO_NICE(present), PAGE_TO_NICE_UNIT(present), PAGE_TO_NICE(swapped), PAGE_TO_NICE_UNIT(swapped), PAGE_TO_NICE(total), PAGE_TO_NICE_UNIT(total), map->name); count++; processes++; } if (smallest != second_smallest) { smallest = second_smallest; goto restart; } printf(" size RSS swapped total name\n"); printf("\n"); } void dump_process_maps(struct rb_root *root, struct process *process_list, struct parse_opts *opts) { struct process *ps; list_for_each_entry(ps, &process_list->list, list) { _dump_process_maps(root, ps, opts); } }