| // SPDX-License-Identifier: GPL-2.0 | |
| /* | |
| * Copyright (C) 2013 Red Hat Inc, Steven Rostedt <srostedt@redhat.com> | |
| * | |
| * | |
| * 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. | |
| */ | |
| #include <dirent.h> | |
| #include <stdio.h> | |
| #include <stdlib.h> | |
| #include <string.h> | |
| #include <getopt.h> | |
| #include <signal.h> | |
| #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) | |
| do_trace_mem(handle); | |
| tracecmd_close(handle); | |
| } |