forked from Qortal/Brooklyn
You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
364 lines
7.6 KiB
364 lines
7.6 KiB
// SPDX-License-Identifier: GPL-2.0 |
|
/* |
|
* Infrastructure for statistic tracing (histogram output). |
|
* |
|
* Copyright (C) 2008-2009 Frederic Weisbecker <[email protected]> |
|
* |
|
* Based on the code from trace_branch.c which is |
|
* Copyright (C) 2008 Steven Rostedt <[email protected]> |
|
* |
|
*/ |
|
|
|
#include <linux/security.h> |
|
#include <linux/list.h> |
|
#include <linux/slab.h> |
|
#include <linux/rbtree.h> |
|
#include <linux/tracefs.h> |
|
#include "trace_stat.h" |
|
#include "trace.h" |
|
|
|
|
|
/* |
|
* List of stat red-black nodes from a tracer |
|
* We use a such tree to sort quickly the stat |
|
* entries from the tracer. |
|
*/ |
|
struct stat_node { |
|
struct rb_node node; |
|
void *stat; |
|
}; |
|
|
|
/* A stat session is the stats output in one file */ |
|
struct stat_session { |
|
struct list_head session_list; |
|
struct tracer_stat *ts; |
|
struct rb_root stat_root; |
|
struct mutex stat_mutex; |
|
struct dentry *file; |
|
}; |
|
|
|
/* All of the sessions currently in use. Each stat file embed one session */ |
|
static LIST_HEAD(all_stat_sessions); |
|
static DEFINE_MUTEX(all_stat_sessions_mutex); |
|
|
|
/* The root directory for all stat files */ |
|
static struct dentry *stat_dir; |
|
|
|
static void __reset_stat_session(struct stat_session *session) |
|
{ |
|
struct stat_node *snode, *n; |
|
|
|
rbtree_postorder_for_each_entry_safe(snode, n, &session->stat_root, node) { |
|
if (session->ts->stat_release) |
|
session->ts->stat_release(snode->stat); |
|
kfree(snode); |
|
} |
|
|
|
session->stat_root = RB_ROOT; |
|
} |
|
|
|
static void reset_stat_session(struct stat_session *session) |
|
{ |
|
mutex_lock(&session->stat_mutex); |
|
__reset_stat_session(session); |
|
mutex_unlock(&session->stat_mutex); |
|
} |
|
|
|
static void destroy_session(struct stat_session *session) |
|
{ |
|
tracefs_remove(session->file); |
|
__reset_stat_session(session); |
|
mutex_destroy(&session->stat_mutex); |
|
kfree(session); |
|
} |
|
|
|
static int insert_stat(struct rb_root *root, void *stat, cmp_func_t cmp) |
|
{ |
|
struct rb_node **new = &(root->rb_node), *parent = NULL; |
|
struct stat_node *data; |
|
|
|
data = kzalloc(sizeof(*data), GFP_KERNEL); |
|
if (!data) |
|
return -ENOMEM; |
|
data->stat = stat; |
|
|
|
/* |
|
* Figure out where to put new node |
|
* This is a descendent sorting |
|
*/ |
|
while (*new) { |
|
struct stat_node *this; |
|
int result; |
|
|
|
this = container_of(*new, struct stat_node, node); |
|
result = cmp(data->stat, this->stat); |
|
|
|
parent = *new; |
|
if (result >= 0) |
|
new = &((*new)->rb_left); |
|
else |
|
new = &((*new)->rb_right); |
|
} |
|
|
|
rb_link_node(&data->node, parent, new); |
|
rb_insert_color(&data->node, root); |
|
return 0; |
|
} |
|
|
|
/* |
|
* For tracers that don't provide a stat_cmp callback. |
|
* This one will force an insertion as right-most node |
|
* in the rbtree. |
|
*/ |
|
static int dummy_cmp(const void *p1, const void *p2) |
|
{ |
|
return -1; |
|
} |
|
|
|
/* |
|
* Initialize the stat rbtree at each trace_stat file opening. |
|
* All of these copies and sorting are required on all opening |
|
* since the stats could have changed between two file sessions. |
|
*/ |
|
static int stat_seq_init(struct stat_session *session) |
|
{ |
|
struct tracer_stat *ts = session->ts; |
|
struct rb_root *root = &session->stat_root; |
|
void *stat; |
|
int ret = 0; |
|
int i; |
|
|
|
mutex_lock(&session->stat_mutex); |
|
__reset_stat_session(session); |
|
|
|
if (!ts->stat_cmp) |
|
ts->stat_cmp = dummy_cmp; |
|
|
|
stat = ts->stat_start(ts); |
|
if (!stat) |
|
goto exit; |
|
|
|
ret = insert_stat(root, stat, ts->stat_cmp); |
|
if (ret) |
|
goto exit; |
|
|
|
/* |
|
* Iterate over the tracer stat entries and store them in an rbtree. |
|
*/ |
|
for (i = 1; ; i++) { |
|
stat = ts->stat_next(stat, i); |
|
|
|
/* End of insertion */ |
|
if (!stat) |
|
break; |
|
|
|
ret = insert_stat(root, stat, ts->stat_cmp); |
|
if (ret) |
|
goto exit_free_rbtree; |
|
} |
|
|
|
exit: |
|
mutex_unlock(&session->stat_mutex); |
|
return ret; |
|
|
|
exit_free_rbtree: |
|
__reset_stat_session(session); |
|
mutex_unlock(&session->stat_mutex); |
|
return ret; |
|
} |
|
|
|
|
|
static void *stat_seq_start(struct seq_file *s, loff_t *pos) |
|
{ |
|
struct stat_session *session = s->private; |
|
struct rb_node *node; |
|
int n = *pos; |
|
int i; |
|
|
|
/* Prevent from tracer switch or rbtree modification */ |
|
mutex_lock(&session->stat_mutex); |
|
|
|
/* If we are in the beginning of the file, print the headers */ |
|
if (session->ts->stat_headers) { |
|
if (n == 0) |
|
return SEQ_START_TOKEN; |
|
n--; |
|
} |
|
|
|
node = rb_first(&session->stat_root); |
|
for (i = 0; node && i < n; i++) |
|
node = rb_next(node); |
|
|
|
return node; |
|
} |
|
|
|
static void *stat_seq_next(struct seq_file *s, void *p, loff_t *pos) |
|
{ |
|
struct stat_session *session = s->private; |
|
struct rb_node *node = p; |
|
|
|
(*pos)++; |
|
|
|
if (p == SEQ_START_TOKEN) |
|
return rb_first(&session->stat_root); |
|
|
|
return rb_next(node); |
|
} |
|
|
|
static void stat_seq_stop(struct seq_file *s, void *p) |
|
{ |
|
struct stat_session *session = s->private; |
|
mutex_unlock(&session->stat_mutex); |
|
} |
|
|
|
static int stat_seq_show(struct seq_file *s, void *v) |
|
{ |
|
struct stat_session *session = s->private; |
|
struct stat_node *l = container_of(v, struct stat_node, node); |
|
|
|
if (v == SEQ_START_TOKEN) |
|
return session->ts->stat_headers(s); |
|
|
|
return session->ts->stat_show(s, l->stat); |
|
} |
|
|
|
static const struct seq_operations trace_stat_seq_ops = { |
|
.start = stat_seq_start, |
|
.next = stat_seq_next, |
|
.stop = stat_seq_stop, |
|
.show = stat_seq_show |
|
}; |
|
|
|
/* The session stat is refilled and resorted at each stat file opening */ |
|
static int tracing_stat_open(struct inode *inode, struct file *file) |
|
{ |
|
int ret; |
|
struct seq_file *m; |
|
struct stat_session *session = inode->i_private; |
|
|
|
ret = security_locked_down(LOCKDOWN_TRACEFS); |
|
if (ret) |
|
return ret; |
|
|
|
ret = stat_seq_init(session); |
|
if (ret) |
|
return ret; |
|
|
|
ret = seq_open(file, &trace_stat_seq_ops); |
|
if (ret) { |
|
reset_stat_session(session); |
|
return ret; |
|
} |
|
|
|
m = file->private_data; |
|
m->private = session; |
|
return ret; |
|
} |
|
|
|
/* |
|
* Avoid consuming memory with our now useless rbtree. |
|
*/ |
|
static int tracing_stat_release(struct inode *i, struct file *f) |
|
{ |
|
struct stat_session *session = i->i_private; |
|
|
|
reset_stat_session(session); |
|
|
|
return seq_release(i, f); |
|
} |
|
|
|
static const struct file_operations tracing_stat_fops = { |
|
.open = tracing_stat_open, |
|
.read = seq_read, |
|
.llseek = seq_lseek, |
|
.release = tracing_stat_release |
|
}; |
|
|
|
static int tracing_stat_init(void) |
|
{ |
|
int ret; |
|
|
|
ret = tracing_init_dentry(); |
|
if (ret) |
|
return -ENODEV; |
|
|
|
stat_dir = tracefs_create_dir("trace_stat", NULL); |
|
if (!stat_dir) { |
|
pr_warn("Could not create tracefs 'trace_stat' entry\n"); |
|
return -ENOMEM; |
|
} |
|
return 0; |
|
} |
|
|
|
static int init_stat_file(struct stat_session *session) |
|
{ |
|
int ret; |
|
|
|
if (!stat_dir && (ret = tracing_stat_init())) |
|
return ret; |
|
|
|
session->file = tracefs_create_file(session->ts->name, 0644, |
|
stat_dir, |
|
session, &tracing_stat_fops); |
|
if (!session->file) |
|
return -ENOMEM; |
|
return 0; |
|
} |
|
|
|
int register_stat_tracer(struct tracer_stat *trace) |
|
{ |
|
struct stat_session *session, *node; |
|
int ret = -EINVAL; |
|
|
|
if (!trace) |
|
return -EINVAL; |
|
|
|
if (!trace->stat_start || !trace->stat_next || !trace->stat_show) |
|
return -EINVAL; |
|
|
|
/* Already registered? */ |
|
mutex_lock(&all_stat_sessions_mutex); |
|
list_for_each_entry(node, &all_stat_sessions, session_list) { |
|
if (node->ts == trace) |
|
goto out; |
|
} |
|
|
|
ret = -ENOMEM; |
|
/* Init the session */ |
|
session = kzalloc(sizeof(*session), GFP_KERNEL); |
|
if (!session) |
|
goto out; |
|
|
|
session->ts = trace; |
|
INIT_LIST_HEAD(&session->session_list); |
|
mutex_init(&session->stat_mutex); |
|
|
|
ret = init_stat_file(session); |
|
if (ret) { |
|
destroy_session(session); |
|
goto out; |
|
} |
|
|
|
ret = 0; |
|
/* Register */ |
|
list_add_tail(&session->session_list, &all_stat_sessions); |
|
out: |
|
mutex_unlock(&all_stat_sessions_mutex); |
|
|
|
return ret; |
|
} |
|
|
|
void unregister_stat_tracer(struct tracer_stat *trace) |
|
{ |
|
struct stat_session *node, *tmp; |
|
|
|
mutex_lock(&all_stat_sessions_mutex); |
|
list_for_each_entry_safe(node, tmp, &all_stat_sessions, session_list) { |
|
if (node->ts == trace) { |
|
list_del(&node->session_list); |
|
destroy_session(node); |
|
break; |
|
} |
|
} |
|
mutex_unlock(&all_stat_sessions_mutex); |
|
}
|
|
|