linux/kernel/trace/trace_stat.c
<<
>>
Prefs
   1// SPDX-License-Identifier: GPL-2.0
   2/*
   3 * Infrastructure for statistic tracing (histogram output).
   4 *
   5 * Copyright (C) 2008-2009 Frederic Weisbecker <fweisbec@gmail.com>
   6 *
   7 * Based on the code from trace_branch.c which is
   8 * Copyright (C) 2008 Steven Rostedt <srostedt@redhat.com>
   9 *
  10 */
  11
  12
  13#include <linux/list.h>
  14#include <linux/slab.h>
  15#include <linux/rbtree.h>
  16#include <linux/tracefs.h>
  17#include "trace_stat.h"
  18#include "trace.h"
  19
  20
  21/*
  22 * List of stat red-black nodes from a tracer
  23 * We use a such tree to sort quickly the stat
  24 * entries from the tracer.
  25 */
  26struct stat_node {
  27        struct rb_node          node;
  28        void                    *stat;
  29};
  30
  31/* A stat session is the stats output in one file */
  32struct stat_session {
  33        struct list_head        session_list;
  34        struct tracer_stat      *ts;
  35        struct rb_root          stat_root;
  36        struct mutex            stat_mutex;
  37        struct dentry           *file;
  38};
  39
  40/* All of the sessions currently in use. Each stat file embed one session */
  41static LIST_HEAD(all_stat_sessions);
  42static DEFINE_MUTEX(all_stat_sessions_mutex);
  43
  44/* The root directory for all stat files */
  45static struct dentry            *stat_dir;
  46
  47static void __reset_stat_session(struct stat_session *session)
  48{
  49        struct stat_node *snode, *n;
  50
  51        rbtree_postorder_for_each_entry_safe(snode, n, &session->stat_root, node) {
  52                if (session->ts->stat_release)
  53                        session->ts->stat_release(snode->stat);
  54                kfree(snode);
  55        }
  56
  57        session->stat_root = RB_ROOT;
  58}
  59
  60static void reset_stat_session(struct stat_session *session)
  61{
  62        mutex_lock(&session->stat_mutex);
  63        __reset_stat_session(session);
  64        mutex_unlock(&session->stat_mutex);
  65}
  66
  67static void destroy_session(struct stat_session *session)
  68{
  69        tracefs_remove(session->file);
  70        __reset_stat_session(session);
  71        mutex_destroy(&session->stat_mutex);
  72        kfree(session);
  73}
  74
  75typedef int (*cmp_stat_t)(void *, void *);
  76
  77static int insert_stat(struct rb_root *root, void *stat, cmp_stat_t cmp)
  78{
  79        struct rb_node **new = &(root->rb_node), *parent = NULL;
  80        struct stat_node *data;
  81
  82        data = kzalloc(sizeof(*data), GFP_KERNEL);
  83        if (!data)
  84                return -ENOMEM;
  85        data->stat = stat;
  86
  87        /*
  88         * Figure out where to put new node
  89         * This is a descendent sorting
  90         */
  91        while (*new) {
  92                struct stat_node *this;
  93                int result;
  94
  95                this = container_of(*new, struct stat_node, node);
  96                result = cmp(data->stat, this->stat);
  97
  98                parent = *new;
  99                if (result >= 0)
 100                        new = &((*new)->rb_left);
 101                else
 102                        new = &((*new)->rb_right);
 103        }
 104
 105        rb_link_node(&data->node, parent, new);
 106        rb_insert_color(&data->node, root);
 107        return 0;
 108}
 109
 110/*
 111 * For tracers that don't provide a stat_cmp callback.
 112 * This one will force an insertion as right-most node
 113 * in the rbtree.
 114 */
 115static int dummy_cmp(void *p1, void *p2)
 116{
 117        return -1;
 118}
 119
 120/*
 121 * Initialize the stat rbtree at each trace_stat file opening.
 122 * All of these copies and sorting are required on all opening
 123 * since the stats could have changed between two file sessions.
 124 */
 125static int stat_seq_init(struct stat_session *session)
 126{
 127        struct tracer_stat *ts = session->ts;
 128        struct rb_root *root = &session->stat_root;
 129        void *stat;
 130        int ret = 0;
 131        int i;
 132
 133        mutex_lock(&session->stat_mutex);
 134        __reset_stat_session(session);
 135
 136        if (!ts->stat_cmp)
 137                ts->stat_cmp = dummy_cmp;
 138
 139        stat = ts->stat_start(ts);
 140        if (!stat)
 141                goto exit;
 142
 143        ret = insert_stat(root, stat, ts->stat_cmp);
 144        if (ret)
 145                goto exit;
 146
 147        /*
 148         * Iterate over the tracer stat entries and store them in an rbtree.
 149         */
 150        for (i = 1; ; i++) {
 151                stat = ts->stat_next(stat, i);
 152
 153                /* End of insertion */
 154                if (!stat)
 155                        break;
 156
 157                ret = insert_stat(root, stat, ts->stat_cmp);
 158                if (ret)
 159                        goto exit_free_rbtree;
 160        }
 161
 162exit:
 163        mutex_unlock(&session->stat_mutex);
 164        return ret;
 165
 166exit_free_rbtree:
 167        __reset_stat_session(session);
 168        mutex_unlock(&session->stat_mutex);
 169        return ret;
 170}
 171
 172
 173static void *stat_seq_start(struct seq_file *s, loff_t *pos)
 174{
 175        struct stat_session *session = s->private;
 176        struct rb_node *node;
 177        int n = *pos;
 178        int i;
 179
 180        /* Prevent from tracer switch or rbtree modification */
 181        mutex_lock(&session->stat_mutex);
 182
 183        /* If we are in the beginning of the file, print the headers */
 184        if (session->ts->stat_headers) {
 185                if (n == 0)
 186                        return SEQ_START_TOKEN;
 187                n--;
 188        }
 189
 190        node = rb_first(&session->stat_root);
 191        for (i = 0; node && i < n; i++)
 192                node = rb_next(node);
 193
 194        return node;
 195}
 196
 197static void *stat_seq_next(struct seq_file *s, void *p, loff_t *pos)
 198{
 199        struct stat_session *session = s->private;
 200        struct rb_node *node = p;
 201
 202        (*pos)++;
 203
 204        if (p == SEQ_START_TOKEN)
 205                return rb_first(&session->stat_root);
 206
 207        return rb_next(node);
 208}
 209
 210static void stat_seq_stop(struct seq_file *s, void *p)
 211{
 212        struct stat_session *session = s->private;
 213        mutex_unlock(&session->stat_mutex);
 214}
 215
 216static int stat_seq_show(struct seq_file *s, void *v)
 217{
 218        struct stat_session *session = s->private;
 219        struct stat_node *l = container_of(v, struct stat_node, node);
 220
 221        if (v == SEQ_START_TOKEN)
 222                return session->ts->stat_headers(s);
 223
 224        return session->ts->stat_show(s, l->stat);
 225}
 226
 227static const struct seq_operations trace_stat_seq_ops = {
 228        .start          = stat_seq_start,
 229        .next           = stat_seq_next,
 230        .stop           = stat_seq_stop,
 231        .show           = stat_seq_show
 232};
 233
 234/* The session stat is refilled and resorted at each stat file opening */
 235static int tracing_stat_open(struct inode *inode, struct file *file)
 236{
 237        int ret;
 238        struct seq_file *m;
 239        struct stat_session *session = inode->i_private;
 240
 241        ret = stat_seq_init(session);
 242        if (ret)
 243                return ret;
 244
 245        ret = seq_open(file, &trace_stat_seq_ops);
 246        if (ret) {
 247                reset_stat_session(session);
 248                return ret;
 249        }
 250
 251        m = file->private_data;
 252        m->private = session;
 253        return ret;
 254}
 255
 256/*
 257 * Avoid consuming memory with our now useless rbtree.
 258 */
 259static int tracing_stat_release(struct inode *i, struct file *f)
 260{
 261        struct stat_session *session = i->i_private;
 262
 263        reset_stat_session(session);
 264
 265        return seq_release(i, f);
 266}
 267
 268static const struct file_operations tracing_stat_fops = {
 269        .open           = tracing_stat_open,
 270        .read           = seq_read,
 271        .llseek         = seq_lseek,
 272        .release        = tracing_stat_release
 273};
 274
 275static int tracing_stat_init(void)
 276{
 277        struct dentry *d_tracing;
 278
 279        d_tracing = tracing_init_dentry();
 280        if (IS_ERR(d_tracing))
 281                return 0;
 282
 283        stat_dir = tracefs_create_dir("trace_stat", d_tracing);
 284        if (!stat_dir)
 285                pr_warn("Could not create tracefs 'trace_stat' entry\n");
 286        return 0;
 287}
 288
 289static int init_stat_file(struct stat_session *session)
 290{
 291        if (!stat_dir && tracing_stat_init())
 292                return -ENODEV;
 293
 294        session->file = tracefs_create_file(session->ts->name, 0644,
 295                                            stat_dir,
 296                                            session, &tracing_stat_fops);
 297        if (!session->file)
 298                return -ENOMEM;
 299        return 0;
 300}
 301
 302int register_stat_tracer(struct tracer_stat *trace)
 303{
 304        struct stat_session *session, *node;
 305        int ret;
 306
 307        if (!trace)
 308                return -EINVAL;
 309
 310        if (!trace->stat_start || !trace->stat_next || !trace->stat_show)
 311                return -EINVAL;
 312
 313        /* Already registered? */
 314        mutex_lock(&all_stat_sessions_mutex);
 315        list_for_each_entry(node, &all_stat_sessions, session_list) {
 316                if (node->ts == trace) {
 317                        mutex_unlock(&all_stat_sessions_mutex);
 318                        return -EINVAL;
 319                }
 320        }
 321        mutex_unlock(&all_stat_sessions_mutex);
 322
 323        /* Init the session */
 324        session = kzalloc(sizeof(*session), GFP_KERNEL);
 325        if (!session)
 326                return -ENOMEM;
 327
 328        session->ts = trace;
 329        INIT_LIST_HEAD(&session->session_list);
 330        mutex_init(&session->stat_mutex);
 331
 332        ret = init_stat_file(session);
 333        if (ret) {
 334                destroy_session(session);
 335                return ret;
 336        }
 337
 338        /* Register */
 339        mutex_lock(&all_stat_sessions_mutex);
 340        list_add_tail(&session->session_list, &all_stat_sessions);
 341        mutex_unlock(&all_stat_sessions_mutex);
 342
 343        return 0;
 344}
 345
 346void unregister_stat_tracer(struct tracer_stat *trace)
 347{
 348        struct stat_session *node, *tmp;
 349
 350        mutex_lock(&all_stat_sessions_mutex);
 351        list_for_each_entry_safe(node, tmp, &all_stat_sessions, session_list) {
 352                if (node->ts == trace) {
 353                        list_del(&node->session_list);
 354                        destroy_session(node);
 355                        break;
 356                }
 357        }
 358        mutex_unlock(&all_stat_sessions_mutex);
 359}
 360