aboutsummaryrefslogtreecommitdiff
path: root/tracecmd/trace-mem.c
diff options
context:
space:
mode:
Diffstat (limited to 'tracecmd/trace-mem.c')
-rw-r--r--tracecmd/trace-mem.c564
1 files changed, 564 insertions, 0 deletions
diff --git a/tracecmd/trace-mem.c b/tracecmd/trace-mem.c
new file mode 100644
index 00000000..25eb0861
--- /dev/null
+++ b/tracecmd/trace-mem.c
@@ -0,0 +1,564 @@
+// 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;
+
+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);
+}