From patchwork Mon Jul 2 14:04:18 2018 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Yordan Karadzhov X-Patchwork-Id: 10758627 Return-Path: Received: from mail-wr0-f196.google.com ([209.85.128.196]:44140 "EHLO mail-wr0-f196.google.com" rhost-flags-OK-OK-OK-OK) by vger.kernel.org with ESMTP id S1752279AbeGBOFB (ORCPT ); Mon, 2 Jul 2018 10:05:01 -0400 Received: by mail-wr0-f196.google.com with SMTP id p12-v6so15682075wrn.11 for ; Mon, 02 Jul 2018 07:05:01 -0700 (PDT) From: "Yordan Karadzhov (VMware)" To: rostedt@goodmis.org Cc: linux-trace-devel@vger.kernel.org, "Yordan Karadzhov (VMware)" Subject: [PATCH v4 3/9] kernel-shark-qt: Add API for loading trace.dat files Date: Mon, 2 Jul 2018 17:04:18 +0300 Message-Id: <20180702140424.23221-3-y.karadz@gmail.com> In-Reply-To: <20180702140424.23221-1-y.karadz@gmail.com> References: <20180702140424.23221-1-y.karadz@gmail.com> Sender: linux-trace-devel-owner@vger.kernel.org List-ID: Content-Length: 19851 This patch introduces the first component of the C API used by the new Qt-based version of KernelShark. The patch includes only the part of the API responsible for loading data files generated by trace-cmd. The following patch will introduce an example, demonstrating the usage of this part of the API. This version of the patch contains a number of improvements suggested by Steven Rostedt. Thanks Steven! Signed-off-by: Yordan Karadzhov (VMware) --- kernel-shark-qt/src/CMakeLists.txt | 9 + kernel-shark-qt/src/libkshark.c | 602 +++++++++++++++++++++++++++++ kernel-shark-qt/src/libkshark.h | 114 ++++++ 3 files changed, 725 insertions(+) create mode 100644 kernel-shark-qt/src/libkshark.c create mode 100644 kernel-shark-qt/src/libkshark.h diff --git a/kernel-shark-qt/src/CMakeLists.txt b/kernel-shark-qt/src/CMakeLists.txt index 8c66424..ed3c60e 100644 --- a/kernel-shark-qt/src/CMakeLists.txt +++ b/kernel-shark-qt/src/CMakeLists.txt @@ -1,4 +1,13 @@ message("\n src ...") +message(STATUS "libkshark") +add_library(kshark SHARED libkshark.c) + +target_link_libraries(kshark ${CMAKE_DL_LIBS} + ${TRACEEVENT_LIBRARY} + ${TRACECMD_LIBRARY}) + +set_target_properties(kshark PROPERTIES SUFFIX ".so.${KS_VERSION_STRING}") + configure_file( ${KS_DIR}/build/deff.h.cmake ${KS_DIR}/src/KsDeff.h) diff --git a/kernel-shark-qt/src/libkshark.c b/kernel-shark-qt/src/libkshark.c new file mode 100644 index 0000000..a5349ed --- /dev/null +++ b/kernel-shark-qt/src/libkshark.c @@ -0,0 +1,602 @@ +// SPDX-License-Identifier: LGPL-2.1 + +/* + * Copyright (C) 2017 VMware Inc, Yordan Karadzhov + */ + + /** + * @file libkshark.c + * @brief API for processing of FTRACE (trace-cmd) data. + */ + +/** Use GNU C Library. */ +#define _GNU_SOURCE 1 + +// C +#include +#include +#include +#include + +// KernelShark +#include "libkshark.h" + +static __thread struct trace_seq seq; + +static struct kshark_context *kshark_context_handler = NULL; + +static bool kshark_default_context(struct kshark_context **context) +{ + struct kshark_context *kshark_ctx; + + kshark_ctx = calloc(1, sizeof(*kshark_ctx)); + if (!kshark_ctx) + return false; + + /* Will free kshark_context_handler. */ + kshark_free(NULL); + + /* Will do nothing if *context is NULL. */ + kshark_free(*context); + + *context = kshark_context_handler = kshark_ctx; + + return true; +} + +static bool init_thread_seq(void) +{ + if (!seq.buffer) + trace_seq_init(&seq); + + return seq.buffer != NULL; +} + +/** + * @brief Initialize a kshark session. This function must be called before + * calling any other kshark function. If the session has been + * initialized, this function can be used to obtain the session's + * context. + * @param kshark_ctx: Optional input/output location for context pointer. + * If it points to a context, that context will become + * the new session. If it points to NULL, it will obtain + * the current (or new) session. The result is only + * valid on return of true. + * @returns True on success, or false on failure. + */ +bool kshark_instance(struct kshark_context **kshark_ctx) +{ + if (*kshark_ctx != NULL) { + /* Will free kshark_context_handler */ + kshark_free(NULL); + + /* Use the context provided by the user. */ + kshark_context_handler = *kshark_ctx; + } else { + if (kshark_context_handler) { + /* + * No context is provided by the user, but the context + * handler is already set. Use the context handler. + */ + *kshark_ctx = kshark_context_handler; + } else { + /* No kshark_context exists. Create a default one. */ + if (!kshark_default_context(kshark_ctx)) + return false; + } + } + + if (!init_thread_seq()) + return false; + + return true; +} + +static void kshark_free_task_list(struct kshark_context *kshark_ctx) +{ + struct kshark_task_list *task; + int i; + + if (!kshark_ctx) + return; + + for (i = 0; i < KS_TASK_HASH_SIZE; ++i) { + while (kshark_ctx->tasks[i]) { + task = kshark_ctx->tasks[i]; + kshark_ctx->tasks[i] = task->next; + free(task); + } + } +} + +/** + * @brief Open and prepare for reading a trace data file specified by "file". + * If the specified file does not exist, or contains no trace data, + * the function returns false. + * @param kshark_ctx: Input location for context pointer. + * @param file: The file to load. + * @returns True on success, or false on failure. + */ +bool kshark_open(struct kshark_context *kshark_ctx, const char *file) +{ + struct tracecmd_input *handle; + + kshark_free_task_list(kshark_ctx); + + handle = tracecmd_open(file); + if (!handle) + return false; + + if (pthread_mutex_init(&kshark_ctx->input_mutex, NULL) != 0) { + tracecmd_close(handle); + return false; + } + + kshark_ctx->handle = handle; + kshark_ctx->pevent = tracecmd_get_pevent(handle); + + /* + * Turn off function trace indent and turn on show parent + * if possible. + */ + trace_util_add_option("ftrace:parent", "1"); + trace_util_add_option("ftrace:indent", "0"); + + return true; +} + +/** + * @brief Close the trace data file and free the trace data handle. + * @param kshark_ctx: Input location for the session context pointer. + */ +void kshark_close(struct kshark_context *kshark_ctx) +{ + if (!kshark_ctx || !kshark_ctx->handle) + return; + + tracecmd_close(kshark_ctx->handle); + kshark_ctx->handle = NULL; + kshark_ctx->pevent = NULL; + + pthread_mutex_destroy(&kshark_ctx->input_mutex); +} + +/** + * @brief Deinitialize kshark session. Should be called after closing all + * open trace data files and before your application terminates. + * @param kshark_ctx: Optional input location for session context pointer. + * If it points to a context of a sessuin, that sessuin + * will be deinitialize. If it points to NULL, it will + * deinitialize the current session. + */ +void kshark_free(struct kshark_context *kshark_ctx) +{ + if (kshark_ctx == NULL) { + if (kshark_context_handler == NULL) + return; + + kshark_ctx = kshark_context_handler; + /* kshark_ctx_handler will be set to NULL below. */ + } + + kshark_free_task_list(kshark_ctx); + + if (seq.buffer) + trace_seq_destroy(&seq); + + if (kshark_ctx == kshark_context_handler) + kshark_context_handler = NULL; + + free(kshark_ctx); +} + +static inline uint8_t knuth_hash8(uint32_t val) +{ + /* + * Hashing functions, based on Donald E. Knuth's Multiplicative + * hashing. See The Art of Computer Programming (TAOCP). + * Multiplication by the Prime number, closest to the golden + * ratio of 2^8. + */ + return UINT8_C(val) * UINT8_C(157); +} + +static struct kshark_task_list * +kshark_find_task(struct kshark_context *kshark_ctx, uint8_t key, int pid) +{ + struct kshark_task_list *list; + + for (list = kshark_ctx->tasks[key]; list; list = list->next) { + if (list->pid == pid) + return list; + } + + return NULL; +} + +static struct kshark_task_list * +kshark_add_task(struct kshark_context *kshark_ctx, int pid) +{ + struct kshark_task_list *list; + uint8_t key; + + key = knuth_hash8(pid); + list = kshark_find_task(kshark_ctx, key, pid); + if (list) + return list; + + list = malloc(sizeof(*list)); + if (!list) + return NULL; + + list->pid = pid; + list->next = kshark_ctx->tasks[key]; + kshark_ctx->tasks[key] = list; + + return list; +} + +/** + * @brief Get an array containing the Process Ids of all tasks presented in + * the loaded trace data file. + * @param kshark_ctx: Input location for context pointer. + * @param pids: Output location for the Pids of the tasks. The user is + * responsible for freeing the elements of the outputted array. + * @returns The size of the outputted array of Pids in the case of success, + * or a negative error code on failure. + */ +ssize_t kshark_get_task_pids(struct kshark_context *kshark_ctx, int **pids) +{ + size_t i, pid_count = 0, pid_size = KS_TASK_HASH_SIZE; + struct kshark_task_list *list; + int *temp_pids; + + *pids = calloc(pid_size, sizeof(int)); + if(!*pids) + goto fail; + + for (i = 0; i < KS_TASK_HASH_SIZE; ++i) { + list = kshark_ctx->tasks[i]; + while (list) { + (*pids)[pid_count] = list->pid; + list = list->next; + if (++pid_count >= pid_size) { + pid_size *= 2; + temp_pids = realloc(*pids, pid_size * sizeof(int)); + if (!temp_pids) { + goto fail; + } + *pids = temp_pids; + } + } + } + + temp_pids = realloc(*pids, pid_count * sizeof(int)); + if (!temp_pids) + goto fail; + + return pid_count; + +fail: + fprintf(stderr, "Failed to allocate memory for Task Pids.\n"); + free(*pids); + *pids = NULL; + return -ENOMEM; +} + +static void kshark_set_entry_values(struct kshark_context *kshark_ctx, + struct pevent_record *record, + struct kshark_entry *entry) +{ + /* Offset of the record */ + entry->offset = record->offset; + + /* CPU Id of the record */ + entry->cpu = record->cpu; + + /* Time stamp of the record */ + entry->ts = record->ts; + + /* Event Id of the record */ + entry->event_id = pevent_data_type(kshark_ctx->pevent, record); + + /* + * Is visible mask. This default value means that the entry + * is visible everywhere. + */ + entry->visible = 0xFF; + + /* Process Id of the record */ + entry->pid = pevent_data_pid(kshark_ctx->pevent, record); +} + +/** + * @brief Load the content of the trace data file into an array of + * kshark_entries. This function provides fast loading, however the + * "latency" and the "info" fields can be accessed only via the offset + * into the file. This makes the access to these two fields much + * slower. + * @param kshark_ctx: Input location for context pointer. + * @param data_rows: Output location for the trace data. The user is + * responsible for freeing the elements of the outputted + * array. + * @returns The size of the outputted data in the case of success, or a + * negative error code on failure. + */ +ssize_t kshark_load_data_entries(struct kshark_context *kshark_ctx, + struct kshark_entry ***data_rows) +{ + struct kshark_entry **cpu_list, **rows; + struct kshark_entry *entry, **next; + struct kshark_task_list *task; + struct pevent_record *rec; + int cpu, n_cpus, next_cpu; + size_t count, total = 0; + uint64_t ts; + + if (*data_rows) + free(*data_rows); + + n_cpus = tracecmd_cpus(kshark_ctx->handle); + cpu_list = calloc(n_cpus, sizeof(struct kshark_entry *)); + + for (cpu = 0; cpu < n_cpus; ++cpu) { + count = 0; + cpu_list[cpu] = NULL; + next = &cpu_list[cpu]; + + rec = tracecmd_read_cpu_first(kshark_ctx->handle, cpu); + while (rec) { + *next = entry = malloc(sizeof(struct kshark_entry)); + if (!entry) + goto fail; + + kshark_set_entry_values(kshark_ctx, rec, entry); + task = kshark_add_task(kshark_ctx, entry->pid); + if (!task) + goto fail; + + entry->next = NULL; + next = &entry->next; + free_record(rec); + + ++count; + rec = tracecmd_read_data(kshark_ctx->handle, cpu); + } + + total += count; + } + + rows = calloc(total, sizeof(struct kshark_entry *)); + if (!rows) + goto fail; + + count = 0; + while (count < total) { + ts = 0; + next_cpu = -1; + for (cpu = 0; cpu < n_cpus; ++cpu) { + if (!cpu_list[cpu]) + continue; + + if (!ts || cpu_list[cpu]->ts < ts) { + ts = cpu_list[cpu]->ts; + next_cpu = cpu; + } + } + + if (next_cpu >= 0) { + rows[count] = cpu_list[next_cpu]; + cpu_list[next_cpu] = cpu_list[next_cpu]->next; + } + ++count; + } + + free(cpu_list); + *data_rows = rows; + return total; + +fail: + fprintf(stderr, "Failed to allocate memory during data loading.\n"); + return -ENOMEM; +} + +/** + * @brief Load the content of the trace data file into an array of + * pevent_records. Use this function only if you need fast access + * to all fields of the record. + * @param kshark_ctx: Input location for the session context pointer. + * @param data_rows: Output location for the trace data. Use free_record() + * to free the elements of the outputted array. + * @returns The size of the outputted data in the case of success, or a + * negative error code on failure. + */ +ssize_t kshark_load_data_records(struct kshark_context *kshark_ctx, + struct pevent_record ***data_rows) +{ + struct temp { + struct pevent_record *rec; + struct temp *next; + } **cpu_list, **temp_next, *temp_rec; + + struct kshark_task_list *task; + struct pevent_record **rows; + struct pevent_record *data; + int cpu, n_cpus, next_cpu; + size_t count, total = 0; + uint64_t ts; + int pid; + + n_cpus = tracecmd_cpus(kshark_ctx->handle); + cpu_list = calloc(n_cpus, sizeof(struct temp *)); + + for (cpu = 0; cpu < n_cpus; ++cpu) { + count = 0; + cpu_list[cpu] = NULL; + temp_next = &cpu_list[cpu]; + + data = tracecmd_read_cpu_first(kshark_ctx->handle, cpu); + while (data) { + *temp_next = temp_rec = malloc(sizeof(*temp_rec)); + if (!temp_rec) + goto fail; + + pid = pevent_data_pid(kshark_ctx->pevent, data); + task = kshark_add_task(kshark_ctx, pid); + if (!task) + goto fail; + + temp_rec->rec = data; + temp_rec->next = NULL; + temp_next = &(temp_rec->next); + + ++count; + data = tracecmd_read_data(kshark_ctx->handle, cpu); + } + + total += count; + } + + rows = calloc(total, sizeof(struct pevent_record *)); + if (!rows) + goto fail; + + count = 0; + while (count < total) { + ts = 0; + next_cpu = -1; + for (cpu = 0; cpu < n_cpus; ++cpu) { + if (!cpu_list[cpu]) + continue; + + if (!ts || cpu_list[cpu]->rec->ts < ts) { + ts = cpu_list[cpu]->rec->ts; + next_cpu = cpu; + } + } + + if (next_cpu >= 0) { + rows[count] = cpu_list[next_cpu]->rec; + temp_rec = cpu_list[next_cpu]; + cpu_list[next_cpu] = cpu_list[next_cpu]->next; + free (temp_rec); + } + + ++count; + } + + free(cpu_list); + *data_rows = rows; + return total; + +fail: + fprintf(stderr, "Failed to allocate memory during data loading.\n"); + return -ENOMEM; +} + +static struct pevent_record *kshark_read_at(struct kshark_context *kshark_ctx, + uint64_t offset) +{ + /* + * It turns that tracecmd_read_at() is not thread-safe. + * TODO: Understand why and see if this can be fixed. + * For the time being use a mutex to protect the access. + */ + pthread_mutex_lock(&kshark_ctx->input_mutex); + + struct pevent_record *data = tracecmd_read_at(kshark_ctx->handle, + offset, NULL); + + pthread_mutex_unlock(&kshark_ctx->input_mutex); + + return data; +} + +static const char *kshark_get_latency(struct pevent *pe, + struct pevent_record *record) +{ + if (!record) + return NULL; + + trace_seq_reset(&seq); + pevent_data_lat_fmt(pe, &seq, record); + return seq.buffer; +} + +static const char *kshark_get_info(struct pevent *pe, + struct pevent_record *record, + struct event_format *event) +{ + char *pos; + + if (!record || !event) + return NULL; + + trace_seq_reset(&seq); + pevent_event_info(&seq, event, record); + + /* + * The event info string contains a trailing newline. + * Remove this newline. + */ + if ((pos = strchr(seq.buffer, '\n')) != NULL) + *pos = '\0'; + + return seq.buffer; +} + +/** + * @brief Dump into a string the content of one entry. The function allocates + * a null terminated string and returns a pointer to this string. The + * user has to free the returned string. + * @param entry: A Kernel Shark entry to be printed. + * @returns The returned string contains a semicolon-separated list of data + * fields. + */ +char* kshark_dump_entry(struct kshark_entry *entry) +{ + const char *event_name, *task, *lat, *info; + struct kshark_context *kshark_ctx; + struct pevent_record *data; + struct event_format *event; + char *temp_str, *entry_str; + int event_id, size = 0; + + kshark_ctx = NULL; + if (!kshark_instance(&kshark_ctx) || !init_thread_seq()) + return NULL; + + data = kshark_read_at(kshark_ctx, entry->offset); + + event_id = pevent_data_type(kshark_ctx->pevent, data); + event = pevent_data_event_from_type(kshark_ctx->pevent, event_id); + + event_name = event? event->name : "[UNKNOWN EVENT]"; + task = pevent_data_comm_from_pid(kshark_ctx->pevent, entry->pid); + lat = kshark_get_latency(kshark_ctx->pevent, data); + + size = asprintf(&temp_str, "%li %s-%i; CPU %i; %s;", + entry->ts, + task, + entry->pid, + entry->cpu, + lat); + + info = kshark_get_info(kshark_ctx->pevent, data, event); + if (size > 0) { + size = asprintf(&entry_str, "%s %s; %s; 0x%x", + temp_str, + event_name, + info, + entry->visible); + + free(temp_str); + } + + free_record(data); + + if (size > 0) + return entry_str; + + return NULL; +} diff --git a/kernel-shark-qt/src/libkshark.h b/kernel-shark-qt/src/libkshark.h new file mode 100644 index 0000000..b59c94c --- /dev/null +++ b/kernel-shark-qt/src/libkshark.h @@ -0,0 +1,114 @@ +/* SPDX-License-Identifier: LGPL-2.1 */ + +/* + * Copyright (C) 2017 VMware Inc, Yordan Karadzhov + */ + + /** + * @file libkshark.h + * @brief API for processing of FTRACE (trace-cmd) data. + */ + +#ifndef _LIB_KSHARK_H +#define _LIB_KSHARK_H + +// C +#include +#include + +#ifdef __cplusplus +extern "C" { +#endif + +// trace-cmd +#include "trace-cmd.h" +#include "event-parse.h" +#include "trace-filter-hash.h" + +/** + * Kernel Shark entry contains all information from one trace record needed + * in order to visualize the time-series of trace records. The part of the + * data which is not directly required for the visualization (latency, record + * info etc.) is available on-demand via the offset into the trace file. + */ +struct kshark_entry { + /** + * A bit mask controlling the visibility of the entry. A value of OxFF + * would mean that the entry is visible everywhere. + */ + uint8_t visible; + + /** The CPU core of the record. */ + uint8_t cpu; + + /** The PID of the task the record was generated. */ + int16_t pid; + + /** Unique Id ot the trace event type. */ + int event_id; + + /** The offset into the trace file, used to find the record. */ + uint64_t offset; + + /** + * The time of the record in nano seconds. The value is taken from + * the timestamps within the trace data file, which are architecture + * dependent. The time usually is the timestamp from when the system + * started. + */ + uint64_t ts; + + /** Pointer to the next (in time) kshark_entry on the same CPU core. */ + struct kshark_entry *next; +}; + +/** Size of the task's hash table. */ +#define KS_TASK_HASH_SIZE 256 + +/** Linked list of tasks. */ +struct kshark_task_list { + /** Pointer to the next task's PID. */ + struct kshark_task_list *next; + + /** PID of a task. */ + int pid; +}; + +/** Structure representing a kshark session. */ +struct kshark_context { + /** Input handle for the trace data file. */ + struct tracecmd_input *handle; + + /** Page event used to parse the page. */ + struct pevent *pevent; + + /** Hash table of task PIDs. */ + struct kshark_task_list *tasks[KS_TASK_HASH_SIZE]; + + /** A mutex, used to protect the access to the input file. */ + pthread_mutex_t input_mutex; +}; + +bool kshark_instance(struct kshark_context **kshark_ctx); + +bool kshark_open(struct kshark_context *kshark_ctx, const char *file); + +ssize_t kshark_load_data_entries(struct kshark_context *kshark_ctx, + struct kshark_entry ***data_rows); + +ssize_t kshark_load_data_records(struct kshark_context *kshark_ctx, + struct pevent_record ***data_rows); + +ssize_t kshark_get_task_pids(struct kshark_context *kshark_ctx, int **pids); + +void kshark_close(struct kshark_context *kshark_ctx); + +void kshark_free(struct kshark_context *kshark_ctx); + +char* kshark_dump_entry(struct kshark_entry *entry); + +#ifdef __cplusplus +} +#endif + +#endif // _LIB_KSHARK_H