blob: c70da1ef6349bcc2fc242ee66d8ec2088987453f [file] [log] [blame]
// SPDX-License-Identifier: LGPL-2.1
/*
* Copyright (C) 2018 VMware Inc, Yordan Karadzhov <y.karadz@gmail.com>
*/
/**
* @file libkshark-collection.c
* @brief Data Collections.
*/
// C
#include <stdbool.h>
#include <stdlib.h>
#include <assert.h>
#include <errno.h>
// KernelShark
#include "libkshark.h"
/* Quiet warnings over documenting simple structures */
//! @cond Doxygen_Suppress
enum collection_point_type {
COLLECTION_IGNORE = 0,
COLLECTION_RESUME,
COLLECTION_BREAK,
};
#define LAST_BIN -3
struct entry_list {
struct entry_list *next;
size_t index;
uint8_t type;
};
enum map_flags {
COLLECTION_BEFORE = -1,
COLLECTION_INSIDE = 0,
COLLECTION_AFTER = 1,
};
//! @endcond
/*
* If the type of the last added entry is COLLECTION_IGNORE, overwrite this
* entry (ignore the old entry values). Else add a new entry to the list.
*/
static bool collection_add_entry(struct entry_list **list,
size_t i, uint8_t type)
{
struct entry_list *entry = *list;
if (entry->type != COLLECTION_IGNORE) {
entry->next = malloc(sizeof(*entry));
if (!entry->next)
return false;
entry = entry->next;
*list = entry;
}
entry->index = i;
entry->type = type;
return true;
}
static struct kshark_entry_collection *
kshark_data_collection_alloc(struct kshark_context *kshark_ctx,
struct kshark_entry **data,
size_t first,
size_t n_rows,
matching_condition_func cond,
int val,
size_t margin)
{
struct kshark_entry_collection *col_ptr = NULL;
struct kshark_entry *last_vis_entry = NULL;
struct entry_list *col_list, *temp;
size_t resume_count = 0, break_count = 0;
size_t i, j, end, last_added = 0;
bool good_data = false;
col_list = malloc(sizeof(*col_list));
if (!col_list)
goto fail;
temp = col_list;
if (margin != 0) {
/*
* If this collection includes margin data, add a margin data
* interval at the very beginning of the data-set.
*/
temp->index = first;
temp->type = COLLECTION_RESUME;
++resume_count;
collection_add_entry(&temp, first + margin - 1,
COLLECTION_BREAK);
++break_count;
} else {
temp->type = COLLECTION_IGNORE;
}
end = first + n_rows - margin;
for (i = first + margin; i < end; ++i) {
if (!cond(kshark_ctx, data[i], val)) {
/*
* The entry is irrelevant for this collection.
* Do nothing.
*/
continue;
}
/* The Matching condition is satisfed. */
if (!good_data) {
/*
* Resume the collection here. Add some margin data
* in front of the data of interest.
*/
good_data = true;
if (last_added == 0 || last_added < i - margin) {
collection_add_entry(&temp, i - margin,
COLLECTION_RESUME);
++resume_count;
} else {
/*
* Ignore the last collection Break point.
* Continue extending the previous data
* interval.
*/
temp->type = COLLECTION_IGNORE;
--break_count;
}
} else if (good_data &&
data[i]->next &&
!cond(kshark_ctx, data[i]->next, val)) {
/*
* Break the collection here. Add some margin data
* after the data of interest.
*/
good_data = false;
last_vis_entry = data[i];
/* Keep adding entries until the "next" record. */
for (j = i + 1;
j != end && last_vis_entry->next != data[j];
j++)
;
/*
* If the number of added entries is smaller than the
* number of margin entries requested, keep adding
* until you fill the margin.
*/
if (i + margin >= j) {
for (;j < i + margin; ++j) {
if (cond(kshark_ctx, data[j], val)) {
/*
* Good data has been found.
* Continue extending the
* previous data interval.
*/
good_data = true;
break;
}
}
}
last_added = i = j;
if (!good_data) {
collection_add_entry(&temp, i, COLLECTION_BREAK);
++break_count;
}
}
}
if (good_data) {
collection_add_entry(&temp, end - 1, COLLECTION_BREAK);
++break_count;
}
if (margin != 0) {
/*
* If this collection includes margin data, add a margin data
* interval at the very end of the data-set.
*/
collection_add_entry(&temp, first + n_rows - margin,
COLLECTION_RESUME);
++resume_count;
collection_add_entry(&temp, first + n_rows - 1,
COLLECTION_BREAK);
++break_count;
}
/*
* If everything is OK, we must have pairs of COLLECTION_RESUME
* and COLLECTION_BREAK points.
*/
assert(break_count == resume_count);
/* Create the collection. */
col_ptr = malloc(sizeof(*col_ptr));
if (!col_ptr)
goto fail;
col_ptr->next = NULL;
col_ptr->resume_points = calloc(resume_count,
sizeof(*col_ptr->resume_points));
if (!col_ptr->resume_points)
goto fail;
col_ptr->break_points = calloc(break_count,
sizeof(*col_ptr->break_points));
if (!col_ptr->break_points) {
free(col_ptr->resume_points);
goto fail;
}
col_ptr->cond = cond;
col_ptr->val = val;
col_ptr->size = resume_count;
for (i = 0; i < col_ptr->size; ++i) {
assert(col_list->type == COLLECTION_RESUME);
col_ptr->resume_points[i] = col_list->index;
temp = col_list;
col_list = col_list->next;
free(temp);
assert(col_list->type == COLLECTION_BREAK);
col_ptr->break_points[i] = col_list->index;
temp = col_list;
col_list = col_list->next;
free(temp);
}
return col_ptr;
fail:
fprintf(stderr, "Failed to allocate memory for Data collection.\n");
free(col_ptr);
for (i = 0; i < resume_count + break_count; ++i) {
temp = col_list;
col_list = col_list->next;
free(temp);
}
return NULL;
}
/*
* This function provides mapping between the index inside the data-set and
* the index of the collection interval. Additional output flag is used to
* resolve the ambiguity of the mapping. If the value of the flag is
* COLLECTION_INSIDE, the "source_index" is inside the returned interval. If
* the value of the flag is COLLECTION_BEFORE, the "source_index" is inside
* the gap before the returned interval. If the value of the flag is
* COLLECTION_AFTER, the "source_index" is inside the gap after the returned
* interval.
*/
static ssize_t
map_collection_index_from_source(const struct kshark_entry_collection *col,
size_t source_index, int *flag)
{
size_t l, h, mid;
if (!col->size)
return KS_EMPTY_BIN;
l = 0;
h = col->size - 1;
if (source_index < col->resume_points[l]) {
*flag = COLLECTION_BEFORE;
return l;
}
if (source_index >= col->resume_points[h]) {
if (source_index < col->break_points[h])
*flag = COLLECTION_INSIDE;
else
*flag = COLLECTION_AFTER;
return h;
}
BSEARCH(h, l, source_index > col->resume_points[mid]);
if (source_index <= col->break_points[l])
*flag = COLLECTION_INSIDE;
else
*flag = COLLECTION_AFTER;
return l;
}
static ssize_t
map_collection_request_init(const struct kshark_entry_collection *col,
struct kshark_entry_request **req,
bool front, size_t *end)
{
struct kshark_entry_request *req_tmp = *req;
int col_index_flag;
ssize_t col_index;
size_t req_end;
if (req_tmp->next || col->size == 0) {
fprintf(stderr, "Unexpected input in ");
fprintf(stderr, "map_collection_request_init()\n");
goto do_nothing;
}
req_end = front ? req_tmp->first + req_tmp->n - 1 :
req_tmp->first - req_tmp->n + 1;
/*
* Find the first Resume Point of the collection which is equal or
* greater than the first index of this request.
*/
col_index = map_collection_index_from_source(col,
req_tmp->first,
&col_index_flag);
/*
* The value of "col_index" is ambiguous. Use the "col_index_flag" to
* deal with all possible cases.
*/
if (col_index == KS_EMPTY_BIN) {
/* Empty collection. */
goto do_nothing;
}
if (col_index_flag == COLLECTION_AFTER) {
/*
* This request starts after the end of interval "col_index".
*/
if (front && (col_index == col->size - 1 ||
req_end < col->resume_points[col_index + 1])) {
/*
* No overlap between the collection and this front
* request. Do nothing.
*/
goto do_nothing;
} else if (!front && req_end > col->break_points[col_index]) {
/*
* No overlap between the collection and this back
* request. Do nothing.
*/
goto do_nothing;
}
/* Remember that the initial request starts in the gap between
* the end of "col_index" interval and the beginning of
* "col_index + 1" interval. If we process a Front request, we
* have to go forwards, so the proper place for starting our
* search will be the Resume point of the "col_index + 1"
* interval. However, if we process a Back request, we will be
* going backwards, so the proper place to start will be the
* Break point of "col_index".
*/
req_tmp->first = front ? col->resume_points[++col_index] :
col->break_points[col_index];
}
if (col_index_flag == COLLECTION_BEFORE) {
/*
* This request starts before the beginning of interval
* "col_index".
*/
if (!front && (col_index == 0 ||
req_end > col->break_points[col_index - 1])) {
/*
* No overlap between the collection and this back
* request. Do nothing.
*/
goto do_nothing;
} else if (front && req_end < col->resume_points[col_index]) {
/*
* No overlap between the collection and this front
* request. Do nothing.
*/
goto do_nothing;
}
/* Remember that the initial request starts in the gap between
* the end of "col_index - 1" interval and the beginning of
* "col_index" interval. If we process a Front request, we have
* to go forwards, so the proper place for starting our search
* will be the Resume point of the "col_index" interval.
* However, if we process a Back request, we will be going
* backwards, so the proper place to start will be the Break
* point of "col_index - 1".
*/
req_tmp->first = front ? col->resume_points[col_index] :
col->break_points[--col_index];
}
*end = req_end;
return col_index;
do_nothing:
kshark_free_entry_request(*req);
*req = NULL;
*end = KS_EMPTY_BIN;
return KS_EMPTY_BIN;
}
/*
* This function uses the intervals of the Data collection to transform the
* inputted single data request into a list of data requests. The new list of
* request will ignore the data outside of the intervals of the collection.
*/
static int
map_collection_back_request(const struct kshark_entry_collection *col,
struct kshark_entry_request **req)
{
struct kshark_entry_request *req_tmp;
size_t req_first, req_end;
ssize_t col_index;
int req_count;
col_index = map_collection_request_init(col, req, false, &req_end);
if (col_index == KS_EMPTY_BIN)
return 0;
/*
* Now loop over the intervals of the collection going backwards till
* the end of the inputted request and create a separate request for
* each of those interest.
*/
req_tmp = *req;
req_count = 1;
while (col_index >= 0 && req_end <= col->break_points[col_index]) {
if (req_end >= col->resume_points[col_index]) {
/*
* The last entry of the original request is inside
* the "col_index" collection interval. Close the
* collection request here and return.
*/
req_tmp->n = req_tmp->first - req_end + 1;
break;
}
/*
* The last entry of the original request is outside of the
* "col_index" interval. Close the collection request at the
* end of this interval and move to the next one. Try to make
* another request there.
*/
req_tmp->n = req_tmp->first -
col->resume_points[col_index] + 1;
--col_index;
if (req_end > col->break_points[col_index]) {
/*
* The last entry of the original request comes before
* the end of the next collection interval. Stop here.
*/
break;
}
if (col_index > 0) {
/* Make a new request. */
req_first = col->break_points[col_index];
req_tmp->next =
kshark_entry_request_alloc(req_first,
0,
req_tmp->cond,
req_tmp->val,
req_tmp->vis_only,
req_tmp->vis_mask);
if (!req_tmp->next)
goto fail;
req_tmp = req_tmp->next;
++req_count;
}
}
return req_count;
fail:
fprintf(stderr, "Failed to allocate memory for ");
fprintf(stderr, "Collection data request.\n");
kshark_free_entry_request(*req);
*req = NULL;
return -ENOMEM;
}
/*
* This function uses the intervals of the Data collection to transform the
* inputted single data request into a list of data requests. The new list of
* requests will ignore the data outside of the intervals of the collection.
*/
static int
map_collection_front_request(const struct kshark_entry_collection *col,
struct kshark_entry_request **req)
{
struct kshark_entry_request *req_tmp;
size_t req_first, req_end;
ssize_t col_index;
int req_count;
col_index = map_collection_request_init(col, req, true, &req_end);
if (col_index == KS_EMPTY_BIN)
return 0;
/*
* Now loop over the intervals of the collection going forwards till
* the end of the inputted request and create a separate request for
* each of those interest.
*/
req_count = 1;
req_tmp = *req;
while (col_index < col->size &&
req_end >= col->resume_points[col_index]) {
if (req_end <= col->break_points[col_index]) {
/*
* The last entry of the original request is inside
* the "col_index" collection interval.
* Close the collection request here and return.
*/
req_tmp->n = req_end - req_tmp->first + 1;
break;
}
/*
* The last entry of the original request is outside this
* collection interval (col_index). Close the collection
* request at the end of the interval and move to the next
* interval. Try to make another request there.
*/
req_tmp->n = col->break_points[col_index] -
req_tmp->first + 1;
++col_index;
if (req_end < col->resume_points[col_index]) {
/*
* The last entry of the original request comes before
* the beginning of next collection interval.
* Stop here.
*/
break;
}
if (col_index < col->size) {
/* Make a new request. */
req_first = col->resume_points[col_index];
req_tmp->next =
kshark_entry_request_alloc(req_first,
0,
req_tmp->cond,
req_tmp->val,
req_tmp->vis_only,
req_tmp->vis_mask);
if (!req_tmp->next)
goto fail;
req_tmp = req_tmp->next;
++req_count;
}
}
return req_count;
fail:
fprintf(stderr, "Failed to allocate memory for ");
fprintf(stderr, "Collection data request.\n");
kshark_free_entry_request(*req);
*req = NULL;
return -ENOMEM;
}
/**
* @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).
* The search is performed only inside the intervals, defined by
* the data collection.
*
* @param req: Input location for a single Data request. The imputted request
* will be transformed into a list of requests. This new list of
* requests will ignore the data outside of the intervals of the
* collection.
* @param data: Input location for the trace data.
* @param col: Input location for the Data collection.
* @param index: Optional output location for the index of the returned
* entry inside the array.
*
* @returns Pointer to the first entry satisfying the matching condition on
* 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_collection_entry_front(struct kshark_entry_request **req,
struct kshark_entry **data,
const struct kshark_entry_collection *col,
ssize_t *index)
{
const struct kshark_entry *entry = NULL;
int req_count;
/*
* Use the intervals of the Data collection to redefine the data
* request in a way which will ignore the data outside of the
* intervals of the collection.
*/
req_count = map_collection_front_request(col, req);
if (index && !req_count)
*index = KS_EMPTY_BIN;
/*
* Loop over the list of redefined requests and search until you find
* the first matching entry.
*/
while (*req) {
entry = kshark_get_entry_front(*req, data, index);
if (entry)
break;
*req = (*req)->next;
}
return entry;
}
/**
* @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).
* The search is performed only inside the intervals, defined by
* the data collection.
*
* @param req: Input location for Data request. The imputed request
* will be transformed into a list of requests. This new list of
* requests will ignore the data outside of the intervals of the
* collection.
* @param data: Input location for the trace data.
* @param col: Input location for the Data collection.
* @param index: Optional output location for the index of the returned
* entry inside the array.
*
* @returns Pointer to the first entry satisfying the matching condition on
* 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_collection_entry_back(struct kshark_entry_request **req,
struct kshark_entry **data,
const struct kshark_entry_collection *col,
ssize_t *index)
{
const struct kshark_entry *entry = NULL;
int req_count;
/*
* Use the intervals of the Data collection to redefine the data
* request in a way which will ignore the data outside of the
* intervals of the collection.
*/
req_count = map_collection_back_request(col, req);
if (index && !req_count)
*index = KS_EMPTY_BIN;
/*
* Loop over the list of redefined requests and search until you find
* the first matching entry.
*/
while (*req) {
entry = kshark_get_entry_back(*req, data, index);
if (entry)
break;
*req = (*req)->next;
}
return entry;
}
/**
* @brief Search the list of Data collections and find the collection defined
* with a given Matching condition function and value.
*
* @param col: Input location for the Data collection list.
* @param cond: Matching condition function.
* @param val: Matching condition value, used by the Matching condition
* function.
*
* @returns Pointer to a Data collections on success, or NULL on failure.
*/
struct kshark_entry_collection *
kshark_find_data_collection(struct kshark_entry_collection *col,
matching_condition_func cond,
int val)
{
while (col) {
if (col->cond == cond && col->val == val)
return col;
col = col->next;
}
return NULL;
}
/**
* @brief Clear all data intervals of the given Data collection.
*
* @param col: Input location for the Data collection.
*/
void kshark_reset_data_collection(struct kshark_entry_collection *col)
{
free(col->resume_points);
col->resume_points = NULL;
free(col->break_points);
col->break_points = NULL;
col->size = 0;
}
static void kshark_free_data_collection(struct kshark_entry_collection *col)
{
free(col->resume_points);
free(col->break_points);
free(col);
}
/**
* @brief Allocate and process data collection, defined with a given Matching
* condition function and value. Add this collection to the list of
* collections used by the session.
*
* @param kshark_ctx: Input location for the session context pointer.
* @param data: Input location for the trace data.
* @param n_rows: The size of the inputted data.
* @param cond: Matching condition function for the collection to be
* registered.
* @param val: Matching condition value of for collection to be registered.
* @param margin: The size of the additional (margin) data which do not
* satisfy the matching condition, but is added at the
* beginning and at the end of each interval of the collection
* as well as at the beginning and at the end of data-set. If
* "0", no margin data is added.
*
* @returns Pointer to the registered Data collections on success, or NULL
* on failure.
*/
struct kshark_entry_collection *
kshark_register_data_collection(struct kshark_context *kshark_ctx,
struct kshark_entry **data,
size_t n_rows,
matching_condition_func cond,
int val,
size_t margin)
{
struct kshark_entry_collection *col;
col = kshark_add_collection_to_list(kshark_ctx,
&kshark_ctx->collections,
data, n_rows,
cond, val,
margin);
return col;
}
/**
* @brief Allocate and process data collection, defined with a given Matching
* condition function and value. Add this collection to a given list of
* collections.
*
* @param kshark_ctx: Input location for the session context pointer.
* @param col_list: Input location for the list of collections.
* @param data: Input location for the trace data.
* @param n_rows: The size of the inputted data.
* @param cond: Matching condition function for the collection to be
* registered.
* @param val: Matching condition value of for collection to be registered.
* @param margin: The size of the additional (margin) data which do not
* satisfy the matching condition, but is added at the
* beginning and at the end of each interval of the collection
* as well as at the beginning and at the end of data-set. If
* "0", no margin data is added.
*
* @returns Pointer to the registered Data collections on success, or NULL
* on failure.
*/
struct kshark_entry_collection *
kshark_add_collection_to_list(struct kshark_context *kshark_ctx,
struct kshark_entry_collection **col_list,
struct kshark_entry **data,
size_t n_rows,
matching_condition_func cond,
int val,
size_t margin)
{
struct kshark_entry_collection *col;
col = kshark_data_collection_alloc(kshark_ctx, data,
0, n_rows,
cond, val,
margin);
if (col) {
col->next = *col_list;
*col_list = col;
}
return col;
}
/**
* @brief Search the list of Data collections for a collection defined
* with a given Matching condition function and value. If such a
* collection exists, unregister (remove and free) this collection
* from the list.
*
* @param col: Input location for the Data collection list.
* @param cond: Matching condition function of the collection to be
* unregistered.
*
* @param val: Matching condition value of the collection to be unregistered.
*/
void kshark_unregister_data_collection(struct kshark_entry_collection **col,
matching_condition_func cond,
int val)
{
struct kshark_entry_collection **last = col;
struct kshark_entry_collection *list;
for (list = *col; list; list = list->next) {
if (list->cond == cond && list->val == val) {
*last = list->next;
kshark_free_data_collection(list);
return;
}
last = &list->next;
}
}
/**
* @brief Free all Data collections in a given list.
*
* @param col: Input location for the Data collection list.
*/
void kshark_free_collection_list(struct kshark_entry_collection *col)
{
struct kshark_entry_collection *last;
while (col) {
last = col;
col = col->next;
kshark_free_data_collection(last);
}
}