|  | // 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. | 
|  | */ | 
|  | #define _LARGEFILE64_SOURCE | 
|  | #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; | 
|  |  | 
|  | struct tep_format_field *common_type_field; | 
|  |  | 
|  | struct tep_format_field *kmalloc_callsite_field; | 
|  | struct tep_format_field *kmalloc_bytes_req_field; | 
|  | struct tep_format_field *kmalloc_bytes_alloc_field; | 
|  | struct tep_format_field *kmalloc_ptr_field; | 
|  |  | 
|  | struct tep_format_field *kmalloc_node_callsite_field; | 
|  | struct tep_format_field *kmalloc_node_bytes_req_field; | 
|  | struct tep_format_field *kmalloc_node_bytes_alloc_field; | 
|  | struct tep_format_field *kmalloc_node_ptr_field; | 
|  |  | 
|  | struct tep_format_field *kfree_ptr_field; | 
|  |  | 
|  | struct tep_format_field *kmem_cache_callsite_field; | 
|  | struct tep_format_field *kmem_cache_bytes_req_field; | 
|  | struct tep_format_field *kmem_cache_bytes_alloc_field; | 
|  | struct tep_format_field *kmem_cache_ptr_field; | 
|  |  | 
|  | struct tep_format_field *kmem_cache_node_callsite_field; | 
|  | struct tep_format_field *kmem_cache_node_bytes_req_field; | 
|  | struct tep_format_field *kmem_cache_node_bytes_alloc_field; | 
|  | struct tep_format_field *kmem_cache_node_ptr_field; | 
|  |  | 
|  | 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_field, 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_pevent(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_field = tep_find_common_field(event, "common_type"); | 
|  | if (!common_type_field) | 
|  | 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); | 
|  | 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 = "trace.dat"; | 
|  |  | 
|  | handle = tracecmd_alloc(input_file); | 
|  | if (!handle) | 
|  | die("can't open %s\n", input_file); | 
|  |  | 
|  | ret = tracecmd_read_headers(handle); | 
|  | if (ret) | 
|  | return; | 
|  |  | 
|  | do_trace_mem(handle); | 
|  |  | 
|  | tracecmd_close(handle); | 
|  | } |