[PATCH v4 3/9] kernel-shark-qt: Add API for loading trace.dat files

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



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) <y.karadz@xxxxxxxxx>
---
 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 <y.karadz@xxxxxxxxx>
+ */
+
+ /**
+ *  @file    libkshark.c
+ *  @brief   API for processing of FTRACE (trace-cmd) data.
+ */
+
+/** Use GNU C Library. */
+#define _GNU_SOURCE 1
+
+// C
+#include <stdlib.h>
+#include <stdio.h>
+#include <assert.h>
+#include <errno.h>
+
+// 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 <y.karadz@xxxxxxxxx>
+ */
+
+ /**
+ *  @file    libkshark.h
+ *  @brief   API for processing of FTRACE (trace-cmd) data.
+ */
+
+#ifndef _LIB_KSHARK_H
+#define _LIB_KSHARK_H
+
+// C
+#include <stdint.h>
+#include <pthread.h>
+
+#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
-- 
2.17.1




[Index of Archives]     [Linux USB Development]     [Linux USB Development]     [Linux Audio Users]     [Yosemite Hiking]     [Linux Kernel]     [Linux SCSI]

  Powered by Linux