blob: 44e553fed154be79a0a75d43d29a6a5919eb9d70 [file] [log] [blame]
// SPDX-License-Identifier: LGPL-2.1
/*
* Copyright (C) 2017 VMware Inc, Yordan Karadzhov (VMware) <y.karadz@gmail.com>
*/
/**
* @file libkshark.c
* @brief API for processing of tracing data.
*/
#ifndef _GNU_SOURCE
/** Use GNU C Library. */
#define _GNU_SOURCE
#endif // _GNU_SOURCE
// C
#include <stdlib.h>
#include <stdio.h>
#include <assert.h>
#include <string.h>
// KernelShark
#include "libkshark.h"
#include "libkshark-tepdata.h"
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;
kshark_ctx->stream = calloc(KS_DEFAULT_NUM_STREAMS,
sizeof(*kshark_ctx->stream));
kshark_ctx->stream_info.array_size = KS_DEFAULT_NUM_STREAMS;
kshark_ctx->stream_info.next_free_stream_id = 0;
kshark_ctx->stream_info.max_stream_id = -1;
/* 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;
}
/**
* @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;
}
}
return true;
}
/**
* @brief Open and prepare for reading a trace data file specified by "file".
*
* @param kshark_ctx: Input location for context pointer.
* @param file: The file to load.
*
* @returns The Id number of the data stream associated with this file on success.
* Otherwise a negative errno code.
*/
int kshark_open(struct kshark_context *kshark_ctx, const char *file)
{
int sd, rt;
sd = kshark_add_stream(kshark_ctx);
if (sd < 0)
return sd;
rt = kshark_stream_open(kshark_ctx->stream[sd], file);
if (rt < 0) {
kshark_remove_stream(kshark_ctx, sd);
return rt;
}
return sd;
}
static void kshark_stream_free(struct kshark_data_stream *stream)
{
if (!stream)
return;
kshark_hash_id_free(stream->idle_cpus);
kshark_hash_id_free(stream->show_task_filter);
kshark_hash_id_free(stream->hide_task_filter);
kshark_hash_id_free(stream->show_event_filter);
kshark_hash_id_free(stream->hide_event_filter);
kshark_hash_id_free(stream->show_cpu_filter);
kshark_hash_id_free(stream->hide_cpu_filter);
kshark_hash_id_free(stream->tasks);
free(stream->calib_array);
free(stream->file);
free(stream->name);
free(stream->interface);
free(stream);
}
static struct kshark_data_stream *kshark_stream_alloc()
{
struct kshark_data_stream *stream;
stream = calloc(1, sizeof(*stream));
if (!stream)
goto fail;
stream->idle_cpus = kshark_hash_id_alloc(KS_FILTER_HASH_NBITS);
stream->show_task_filter = kshark_hash_id_alloc(KS_FILTER_HASH_NBITS);
stream->hide_task_filter = kshark_hash_id_alloc(KS_FILTER_HASH_NBITS);
stream->show_event_filter = kshark_hash_id_alloc(KS_FILTER_HASH_NBITS);
stream->hide_event_filter = kshark_hash_id_alloc(KS_FILTER_HASH_NBITS);
stream->show_cpu_filter = kshark_hash_id_alloc(KS_FILTER_HASH_NBITS);
stream->hide_cpu_filter = kshark_hash_id_alloc(KS_FILTER_HASH_NBITS);
stream->tasks = kshark_hash_id_alloc(KS_TASK_HASH_NBITS);
if (!stream->show_task_filter ||
!stream->hide_task_filter ||
!stream->show_event_filter ||
!stream->hide_event_filter ||
!stream->tasks) {
goto fail;
}
stream->filter_is_applied = false;
kshark_set_data_format(stream->data_format, KS_INVALID_DATA);
stream->name = strdup(KS_UNNAMED);
return stream;
fail:
kshark_stream_free(stream);
return NULL;
}
/**
* The maximum number of Data streams that can be added simultaneously.
* The limit is determined by the 16 bit integer used to store the stream Id
* inside struct kshark_entry.
*/
#define KS_MAX_STREAM_ID INT16_MAX
/**
* Bit mask (0 - 15) used when converting indexes to pointers and vise-versa.
*/
#define INDEX_MASK UINT16_MAX
/**
* Bit mask (16 - 31/63) used when converting indexes to pointers and
* vise-versa.
*/
#define INVALID_STREAM_MASK (~((unsigned long) INDEX_MASK))
static int index_from_ptr(void *ptr)
{
unsigned long index = (unsigned long) ptr;
return (int) (index & INDEX_MASK);
}
static void *index_to_ptr(unsigned int index)
{
unsigned long p;
p = INVALID_STREAM_MASK | index;
return (void *) p;
}
static bool kshark_is_valid_stream(void *ptr)
{
unsigned long p = (unsigned long) ptr;
bool v = !((p & ~INDEX_MASK) == INVALID_STREAM_MASK);
return p && v;
}
/**
* @brief Add new Data stream.
*
* @param kshark_ctx: Input location for context pointer.
*
* @returns Zero on success or a negative errno code on failure.
*/
int kshark_add_stream(struct kshark_context *kshark_ctx)
{
struct kshark_data_stream *stream;
int new_stream;
if(kshark_ctx->stream_info.next_free_stream_id > KS_MAX_STREAM_ID)
return -ENODEV;
if (kshark_ctx->stream_info.next_free_stream_id ==
kshark_ctx->stream_info.array_size) {
if (!KS_DOUBLE_SIZE(kshark_ctx->stream,
kshark_ctx->stream_info.array_size))
return -ENOMEM;
}
stream = kshark_stream_alloc();
if (!stream)
return -ENOMEM;
if (pthread_mutex_init(&stream->input_mutex, NULL) != 0) {
kshark_stream_free(stream);
return -EAGAIN;
}
if (kshark_ctx->stream_info.next_free_stream_id >
kshark_ctx->stream_info.max_stream_id) {
new_stream = ++kshark_ctx->stream_info.max_stream_id;
kshark_ctx->stream_info.next_free_stream_id = new_stream + 1;
kshark_ctx->stream[new_stream] = stream;
stream->stream_id = new_stream;
} else {
new_stream = kshark_ctx->stream_info.next_free_stream_id;
kshark_ctx->stream_info.next_free_stream_id =
index_from_ptr(kshark_ctx->stream[new_stream]);
kshark_ctx->stream[new_stream] = stream;
stream->stream_id = new_stream;
}
kshark_ctx->n_streams++;
return stream->stream_id;
}
/**
* @brief Use an existing Trace data stream to open and prepare for reading
* a trace data file specified by "file".
*
* @param stream: Input location for a Trace data stream pointer.
* @param file: The file to load.
*
* @returns Zero on success or a negative error code in the case of an errno.
*/
int kshark_stream_open(struct kshark_data_stream *stream, const char *file)
{
struct kshark_context *kshark_ctx = NULL;
struct kshark_dri_list *input;
if (!stream || !kshark_instance(&kshark_ctx))
return -EFAULT;
stream->file = strdup(file);
if (!stream->file)
return -ENOMEM;
if (kshark_tep_check_data(file)) {
kshark_set_data_format(stream->data_format,
TEP_DATA_FORMAT_IDENTIFIER);
return kshark_tep_init_input(stream);
}
for (input = kshark_ctx->inputs; input; input = input->next)
if (input->interface->check_data(file)) {
strcpy(stream->data_format,
input->interface->data_format);
return input->interface->init(stream);
}
return -ENODATA;
}
/**
* @brief Remove Data stream.
*
* @param kshark_ctx: Input location for context pointer.
* @param sd: Data stream identifier.
*
* @returns Zero on success or a negative errno code on failure.
*/
int kshark_remove_stream(struct kshark_context *kshark_ctx, int sd)
{
struct kshark_data_stream *stream;
if (sd < 0 ||
sd > kshark_ctx->stream_info.max_stream_id ||
!kshark_is_valid_stream(kshark_ctx->stream[sd]))
return -EFAULT;
stream = kshark_ctx->stream[sd];
pthread_mutex_destroy(&stream->input_mutex);
kshark_stream_free(stream);
kshark_ctx->stream[sd] =
index_to_ptr(kshark_ctx->stream_info.next_free_stream_id);
kshark_ctx->stream_info.next_free_stream_id = sd;
kshark_ctx->n_streams--;
return 0;
}
/**
* @brief Get the Data stream object having given Id.
*
* @param kshark_ctx: Input location for context pointer.
* @param sd: Data stream identifier.
*
* @returns Pointer to a Data stream object if the sream exists. Otherwise
* NULL.
*/
struct kshark_data_stream *
kshark_get_data_stream(struct kshark_context *kshark_ctx, int sd)
{
if (sd >= 0 && sd <= kshark_ctx->stream_info.max_stream_id)
if (kshark_ctx->stream[sd] &&
kshark_is_valid_stream(kshark_ctx->stream[sd]) &&
kshark_ctx->stream[sd]->interface)
return kshark_ctx->stream[sd];
return NULL;
}
static struct kshark_data_stream *
get_stream_object(struct kshark_context *kshark_ctx, int sd)
{
if (sd >= 0 && sd <= kshark_ctx->stream_info.max_stream_id)
if (kshark_ctx->stream[sd] &&
kshark_is_valid_stream(kshark_ctx->stream[sd]))
return kshark_ctx->stream[sd];
return NULL;
}
/**
* @brief Get the Data stream object corresponding to a given entry
*
* @param entry: Input location for the KernelShark entry.
*
* @returns Pointer to a Data stream object on success. Otherwise NULL.
*/
struct kshark_data_stream *
kshark_get_stream_from_entry(const struct kshark_entry *entry)
{
struct kshark_context *kshark_ctx = NULL;
if (!kshark_instance(&kshark_ctx))
return NULL;
return kshark_get_data_stream(kshark_ctx, entry->stream_id);
}
/**
* @brief Get an array containing the Ids of all opened Trace data streams.
* The User is responsible for freeing the array.
*
* @param kshark_ctx: Input location for context pointer.
*/
int *kshark_all_streams(struct kshark_context *kshark_ctx)
{
int *ids, i, count = 0;
ids = calloc(kshark_ctx->n_streams, (sizeof(*ids)));
if (!ids)
return NULL;
for (i = 0; i <= kshark_ctx->stream_info.max_stream_id; ++i)
if (kshark_ctx->stream[i] &&
kshark_is_valid_stream(kshark_ctx->stream[i]))
ids[count++] = i;
return ids;
}
static int kshark_stream_close(struct kshark_data_stream *stream)
{
struct kshark_context *kshark_ctx = NULL;
struct kshark_dri_list *input;
if (!stream || !kshark_instance(&kshark_ctx))
return -EFAULT;
/*
* All filters are file specific. Make sure that all Process Ids and
* Event Ids from this file are not going to be used with another file.
*/
kshark_hash_id_clear(stream->show_task_filter);
kshark_hash_id_clear(stream->hide_task_filter);
kshark_hash_id_clear(stream->show_event_filter);
kshark_hash_id_clear(stream->hide_event_filter);
kshark_hash_id_clear(stream->show_cpu_filter);
kshark_hash_id_clear(stream->hide_cpu_filter);
kshark_hash_id_clear(stream->idle_cpus);
if (kshark_is_tep(stream))
return kshark_tep_close_interface(stream);
for (input = kshark_ctx->inputs; input; input = input->next)
if (strcmp(stream->data_format, input->interface->data_format) == 0)
return input->interface->close(stream);
return -ENODATA;
}
/**
* @brief Close the trace data file and free the trace data handle.
*
* @param kshark_ctx: Input location for the session context pointer.
* @param sd: Data stream identifier.
*
* @returns Zero on success or a negative errno code on failure.
*/
int kshark_close(struct kshark_context *kshark_ctx, int sd)
{
struct kshark_data_stream *stream;
int ret;
stream = get_stream_object(kshark_ctx, sd);
if (!stream)
return -EFAULT;
/*
* All data collections are file specific. Make sure that collections
* from this file are not going to be used with another file.
*/
kshark_unregister_stream_collections(&kshark_ctx->collections, sd);
/* Close all active plugins for this stream. */
if (stream->plugins) {
kshark_handle_all_dpis(stream, KSHARK_PLUGIN_CLOSE);
kshark_free_event_handler_list(stream->event_handlers);
kshark_free_dpi_list(stream->plugins);
}
ret = kshark_stream_close(stream);
kshark_remove_stream(kshark_ctx, stream->stream_id);
return ret;
}
/**
* @brief Close all currently open trace data file and free the trace data handle.
*
* @param kshark_ctx: Input location for the session context pointer.
*/
void kshark_close_all(struct kshark_context *kshark_ctx)
{
size_t mem_reset_size;
int i;
if (kshark_ctx->stream_info.max_stream_id < 0)
return;
for (i = 0; i <= kshark_ctx->stream_info.max_stream_id; ++i)
kshark_close(kshark_ctx, i);
/* Reset the array of data stream descriptors. */
mem_reset_size = (kshark_ctx->stream_info.max_stream_id + 1 ) *
sizeof(*kshark_ctx->stream);
memset(kshark_ctx->stream, 0, mem_reset_size);
kshark_ctx->stream_info.next_free_stream_id = 0;
kshark_ctx->stream_info.max_stream_id = -1;
}
/**
* @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 session, that session
* 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_close_all(kshark_ctx);
free(kshark_ctx->stream);
if (kshark_ctx->plugins)
kshark_free_plugin_list(kshark_ctx->plugins);
kshark_free_dri_list(kshark_ctx->inputs);
if (kshark_ctx == kshark_context_handler)
kshark_context_handler = NULL;
free(kshark_ctx);
}
/**
* @brief Get the name of the command/task from its Process Id.
*
* @param sd: Data stream identifier.
* @param pid: Process Id of the command/task.
*/
char *kshark_comm_from_pid(int sd, int pid)
{
struct kshark_generic_stream_interface *interface;
struct kshark_context *kshark_ctx = NULL;
struct kshark_data_stream *stream;
struct kshark_entry e;
if (!kshark_instance(&kshark_ctx))
return NULL;
stream = kshark_get_data_stream(kshark_ctx, sd);
if (!stream)
return NULL;
interface = stream->interface;
if (interface->type == KS_GENERIC_DATA_INTERFACE &&
interface->get_task) {
e.visible = KS_PLUGIN_UNTOUCHED_MASK;
e.pid = pid;
return interface->get_task(stream, &e);
}
return NULL;
}
/**
* @brief Get the name of the event from its Id.
*
* @param sd: Data stream identifier.
* @param event_id: The unique Id of the event type.
*/
char *kshark_event_from_id(int sd, int event_id)
{
struct kshark_generic_stream_interface *interface;
struct kshark_context *kshark_ctx = NULL;
struct kshark_data_stream *stream;
struct kshark_entry e;
if (!kshark_instance(&kshark_ctx))
return NULL;
stream = kshark_get_data_stream(kshark_ctx, sd);
if (!stream)
return NULL;
interface = stream->interface;
if (interface->type == KS_GENERIC_DATA_INTERFACE &&
interface->get_event_name) {
e.visible = KS_PLUGIN_UNTOUCHED_MASK;
e.event_id = event_id;
return interface->get_event_name(stream, &e);
}
return NULL;
}
/**
* @brief Get the original process Id of the entry. Using this function make
* sense only in cases when the original value can be overwritten by
* plugins. If you know that no plugins are loaded use "entry->pid"
* directly.
*
* @param entry: Input location for an entry.
*/
int kshark_get_pid(const struct kshark_entry *entry)
{
struct kshark_generic_stream_interface *interface;
struct kshark_data_stream *stream =
kshark_get_stream_from_entry(entry);
if (!stream)
return -EFAULT;
interface = stream->interface;
if (interface->type == KS_GENERIC_DATA_INTERFACE &&
interface->get_pid)
return interface->get_pid(stream, entry);
return -EFAULT;
}
/**
* @brief Get the original event Id of the entry. Using this function make
* sense only in cases when the original value can be overwritten by
* plugins. If you know that no plugins are loaded use "entry->event_id"
* directly.
*
* @param entry: Input location for an entry.
*/
int kshark_get_event_id(const struct kshark_entry *entry)
{
struct kshark_generic_stream_interface *interface;
struct kshark_data_stream *stream =
kshark_get_stream_from_entry(entry);
if (!stream)
return -EFAULT;
interface = stream->interface;
if (interface->type == KS_GENERIC_DATA_INTERFACE &&
interface->get_event_id)
return interface->get_event_id(stream, entry);
return -EFAULT;
}
/**
* @brief Get an array of all event Ids for a given data stream.
*
* @param stream: Input location for a Trace data stream pointer.
*
* @returns An array of event Ids. The user is responsible for freeing the
* outputted array.
*/
int *kshark_get_all_event_ids(struct kshark_data_stream *stream)
{
struct kshark_generic_stream_interface *interface;
if (!stream)
return NULL;
interface = stream->interface;
if (interface->type == KS_GENERIC_DATA_INTERFACE &&
interface->get_all_event_ids)
return interface->get_all_event_ids(stream);
return NULL;
}
/**
* @brief Find the event Ids corresponding to a given event name.
*
* @param stream: Input location for a Trace data stream pointer.
* @param event_name: The name of the event.
*
* @returns Event Ids number.
*/
int kshark_find_event_id(struct kshark_data_stream *stream,
const char *event_name)
{
struct kshark_generic_stream_interface *interface;
if (!stream)
return -EFAULT;
interface = stream->interface;
if (interface->type == KS_GENERIC_DATA_INTERFACE &&
interface->find_event_id)
return interface->find_event_id(stream, event_name);
return -EFAULT;
}
/**
* @brief Find the event name corresponding to a given entry.
*
* @param entry: Input location for an entry.
*
* @returns The mane of the event on success, or NULL in case of failure.
* The use is responsible for freeing the output string.
*/
char *kshark_get_event_name(const struct kshark_entry *entry)
{
struct kshark_generic_stream_interface *interface;
struct kshark_data_stream *stream =
kshark_get_stream_from_entry(entry);
if (!stream)
return NULL;
interface = stream->interface;
if (interface->type == KS_GENERIC_DATA_INTERFACE &&
interface->get_event_name)
return interface->get_event_name(stream, entry);
return NULL;
}
/**
* @brief Find the task name corresponding to a given entry.
*
* @param entry: Input location for an entry.
*
* @returns The mane of the task on success, or NULL in case of failure.
* The use is responsible for freeing the output string.
*/
char *kshark_get_task(const struct kshark_entry *entry)
{
struct kshark_generic_stream_interface *interface;
struct kshark_data_stream *stream =
kshark_get_stream_from_entry(entry);
if (!stream)
return NULL;
interface = stream->interface;
if (interface->type == KS_GENERIC_DATA_INTERFACE &&
interface->get_task)
return interface->get_task(stream, entry);
return NULL;
}
/**
* @brief Get the basic information (text) about the entry.
*
* @param entry: Input location for an entry.
*
* @returns A the info text. The user is responsible for freeing the
* outputted string.
*/
char *kshark_get_info(const struct kshark_entry *entry)
{
struct kshark_generic_stream_interface *interface;
struct kshark_data_stream *stream =
kshark_get_stream_from_entry(entry);
if (!stream)
return NULL;
interface = stream->interface;
if (interface->type == KS_GENERIC_DATA_INTERFACE &&
interface->get_info)
return interface->get_info(stream, entry);
return NULL;
}
/**
* @brief Get the auxiliary information about the entry. In the case of
* TEP (Ftrace) data, this function provides the latency info.
*
* @param entry: Input location for an entry.
*
* @returns A the auxiliary text info. The user is responsible for freeing the
* outputted string.
*/
char *kshark_get_aux_info(const struct kshark_entry *entry)
{
struct kshark_generic_stream_interface *interface;
struct kshark_data_stream *stream =
kshark_get_stream_from_entry(entry);
if (!stream)
return NULL;
interface = stream->interface;
if (interface->type == KS_GENERIC_DATA_INTERFACE &&
interface->aux_info)
return interface->aux_info(stream, entry);
return NULL;
}
/**
* @brief Get an array of all data field names associated with a given entry.
*
* @param entry: Input location for an entry.
* @param fields: Output location of the array of field names. The user is
* responsible for freeing the elements of the outputted array.
*
* @returns Total number of event fields on success, or a negative errno in
* the case of a failure.
*/
int kshark_get_all_event_field_names(const struct kshark_entry *entry,
char ***fields)
{
struct kshark_generic_stream_interface *interface;
struct kshark_data_stream *stream =
kshark_get_stream_from_entry(entry);
if (!stream)
return -EFAULT;
interface = stream->interface;
if (interface->type == KS_GENERIC_DATA_INTERFACE &&
interface->get_all_event_field_names)
return interface->get_all_event_field_names(stream,
entry, fields);
return -EFAULT;
}
/**
* @brief Get the value type of an event field corresponding to a given entry.
*
* @param entry: Input location for an entry.
* @param field: The name of the data field.
*
* @returns The type of the data field on success, or KS_INVALID_FIELD in
* the case of a failure.
*/
kshark_event_field_format
kshark_get_event_field_type(const struct kshark_entry *entry,
const char *field)
{
struct kshark_generic_stream_interface *interface;
struct kshark_data_stream *stream =
kshark_get_stream_from_entry(entry);
if (!stream)
return KS_INVALID_FIELD;
interface = stream->interface;
if (interface->type == KS_GENERIC_DATA_INTERFACE &&
interface->get_event_field_type)
return interface->get_event_field_type(stream, entry, field);
return KS_INVALID_FIELD;
}
/**
* @brief Get the value of an event field in a given trace record.
*
* @param stream: Input location for a Trace data stream pointer.
* @param rec: Input location for a record.
* @param field: The name of the data field.
* @param val: Output location for the value of the field.
*
* @returns Zero on success or a negative errno in the case of a failure.
*/
int kshark_read_record_field_int(struct kshark_data_stream *stream, void *rec,
const char *field, int64_t *val)
{
struct kshark_generic_stream_interface *interface;
if (!stream)
return -EFAULT;
interface = stream->interface;
if (interface->type == KS_GENERIC_DATA_INTERFACE &&
interface->read_record_field_int64)
return interface->read_record_field_int64(stream, rec,
field, val);
return -EFAULT;
}
/**
* @brief Get the value of an event field corresponding to a given entry.
* The value is retrieved via the offset in the file of the original
* record.
*
* @param entry: Input location for an entry.
* @param field: The name of the data field.
* @param val: Output location for the value of the field.
*
* @returns Zero on success or a negative errno in the case of a failure.
*/
int kshark_read_event_field_int(const struct kshark_entry *entry,
const char* field, int64_t *val)
{
struct kshark_generic_stream_interface *interface;
struct kshark_data_stream *stream =
kshark_get_stream_from_entry(entry);
if (!stream)
return -EFAULT;
interface = stream->interface;
if (interface->type == KS_GENERIC_DATA_INTERFACE &&
interface->read_event_field_int64)
return interface->read_event_field_int64(stream, entry, field, val);
return -EFAULT;
}
/**
* @brief Get a summary of the entry.
*
* @param entry: Input location for an entry.
*
* @returns A summary text info. The user is responsible for freeing the
* outputted string.
*/
char *kshark_dump_entry(const struct kshark_entry *entry)
{
struct kshark_generic_stream_interface *interface;
struct kshark_data_stream *stream =
kshark_get_stream_from_entry(entry);
if (!stream)
return NULL;
interface = stream->interface;
if (interface->type == KS_GENERIC_DATA_INTERFACE &&
interface->dump_entry)
return interface->dump_entry(stream, entry);
return NULL;
}
/** @brief Print the entry. */
void kshark_print_entry(const struct kshark_entry *entry)
{
char *entry_str = kshark_dump_entry(entry);
if (!entry_str)
puts("(nil)");
puts(entry_str);
free(entry_str);
}
/**
* @brief Load the content of the trace data file asociated with a given
* Data stream into an array of kshark_entries.
* If one or more filters are set, the "visible" fields of each entry
* is updated according to the criteria provided by the filters. The
* field "filter_mask" of the session's context is used to control the
* level of visibility/invisibility of the filtered entries.
*
* @param kshark_ctx: Input location for context pointer.
* @param sd: Data stream identifier.
* @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_entries(struct kshark_context *kshark_ctx, int sd,
struct kshark_entry ***data_rows)
{
struct kshark_generic_stream_interface *interface;
struct kshark_data_stream *stream =
kshark_get_data_stream(kshark_ctx, sd);
if (!stream)
return -EFAULT;
interface = stream->interface;
if (interface->type == KS_GENERIC_DATA_INTERFACE &&
interface->load_entries)
return interface->load_entries(stream, kshark_ctx, data_rows);
return -EFAULT;
}
/**
* @brief Load the content of the trace data file asociated with a given
* Data stream into a data matrix. The user is responsible
* for freeing the outputted data.
*
* @param kshark_ctx: Input location for context pointer.
* @param sd: Data stream identifier.
* @param cpu_array: Output location for the CPU column (array) of the matrix.
* @param event_array: Output location for the Event Id column (array) of the
* matrix.
* @param pid_array: Output location for the PID column (array) of the matrix.
* @param offset_array: Output location for the offset column (array) of the
* matrix.
* @param ts_array: Output location for the time stamp column (array) of the
* matrix.
*/
ssize_t kshark_load_matrix(struct kshark_context *kshark_ctx, int sd,
int16_t **event_array,
int16_t **cpu_array,
int32_t **pid_array,
int64_t **offset_array,
int64_t **ts_array)
{
struct kshark_generic_stream_interface *interface;
struct kshark_data_stream *stream =
kshark_get_data_stream(kshark_ctx, sd);
if (!stream)
return -EFAULT;
interface = stream->interface;
if (interface->type == KS_GENERIC_DATA_INTERFACE &&
interface->load_matrix)
return interface->load_matrix(stream, kshark_ctx, event_array,
cpu_array,
pid_array,
offset_array,
ts_array);
return -EFAULT;
}
/**
* @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 sd: Data stream identifier.
* @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 sd,
int **pids)
{
struct kshark_data_stream *stream;
stream = kshark_get_data_stream(kshark_ctx, sd);
if (!stream)
return -EBADF;
*pids = kshark_hash_ids(stream->tasks);
return stream->tasks->count;
}
static bool filter_find(struct kshark_hash_id *filter, int pid,
bool test)
{
return !filter || !filter->count ||
kshark_hash_id_find(filter, pid) == test;
}
static bool kshark_show_task(struct kshark_data_stream *stream, int pid)
{
return filter_find(stream->show_task_filter, pid, true) &&
filter_find(stream->hide_task_filter, pid, false);
}
static bool kshark_show_event(struct kshark_data_stream *stream, int pid)
{
return filter_find(stream->show_event_filter, pid, true) &&
filter_find(stream->hide_event_filter, pid, false);
}
static bool kshark_show_cpu(struct kshark_data_stream *stream, int cpu)
{
return filter_find(stream->show_cpu_filter, cpu, true) &&
filter_find(stream->hide_cpu_filter, cpu, false);
}
static struct kshark_hash_id *get_filter(struct kshark_context *kshark_ctx,
int sd,
enum kshark_filter_type filter_id)
{
struct kshark_data_stream *stream;
stream = kshark_get_data_stream(kshark_ctx, sd);
if (!stream)
return NULL;
return kshark_get_filter(stream, filter_id);
}
/**
* @brief Get an Id Filter.
*
* @param stream: Input location for a Trace data stream pointer.
* @param filter_id: Identifier of the filter.
*/
struct kshark_hash_id *
kshark_get_filter(struct kshark_data_stream *stream,
enum kshark_filter_type filter_id)
{
switch (filter_id) {
case KS_SHOW_CPU_FILTER:
return stream->show_cpu_filter;
case KS_HIDE_CPU_FILTER:
return stream->hide_cpu_filter;
case KS_SHOW_EVENT_FILTER:
return stream->show_event_filter;
case KS_HIDE_EVENT_FILTER:
return stream->hide_event_filter;
case KS_SHOW_TASK_FILTER:
return stream->show_task_filter;
case KS_HIDE_TASK_FILTER:
return stream->hide_task_filter;
default:
return NULL;
}
}
/**
* @brief Add an Id value to the filter specified by "filter_id".
*
* @param kshark_ctx: Input location for the session context pointer.
* @param sd: Data stream identifier.
* @param filter_id: Identifier of the filter.
* @param id: Id value to be added to the filter.
*/
void kshark_filter_add_id(struct kshark_context *kshark_ctx, int sd,
int filter_id, int id)
{
struct kshark_hash_id *filter;
filter = get_filter(kshark_ctx, sd, filter_id);
if (filter)
kshark_hash_id_add(filter, id);
}
/**
* @brief Get an array containing all Ids associated with a given Id Filter.
*
* @param kshark_ctx: Input location for context pointer.
* @param sd: Data stream identifier.
* @param filter_id: Identifier of the filter.
* @param n: Output location for the size of the returned array.
*
* @return The user is responsible for freeing the array.
*/
int *kshark_get_filter_ids(struct kshark_context *kshark_ctx, int sd,
int filter_id, int *n)
{
struct kshark_hash_id *filter;
filter = get_filter(kshark_ctx, sd, filter_id);
if (filter) {
if (n)
*n = filter->count;
return kshark_hash_ids(filter);
}
if (n)
*n = 0;
return NULL;
}
/**
* @brief Clear (reset) the filter specified by "filter_id".
*
* @param kshark_ctx: Input location for the session context pointer.
* @param sd: Data stream identifier.
* @param filter_id: Identifier of the filter.
*/
void kshark_filter_clear(struct kshark_context *kshark_ctx, int sd,
int filter_id)
{
struct kshark_hash_id *filter;
filter = get_filter(kshark_ctx, sd, filter_id);
if (filter)
kshark_hash_id_clear(filter);
}
/**
* @brief Check if a given Id filter is set.
*
* @param filter: Input location for the Id filster.
*
* @returns True if the Id filter is set, otherwise False.
*/
bool kshark_this_filter_is_set(struct kshark_hash_id *filter)
{
return filter && filter->count;
}
/**
* @brief Check if an Id filter is set.
*
* @param kshark_ctx: Input location for the session context pointer.
* @param sd: Data stream identifier.
*
* @returns True if at least one Id filter of the stream is set, otherwise
* False.
*/
bool kshark_filter_is_set(struct kshark_context *kshark_ctx, int sd)
{
struct kshark_data_stream *stream;
stream = get_stream_object(kshark_ctx, sd);
if (!stream)
return false;
return kshark_this_filter_is_set(stream->show_task_filter) ||
kshark_this_filter_is_set(stream->hide_task_filter) ||
kshark_this_filter_is_set(stream->show_cpu_filter) ||
kshark_this_filter_is_set(stream->hide_cpu_filter) ||
kshark_this_filter_is_set(stream->show_event_filter) ||
kshark_this_filter_is_set(stream->hide_event_filter);
}
/**
* @brief Apply filters to a given entry.
*
* @param kshark_ctx: Input location for the session context pointer.
* @param stream: Input location for a Trace data stream pointer.
* @param entry: Input location for entry.
*/
void kshark_apply_filters(struct kshark_context *kshark_ctx,
struct kshark_data_stream *stream,
struct kshark_entry *entry)
{
/* Apply event filtering. */
if (!kshark_show_event(stream, entry->event_id))
unset_event_filter_flag(kshark_ctx, entry);
/* Apply CPU filtering. */
if (!kshark_show_cpu(stream, entry->cpu))
entry->visible &= ~kshark_ctx->filter_mask;
/* Apply task filtering. */
if (!kshark_show_task(stream, entry->pid))
entry->visible &= ~kshark_ctx->filter_mask;
}
static void set_all_visible(uint16_t *v) {
/* Keep the original value of the PLUGIN_UNTOUCHED bit flag. */
*v |= 0xFF & ~KS_PLUGIN_UNTOUCHED_MASK;
}
static void filter_entries(struct kshark_context *kshark_ctx, int sd,
struct kshark_entry **data, size_t n_entries)
{
struct kshark_data_stream *stream = NULL;
size_t i;
/* Sanity checks before starting. */
if (sd >= 0) {
/* We will filter particular Data stream. */
stream = kshark_get_data_stream(kshark_ctx, sd);
if (!stream)
return;
if (kshark_is_tep(stream) &&
kshark_tep_filter_is_set(stream)) {
/* The advanced filter is set. */
fprintf(stderr,
"Failed to filter (sd = %i)!\n", sd);
fprintf(stderr,
"Reset the Advanced filter or reload the data.\n");
return;
}
if (!kshark_filter_is_set(kshark_ctx, sd) &&
!stream->filter_is_applied) {
/* Nothing to be done. */
return;
}
}
/* Apply only the Id filters. */
for (i = 0; i < n_entries; ++i) {
if (sd >= 0) {
/*
* We only filter particular stream. Chack is the entry
* belongs to this stream.
*/
if (data[i]->stream_id != sd)
continue;
} else {
/* We filter all streams. */
stream = kshark_ctx->stream[data[i]->stream_id];
}
/* Start with and entry which is visible everywhere. */
set_all_visible(&data[i]->visible);
/* Apply Id filtering. */
kshark_apply_filters(kshark_ctx, stream, data[i]);
stream->filter_is_applied =
kshark_filter_is_set(kshark_ctx, sd)? true : false;
}
}
/**
* @brief This function loops over the array of entries specified by "data"
* and "n_entries" and sets the "visible" fields of each entry from a
* given Data stream according to the criteria provided by the filters
* of the session's context. The field "filter_mask" of the session's
* context is used to control the level of visibility/invisibility of
* the entries which are filtered-out.
* WARNING: Do not use this function if the advanced filter is set.
* Applying the advanced filter requires access to prevent_record,
* hence the data has to be reloaded using kshark_load_entries().
*
* @param kshark_ctx: Input location for the session context pointer.
* @param sd: Data stream identifier.
* @param data: Input location for the trace data to be filtered.
* @param n_entries: The size of the inputted data.
*/
void kshark_filter_stream_entries(struct kshark_context *kshark_ctx,
int sd,
struct kshark_entry **data,
size_t n_entries)
{
if (sd >= 0)
filter_entries(kshark_ctx, sd, data, n_entries);
}
/**
* @brief This function loops over the array of entries specified by "data"
* and "n_entries" and sets the "visible" fields of each entry from
* all Data stream according to the criteria provided by the filters
* of the session's context. The field "filter_mask" of the session's
* context is used to control the level of visibility/invisibility of
* the entries which are filtered-out.
* WARNING: Do not use this function if the advanced filter is set.
* Applying the advanced filter requires access to prevent_record,
* hence the data has to be reloaded using kshark_load_data_entries().
*
* @param kshark_ctx: Input location for the session context pointer.
* @param data: Input location for the trace data to be filtered.
* @param n_entries: The size of the inputted data.
*/
void kshark_filter_all_entries(struct kshark_context *kshark_ctx,
struct kshark_entry **data, size_t n_entries)
{
filter_entries(kshark_ctx, -1, data, n_entries);
}
/**
* @brief This function loops over the array of entries specified by "data"
* and "n_entries" and resets the "visible" fields of each entry to
* the default value of "0xFF" (visible everywhere).
*
* @param kshark_ctx: Input location for the session context pointer.
* @param data: Input location for the trace data to be unfiltered.
* @param n_entries: The size of the inputted data.
*/
void kshark_clear_all_filters(struct kshark_context *kshark_ctx,
struct kshark_entry **data,
size_t n_entries)
{
struct kshark_data_stream *stream;
int *stream_ids, i;
for (i = 0; i < n_entries; ++i)
set_all_visible(&data[i]->visible);
stream_ids = kshark_all_streams(kshark_ctx);
for (i = 0; i < kshark_ctx->n_streams; i++) {
stream = kshark_get_data_stream(kshark_ctx, stream_ids[i]);
stream->filter_is_applied = false;
}
free(stream_ids);
}
/**
* @brief Process all registered event-specific plugin actions.
*
* @param stream: Input location for a Trace data stream pointer.
* @param record: Input location for the trace record.
* @param entry: Output location for entry.
*/
void kshark_plugin_actions(struct kshark_data_stream *stream,
void *record, struct kshark_entry *entry)
{
if (stream->event_handlers) {
/* Execute all plugin-provided actions for this event (if any). */
struct kshark_event_proc_handler *evt_handler = stream->event_handlers;
while ((evt_handler = kshark_find_event_handler(evt_handler,
entry->event_id))) {
evt_handler->event_func(stream, record, entry);
evt_handler = evt_handler->next;
entry->visible &= ~KS_PLUGIN_UNTOUCHED_MASK;
}
}
}
/**
* @brief Time calibration of the timestamp of the entry.
*
* @param stream: Input location for a Trace data stream pointer.
* @param entry: Output location for entry.
*/
void kshark_calib_entry(struct kshark_data_stream *stream,
struct kshark_entry *entry)
{
if (stream->calib && stream->calib_array) {
/* Calibrate the timestamp of the entry. */
stream->calib(&entry->ts, stream->calib_array);
}
}
/**
* @brief Post-process the content of the entry. This includes time calibration
* and all registered event-specific plugin actions.
*
* @param stream: Input location for a Trace data stream pointer.
* @param record: Input location for the trace record.
* @param entry: Output location for entry.
*/
void kshark_postprocess_entry(struct kshark_data_stream *stream,
void *record, struct kshark_entry *entry)
{
kshark_calib_entry(stream, entry);
kshark_plugin_actions(stream, record, entry);
}
static inline void free_ptr(void *ptr)
{
if (ptr)
free(*(void **)ptr);
}
/**
* @brief Allocate data arrays (matrix columns) to be used to load the tracing
* data into a data matrix form.
*
* @param n_rows: Number matrix rows to be allocated. Must be equal to the
* number of trace records.
* @param cpu_array: Output location for the CPU Id column.
* @param pid_array: Output location for the PID column.
* @param event_array: Output location for the Event Id column.
* @param offset_array: Output location for the record offset column.
* @param ts_array: Output location for the timestamp column.
*
* @returns True on success. Else false.
*/
bool kshark_data_matrix_alloc(size_t n_rows, int16_t **event_array,
int16_t **cpu_array,
int32_t **pid_array,
int64_t **offset_array,
int64_t **ts_array)
{
if (offset_array) {
*offset_array = calloc(n_rows, sizeof(**offset_array));
if (!*offset_array)
return false;
}
if (cpu_array) {
*cpu_array = calloc(n_rows, sizeof(**cpu_array));
if (!*cpu_array)
goto free_offset;
}
if (ts_array) {
*ts_array = calloc(n_rows, sizeof(**ts_array));
if (!*ts_array)
goto free_cpu;
}
if (pid_array) {
*pid_array = calloc(n_rows, sizeof(**pid_array));
if (!*pid_array)
goto free_ts;
}
if (event_array) {
*event_array = calloc(n_rows, sizeof(**event_array));
if (!*event_array)
goto free_pid;
}
return true;
free_pid:
free_ptr(pid_array);
free_ts:
free_ptr(ts_array);
free_cpu:
free_ptr(cpu_array);
free_offset:
free_ptr(offset_array);
fprintf(stderr, "Failed to allocate memory during data loading.\n");
return false;
}
/**
* @brief Convert the timestamp of the trace record (nanosecond precision) into
* seconds and microseconds.
*
* @param time: Input location for the timestamp.
* @param sec: Output location for the value of the seconds.
* @param usec: Output location for the value of the microseconds.
*/
void kshark_convert_nano(uint64_t time, uint64_t *sec, uint64_t *usec)
{
uint64_t s;
*sec = s = time / 1000000000ULL;
*usec = (time - s * 1000000000ULL) / 1000;
}
/**
* @brief Binary search inside a time-sorted array of kshark_entries.
*
* @param time: The value of time to search for.
* @param data: Input location for the trace data.
* @param l: Array index specifying the lower edge of the range to search in.
* @param h: Array index specifying the upper edge of the range to search in.
*
* @returns On success, the first kshark_entry inside the range, having a
* timestamp equal or bigger than "time".
* If all entries inside the range have timestamps greater than "time"
* the function returns BSEARCH_ALL_GREATER (negative value).
* If all entries inside the range have timestamps smaller than "time"
* the function returns BSEARCH_ALL_SMALLER (negative value).
*/
ssize_t kshark_find_entry_by_time(int64_t time,
struct kshark_entry **data,
size_t l, size_t h)
{
size_t mid;
if (data[l]->ts > time)
return BSEARCH_ALL_GREATER;
if (data[h]->ts < time)
return BSEARCH_ALL_SMALLER;
/*
* After executing the BSEARCH macro, "l" will be the index of the last
* entry having timestamp < time and "h" will be the index of the first
* entry having timestamp >= time.
*/
BSEARCH(h, l, data[mid]->ts < time);
return h;
}
/**
* @brief Simple Pid matching function to be user for data requests.
*
* @param kshark_ctx: Input location for the session context pointer.
* @param e: kshark_entry to be checked.
* @param sd: Data stream identifier.
* @param pid: Matching condition value.
*
* @returns True if the Pid of the entry matches the value of "pid".
* Else false.
*/
bool kshark_match_pid(struct kshark_context *kshark_ctx,
struct kshark_entry *e, int sd, int *pid)
{
if (e->stream_id == sd && e->pid == *pid)
return true;
return false;
}
/**
* @brief Simple Cpu matching function to be user for data requests.
*
* @param kshark_ctx: Input location for the session context pointer.
* @param e: kshark_entry to be checked.
* @param sd: Data stream identifier.
* @param cpu: Matching condition value.
*
* @returns True if the Cpu of the entry matches the value of "cpu".
* Else false.
*/
bool kshark_match_cpu(struct kshark_context *kshark_ctx,
struct kshark_entry *e, int sd, int *cpu)
{
if (e->stream_id == sd && e->cpu == *cpu)
return true;
return false;
}
/**
* @brief Simple event Id matching function to be user for data requests.
*
* @param kshark_ctx: Input location for the session context pointer.
* @param e: kshark_entry to be checked.
* @param sd: Data stream identifier.
* @param event_id: Matching condition value.
*
* @returns True if the event Id of the entry matches the value of "event_id".
* Else false.
*/
bool kshark_match_event_id(struct kshark_context *kshark_ctx,
struct kshark_entry *e, int sd, int *event_id)
{
return e->stream_id == sd && e->event_id == *event_id;
}
/**
* @brief Simple Event Id and PID matching function to be user for data requests.
*
* @param kshark_ctx: Input location for the session context pointer.
* @param e: kshark_entry to be checked.
* @param sd: Data stream identifier.
* @param values: An array of matching condition value.
* values[0] is the matches PID and values[1] is the matches event Id.
*
* @returns True if the event Id of the entry matches the values.
* Else false.
*/
bool kshark_match_event_and_pid(struct kshark_context *kshark_ctx,
struct kshark_entry *e,
int sd, int *values)
{
return e->stream_id == sd &&
e->event_id == values[0] &&
e->pid == values[1];
}
/**
* @brief Simple Event Id and CPU matching function to be user for data requests.
*
* @param kshark_ctx: Input location for the session context pointer.
* @param e: kshark_entry to be checked.
* @param sd: Data stream identifier.
* @param values: An array of matching condition value.
* values[0] is the matches PID and values[1] is the matches event Id.
*
* @returns True if the event Id of the entry matches the values.
* Else false.
*/
bool kshark_match_event_and_cpu(struct kshark_context *kshark_ctx,
struct kshark_entry *e,
int sd, int *values)
{
return e->stream_id == sd &&
e->event_id == values[0] &&
e->cpu == values[1];
}
/**
* @brief Create Data request. The request defines the properties of the
* requested kshark_entry.
*
* @param first: Array index specifying the position inside the array from
* where the search starts.
* @param n: Number of array elements to search in.
* @param cond: Matching condition function.
* @param sd: Data stream identifier.
* @param values: Matching condition values, used by the Matching condition
* function.
* @param vis_only: If true, a visible entry is requested.
* @param vis_mask: If "vis_only" is true, use this mask to specify the level
* of visibility of the requested entry.
*
* @returns Pointer to kshark_entry_request on success, or NULL on failure.
* The user is responsible for freeing the returned
* kshark_entry_request.
*/
struct kshark_entry_request *
kshark_entry_request_alloc(size_t first, size_t n,
matching_condition_func cond, int sd, int *values,
bool vis_only, int vis_mask)
{
struct kshark_entry_request *req = malloc(sizeof(*req));
if (!req) {
fprintf(stderr,
"Failed to allocate memory for entry request.\n");
return NULL;
}
req->next = NULL;
req->first = first;
req->n = n;
req->cond = cond;
req->sd = sd;
req->values = values;
req->vis_only = vis_only;
req->vis_mask = vis_mask;
return req;
}
/**
* @brief Free all Data requests in a given list.
* @param req: Intput location for the Data request list.
*/
void kshark_free_entry_request(struct kshark_entry_request *req)
{
struct kshark_entry_request *last;
while (req) {
last = req;
req = req->next;
free(last);
}
}
/** Dummy entry, used to indicate the existence of filtered entries. */
const struct kshark_entry dummy_entry = {
.next = NULL,
.visible = 0x00,
.cpu = KS_FILTERED_BIN,
.pid = KS_FILTERED_BIN,
.event_id = -1,
.offset = 0,
.ts = 0
};
static const struct kshark_entry *
get_entry(const struct kshark_entry_request *req,
struct kshark_entry **data,
ssize_t *index, ssize_t start, ssize_t end, int inc)
{
struct kshark_context *kshark_ctx = NULL;
const struct kshark_entry *e = NULL;
ssize_t i;
if (index)
*index = KS_EMPTY_BIN;
if (!kshark_instance(&kshark_ctx))
return e;
/*
* We will do a sanity check in order to protect against infinite
* loops.
*/
assert((inc > 0 && start < end) || (inc < 0 && start > end));
for (i = start; i != end; i += inc) {
if (req->cond(kshark_ctx, data[i], req->sd, req->values)) {
/*
* Data satisfying the condition has been found.
*/
if (req->vis_only &&
!(data[i]->visible & req->vis_mask)) {
/* This data entry has been filtered. */
e = &dummy_entry;
} else {
e = data[i];
break;
}
}
}
if (index) {
if (e)
*index = (e->cpu != KS_FILTERED_BIN)? i : KS_FILTERED_BIN;
else
*index = KS_EMPTY_BIN;
}
return e;
}
/**
* @brief Search for an entry satisfying the requirements of a given Data
* request. Start from the position provided by the request and go
* searching in the direction of the increasing timestamps (front).
*
* @param req: Input location for Data request.
* @param data: Input location for the trace data.
* @param index: Optional output location for the index of the returned
* entry inside the array.
*
* @returns Pointer to the first entry satisfying the matching conditionon
* success, or NULL on failure.
* In the special case when some entries, satisfying the Matching
* condition function have been found, but all these entries have
* been discarded because of the visibility criteria (filtered
* entries), the function returns a pointer to a special
* "Dummy entry".
*/
const struct kshark_entry *
kshark_get_entry_front(const struct kshark_entry_request *req,
struct kshark_entry **data,
ssize_t *index)
{
ssize_t end = req->first + req->n;
return get_entry(req, data, index, req->first, end, +1);
}
/**
* @brief Search for an entry satisfying the requirements of a given Data
* request. Start from the position provided by the request and go
* searching in the direction of the decreasing timestamps (back).
*
* @param req: Input location for Data request.
* @param data: Input location for the trace data.
* @param index: Optional output location for the index of the returned
* entry inside the array.
*
* @returns Pointer to the first entry satisfying the matching conditionon
* success, or NULL on failure.
* In the special case when some entries, satisfying the Matching
* condition function have been found, but all these entries have
* been discarded because of the visibility criteria (filtered
* entries), the function returns a pointer to a special
* "Dummy entry".
*/
const struct kshark_entry *
kshark_get_entry_back(const struct kshark_entry_request *req,
struct kshark_entry **data,
ssize_t *index)
{
ssize_t end = req->first - req->n;
if (end < 0)
end = -1;
return get_entry(req, data, index, req->first, end, -1);
}
static int compare_time(const void* a, const void* b)
{
const struct kshark_entry *entry_a, *entry_b;
entry_a = *(const struct kshark_entry **) a;
entry_b = *(const struct kshark_entry **) b;
if (entry_a->ts > entry_b->ts)
return 1;
if (entry_a->ts < entry_b->ts)
return -1;
return 0;
}
static void kshark_data_qsort(struct kshark_entry **entries, size_t size)
{
qsort(entries, size, sizeof(struct kshark_entry *), compare_time);
}
/**
* Add constant offset to the timestamp of the entry. To be used by the sream
* object as a System clock calibration callback function.
*/
void kshark_offset_calib(int64_t *ts, int64_t *argv)
{
*ts += argv[0];
}
/**
* @brief Apply constant offset to the timestamps of all entries from a given
* Data stream.
*
* @param kshark_ctx: Input location for the session context pointer.
* @param entries: Input location for the trace data.
* @param size: The size of the trace data.
* @param sd: Data stream identifier.
* @param offset: The constant offset to be added (in nanosecond).
*/
void kshark_set_clock_offset(struct kshark_context *kshark_ctx,
struct kshark_entry **entries, size_t size,
int sd, int64_t offset)
{
struct kshark_data_stream *stream;
int64_t correction;
stream = kshark_get_data_stream(kshark_ctx, sd);
if (!stream)
return;
if (!stream->calib_array) {
stream->calib = kshark_offset_calib;
stream->calib_array = calloc(1, sizeof(*stream->calib_array));
stream->calib_array_size = 1;
}
correction = offset - stream->calib_array[0];
stream->calib_array[0] = offset;
for (size_t i = 0; i < size; ++i)
if (entries[i]->stream_id == sd)
entries[i]->ts += correction;
kshark_data_qsort(entries, size);
}
static int first_in_time_entry(struct kshark_entry_data_set *buffer, int n_buffers, size_t *count)
{
int64_t t_min = INT64_MAX;
int i, min = -1;
for (i = 0; i < n_buffers; ++i) {
if (count[i] == buffer[i].n_rows)
continue;
if (t_min > buffer[i].data[count[i]]->ts) {
t_min = buffer[i].data[count[i]]->ts;
min = i;
}
}
return min;
}
/**
* @brief Merge trace data streams.
*
* @param buffers: Input location for the data-sets to be merged.
* @param n_buffers: The number of the data-sets to be merged.
*
* @returns Merged and sorted in time trace data entries. The user is
* responsible for freeing the elements of the outputted array.
*/
struct kshark_entry **
kshark_merge_data_entries(struct kshark_entry_data_set *buffers, int n_buffers)
{
struct kshark_entry **merged_data;
size_t i, tot = 0, count[n_buffers];
int i_first;
if (n_buffers < 2) {
fputs("kshark_merge_data_entries needs multipl data sets.\n",
stderr);
return NULL;
}
for (i = 0; i < n_buffers; ++i) {
count[i] = 0;
if (buffers[i].n_rows > 0)
tot += buffers[i].n_rows;
}
merged_data = calloc(tot, sizeof(*merged_data));
if (!merged_data) {
fputs("Failed to allocate memory for mergeing data entries.\n",
stderr);
return NULL;
}
for (i = 0; i < tot; ++i) {
i_first = first_in_time_entry(buffers, n_buffers, count);
assert(i_first >= 0);
merged_data[i] = buffers[i_first].data[count[i_first]];
++count[i_first];
}
return merged_data;
}
static ssize_t load_all_entries(struct kshark_context *kshark_ctx,
struct kshark_entry **loaded_rows,
ssize_t n_loaded,
int sd_first_new, int n_streams,
struct kshark_entry ***data_rows)
{
int i, j = 0, n_data_sets;
ssize_t data_size = 0;
if (n_streams <= 0 || sd_first_new < 0)
return data_size;
n_data_sets = n_streams - sd_first_new;
if (loaded_rows && n_loaded > 0)
++n_data_sets;
struct kshark_entry_data_set buffers[n_data_sets];
memset(buffers, 0, sizeof(buffers));
if (loaded_rows && n_loaded > 0) {
/* Add the data that is already loaded. */
data_size = buffers[n_data_sets - 1].n_rows = n_loaded;
buffers[n_data_sets - 1].data = loaded_rows;
}
/* Add the data of the new streams. */
for (i = sd_first_new; i < n_streams; ++i) {
buffers[j].data = NULL;
buffers[j].n_rows = kshark_load_entries(kshark_ctx, i,
&buffers[j].data);
if (buffers[j].n_rows < 0) {
/* Loading failed. */
data_size = buffers[j].n_rows;
goto error;
}
data_size += buffers[j++].n_rows;
}
if (n_data_sets == 1) {
*data_rows = buffers[0].data;
} else {
/* Merge all streams. */
*data_rows = kshark_merge_data_entries(buffers, n_data_sets);
}
error:
for (i = 1; i < n_data_sets; ++i)
free(buffers[i].data);
return data_size;
}
/**
* @brief Load the content of the all opened data file into an array of
* kshark_entries.
* If one or more filters are set, the "visible" fields of each entry
* is updated according to the criteria provided by the filters. The
* field "filter_mask" of the session's context is used to control the
* level of visibility/invisibility of the filtered entries.
*
* @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_all_entries(struct kshark_context *kshark_ctx,
struct kshark_entry ***data_rows)
{
return load_all_entries(kshark_ctx,
NULL, 0,
0,
kshark_ctx->n_streams,
data_rows);
}
/**
* @brief Append the content of the all opened data file into an array of
* kshark_entries.
* If one or more filters are set, the "visible" fields of each entry
* is updated according to the criteria provided by the filters. The
* field "filter_mask" of the session's context is used to control the
* level of visibility/invisibility of the filtered entries.
*
* @param kshark_ctx: Input location for context pointer.
* @param prior_data: Input location for the already loaded trace data.
* @param n_prior_rows: The size of the already loaded trace data.
* @param sd_first_new: Data stream identifier of the first data stream to be
* appended.
* @param merged_data: 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_append_all_entries(struct kshark_context *kshark_ctx,
struct kshark_entry **prior_data,
ssize_t n_prior_rows,
int sd_first_new,
struct kshark_entry ***merged_data)
{
return load_all_entries(kshark_ctx,
prior_data,
n_prior_rows,
sd_first_new,
kshark_ctx->n_streams,
merged_data);
}
static int first_in_time_row(struct kshark_matrix_data_set *buffers, int n_buffers, size_t *count)
{
int64_t t_min = INT64_MAX;
int i, min = -1;
for (i = 0; i < n_buffers; ++i) {
if (count[i] == buffers[i].n_rows)
continue;
if (t_min > buffers[i].ts_array[count[i]]) {
t_min = buffers[i].ts_array[count[i]];
min = i;
}
}
return min;
}
/**
* @brief Merge trace data streams.
*
* @param buffers: Input location for the data-sets to be merged.
* @param n_buffers: The number of the data-sets to be merged.
*
* @returns Merged and sorted in time trace data matrix. The user is
* responsible for freeing the columns (arrays) of the outputted
* matrix.
*/
struct kshark_matrix_data_set
kshark_merge_data_matrices(struct kshark_matrix_data_set *buffers, int n_buffers)
{
struct kshark_matrix_data_set merged_data;
size_t i, tot = 0, count[n_buffers];
int i_first;
bool status;
merged_data.n_rows = -1;
if (n_buffers < 2) {
fputs("kshark_merge_data_matrices needs multipl data sets.\n",
stderr);
goto end;
}
for (i = 0; i < n_buffers; ++i) {
count[i] = 0;
if (buffers[i].n_rows > 0)
tot += buffers[i].n_rows;
}
status = kshark_data_matrix_alloc(tot, &merged_data.event_array,
&merged_data.cpu_array,
&merged_data.pid_array,
&merged_data.offset_array,
&merged_data.ts_array);
if (!status) {
fputs("Failed to allocate memory for mergeing data matrices.\n",
stderr);
goto end;
}
merged_data.n_rows = tot;
for (i = 0; i < tot; ++i) {
i_first = first_in_time_row(buffers, n_buffers, count);
assert(i_first >= 0);
merged_data.cpu_array[i] = buffers[i_first].cpu_array[count[i_first]];
merged_data.pid_array[i] = buffers[i_first].pid_array[count[i_first]];
merged_data.event_array[i] = buffers[i_first].event_array[count[i_first]];
merged_data.offset_array[i] = buffers[i_first].offset_array[count[i_first]];
merged_data.ts_array[i] = buffers[i_first].ts_array[count[i_first]];
++count[i_first];
}
end:
return merged_data;
}
/** @brief Allocate memory for kshark_data_container. */
struct kshark_data_container *kshark_init_data_container()
{
struct kshark_data_container *container;
container = calloc(1, sizeof(*container));
if (!container)
goto fail;
container->data = calloc(KS_CONTAINER_DEFAULT_SIZE,
sizeof(*container->data));
if (!container->data)
goto fail;
container->capacity = KS_CONTAINER_DEFAULT_SIZE;
container->sorted = false;
return container;
fail:
fprintf(stderr, "Failed to allocate memory for data container.\n");
kshark_free_data_container(container);
return NULL;
}
/**
* @brief Free the memory allocated for a kshark_data_container
* @param container: Input location for the kshark_data_container object.
*/
void kshark_free_data_container(struct kshark_data_container *container)
{
if (!container)
return;
for (ssize_t i = 0; i < container->size; ++i)
free(container->data[i]);
free(container->data);
free(container);
}
/**
* @brief Append data field value to a kshark_data_container
* @param container: Input location for the kshark_data_container object.
* @param entry: The entry that needs addition data field value.
* @param field: The value of data field to be added.
*
* @returns The size of the container after the addition.
*/
ssize_t kshark_data_container_append(struct kshark_data_container *container,
struct kshark_entry *entry, int64_t field)
{
struct kshark_data_field_int64 *data_field;
if (container->capacity == container->size) {
if (!KS_DOUBLE_SIZE(container->data,
container->capacity))
return -ENOMEM;
}
data_field = malloc(sizeof(*data_field));
data_field->entry = entry;
data_field->field = field;
container->data[container->size++] = data_field;
return container->size;
}
static int compare_time_dc(const void* a, const void* b)
{
const struct kshark_data_field_int64 *field_a, *field_b;
field_a = *(const struct kshark_data_field_int64 **) a;
field_b = *(const struct kshark_data_field_int64 **) b;
if (field_a->entry->ts > field_b->entry->ts)
return 1;
if (field_a->entry->ts < field_b->entry->ts)
return -1;
return 0;
}
/**
* @brief Sort in time the records in kshark_data_container. The container is
* resized in order to free the unused memory capacity.
*
* @param container: Input location for the kshark_data_container object.
*/
void kshark_data_container_sort(struct kshark_data_container *container)
{
struct kshark_data_field_int64 **data_tmp;
qsort(container->data, container->size,
sizeof(struct kshark_data_field_int64 *),
compare_time_dc);
container->sorted = true;
data_tmp = realloc(container->data,
container->size * sizeof(*container->data));
if (!data_tmp)
return;
container->data = data_tmp;
container->capacity = container->size;
}
/**
* @brief Binary search inside a time-sorted array of kshark_data_field_int64.
*
* @param time: The value of time to search for.
* @param data: Input location for the data.
* @param l: Array index specifying the lower edge of the range to search in.
* @param h: Array index specifying the upper edge of the range to search in.
*
* @returns On success, the index of the first kshark_data_field_int64 inside
* the range, having a timestamp equal or bigger than "time".
* If all fields inside the range have timestamps greater than "time"
* the function returns BSEARCH_ALL_GREATER (negative value).
* If all fields inside the range have timestamps smaller than "time"
* the function returns BSEARCH_ALL_SMALLER (negative value).
*/
ssize_t kshark_find_entry_field_by_time(int64_t time,
struct kshark_data_field_int64 **data,
size_t l, size_t h)
{
size_t mid;
if (data[l]->entry->ts > time)
return BSEARCH_ALL_GREATER;
if (data[h]->entry->ts < time)
return BSEARCH_ALL_SMALLER;
/*
* After executing the BSEARCH macro, "l" will be the index of the last
* entry having timestamp < time and "h" will be the index of the first
* entry having timestamp >= time.
*/
BSEARCH(h, l, data[mid]->entry->ts < time);
return h;
}