Resguardo inicial

This commit is contained in:
lastdragon
2026-09-06 12:53:21 -06:00
parent 662efe3c2a
commit ad2088d178
94853 changed files with 43200404 additions and 0 deletions
+4
View File
@@ -0,0 +1,4 @@
# SPDX-License-Identifier: GPL-2.0-only
slabinfo
page-types
page_owner_sort
+33
View File
@@ -0,0 +1,33 @@
# SPDX-License-Identifier: GPL-2.0
# Makefile for vm tools
#
include ../scripts/Makefile.include
BUILD_TARGETS=page-types slabinfo page_owner_sort thp_swap_allocator_test
INSTALL_TARGETS = $(BUILD_TARGETS) thpmaps
LIB_DIR = ../lib/api
LIBS = $(LIB_DIR)/libapi.a
CFLAGS += -Wall -Wextra -I../lib/ -pthread
LDFLAGS += $(LIBS) -pthread
all: $(BUILD_TARGETS)
$(BUILD_TARGETS): $(LIBS)
$(LIBS):
make -C $(LIB_DIR)
%: %.c
$(CC) $(CFLAGS) -o $@ $< $(LDFLAGS)
clean:
$(RM) page-types slabinfo page_owner_sort thp_swap_allocator_test
make -C $(LIB_DIR) clean
sbindir ?= /usr/sbin
install: all
install -d $(DESTDIR)$(sbindir)
install -m 755 -p $(INSTALL_TARGETS) $(DESTDIR)$(sbindir)
File diff suppressed because it is too large Load Diff
+893
View File
@@ -0,0 +1,893 @@
// SPDX-License-Identifier: GPL-2.0
/*
* User-space helper to sort the output of /sys/kernel/debug/page_owner
*
* Example use:
* cat /sys/kernel/debug/page_owner > page_owner_full.txt
* ./page_owner_sort page_owner_full.txt sorted_page_owner.txt
* Or sort by total memory:
* ./page_owner_sort -m page_owner_full.txt sorted_page_owner.txt
*
* See Documentation/mm/page_owner.rst
*/
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#include <regex.h>
#include <errno.h>
#include <linux/types.h>
#include <getopt.h>
#define TASK_COMM_LEN 16
struct block_list {
char *txt;
char *comm; // task command name
char *stacktrace;
__u64 ts_nsec;
int len;
int num;
int page_num;
pid_t pid;
pid_t tgid;
int allocator;
};
enum FILTER_BIT {
FILTER_PID = 1<<1,
FILTER_TGID = 1<<2,
FILTER_COMM = 1<<3
};
enum CULL_BIT {
CULL_PID = 1<<1,
CULL_TGID = 1<<2,
CULL_COMM = 1<<3,
CULL_STACKTRACE = 1<<4,
CULL_ALLOCATOR = 1<<5
};
enum ALLOCATOR_BIT {
ALLOCATOR_CMA = 1<<1,
ALLOCATOR_SLAB = 1<<2,
ALLOCATOR_VMALLOC = 1<<3,
ALLOCATOR_OTHERS = 1<<4
};
enum ARG_TYPE {
ARG_TXT, ARG_COMM, ARG_STACKTRACE, ARG_ALLOC_TS, ARG_CULL_TIME,
ARG_PAGE_NUM, ARG_PID, ARG_TGID, ARG_UNKNOWN, ARG_ALLOCATOR
};
enum SORT_ORDER {
SORT_ASC = 1,
SORT_DESC = -1,
};
enum COMP_FLAG {
COMP_NO_FLAG = 0,
COMP_ALLOC = 1<<0,
COMP_PAGE_NUM = 1<<1,
COMP_PID = 1<<2,
COMP_STACK = 1<<3,
COMP_NUM = 1<<4,
COMP_TGID = 1<<5,
COMP_COMM = 1<<6
};
struct filter_condition {
pid_t *pids;
pid_t *tgids;
char **comms;
int pids_size;
int tgids_size;
int comms_size;
};
struct sort_condition {
int (**cmps)(const void *, const void *);
int *signs;
int size;
};
static struct filter_condition fc;
static struct sort_condition sc;
static regex_t order_pattern;
static regex_t pid_pattern;
static regex_t tgid_pattern;
static regex_t comm_pattern;
static regex_t ts_nsec_pattern;
static struct block_list *list;
static int list_size;
static int max_size;
static int cull;
static int filter;
static bool debug_on;
static void set_single_cmp(int (*cmp)(const void *, const void *), int sign);
int read_block(char *buf, char *ext_buf, int buf_size, FILE *fin)
{
char *curr = buf, *const buf_end = buf + buf_size;
while (buf_end - curr > 1 && fgets(curr, buf_end - curr, fin)) {
if (*curr == '\n') { /* empty line */
return curr - buf;
}
if (!strncmp(curr, "PFN", 3)) {
strcpy(ext_buf, curr);
continue;
}
curr += strlen(curr);
}
return -1; /* EOF or no space left in buf. */
}
static int compare_txt(const void *p1, const void *p2)
{
const struct block_list *l1 = p1, *l2 = p2;
return strcmp(l1->txt, l2->txt);
}
static int compare_stacktrace(const void *p1, const void *p2)
{
const struct block_list *l1 = p1, *l2 = p2;
return strcmp(l1->stacktrace, l2->stacktrace);
}
static int compare_num(const void *p1, const void *p2)
{
const struct block_list *l1 = p1, *l2 = p2;
return l1->num - l2->num;
}
static int compare_page_num(const void *p1, const void *p2)
{
const struct block_list *l1 = p1, *l2 = p2;
return l1->page_num - l2->page_num;
}
static int compare_pid(const void *p1, const void *p2)
{
const struct block_list *l1 = p1, *l2 = p2;
return l1->pid - l2->pid;
}
static int compare_tgid(const void *p1, const void *p2)
{
const struct block_list *l1 = p1, *l2 = p2;
return l1->tgid - l2->tgid;
}
static int compare_allocator(const void *p1, const void *p2)
{
const struct block_list *l1 = p1, *l2 = p2;
return l1->allocator - l2->allocator;
}
static int compare_comm(const void *p1, const void *p2)
{
const struct block_list *l1 = p1, *l2 = p2;
return strcmp(l1->comm, l2->comm);
}
static int compare_ts(const void *p1, const void *p2)
{
const struct block_list *l1 = p1, *l2 = p2;
if (l1->ts_nsec < l2->ts_nsec)
return -1;
if (l1->ts_nsec > l2->ts_nsec)
return 1;
return 0;
}
static int compare_cull_condition(const void *p1, const void *p2)
{
if (cull == 0)
return compare_txt(p1, p2);
if ((cull & CULL_STACKTRACE) && compare_stacktrace(p1, p2))
return compare_stacktrace(p1, p2);
if ((cull & CULL_PID) && compare_pid(p1, p2))
return compare_pid(p1, p2);
if ((cull & CULL_TGID) && compare_tgid(p1, p2))
return compare_tgid(p1, p2);
if ((cull & CULL_COMM) && compare_comm(p1, p2))
return compare_comm(p1, p2);
if ((cull & CULL_ALLOCATOR) && compare_allocator(p1, p2))
return compare_allocator(p1, p2);
return 0;
}
static int compare_sort_condition(const void *p1, const void *p2)
{
int cmp = 0;
for (int i = 0; i < sc.size; ++i)
if (cmp == 0)
cmp = sc.signs[i] * sc.cmps[i](p1, p2);
return cmp;
}
static int remove_pattern(regex_t *pattern, char *buf, int len)
{
regmatch_t pmatch[2];
int err;
err = regexec(pattern, buf, 2, pmatch, REG_NOTBOL);
if (err != 0 || pmatch[1].rm_so == -1)
return len;
memcpy(buf + pmatch[1].rm_so,
buf + pmatch[1].rm_eo, len - pmatch[1].rm_eo);
return len - (pmatch[1].rm_eo - pmatch[1].rm_so);
}
static int search_pattern(regex_t *pattern, char *pattern_str, char *buf)
{
int err, val_len;
regmatch_t pmatch[2];
err = regexec(pattern, buf, 2, pmatch, REG_NOTBOL);
if (err != 0 || pmatch[1].rm_so == -1) {
if (debug_on)
fprintf(stderr, "no matching pattern in %s\n", buf);
return -1;
}
val_len = pmatch[1].rm_eo - pmatch[1].rm_so;
memcpy(pattern_str, buf + pmatch[1].rm_so, val_len);
return 0;
}
static bool check_regcomp(regex_t *pattern, const char *regex)
{
int err;
err = regcomp(pattern, regex, REG_EXTENDED | REG_NEWLINE);
if (err != 0 || pattern->re_nsub != 1) {
fprintf(stderr, "Invalid pattern %s code %d\n", regex, err);
return false;
}
return true;
}
static char **explode(char sep, const char *str, int *size)
{
int count = 0, len = strlen(str);
int lastindex = -1, j = 0;
for (int i = 0; i < len; i++)
if (str[i] == sep)
count++;
char **ret = calloc(++count, sizeof(char *));
for (int i = 0; i < len; i++) {
if (str[i] == sep) {
ret[j] = calloc(i - lastindex, sizeof(char));
memcpy(ret[j++], str + lastindex + 1, i - lastindex - 1);
lastindex = i;
}
}
if (lastindex <= len - 1) {
ret[j] = calloc(len - lastindex, sizeof(char));
memcpy(ret[j++], str + lastindex + 1, strlen(str) - 1 - lastindex);
}
*size = j;
return ret;
}
static void free_explode(char **arr, int size)
{
for (int i = 0; i < size; i++)
free(arr[i]);
free(arr);
}
# define FIELD_BUFF 25
static int get_page_num(char *buf)
{
int order_val;
char order_str[FIELD_BUFF] = {0};
char *endptr;
search_pattern(&order_pattern, order_str, buf);
errno = 0;
order_val = strtol(order_str, &endptr, 10);
if (order_val > 64 || errno != 0 || endptr == order_str || *endptr != '\0') {
if (debug_on)
fprintf(stderr, "wrong order in follow buf:\n%s\n", buf);
return 0;
}
return 1 << order_val;
}
static pid_t get_pid(char *buf)
{
pid_t pid;
char pid_str[FIELD_BUFF] = {0};
char *endptr;
search_pattern(&pid_pattern, pid_str, buf);
errno = 0;
pid = strtol(pid_str, &endptr, 10);
if (errno != 0 || endptr == pid_str || *endptr != '\0') {
if (debug_on)
fprintf(stderr, "wrong/invalid pid in follow buf:\n%s\n", buf);
return -1;
}
return pid;
}
static pid_t get_tgid(char *buf)
{
pid_t tgid;
char tgid_str[FIELD_BUFF] = {0};
char *endptr;
search_pattern(&tgid_pattern, tgid_str, buf);
errno = 0;
tgid = strtol(tgid_str, &endptr, 10);
if (errno != 0 || endptr == tgid_str || *endptr != '\0') {
if (debug_on)
fprintf(stderr, "wrong/invalid tgid in follow buf:\n%s\n", buf);
return -1;
}
return tgid;
}
static __u64 get_ts_nsec(char *buf)
{
__u64 ts_nsec;
char ts_nsec_str[FIELD_BUFF] = {0};
char *endptr;
search_pattern(&ts_nsec_pattern, ts_nsec_str, buf);
errno = 0;
ts_nsec = strtoull(ts_nsec_str, &endptr, 10);
if (errno != 0 || endptr == ts_nsec_str || *endptr != '\0') {
if (debug_on)
fprintf(stderr, "wrong ts_nsec in follow buf:\n%s\n", buf);
return -1;
}
return ts_nsec;
}
static char *get_comm(char *buf)
{
char *comm_str = malloc(TASK_COMM_LEN);
memset(comm_str, 0, TASK_COMM_LEN);
search_pattern(&comm_pattern, comm_str, buf);
errno = 0;
if (errno != 0) {
if (debug_on)
fprintf(stderr, "wrong comm in follow buf:\n%s\n", buf);
free(comm_str);
return NULL;
}
return comm_str;
}
static int get_arg_type(const char *arg)
{
if (!strcmp(arg, "pid") || !strcmp(arg, "p"))
return ARG_PID;
else if (!strcmp(arg, "tgid") || !strcmp(arg, "tg"))
return ARG_TGID;
else if (!strcmp(arg, "name") || !strcmp(arg, "n"))
return ARG_COMM;
else if (!strcmp(arg, "stacktrace") || !strcmp(arg, "st"))
return ARG_STACKTRACE;
else if (!strcmp(arg, "txt") || !strcmp(arg, "T"))
return ARG_TXT;
else if (!strcmp(arg, "alloc_ts") || !strcmp(arg, "at"))
return ARG_ALLOC_TS;
else if (!strcmp(arg, "allocator") || !strcmp(arg, "ator"))
return ARG_ALLOCATOR;
else {
return ARG_UNKNOWN;
}
}
static int get_allocator(const char *buf, const char *migrate_info)
{
char *tmp, *first_line, *second_line;
int allocator = 0;
if (strstr(migrate_info, "CMA"))
allocator |= ALLOCATOR_CMA;
if (strstr(migrate_info, "slab"))
allocator |= ALLOCATOR_SLAB;
tmp = strstr(buf, "__vmalloc_node_range");
if (tmp) {
second_line = tmp;
while (*tmp != '\n')
tmp--;
tmp--;
while (*tmp != '\n')
tmp--;
first_line = ++tmp;
tmp = strstr(tmp, "alloc_pages");
if (tmp && first_line <= tmp && tmp < second_line)
allocator |= ALLOCATOR_VMALLOC;
}
if (allocator == 0)
allocator = ALLOCATOR_OTHERS;
return allocator;
}
static bool match_num_list(int num, int *list, int list_size)
{
for (int i = 0; i < list_size; ++i)
if (list[i] == num)
return true;
return false;
}
static bool match_str_list(const char *str, char **list, int list_size)
{
for (int i = 0; i < list_size; ++i)
if (!strcmp(list[i], str))
return true;
return false;
}
static bool is_need(char *buf)
{
if ((filter & FILTER_PID) && !match_num_list(get_pid(buf), fc.pids, fc.pids_size))
return false;
if ((filter & FILTER_TGID) &&
!match_num_list(get_tgid(buf), fc.tgids, fc.tgids_size))
return false;
char *comm = get_comm(buf);
if ((filter & FILTER_COMM) &&
!match_str_list(comm, fc.comms, fc.comms_size)) {
free(comm);
return false;
}
free(comm);
return true;
}
static bool add_list(char *buf, int len, char *ext_buf)
{
if (list_size == max_size) {
fprintf(stderr, "max_size too small??\n");
return false;
}
if (!is_need(buf))
return true;
list[list_size].pid = get_pid(buf);
list[list_size].tgid = get_tgid(buf);
list[list_size].comm = get_comm(buf);
list[list_size].txt = malloc(len+1);
if (!list[list_size].txt) {
fprintf(stderr, "Out of memory\n");
return false;
}
memcpy(list[list_size].txt, buf, len);
if (sc.cmps[0] != compare_ts) {
len = remove_pattern(&ts_nsec_pattern, list[list_size].txt, len);
}
list[list_size].txt[len] = 0;
list[list_size].len = len;
list[list_size].num = 1;
list[list_size].page_num = get_page_num(buf);
list[list_size].stacktrace = strchr(list[list_size].txt, '\n') ?: "";
if (*list[list_size].stacktrace == '\n')
list[list_size].stacktrace++;
list[list_size].ts_nsec = get_ts_nsec(buf);
list[list_size].allocator = get_allocator(buf, ext_buf);
list_size++;
if (list_size % 1000 == 0) {
printf("loaded %d\r", list_size);
fflush(stdout);
}
return true;
}
static bool parse_cull_args(const char *arg_str)
{
int size = 0;
char **args = explode(',', arg_str, &size);
for (int i = 0; i < size; ++i) {
int arg_type = get_arg_type(args[i]);
if (arg_type == ARG_PID)
cull |= CULL_PID;
else if (arg_type == ARG_TGID)
cull |= CULL_TGID;
else if (arg_type == ARG_COMM)
cull |= CULL_COMM;
else if (arg_type == ARG_STACKTRACE)
cull |= CULL_STACKTRACE;
else if (arg_type == ARG_ALLOCATOR)
cull |= CULL_ALLOCATOR;
else {
free_explode(args, size);
return false;
}
}
free_explode(args, size);
if (sc.size == 0)
set_single_cmp(compare_num, SORT_DESC);
return true;
}
static void set_single_cmp(int (*cmp)(const void *, const void *), int sign)
{
if (sc.signs == NULL || sc.size < 1)
sc.signs = calloc(1, sizeof(int));
sc.signs[0] = sign;
if (sc.cmps == NULL || sc.size < 1)
sc.cmps = calloc(1, sizeof(int *));
sc.cmps[0] = cmp;
sc.size = 1;
}
static bool parse_sort_args(const char *arg_str)
{
int size = 0;
if (sc.size != 0) { /* reset sort_condition */
free(sc.signs);
free(sc.cmps);
size = 0;
}
char **args = explode(',', arg_str, &size);
sc.signs = calloc(size, sizeof(int));
sc.cmps = calloc(size, sizeof(int *));
for (int i = 0; i < size; ++i) {
int offset = 0;
sc.signs[i] = SORT_ASC;
if (args[i][0] == '-' || args[i][0] == '+') {
if (args[i][0] == '-')
sc.signs[i] = SORT_DESC;
offset = 1;
}
int arg_type = get_arg_type(args[i]+offset);
if (arg_type == ARG_PID)
sc.cmps[i] = compare_pid;
else if (arg_type == ARG_TGID)
sc.cmps[i] = compare_tgid;
else if (arg_type == ARG_COMM)
sc.cmps[i] = compare_comm;
else if (arg_type == ARG_STACKTRACE)
sc.cmps[i] = compare_stacktrace;
else if (arg_type == ARG_ALLOC_TS)
sc.cmps[i] = compare_ts;
else if (arg_type == ARG_TXT)
sc.cmps[i] = compare_txt;
else if (arg_type == ARG_ALLOCATOR)
sc.cmps[i] = compare_allocator;
else {
free_explode(args, size);
sc.size = 0;
return false;
}
}
sc.size = size;
free_explode(args, size);
return true;
}
static int *parse_nums_list(char *arg_str, int *list_size)
{
int size = 0;
char **args = explode(',', arg_str, &size);
int *list = calloc(size, sizeof(int));
errno = 0;
for (int i = 0; i < size; ++i) {
char *endptr = NULL;
list[i] = strtol(args[i], &endptr, 10);
if (errno != 0 || endptr == args[i] || *endptr != '\0') {
free(list);
return NULL;
}
}
*list_size = size;
free_explode(args, size);
return list;
}
static void print_allocator(FILE *out, int allocator)
{
fprintf(out, "allocated by ");
if (allocator & ALLOCATOR_CMA)
fprintf(out, "CMA ");
if (allocator & ALLOCATOR_SLAB)
fprintf(out, "SLAB ");
if (allocator & ALLOCATOR_VMALLOC)
fprintf(out, "VMALLOC ");
if (allocator & ALLOCATOR_OTHERS)
fprintf(out, "OTHERS ");
}
#define BUF_SIZE (128 * 1024)
static void usage(void)
{
printf("Usage: ./page_owner_sort [OPTIONS] <input> <output>\n"
"-a\t\t\tSort by memory allocation time.\n"
"-m\t\t\tSort by total memory.\n"
"-n\t\t\tSort by task command name.\n"
"-p\t\t\tSort by pid.\n"
"-P\t\t\tSort by tgid.\n"
"-s\t\t\tSort by the stacktrace.\n"
"-t\t\t\tSort by number of times record is seen (default).\n\n"
"--pid <pidlist>\t\tSelect by pid. This selects the information"
" of\n\t\t\tblocks whose process ID numbers appear in <pidlist>.\n"
"--tgid <tgidlist>\tSelect by tgid. This selects the information"
" of\n\t\t\tblocks whose Thread Group ID numbers appear in "
"<tgidlist>.\n"
"--name <cmdlist>\tSelect by command name. This selects the"
" information\n\t\t\tof blocks whose command name appears in"
" <cmdlist>.\n"
"--cull <rules>\t\tCull by user-defined rules. <rules> is a "
"single\n\t\t\targument in the form of a comma-separated list "
"with some\n\t\t\tcommon fields predefined (pid, tgid, comm, "
"stacktrace, allocator)\n"
"--sort <order>\t\tSpecify sort order as: [+|-]key[,[+|-]key[,...]]\n"
);
}
int main(int argc, char **argv)
{
FILE *fin, *fout;
char *buf, *ext_buf;
int i, count, compare_flag;
struct stat st;
int opt;
struct option longopts[] = {
{ "pid", required_argument, NULL, 1 },
{ "tgid", required_argument, NULL, 2 },
{ "name", required_argument, NULL, 3 },
{ "cull", required_argument, NULL, 4 },
{ "sort", required_argument, NULL, 5 },
{ "help", no_argument, NULL, 'h' },
{ 0, 0, 0, 0},
};
compare_flag = COMP_NO_FLAG;
while ((opt = getopt_long(argc, argv, "admnpstPh", longopts, NULL)) != -1)
switch (opt) {
case 'a':
compare_flag |= COMP_ALLOC;
break;
case 'd':
debug_on = true;
break;
case 'm':
compare_flag |= COMP_PAGE_NUM;
break;
case 'p':
compare_flag |= COMP_PID;
break;
case 's':
compare_flag |= COMP_STACK;
break;
case 't':
compare_flag |= COMP_NUM;
break;
case 'P':
compare_flag |= COMP_TGID;
break;
case 'n':
compare_flag |= COMP_COMM;
break;
case 'h':
usage();
exit(0);
case 1:
filter = filter | FILTER_PID;
fc.pids = parse_nums_list(optarg, &fc.pids_size);
if (fc.pids == NULL) {
fprintf(stderr, "wrong/invalid pid in from the command line:%s\n",
optarg);
exit(1);
}
break;
case 2:
filter = filter | FILTER_TGID;
fc.tgids = parse_nums_list(optarg, &fc.tgids_size);
if (fc.tgids == NULL) {
fprintf(stderr, "wrong/invalid tgid in from the command line:%s\n",
optarg);
exit(1);
}
break;
case 3:
filter = filter | FILTER_COMM;
fc.comms = explode(',', optarg, &fc.comms_size);
break;
case 4:
if (!parse_cull_args(optarg)) {
fprintf(stderr, "wrong argument after --cull option:%s\n",
optarg);
exit(1);
}
break;
case 5:
if (!parse_sort_args(optarg)) {
fprintf(stderr, "wrong argument after --sort option:%s\n",
optarg);
exit(1);
}
break;
default:
usage();
exit(1);
}
if (optind >= (argc - 1)) {
usage();
exit(1);
}
/* Only one compare option is allowed, yet we also want handle the
* default case were no option is provided, but we still want to
* match the behavior of the -t option (compare by number of times
* a record is seen
*/
switch (compare_flag) {
case COMP_ALLOC:
set_single_cmp(compare_ts, SORT_ASC);
break;
case COMP_PAGE_NUM:
set_single_cmp(compare_page_num, SORT_DESC);
break;
case COMP_PID:
set_single_cmp(compare_pid, SORT_ASC);
break;
case COMP_STACK:
set_single_cmp(compare_stacktrace, SORT_ASC);
break;
case COMP_NO_FLAG:
case COMP_NUM:
set_single_cmp(compare_num, SORT_DESC);
break;
case COMP_TGID:
set_single_cmp(compare_tgid, SORT_ASC);
break;
case COMP_COMM:
set_single_cmp(compare_comm, SORT_ASC);
break;
default:
usage();
exit(1);
}
fin = fopen(argv[optind], "r");
fout = fopen(argv[optind + 1], "w");
if (!fin || !fout) {
usage();
perror("open: ");
exit(1);
}
if (!check_regcomp(&order_pattern, "order\\s*([0-9]*),"))
goto out_order;
if (!check_regcomp(&pid_pattern, "pid\\s*([0-9]*),"))
goto out_pid;
if (!check_regcomp(&tgid_pattern, "tgid\\s*([0-9]*) "))
goto out_tgid;
if (!check_regcomp(&comm_pattern, "tgid\\s*[0-9]*\\s*\\((.*)\\),\\s*ts"))
goto out_comm;
if (!check_regcomp(&ts_nsec_pattern, "ts\\s*([0-9]*)\\s*ns"))
goto out_ts;
fstat(fileno(fin), &st);
max_size = st.st_size / 100; /* hack ... */
list = malloc(max_size * sizeof(*list));
buf = malloc(BUF_SIZE);
ext_buf = malloc(BUF_SIZE);
if (!list || !buf || !ext_buf) {
fprintf(stderr, "Out of memory\n");
goto out_free;
}
for ( ; ; ) {
int buf_len = read_block(buf, ext_buf, BUF_SIZE, fin);
if (buf_len < 0)
break;
if (!add_list(buf, buf_len, ext_buf))
goto out_free;
}
printf("loaded %d\n", list_size);
printf("sorting ....\n");
qsort(list, list_size, sizeof(list[0]), compare_cull_condition);
printf("culling\n");
for (i = count = 0; i < list_size; i++) {
if (count == 0 ||
compare_cull_condition((void *)(&list[count-1]), (void *)(&list[i])) != 0) {
list[count++] = list[i];
} else {
list[count-1].num += list[i].num;
list[count-1].page_num += list[i].page_num;
}
}
qsort(list, count, sizeof(list[0]), compare_sort_condition);
for (i = 0; i < count; i++) {
if (cull == 0) {
fprintf(fout, "%d times, %d pages, ", list[i].num, list[i].page_num);
print_allocator(fout, list[i].allocator);
fprintf(fout, ":\n%s\n", list[i].txt);
}
else {
fprintf(fout, "%d times, %d pages",
list[i].num, list[i].page_num);
if (cull & CULL_PID || filter & FILTER_PID)
fprintf(fout, ", PID %d", list[i].pid);
if (cull & CULL_TGID || filter & FILTER_TGID)
fprintf(fout, ", TGID %d", list[i].tgid);
if (cull & CULL_COMM || filter & FILTER_COMM)
fprintf(fout, ", task_comm_name: %s", list[i].comm);
if (cull & CULL_ALLOCATOR) {
fprintf(fout, ", ");
print_allocator(fout, list[i].allocator);
}
if (cull & CULL_STACKTRACE)
fprintf(fout, ":\n%s", list[i].stacktrace);
fprintf(fout, "\n");
}
}
out_free:
if (ext_buf)
free(ext_buf);
if (buf)
free(buf);
if (list)
free(list);
out_ts:
regfree(&ts_nsec_pattern);
out_comm:
regfree(&comm_pattern);
out_tgid:
regfree(&tgid_pattern);
out_pid:
regfree(&pid_pattern);
out_order:
regfree(&order_pattern);
return 0;
}
+169
View File
@@ -0,0 +1,169 @@
#!/usr/bin/env drgn
# SPDX-License-Identifier: GPL-2.0-only
# Copyright (C) 2025 Ye Liu <liuye@kylinos.cn>
import argparse
import sys
from drgn import Object, FaultError, PlatformFlags, cast
from drgn.helpers.linux import find_task, follow_page, page_size
from drgn.helpers.linux.mm import (
decode_page_flags, page_to_pfn, page_to_phys, page_to_virt, vma_find,
PageSlab, PageCompound, PageHead, PageTail, compound_head, compound_order, compound_nr
)
from drgn.helpers.linux.cgroup import cgroup_name, cgroup_path
DESC = """
This is a drgn script to show the page state.
For more info on drgn, visit https://github.com/osandov/drgn.
"""
def format_page_data(page):
"""
Format raw page data into a readable hex dump with "RAW:" prefix.
:param page: drgn.Object instance representing the page.
:return: Formatted string of memory contents.
"""
try:
address = page.value_()
size = prog.type("struct page").size
if prog.platform.flags & PlatformFlags.IS_64_BIT:
word_size = 8
else:
word_size = 4
num_words = size // word_size
values = []
for i in range(num_words):
word_address = address + i * word_size
word = prog.read_word(word_address)
values.append(f"{word:0{word_size * 2}x}")
lines = [f"RAW: {' '.join(values[i:i + 4])}" for i in range(0, len(values), 4)]
return "\n".join(lines)
except FaultError as e:
return f"Error reading memory: {e}"
except Exception as e:
return f"Unexpected error: {e}"
def get_memcg_info(page):
"""Retrieve memory cgroup information for a page."""
try:
MEMCG_DATA_OBJEXTS = prog.constant("MEMCG_DATA_OBJEXTS").value_()
MEMCG_DATA_KMEM = prog.constant("MEMCG_DATA_KMEM").value_()
mask = prog.constant('__NR_MEMCG_DATA_FLAGS').value_() - 1
memcg_data = page.memcg_data.read_()
if memcg_data & MEMCG_DATA_OBJEXTS:
slabobj_ext = cast("struct slabobj_ext *", memcg_data & ~mask)
memcg = slabobj_ext.objcg.memcg.value_()
elif memcg_data & MEMCG_DATA_KMEM:
objcg = cast("struct obj_cgroup *", memcg_data & ~mask)
memcg = objcg.memcg.value_()
else:
memcg = cast("struct mem_cgroup *", memcg_data & ~mask)
if memcg.value_() == 0:
return "none", "/sys/fs/cgroup/memory/"
cgrp = memcg.css.cgroup
return cgroup_name(cgrp).decode(), f"/sys/fs/cgroup/memory{cgroup_path(cgrp).decode()}"
except FaultError as e:
return "unknown", f"Error retrieving memcg info: {e}"
except Exception as e:
return "unknown", f"Unexpected error: {e}"
def show_page_state(page, addr, mm, pid, task):
"""Display detailed information about a page."""
try:
print(f'PID: {pid} Comm: {task.comm.string_().decode()} mm: {hex(mm)}')
try:
print(format_page_data(page))
except FaultError as e:
print(f"Error reading page data: {e}")
fields = {
"Page Address": hex(page.value_()),
"Page Flags": decode_page_flags(page),
"Page Size": prog["PAGE_SIZE"].value_(),
"Page PFN": hex(page_to_pfn(page).value_()),
"Page Physical": hex(page_to_phys(page).value_()),
"Page Virtual": hex(page_to_virt(page).value_()),
"Page Refcount": page._refcount.counter.value_(),
"Page Mapcount": page._mapcount.counter.value_(),
"Page Index": hex(page.__folio_index.value_()),
"Page Memcg Data": hex(page.memcg_data.value_()),
}
memcg_name, memcg_path = get_memcg_info(page)
fields["Memcg Name"] = memcg_name
fields["Memcg Path"] = memcg_path
fields["Page Mapping"] = hex(page.mapping.value_())
fields["Page Anon/File"] = "Anon" if page.mapping.value_() & 0x1 else "File"
try:
vma = vma_find(mm, addr)
fields["Page VMA"] = hex(vma.value_())
fields["VMA Start"] = hex(vma.vm_start.value_())
fields["VMA End"] = hex(vma.vm_end.value_())
except FaultError as e:
fields["Page VMA"] = "Unavailable"
fields["VMA Start"] = "Unavailable"
fields["VMA End"] = "Unavailable"
print(f"Error retrieving VMA information: {e}")
# Calculate the maximum field name length for alignment
max_field_len = max(len(field) for field in fields)
# Print aligned fields
for field, value in fields.items():
print(f"{field}:".ljust(max_field_len + 2) + f"{value}")
# Additional information about the page
if PageSlab(page):
print("This page belongs to the slab allocator.")
if PageCompound(page):
print("This page is part of a compound page.")
if PageHead(page):
print("This page is the head page of a compound page.")
if PageTail(page):
print("This page is the tail page of a compound page.")
print(f"{'Head Page:'.ljust(max_field_len + 2)}{hex(compound_head(page).value_())}")
print(f"{'Compound Order:'.ljust(max_field_len + 2)}{compound_order(page).value_()}")
print(f"{'Number of Pages:'.ljust(max_field_len + 2)}{compound_nr(page).value_()}")
else:
print("This page is not part of a compound page.")
except FaultError as e:
print(f"Error accessing page state: {e}")
except Exception as e:
print(f"Unexpected error: {e}")
def main():
"""Main function to parse arguments and display page state."""
parser = argparse.ArgumentParser(description=DESC, formatter_class=argparse.RawTextHelpFormatter)
parser.add_argument('pid', metavar='PID', type=int, help='Target process ID (PID)')
parser.add_argument('vaddr', metavar='VADDR', type=str, help='Target virtual address in hexadecimal format (e.g., 0x7fff1234abcd)')
args = parser.parse_args()
try:
vaddr = int(args.vaddr, 16)
except ValueError:
sys.exit(f"Error: Invalid virtual address format: {args.vaddr}")
try:
task = find_task(args.pid)
mm = task.mm
page = follow_page(mm, vaddr)
if page:
show_page_state(page, vaddr, mm, args.pid, task)
else:
sys.exit(f"Address {hex(vaddr)} is not mapped.")
except FaultError as e:
sys.exit(f"Error accessing task or memory: {e}")
except Exception as e:
sys.exit(f"Unexpected error: {e}")
if __name__ == "__main__":
main()
+268
View File
@@ -0,0 +1,268 @@
#!/bin/bash
# SPDX-License-Identifier: GPL-2.0-only
# Sergey Senozhatsky, 2015
# sergey.senozhatsky.work@gmail.com
#
# This program is intended to plot a `slabinfo -X' stats, collected,
# for example, using the following command:
# while [ 1 ]; do slabinfo -X >> stats; sleep 1; done
#
# Use `slabinfo-gnuplot.sh stats' to pre-process collected records
# and generate graphs (totals, slabs sorted by size, slabs sorted
# by size).
#
# Graphs can be [individually] regenerate with different ranges and
# size (-r %d,%d and -s %d,%d options).
#
# To visually compare N `totals' graphs, do
# slabinfo-gnuplot.sh -t FILE1-totals FILE2-totals ... FILEN-totals
#
min_slab_name_size=11
xmin=0
xmax=0
width=1500
height=700
mode=preprocess
usage()
{
echo "Usage: [-s W,H] [-r MIN,MAX] [-t|-l] FILE1 [FILE2 ..]"
echo "FILEs must contain 'slabinfo -X' samples"
echo "-t - plot totals for FILE(s)"
echo "-l - plot slabs stats for FILE(s)"
echo "-s %d,%d - set image width and height"
echo "-r %d,%d - use data samples from a given range"
}
check_file_exist()
{
if [ ! -f "$1" ]; then
echo "File '$1' does not exist"
exit 1
fi
}
do_slabs_plotting()
{
local file=$1
local out_file
local range="every ::$xmin"
local xtic=""
local xtic_rotate="norotate"
local lines=2000000
local wc_lines
check_file_exist "$file"
out_file=`basename "$file"`
if [ $xmax -ne 0 ]; then
range="$range::$xmax"
lines=$((xmax-xmin))
fi
wc_lines=`cat "$file" | wc -l`
if [ $? -ne 0 ] || [ "$wc_lines" -eq 0 ] ; then
wc_lines=$lines
fi
if [ "$wc_lines" -lt "$lines" ]; then
lines=$wc_lines
fi
if [ $((width / lines)) -gt $min_slab_name_size ]; then
xtic=":xtic(1)"
xtic_rotate=90
fi
gnuplot -p << EOF
#!/usr/bin/env gnuplot
set terminal png enhanced size $width,$height large
set output '$out_file.png'
set autoscale xy
set xlabel 'samples'
set ylabel 'bytes'
set style histogram columnstacked title textcolor lt -1
set style fill solid 0.15
set xtics rotate $xtic_rotate
set key left above Left title reverse
plot "$file" $range u 2$xtic title 'SIZE' with boxes,\
'' $range u 3 title 'LOSS' with boxes
EOF
if [ $? -eq 0 ]; then
echo "$out_file.png"
fi
}
do_totals_plotting()
{
local gnuplot_cmd=""
local range="every ::$xmin"
local file=""
if [ $xmax -ne 0 ]; then
range="$range::$xmax"
fi
for i in "${t_files[@]}"; do
check_file_exist "$i"
file="$file"`basename "$i"`
gnuplot_cmd="$gnuplot_cmd '$i' $range using 1 title\
'$i Memory usage' with lines,"
gnuplot_cmd="$gnuplot_cmd '' $range using 2 title \
'$i Loss' with lines,"
done
gnuplot -p << EOF
#!/usr/bin/env gnuplot
set terminal png enhanced size $width,$height large
set autoscale xy
set output '$file.png'
set xlabel 'samples'
set ylabel 'bytes'
set key left above Left title reverse
plot $gnuplot_cmd
EOF
if [ $? -eq 0 ]; then
echo "$file.png"
fi
}
do_preprocess()
{
local out
local lines
local in=$1
check_file_exist "$in"
# use only 'TOP' slab (biggest memory usage or loss)
let lines=3
out=`basename "$in"`"-slabs-by-loss"
`cat "$in" | grep -A "$lines" 'Slabs sorted by loss' |\
grep -E -iv '\-\-|Name|Slabs'\
| awk '{print $1" "$4+$2*$3" "$4}' > "$out"`
if [ $? -eq 0 ]; then
do_slabs_plotting "$out"
fi
let lines=3
out=`basename "$in"`"-slabs-by-size"
`cat "$in" | grep -A "$lines" 'Slabs sorted by size' |\
grep -E -iv '\-\-|Name|Slabs'\
| awk '{print $1" "$4" "$4-$2*$3}' > "$out"`
if [ $? -eq 0 ]; then
do_slabs_plotting "$out"
fi
out=`basename "$in"`"-totals"
`cat "$in" | grep "Memory used" |\
awk '{print $3" "$7}' > "$out"`
if [ $? -eq 0 ]; then
t_files[0]=$out
do_totals_plotting
fi
}
parse_opts()
{
local opt
while getopts "tlr::s::h" opt; do
case $opt in
t)
mode=totals
;;
l)
mode=slabs
;;
s)
array=(${OPTARG//,/ })
width=${array[0]}
height=${array[1]}
;;
r)
array=(${OPTARG//,/ })
xmin=${array[0]}
xmax=${array[1]}
;;
h)
usage
exit 0
;;
\?)
echo "Invalid option: -$OPTARG" >&2
exit 1
;;
:)
echo "-$OPTARG requires an argument." >&2
exit 1
;;
esac
done
return $OPTIND
}
parse_args()
{
local idx=0
local p
for p in "$@"; do
case $mode in
preprocess)
files[$idx]=$p
idx=$idx+1
;;
totals)
t_files[$idx]=$p
idx=$idx+1
;;
slabs)
files[$idx]=$p
idx=$idx+1
;;
esac
done
}
parse_opts "$@"
argstart=$?
parse_args "${@:$argstart}"
if [ ${#files[@]} -eq 0 ] && [ ${#t_files[@]} -eq 0 ]; then
usage
exit 1
fi
case $mode in
preprocess)
for i in "${files[@]}"; do
do_preprocess "$i"
done
;;
totals)
do_totals_plotting
;;
slabs)
for i in "${files[@]}"; do
do_slabs_plotting "$i"
done
;;
*)
echo "Unknown mode $mode" >&2
usage
exit 1
;;
esac
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,234 @@
// SPDX-License-Identifier: GPL-2.0-or-later
/*
* thp_swap_allocator_test
*
* The purpose of this test program is helping check if THP swpout
* can correctly get swap slots to swap out as a whole instead of
* being split. It randomly releases swap entries through madvise
* DONTNEED and swapin/out on two memory areas: a memory area for
* 64KB THP and the other area for small folios. The second memory
* can be enabled by "-s".
* Before running the program, we need to setup a zRAM or similar
* swap device by:
* echo lzo > /sys/block/zram0/comp_algorithm
* echo 64M > /sys/block/zram0/disksize
* echo never > /sys/kernel/mm/transparent_hugepage/hugepages-2048kB/enabled
* echo always > /sys/kernel/mm/transparent_hugepage/hugepages-64kB/enabled
* mkswap /dev/zram0
* swapon /dev/zram0
* The expected result should be 0% anon swpout fallback ratio w/ or
* w/o "-s".
*
* Author(s): Barry Song <v-songbaohua@oppo.com>
*/
#define _GNU_SOURCE
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
#include <linux/mman.h>
#include <sys/mman.h>
#include <errno.h>
#include <time.h>
#define MEMSIZE_MTHP (60 * 1024 * 1024)
#define MEMSIZE_SMALLFOLIO (4 * 1024 * 1024)
#define ALIGNMENT_MTHP (64 * 1024)
#define ALIGNMENT_SMALLFOLIO (4 * 1024)
#define TOTAL_DONTNEED_MTHP (16 * 1024 * 1024)
#define TOTAL_DONTNEED_SMALLFOLIO (1 * 1024 * 1024)
#define MTHP_FOLIO_SIZE (64 * 1024)
#define SWPOUT_PATH \
"/sys/kernel/mm/transparent_hugepage/hugepages-64kB/stats/swpout"
#define SWPOUT_FALLBACK_PATH \
"/sys/kernel/mm/transparent_hugepage/hugepages-64kB/stats/swpout_fallback"
static void *aligned_alloc_mem(size_t size, size_t alignment)
{
void *mem = NULL;
if (posix_memalign(&mem, alignment, size) != 0) {
perror("posix_memalign");
return NULL;
}
return mem;
}
/*
* This emulates the behavior of native libc and Java heap,
* as well as process exit and munmap. It helps generate mTHP
* and ensures that iterations can proceed with mTHP, as we
* currently don't support large folios swap-in.
*/
static void random_madvise_dontneed(void *mem, size_t mem_size,
size_t align_size, size_t total_dontneed_size)
{
size_t num_pages = total_dontneed_size / align_size;
size_t i;
size_t offset;
void *addr;
for (i = 0; i < num_pages; ++i) {
offset = (rand() % (mem_size / align_size)) * align_size;
addr = (char *)mem + offset;
if (madvise(addr, align_size, MADV_DONTNEED) != 0)
perror("madvise dontneed");
memset(addr, 0x11, align_size);
}
}
static void random_swapin(void *mem, size_t mem_size,
size_t align_size, size_t total_swapin_size)
{
size_t num_pages = total_swapin_size / align_size;
size_t i;
size_t offset;
void *addr;
for (i = 0; i < num_pages; ++i) {
offset = (rand() % (mem_size / align_size)) * align_size;
addr = (char *)mem + offset;
memset(addr, 0x11, align_size);
}
}
static unsigned long read_stat(const char *path)
{
FILE *file;
unsigned long value;
file = fopen(path, "r");
if (!file) {
perror("fopen");
return 0;
}
if (fscanf(file, "%lu", &value) != 1) {
perror("fscanf");
fclose(file);
return 0;
}
fclose(file);
return value;
}
int main(int argc, char *argv[])
{
int use_small_folio = 0, aligned_swapin = 0;
void *mem1 = NULL, *mem2 = NULL;
int i;
for (i = 1; i < argc; ++i) {
if (strcmp(argv[i], "-s") == 0)
use_small_folio = 1;
else if (strcmp(argv[i], "-a") == 0)
aligned_swapin = 1;
}
mem1 = aligned_alloc_mem(MEMSIZE_MTHP, ALIGNMENT_MTHP);
if (mem1 == NULL) {
fprintf(stderr, "Failed to allocate large folios memory\n");
return EXIT_FAILURE;
}
if (madvise(mem1, MEMSIZE_MTHP, MADV_HUGEPAGE) != 0) {
perror("madvise hugepage for mem1");
free(mem1);
return EXIT_FAILURE;
}
if (use_small_folio) {
mem2 = aligned_alloc_mem(MEMSIZE_SMALLFOLIO, ALIGNMENT_SMALLFOLIO);
if (mem2 == NULL) {
fprintf(stderr, "Failed to allocate small folios memory\n");
free(mem1);
return EXIT_FAILURE;
}
if (madvise(mem2, MEMSIZE_SMALLFOLIO, MADV_NOHUGEPAGE) != 0) {
perror("madvise nohugepage for mem2");
free(mem1);
free(mem2);
return EXIT_FAILURE;
}
}
/* warm-up phase to occupy the swapfile */
memset(mem1, 0x11, MEMSIZE_MTHP);
madvise(mem1, MEMSIZE_MTHP, MADV_PAGEOUT);
if (use_small_folio) {
memset(mem2, 0x11, MEMSIZE_SMALLFOLIO);
madvise(mem2, MEMSIZE_SMALLFOLIO, MADV_PAGEOUT);
}
/* iterations with newly created mTHP, swap-in, and swap-out */
for (i = 0; i < 100; ++i) {
unsigned long initial_swpout;
unsigned long initial_swpout_fallback;
unsigned long final_swpout;
unsigned long final_swpout_fallback;
unsigned long swpout_inc;
unsigned long swpout_fallback_inc;
double fallback_percentage;
initial_swpout = read_stat(SWPOUT_PATH);
initial_swpout_fallback = read_stat(SWPOUT_FALLBACK_PATH);
/*
* The following setup creates a 1:1 ratio of mTHP to small folios
* since large folio swap-in isn't supported yet. Once we support
* mTHP swap-in, we'll likely need to reduce MEMSIZE_MTHP and
* increase MEMSIZE_SMALLFOLIO to maintain the ratio.
*/
random_swapin(mem1, MEMSIZE_MTHP,
aligned_swapin ? ALIGNMENT_MTHP : ALIGNMENT_SMALLFOLIO,
TOTAL_DONTNEED_MTHP);
random_madvise_dontneed(mem1, MEMSIZE_MTHP, ALIGNMENT_MTHP,
TOTAL_DONTNEED_MTHP);
if (use_small_folio) {
random_swapin(mem2, MEMSIZE_SMALLFOLIO,
ALIGNMENT_SMALLFOLIO,
TOTAL_DONTNEED_SMALLFOLIO);
}
if (madvise(mem1, MEMSIZE_MTHP, MADV_PAGEOUT) != 0) {
perror("madvise pageout for mem1");
free(mem1);
if (mem2 != NULL)
free(mem2);
return EXIT_FAILURE;
}
if (use_small_folio) {
if (madvise(mem2, MEMSIZE_SMALLFOLIO, MADV_PAGEOUT) != 0) {
perror("madvise pageout for mem2");
free(mem1);
free(mem2);
return EXIT_FAILURE;
}
}
final_swpout = read_stat(SWPOUT_PATH);
final_swpout_fallback = read_stat(SWPOUT_FALLBACK_PATH);
swpout_inc = final_swpout - initial_swpout;
swpout_fallback_inc = final_swpout_fallback - initial_swpout_fallback;
fallback_percentage = (double)swpout_fallback_inc /
(swpout_fallback_inc + swpout_inc) * 100;
printf("Iteration %d: swpout inc: %lu, swpout fallback inc: %lu, Fallback percentage: %.2f%%\n",
i + 1, swpout_inc, swpout_fallback_inc, fallback_percentage);
}
free(mem1);
if (mem2 != NULL)
free(mem2);
return EXIT_SUCCESS;
}
+675
View File
@@ -0,0 +1,675 @@
#!/usr/bin/env python3
# SPDX-License-Identifier: GPL-2.0-only
# Copyright (C) 2024 ARM Ltd.
#
# Utility providing smaps-like output detailing transparent hugepage usage.
# For more info, run:
# ./thpmaps --help
#
# Requires numpy:
# pip3 install numpy
import argparse
import collections
import math
import os
import re
import resource
import shutil
import sys
import textwrap
import time
import numpy as np
with open('/sys/kernel/mm/transparent_hugepage/hpage_pmd_size') as f:
PAGE_SIZE = resource.getpagesize()
PAGE_SHIFT = int(math.log2(PAGE_SIZE))
PMD_SIZE = int(f.read())
PMD_ORDER = int(math.log2(PMD_SIZE / PAGE_SIZE))
def align_forward(v, a):
return (v + (a - 1)) & ~(a - 1)
def align_offset(v, a):
return v & (a - 1)
def kbnr(kb):
# Convert KB to number of pages.
return (kb << 10) >> PAGE_SHIFT
def nrkb(nr):
# Convert number of pages to KB.
return (nr << PAGE_SHIFT) >> 10
def odkb(order):
# Convert page order to KB.
return (PAGE_SIZE << order) >> 10
def cont_ranges_all(search, index):
# Given a list of arrays, find the ranges for which values are monotonically
# incrementing in all arrays. all arrays in search and index must be the
# same size.
sz = len(search[0])
r = np.full(sz, 2)
d = np.diff(search[0]) == 1
for dd in [np.diff(arr) == 1 for arr in search[1:]]:
d &= dd
r[1:] -= d
r[:-1] -= d
return [np.repeat(arr, r).reshape(-1, 2) for arr in index]
class ArgException(Exception):
pass
class FileIOException(Exception):
pass
class BinArrayFile:
# Base class used to read /proc/<pid>/pagemap and /proc/kpageflags into a
# numpy array. Use inherrited class in a with clause to ensure file is
# closed when it goes out of scope.
def __init__(self, filename, element_size):
self.element_size = element_size
self.filename = filename
self.fd = os.open(self.filename, os.O_RDONLY)
def cleanup(self):
os.close(self.fd)
def __enter__(self):
return self
def __exit__(self, exc_type, exc_val, exc_tb):
self.cleanup()
def _readin(self, offset, buffer):
length = os.preadv(self.fd, (buffer,), offset)
if len(buffer) != length:
raise FileIOException('error: {} failed to read {} bytes at {:x}'
.format(self.filename, len(buffer), offset))
def _toarray(self, buf):
assert(self.element_size == 8)
return np.frombuffer(buf, dtype=np.uint64)
def getv(self, vec):
vec *= self.element_size
offsets = vec[:, 0]
lengths = (np.diff(vec) + self.element_size).reshape(len(vec))
buf = bytearray(int(np.sum(lengths)))
view = memoryview(buf)
pos = 0
for offset, length in zip(offsets, lengths):
offset = int(offset)
length = int(length)
self._readin(offset, view[pos:pos+length])
pos += length
return self._toarray(buf)
def get(self, index, nr=1):
offset = index * self.element_size
length = nr * self.element_size
buf = bytearray(length)
self._readin(offset, buf)
return self._toarray(buf)
PM_PAGE_PRESENT = 1 << 63
PM_PFN_MASK = (1 << 55) - 1
class PageMap(BinArrayFile):
# Read ranges of a given pid's pagemap into a numpy array.
def __init__(self, pid='self'):
super().__init__(f'/proc/{pid}/pagemap', 8)
KPF_ANON = 1 << 12
KPF_COMPOUND_HEAD = 1 << 15
KPF_COMPOUND_TAIL = 1 << 16
KPF_THP = 1 << 22
class KPageFlags(BinArrayFile):
# Read ranges of /proc/kpageflags into a numpy array.
def __init__(self):
super().__init__(f'/proc/kpageflags', 8)
vma_all_stats = set([
"Size",
"Rss",
"Pss",
"Pss_Dirty",
"Shared_Clean",
"Shared_Dirty",
"Private_Clean",
"Private_Dirty",
"Referenced",
"Anonymous",
"KSM",
"LazyFree",
"AnonHugePages",
"ShmemPmdMapped",
"FilePmdMapped",
"Shared_Hugetlb",
"Private_Hugetlb",
"Swap",
"SwapPss",
"Locked",
])
vma_min_stats = set([
"Rss",
"Anonymous",
"AnonHugePages",
"ShmemPmdMapped",
"FilePmdMapped",
])
VMA = collections.namedtuple('VMA', [
'name',
'start',
'end',
'read',
'write',
'execute',
'private',
'pgoff',
'major',
'minor',
'inode',
'stats',
])
class VMAList:
# A container for VMAs, parsed from /proc/<pid>/smaps. Iterate over the
# instance to receive VMAs.
def __init__(self, pid='self', stats=[]):
self.vmas = []
with open(f'/proc/{pid}/smaps', 'r') as file:
for line in file:
elements = line.split()
if '-' in elements[0]:
start, end = map(lambda x: int(x, 16), elements[0].split('-'))
major, minor = map(lambda x: int(x, 16), elements[3].split(':'))
self.vmas.append(VMA(
name=elements[5] if len(elements) == 6 else '',
start=start,
end=end,
read=elements[1][0] == 'r',
write=elements[1][1] == 'w',
execute=elements[1][2] == 'x',
private=elements[1][3] == 'p',
pgoff=int(elements[2], 16),
major=major,
minor=minor,
inode=int(elements[4], 16),
stats={},
))
else:
param = elements[0][:-1]
if param in stats:
value = int(elements[1])
self.vmas[-1].stats[param] = {'type': None, 'value': value}
def __iter__(self):
yield from self.vmas
def thp_parse(vma, kpageflags, ranges, indexes, vfns, pfns, anons, heads):
# Given 4 same-sized arrays representing a range within a page table backed
# by THPs (vfns: virtual frame numbers, pfns: physical frame numbers, anons:
# True if page is anonymous, heads: True if page is head of a THP), return a
# dictionary of statistics describing the mapped THPs.
stats = {
'file': {
'partial': 0,
'aligned': [0] * (PMD_ORDER + 1),
'unaligned': [0] * (PMD_ORDER + 1),
},
'anon': {
'partial': 0,
'aligned': [0] * (PMD_ORDER + 1),
'unaligned': [0] * (PMD_ORDER + 1),
},
}
for rindex, rpfn in zip(ranges[0], ranges[2]):
index_next = int(rindex[0])
index_end = int(rindex[1]) + 1
pfn_end = int(rpfn[1]) + 1
folios = indexes[index_next:index_end][heads[index_next:index_end]]
# Account pages for any partially mapped THP at the front. In that case,
# the first page of the range is a tail.
nr = (int(folios[0]) if len(folios) else index_end) - index_next
stats['anon' if anons[index_next] else 'file']['partial'] += nr
# Account pages for any partially mapped THP at the back. In that case,
# the next page after the range is a tail.
if len(folios):
flags = int(kpageflags.get(pfn_end)[0])
if flags & KPF_COMPOUND_TAIL:
nr = index_end - int(folios[-1])
folios = folios[:-1]
index_end -= nr
stats['anon' if anons[index_end - 1] else 'file']['partial'] += nr
# Account fully mapped THPs in the middle of the range.
if len(folios):
folio_nrs = np.append(np.diff(folios), np.uint64(index_end - folios[-1]))
folio_orders = np.log2(folio_nrs).astype(np.uint64)
for index, order in zip(folios, folio_orders):
index = int(index)
order = int(order)
nr = 1 << order
vfn = int(vfns[index])
align = 'aligned' if align_forward(vfn, nr) == vfn else 'unaligned'
anon = 'anon' if anons[index] else 'file'
stats[anon][align][order] += nr
# Account PMD-mapped THPs spearately, so filter out of the stats. There is a
# race between acquiring the smaps stats and reading pagemap, where memory
# could be deallocated. So clamp to zero incase it would have gone negative.
anon_pmd_mapped = vma.stats['AnonHugePages']['value']
file_pmd_mapped = vma.stats['ShmemPmdMapped']['value'] + \
vma.stats['FilePmdMapped']['value']
stats['anon']['aligned'][PMD_ORDER] = max(0, stats['anon']['aligned'][PMD_ORDER] - kbnr(anon_pmd_mapped))
stats['file']['aligned'][PMD_ORDER] = max(0, stats['file']['aligned'][PMD_ORDER] - kbnr(file_pmd_mapped))
rstats = {
f"anon-thp-pmd-aligned-{odkb(PMD_ORDER)}kB": {'type': 'anon', 'value': anon_pmd_mapped},
f"file-thp-pmd-aligned-{odkb(PMD_ORDER)}kB": {'type': 'file', 'value': file_pmd_mapped},
}
def flatten_sub(type, subtype, stats):
param = f"{type}-thp-pte-{subtype}-{{}}kB"
for od, nr in enumerate(stats[2:], 2):
rstats[param.format(odkb(od))] = {'type': type, 'value': nrkb(nr)}
def flatten_type(type, stats):
flatten_sub(type, 'aligned', stats['aligned'])
flatten_sub(type, 'unaligned', stats['unaligned'])
rstats[f"{type}-thp-pte-partial"] = {'type': type, 'value': nrkb(stats['partial'])}
flatten_type('anon', stats['anon'])
flatten_type('file', stats['file'])
return rstats
def cont_parse(vma, order, ranges, anons, heads):
# Given 4 same-sized arrays representing a range within a page table backed
# by THPs (vfns: virtual frame numbers, pfns: physical frame numbers, anons:
# True if page is anonymous, heads: True if page is head of a THP), return a
# dictionary of statistics describing the contiguous blocks.
nr_cont = 1 << order
nr_anon = 0
nr_file = 0
for rindex, rvfn, rpfn in zip(*ranges):
index_next = int(rindex[0])
index_end = int(rindex[1]) + 1
vfn_start = int(rvfn[0])
pfn_start = int(rpfn[0])
if align_offset(pfn_start, nr_cont) != align_offset(vfn_start, nr_cont):
continue
off = align_forward(vfn_start, nr_cont) - vfn_start
index_next += off
while index_next + nr_cont <= index_end:
folio_boundary = heads[index_next+1:index_next+nr_cont].any()
if not folio_boundary:
if anons[index_next]:
nr_anon += nr_cont
else:
nr_file += nr_cont
index_next += nr_cont
# Account blocks that are PMD-mapped spearately, so filter out of the stats.
# There is a race between acquiring the smaps stats and reading pagemap,
# where memory could be deallocated. So clamp to zero incase it would have
# gone negative.
anon_pmd_mapped = vma.stats['AnonHugePages']['value']
file_pmd_mapped = vma.stats['ShmemPmdMapped']['value'] + \
vma.stats['FilePmdMapped']['value']
nr_anon = max(0, nr_anon - kbnr(anon_pmd_mapped))
nr_file = max(0, nr_file - kbnr(file_pmd_mapped))
rstats = {
f"anon-cont-pmd-aligned-{nrkb(nr_cont)}kB": {'type': 'anon', 'value': anon_pmd_mapped},
f"file-cont-pmd-aligned-{nrkb(nr_cont)}kB": {'type': 'file', 'value': file_pmd_mapped},
}
rstats[f"anon-cont-pte-aligned-{nrkb(nr_cont)}kB"] = {'type': 'anon', 'value': nrkb(nr_anon)}
rstats[f"file-cont-pte-aligned-{nrkb(nr_cont)}kB"] = {'type': 'file', 'value': nrkb(nr_file)}
return rstats
def vma_print(vma, pid):
# Prints a VMA instance in a format similar to smaps. The main difference is
# that the pid is included as the first value.
print("{:010d}: {:016x}-{:016x} {}{}{}{} {:08x} {:02x}:{:02x} {:08x} {}"
.format(
pid, vma.start, vma.end,
'r' if vma.read else '-', 'w' if vma.write else '-',
'x' if vma.execute else '-', 'p' if vma.private else 's',
vma.pgoff, vma.major, vma.minor, vma.inode, vma.name
))
def stats_print(stats, tot_anon, tot_file, inc_empty):
# Print a statistics dictionary.
label_field = 32
for label, stat in stats.items():
type = stat['type']
value = stat['value']
if value or inc_empty:
pad = max(0, label_field - len(label) - 1)
if type == 'anon' and tot_anon > 0:
percent = f' ({value / tot_anon:3.0%})'
elif type == 'file' and tot_file > 0:
percent = f' ({value / tot_file:3.0%})'
else:
percent = ''
print(f"{label}:{' ' * pad}{value:8} kB{percent}")
def vma_parse(vma, pagemap, kpageflags, contorders):
# Generate thp and cont statistics for a single VMA.
start = vma.start >> PAGE_SHIFT
end = vma.end >> PAGE_SHIFT
pmes = pagemap.get(start, end - start)
present = pmes & PM_PAGE_PRESENT != 0
pfns = pmes & PM_PFN_MASK
pfns = pfns[present]
vfns = np.arange(start, end, dtype=np.uint64)
vfns = vfns[present]
pfn_vec = cont_ranges_all([pfns], [pfns])[0]
flags = kpageflags.getv(pfn_vec)
anons = flags & KPF_ANON != 0
heads = flags & KPF_COMPOUND_HEAD != 0
thps = flags & KPF_THP != 0
vfns = vfns[thps]
pfns = pfns[thps]
anons = anons[thps]
heads = heads[thps]
indexes = np.arange(len(vfns), dtype=np.uint64)
ranges = cont_ranges_all([vfns, pfns], [indexes, vfns, pfns])
thpstats = thp_parse(vma, kpageflags, ranges, indexes, vfns, pfns, anons, heads)
contstats = [cont_parse(vma, order, ranges, anons, heads) for order in contorders]
tot_anon = vma.stats['Anonymous']['value']
tot_file = vma.stats['Rss']['value'] - tot_anon
return {
**thpstats,
**{k: v for s in contstats for k, v in s.items()}
}, tot_anon, tot_file
def do_main(args):
pids = set()
rollup = {}
rollup_anon = 0
rollup_file = 0
if args.cgroup:
strict = False
for walk_info in os.walk(args.cgroup):
cgroup = walk_info[0]
with open(f'{cgroup}/cgroup.procs') as pidfile:
for line in pidfile.readlines():
pids.add(int(line.strip()))
elif args.pid:
strict = True
pids = pids.union(args.pid)
else:
strict = False
for pid in os.listdir('/proc'):
if pid.isdigit():
pids.add(int(pid))
if not args.rollup:
print(" PID START END PROT OFFSET DEV INODE OBJECT")
for pid in pids:
try:
with PageMap(pid) as pagemap:
with KPageFlags() as kpageflags:
for vma in VMAList(pid, vma_all_stats if args.inc_smaps else vma_min_stats):
if (vma.read or vma.write or vma.execute) and vma.stats['Rss']['value'] > 0:
stats, vma_anon, vma_file = vma_parse(vma, pagemap, kpageflags, args.cont)
else:
stats = {}
vma_anon = 0
vma_file = 0
if args.inc_smaps:
stats = {**vma.stats, **stats}
if args.rollup:
for k, v in stats.items():
if k in rollup:
assert(rollup[k]['type'] == v['type'])
rollup[k]['value'] += v['value']
else:
rollup[k] = v
rollup_anon += vma_anon
rollup_file += vma_file
else:
vma_print(vma, pid)
stats_print(stats, vma_anon, vma_file, args.inc_empty)
except (FileNotFoundError, ProcessLookupError, FileIOException):
if strict:
raise
if args.rollup:
stats_print(rollup, rollup_anon, rollup_file, args.inc_empty)
def main():
docs_width = shutil.get_terminal_size().columns
docs_width -= 2
docs_width = min(80, docs_width)
def format(string):
text = re.sub(r'\s+', ' ', string)
text = re.sub(r'\s*\\n\s*', '\n', text)
paras = text.split('\n')
paras = [textwrap.fill(p, width=docs_width) for p in paras]
return '\n'.join(paras)
def formatter(prog):
return argparse.RawDescriptionHelpFormatter(prog, width=docs_width)
def size2order(human):
units = {
"K": 2**10, "M": 2**20, "G": 2**30,
"k": 2**10, "m": 2**20, "g": 2**30,
}
unit = 1
if human[-1] in units:
unit = units[human[-1]]
human = human[:-1]
try:
size = int(human)
except ValueError:
raise ArgException('error: --cont value must be integer size with optional KMG unit')
size *= unit
order = int(math.log2(size / PAGE_SIZE))
if order < 1:
raise ArgException('error: --cont value must be size of at least 2 pages')
if (1 << order) * PAGE_SIZE != size:
raise ArgException('error: --cont value must be size of power-of-2 pages')
if order > PMD_ORDER:
raise ArgException('error: --cont value must be less than or equal to PMD order')
return order
parser = argparse.ArgumentParser(formatter_class=formatter,
description=format("""Prints information about how transparent huge
pages are mapped, either system-wide, or for a specified
process or cgroup.\\n
\\n
When run with --pid, the user explicitly specifies the set
of pids to scan. e.g. "--pid 10 [--pid 134 ...]". When run
with --cgroup, the user passes either a v1 or v2 cgroup and
all pids that belong to the cgroup subtree are scanned. When
run with neither --pid nor --cgroup, the full set of pids on
the system is gathered from /proc and scanned as if the user
had provided "--pid 1 --pid 2 ...".\\n
\\n
A default set of statistics is always generated for THP
mappings. However, it is also possible to generate
additional statistics for "contiguous block mappings" where
the block size is user-defined.\\n
\\n
Statistics are maintained independently for anonymous and
file-backed (pagecache) memory and are shown both in kB and
as a percentage of either total anonymous or total
file-backed memory as appropriate.\\n
\\n
THP Statistics\\n
--------------\\n
\\n
Statistics are always generated for fully- and
contiguously-mapped THPs whose mapping address is aligned to
their size, for each <size> supported by the system.
Separate counters describe THPs mapped by PTE vs those
mapped by PMD. (Although note a THP can only be mapped by
PMD if it is PMD-sized):\\n
\\n
- anon-thp-pte-aligned-<size>kB\\n
- file-thp-pte-aligned-<size>kB\\n
- anon-thp-pmd-aligned-<size>kB\\n
- file-thp-pmd-aligned-<size>kB\\n
\\n
Similarly, statistics are always generated for fully- and
contiguously-mapped THPs whose mapping address is *not*
aligned to their size, for each <size> supported by the
system. Due to the unaligned mapping, it is impossible to
map by PMD, so there are only PTE counters for this case:\\n
\\n
- anon-thp-pte-unaligned-<size>kB\\n
- file-thp-pte-unaligned-<size>kB\\n
\\n
Statistics are also always generated for mapped pages that
belong to a THP but where the is THP is *not* fully- and
contiguously- mapped. These "partial" mappings are all
counted in the same counter regardless of the size of the
THP that is partially mapped:\\n
\\n
- anon-thp-pte-partial\\n
- file-thp-pte-partial\\n
\\n
Contiguous Block Statistics\\n
---------------------------\\n
\\n
An optional, additional set of statistics is generated for
every contiguous block size specified with `--cont <size>`.
These statistics show how much memory is mapped in
contiguous blocks of <size> and also aligned to <size>. A
given contiguous block must all belong to the same THP, but
there is no requirement for it to be the *whole* THP.
Separate counters describe contiguous blocks mapped by PTE
vs those mapped by PMD:\\n
\\n
- anon-cont-pte-aligned-<size>kB\\n
- file-cont-pte-aligned-<size>kB\\n
- anon-cont-pmd-aligned-<size>kB\\n
- file-cont-pmd-aligned-<size>kB\\n
\\n
As an example, if monitoring 64K contiguous blocks (--cont
64K), there are a number of sources that could provide such
blocks: a fully- and contiguously-mapped 64K THP that is
aligned to a 64K boundary would provide 1 block. A fully-
and contiguously-mapped 128K THP that is aligned to at least
a 64K boundary would provide 2 blocks. Or a 128K THP that
maps its first 100K, but contiguously and starting at a 64K
boundary would provide 1 block. A fully- and
contiguously-mapped 2M THP would provide 32 blocks. There
are many other possible permutations.\\n"""),
epilog=format("""Requires root privilege to access pagemap and
kpageflags."""))
group = parser.add_mutually_exclusive_group(required=False)
group.add_argument('--pid',
metavar='pid', required=False, type=int, default=[], action='append',
help="""Process id of the target process. Maybe issued multiple times to
scan multiple processes. --pid and --cgroup are mutually exclusive.
If neither are provided, all processes are scanned to provide
system-wide information.""")
group.add_argument('--cgroup',
metavar='path', required=False,
help="""Path to the target cgroup in sysfs. Iterates over every pid in
the cgroup and its children. --pid and --cgroup are mutually
exclusive. If neither are provided, all processes are scanned to
provide system-wide information.""")
parser.add_argument('--rollup',
required=False, default=False, action='store_true',
help="""Sum the per-vma statistics to provide a summary over the whole
system, process or cgroup.""")
parser.add_argument('--cont',
metavar='size[KMG]', required=False, default=[], action='append',
help="""Adds stats for memory that is mapped in contiguous blocks of
<size> and also aligned to <size>. May be issued multiple times to
track multiple sized blocks. Useful to infer e.g. arm64 contpte and
hpa mappings. Size must be a power-of-2 number of pages.""")
parser.add_argument('--inc-smaps',
required=False, default=False, action='store_true',
help="""Include all numerical, additive /proc/<pid>/smaps stats in the
output.""")
parser.add_argument('--inc-empty',
required=False, default=False, action='store_true',
help="""Show all statistics including those whose value is 0.""")
parser.add_argument('--periodic',
metavar='sleep_ms', required=False, type=int,
help="""Run in a loop, polling every sleep_ms milliseconds.""")
args = parser.parse_args()
try:
args.cont = [size2order(cont) for cont in args.cont]
except ArgException as e:
parser.print_usage()
raise
if args.periodic:
while True:
do_main(args)
print()
time.sleep(args.periodic / 1000)
else:
do_main(args)
if __name__ == "__main__":
try:
main()
except Exception as e:
prog = os.path.basename(sys.argv[0])
print(f'{prog}: {e}')
exit(1)