// SPDX-License-Identifier: GPL-2.0 /* * Copyright (C) 2013 Red Hat Inc, Steven Rostedt * * * This code was inspired by Ezequiel Garcia's trace_analyze program: * git://github.com/ezequielgarcia/trace_analyze.git * * Unfortuntately, I hate working with Python, and I also had trouble * getting it to work, as I had an old python on my Fedora 13, and it * was written for the newer version. I decided to do some of it here * in C. */ #define _LARGEFILE64_SOURCE #include #include #include #include #include #include #include "trace-local.h" #include "trace-hash-local.h" #include "list.h" static int kmalloc_type; static int kmalloc_node_type; static int kfree_type; static int kmem_cache_alloc_type; static int kmem_cache_alloc_node_type; static int kmem_cache_free_type; static struct tep_format_field *common_type_mem; static struct tep_format_field *kmalloc_callsite_field; static struct tep_format_field *kmalloc_bytes_req_field; static struct tep_format_field *kmalloc_bytes_alloc_field; static struct tep_format_field *kmalloc_ptr_field; static struct tep_format_field *kmalloc_node_callsite_field; static struct tep_format_field *kmalloc_node_bytes_req_field; static struct tep_format_field *kmalloc_node_bytes_alloc_field; static struct tep_format_field *kmalloc_node_ptr_field; static struct tep_format_field *kfree_ptr_field; static struct tep_format_field *kmem_cache_callsite_field; static struct tep_format_field *kmem_cache_bytes_req_field; static struct tep_format_field *kmem_cache_bytes_alloc_field; static struct tep_format_field *kmem_cache_ptr_field; static struct tep_format_field *kmem_cache_node_callsite_field; static struct tep_format_field *kmem_cache_node_bytes_req_field; static struct tep_format_field *kmem_cache_node_bytes_alloc_field; static struct tep_format_field *kmem_cache_node_ptr_field; static struct tep_format_field *kmem_cache_free_ptr_field; static void *zalloc(size_t size) { return calloc(1, size); } static struct tep_event * update_event(struct tep_handle *pevent, const char *sys, const char *name, int *id) { struct tep_event *event; event = tep_find_event_by_name(pevent, sys, name); if (!event) return NULL; *id = event->id; return event; } static void update_kmalloc(struct tep_handle *pevent) { struct tep_event *event; event = update_event(pevent, "kmem", "kmalloc", &kmalloc_type); if (!event) return; kmalloc_callsite_field = tep_find_field(event, "call_site"); kmalloc_bytes_req_field = tep_find_field(event, "bytes_req"); kmalloc_bytes_alloc_field = tep_find_field(event, "bytes_alloc"); kmalloc_ptr_field = tep_find_field(event, "ptr"); } static void update_kmalloc_node(struct tep_handle *pevent) { struct tep_event *event; event = update_event(pevent, "kmem", "kmalloc_node", &kmalloc_node_type); if (!event) return; kmalloc_node_callsite_field = tep_find_field(event, "call_site"); kmalloc_node_bytes_req_field = tep_find_field(event, "bytes_req"); kmalloc_node_bytes_alloc_field = tep_find_field(event, "bytes_alloc"); kmalloc_node_ptr_field = tep_find_field(event, "ptr"); } static void update_kfree(struct tep_handle *pevent) { struct tep_event *event; event = update_event(pevent, "kmem", "kfree", &kfree_type); if (!event) return; kfree_ptr_field = tep_find_field(event, "ptr"); } static void update_kmem_cache_alloc(struct tep_handle *pevent) { struct tep_event *event; event = update_event(pevent, "kmem", "kmem_cache_alloc", &kmem_cache_alloc_type); if (!event) return; kmem_cache_callsite_field = tep_find_field(event, "call_site"); kmem_cache_bytes_req_field = tep_find_field(event, "bytes_req"); kmem_cache_bytes_alloc_field = tep_find_field(event, "bytes_alloc"); kmem_cache_ptr_field = tep_find_field(event, "ptr"); } static void update_kmem_cache_alloc_node(struct tep_handle *pevent) { struct tep_event *event; event = update_event(pevent, "kmem", "kmem_cache_alloc_node", &kmem_cache_alloc_node_type); if (!event) return; kmem_cache_node_callsite_field = tep_find_field(event, "call_site"); kmem_cache_node_bytes_req_field = tep_find_field(event, "bytes_req"); kmem_cache_node_bytes_alloc_field = tep_find_field(event, "bytes_alloc"); kmem_cache_node_ptr_field = tep_find_field(event, "ptr"); } static void update_kmem_cache_free(struct tep_handle *pevent) { struct tep_event *event; event = update_event(pevent, "kmem", "kmem_cache_free", &kmem_cache_free_type); if (!event) return; kmem_cache_free_ptr_field = tep_find_field(event, "ptr"); } struct func_descr { struct func_descr *next; const char *func; unsigned long total_alloc; unsigned long total_req; unsigned long current_alloc; unsigned long current_req; unsigned long max_alloc; unsigned long max_req; unsigned long waste; unsigned long max_waste; }; struct ptr_descr { struct ptr_descr *next; struct func_descr *func; unsigned long long ptr; unsigned long alloc; unsigned long req; }; #define HASH_BITS 12 #define HASH_SIZE (1 << HASH_BITS) #define HASH_MASK (HASH_SIZE - 1); static struct func_descr *func_hash[HASH_SIZE]; static struct ptr_descr *ptr_hash[HASH_SIZE]; static struct func_descr **func_list; static unsigned func_count; static int make_key(const void *ptr, int size) { int key = 0; int i; char *kp = (char *)&key; const char *indx = ptr; for (i = 0; i < size; i++) kp[i & 3] ^= indx[i]; return trace_hash(key); } static struct func_descr *find_func(const char *func) { struct func_descr *funcd; int key = make_key(func, strlen(func)) & HASH_MASK; for (funcd = func_hash[key]; funcd; funcd = funcd->next) { /* * As func is always a constant to one pointer, * we can use a direct compare instead of strcmp. */ if (funcd->func == func) return funcd; } return NULL; } static struct func_descr *create_func(const char *func) { struct func_descr *funcd; int key = make_key(func, strlen(func)) & HASH_MASK; funcd = zalloc(sizeof(*funcd)); if (!funcd) die("malloc"); funcd->func = func; funcd->next = func_hash[key]; func_hash[key] = funcd; func_count++; return funcd; } static struct ptr_descr *find_ptr(unsigned long long ptr) { struct ptr_descr *ptrd; int key = make_key(&ptr, sizeof(ptr)) & HASH_MASK; for (ptrd = ptr_hash[key]; ptrd; ptrd = ptrd->next) { if (ptrd->ptr == ptr) return ptrd; } return NULL; } static struct ptr_descr *create_ptr(unsigned long long ptr) { struct ptr_descr *ptrd; int key = make_key(&ptr, sizeof(ptr)) & HASH_MASK; ptrd = zalloc(sizeof(*ptrd)); if (!ptrd) die("malloc"); ptrd->ptr = ptr; ptrd->next = ptr_hash[key]; ptr_hash[key] = ptrd; return ptrd; } static void remove_ptr(unsigned long long ptr) { struct ptr_descr *ptrd, **last; int key = make_key(&ptr, sizeof(ptr)) & HASH_MASK; last = &ptr_hash[key]; for (ptrd = ptr_hash[key]; ptrd; ptrd = ptrd->next) { if (ptrd->ptr == ptr) break; last = &ptrd->next; } if (!ptrd) return; *last = ptrd->next; free(ptrd); } static void add_kmalloc(const char *func, unsigned long long ptr, unsigned int req, int alloc) { struct func_descr *funcd; struct ptr_descr *ptrd; funcd = find_func(func); if (!funcd) funcd = create_func(func); funcd->total_alloc += alloc; funcd->total_req += req; funcd->current_alloc += alloc; funcd->current_req += req; if (funcd->current_alloc > funcd->max_alloc) funcd->max_alloc = funcd->current_alloc; if (funcd->current_req > funcd->max_req) funcd->max_req = funcd->current_req; ptrd = find_ptr(ptr); if (!ptrd) ptrd = create_ptr(ptr); ptrd->alloc = alloc; ptrd->req = req; ptrd->func = funcd; } static void remove_kmalloc(unsigned long long ptr) { struct func_descr *funcd; struct ptr_descr *ptrd; ptrd = find_ptr(ptr); if (!ptrd) return; funcd = ptrd->func; funcd->current_alloc -= ptrd->alloc; funcd->current_req -= ptrd->req; remove_ptr(ptr); } static void process_kmalloc(struct tep_handle *pevent, struct tep_record *record, struct tep_format_field *callsite_field, struct tep_format_field *bytes_req_field, struct tep_format_field *bytes_alloc_field, struct tep_format_field *ptr_field) { unsigned long long callsite; unsigned long long val; unsigned long long ptr; unsigned int req; int alloc; const char *func; tep_read_number_field(callsite_field, record->data, &callsite); tep_read_number_field(bytes_req_field, record->data, &val); req = val; tep_read_number_field(bytes_alloc_field, record->data, &val); alloc = val; tep_read_number_field(ptr_field, record->data, &ptr); func = tep_find_function(pevent, callsite); add_kmalloc(func, ptr, req, alloc); } static void process_kfree(struct tep_handle *pevent, struct tep_record *record, struct tep_format_field *ptr_field) { unsigned long long ptr; tep_read_number_field(ptr_field, record->data, &ptr); remove_kmalloc(ptr); } static void process_record(struct tep_handle *pevent, struct tep_record *record) { unsigned long long val; int type; tep_read_number_field(common_type_mem, record->data, &val); type = val; if (type == kmalloc_type) return process_kmalloc(pevent, record, kmalloc_callsite_field, kmalloc_bytes_req_field, kmalloc_bytes_alloc_field, kmalloc_ptr_field); if (type == kmalloc_node_type) return process_kmalloc(pevent, record, kmalloc_node_callsite_field, kmalloc_node_bytes_req_field, kmalloc_node_bytes_alloc_field, kmalloc_node_ptr_field); if (type == kfree_type) return process_kfree(pevent, record, kfree_ptr_field); if (type == kmem_cache_alloc_type) return process_kmalloc(pevent, record, kmem_cache_callsite_field, kmem_cache_bytes_req_field, kmem_cache_bytes_alloc_field, kmem_cache_ptr_field); if (type == kmem_cache_alloc_node_type) return process_kmalloc(pevent, record, kmem_cache_node_callsite_field, kmem_cache_node_bytes_req_field, kmem_cache_node_bytes_alloc_field, kmem_cache_node_ptr_field); if (type == kmem_cache_free_type) return process_kfree(pevent, record, kmem_cache_free_ptr_field); } static int func_cmp(const void *a, const void *b) { const struct func_descr *fa = *(const struct func_descr **)a; const struct func_descr *fb = *(const struct func_descr **)b; if (fa->waste > fb->waste) return -1; if (fa->waste < fb->waste) return 1; return 0; } static void sort_list(void) { struct func_descr *funcd; int h; int i = 0; func_list = zalloc(sizeof(*func_list) * func_count); for (h = 0; h < HASH_SIZE; h++) { for (funcd = func_hash[h]; funcd; funcd = funcd->next) { funcd->waste = funcd->current_alloc - funcd->current_req; funcd->max_waste = funcd->max_alloc - funcd->max_req; if (i == func_count) die("more funcs than expected\n"); func_list[i++] = funcd; } } qsort(func_list, func_count, sizeof(*func_list), func_cmp); } static void print_list(void) { struct func_descr *funcd; int i; printf(" Function \t"); printf("Waste\tAlloc\treq\t\tTotAlloc TotReq\t\tMaxAlloc MaxReq\t"); printf("MaxWaste\n"); printf(" -------- \t"); printf("-----\t-----\t---\t\t-------- ------\t\t-------- ------\t"); printf("--------\n"); for (i = 0; i < func_count; i++) { funcd = func_list[i]; printf("%32s\t%ld\t%ld\t%ld\t\t%8ld %8ld\t\t%8ld %8ld\t%ld\n", funcd->func, funcd->waste, funcd->current_alloc, funcd->current_req, funcd->total_alloc, funcd->total_req, funcd->max_alloc, funcd->max_req, funcd->max_waste); } } static void do_trace_mem(struct tracecmd_input *handle) { struct tep_handle *pevent = tracecmd_get_tep(handle); struct tep_record *record; struct tep_event *event; int missed_events = 0; int cpus; int cpu; int ret; ret = tracecmd_init_data(handle); if (ret < 0) die("failed to init data"); if (ret > 0) die("trace-cmd mem does not work with latency traces\n"); cpus = tracecmd_cpus(handle); /* Need to get any event */ for (cpu = 0; cpu < cpus; cpu++) { record = tracecmd_peek_data(handle, cpu); if (record) break; } if (!record) die("No records found in file"); ret = tep_data_type(pevent, record); event = tep_find_event(pevent, ret); common_type_mem = tep_find_common_field(event, "common_type"); if (!common_type_mem) die("Can't find a 'type' field?"); update_kmalloc(pevent); update_kmalloc_node(pevent); update_kfree(pevent); update_kmem_cache_alloc(pevent); update_kmem_cache_alloc_node(pevent); update_kmem_cache_free(pevent); while ((record = tracecmd_read_next_data(handle, &cpu))) { /* record missed event */ if (!missed_events && record->missed_events) missed_events = 1; process_record(pevent, record); tracecmd_free_record(record); } sort_list(); print_list(); } void trace_mem(int argc, char **argv) { struct tracecmd_input *handle; const char *input_file = NULL; int ret; for (;;) { int c; c = getopt(argc-1, argv+1, "+hi:"); if (c == -1) break; switch (c) { case 'h': usage(argv); break; case 'i': if (input_file) die("Only one input for mem"); input_file = optarg; break; default: usage(argv); } } if ((argc - optind) >= 2) { if (input_file) usage(argv); input_file = argv[optind + 1]; } if (!input_file) input_file = DEFAULT_INPUT_FILE; handle = tracecmd_alloc(input_file, 0); if (!handle) die("can't open %s\n", input_file); ret = tracecmd_read_headers(handle, 0); if (ret) return; do_trace_mem(handle); tracecmd_close(handle); }