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#include <linux/security.h>
  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
  75static int insert_stat(struct rb_root *root, void *stat, cmp_func_t cmp)
  76{
  77        struct rb_node **new = &(root->rb_node), *parent = NULL;
  78        struct stat_node *data;
  79
  80        data = kzalloc(sizeof(*data), GFP_KERNEL);
  81        if (!data)
  82                return -ENOMEM;
  83        data->stat = stat;
  84
  85        /*
  86         * Figure out where to put new node
  87         * This is a descendent sorting
  88         */
  89        while (*new) {
  90                struct stat_node *this;
  91                int result;
  92
  93                this = container_of(*new, struct stat_node, node);
  94                result = cmp(data->stat, this->stat);
  95
  96                parent = *new;
  97                if (result >= 0)
  98                        new = &((*new)->rb_left);
  99                else
 100                        new = &((*new)->rb_right);
 101        }
 102
 103        rb_link_node(&data->node, parent, new);
 104        rb_insert_color(&data->node, root);
 105        return 0;
 106}
 107
 108/*
 109 * For tracers that don't provide a stat_cmp callback.
 110 * This one will force an insertion as right-most node
 111 * in the rbtree.
 112 */
 113static int dummy_cmp(const void *p1, const void *p2)
 114{
 115        return -1;
 116}
 117
 118/*
 119 * Initialize the stat rbtree at each trace_stat file opening.
 120 * All of these copies and sorting are required on all opening
 121 * since the stats could have changed between two file sessions.
 122 */
 123static int stat_seq_init(struct stat_session *session)
 124{
 125        struct tracer_stat *ts = session->ts;
 126        struct rb_root *root = &session->stat_root;
 127        void *stat;
 128        int ret = 0;
 129        int i;
 130
 131        mutex_lock(&session->stat_mutex);
 132        __reset_stat_session(session);
 133
 134        if (!ts->stat_cmp)
 135                ts->stat_cmp = dummy_cmp;
 136
 137        stat = ts->stat_start(ts);
 138        if (!stat)
 139                goto exit;
 140
 141        ret = insert_stat(root, stat, ts->stat_cmp);
 142        if (ret)
 143                goto exit;
 144
 145        /*
 146         * Iterate over the tracer stat entries and store them in an rbtree.
 147         */
 148        for (i = 1; ; i++) {
 149                stat = ts->stat_next(stat, i);
 150
 151                /* End of insertion */
 152                if (!stat)
 153                        break;
 154
 155                ret = insert_stat(root, stat, ts->stat_cmp);
 156                if (ret)
 157                        goto exit_free_rbtree;
 158        }
 159
 160exit:
 161        mutex_unlock(&session->stat_mutex);
 162        return ret;
 163
 164exit_free_rbtree:
 165        __reset_stat_session(session);
 166        mutex_unlock(&session->stat_mutex);
 167        return ret;
 168}
 169
 170
 171static void *stat_seq_start(struct seq_file *s, loff_t *pos)
 172{
 173        struct stat_session *session = s->private;
 174        struct rb_node *node;
 175        int n = *pos;
 176        int i;
 177
 178        /* Prevent from tracer switch or rbtree modification */
 179        mutex_lock(&session->stat_mutex);
 180
 181        /* If we are in the beginning of the file, print the headers */
 182        if (session->ts->stat_headers) {
 183                if (n == 0)
 184                        return SEQ_START_TOKEN;
 185                n--;
 186        }
 187
 188        node = rb_first(&session->stat_root);
 189        for (i = 0; node && i < n; i++)
 190                node = rb_next(node);
 191
 192        return node;
 193}
 194
 195static void *stat_seq_next(struct seq_file *s, void *p, loff_t *pos)
 196{
 197        struct stat_session *session = s->private;
 198        struct rb_node *node = p;
 199
 200        (*pos)++;
 201
 202        if (p == SEQ_START_TOKEN)
 203                return rb_first(&session->stat_root);
 204
 205        return rb_next(node);
 206}
 207
 208static void stat_seq_stop(struct seq_file *s, void *p)
 209{
 210        struct stat_session *session = s->private;
 211        mutex_unlock(&session->stat_mutex);
 212}
 213
 214static int stat_seq_show(struct seq_file *s, void *v)
 215{
 216        struct stat_session *session = s->private;
 217        struct stat_node *l = container_of(v, struct stat_node, node);
 218
 219        if (v == SEQ_START_TOKEN)
 220                return session->ts->stat_headers(s);
 221
 222        return session->ts->stat_show(s, l->stat);
 223}
 224
 225static const struct seq_operations trace_stat_seq_ops = {
 226        .start          = stat_seq_start,
 227        .next           = stat_seq_next,
 228        .stop           = stat_seq_stop,
 229        .show           = stat_seq_show
 230};
 231
 232/* The session stat is refilled and resorted at each stat file opening */
 233static int tracing_stat_open(struct inode *inode, struct file *file)
 234{
 235        int ret;
 236        struct seq_file *m;
 237        struct stat_session *session = inode->i_private;
 238
 239        ret = security_locked_down(LOCKDOWN_TRACEFS);
 240        if (ret)
 241                return ret;
 242
 243        ret = stat_seq_init(session);
 244        if (ret)
 245                return ret;
 246
 247        ret = seq_open(file, &trace_stat_seq_ops);
 248        if (ret) {
 249                reset_stat_session(session);
 250                return ret;
 251        }
 252
 253        m = file->private_data;
 254        m->private = session;
 255        return ret;
 256}
 257
 258/*
 259 * Avoid consuming memory with our now useless rbtree.
 260 */
 261static int tracing_stat_release(struct inode *i, struct file *f)
 262{
 263        struct stat_session *session = i->i_private;
 264
 265        reset_stat_session(session);
 266
 267        return seq_release(i, f);
 268}
 269
 270static const struct file_operations tracing_stat_fops = {
 271        .open           = tracing_stat_open,
 272        .read           = seq_read,
 273        .llseek         = seq_lseek,
 274        .release        = tracing_stat_release
 275};
 276
 277static int tracing_stat_init(void)
 278{
 279        int ret;
 280
 281        ret = tracing_init_dentry();
 282        if (ret)
 283                return -ENODEV;
 284
 285        stat_dir = tracefs_create_dir("trace_stat", NULL);
 286        if (!stat_dir) {
 287                pr_warn("Could not create tracefs 'trace_stat' entry\n");
 288                return -ENOMEM;
 289        }
 290        return 0;
 291}
 292
 293static int init_stat_file(struct stat_session *session)
 294{
 295        int ret;
 296
 297        if (!stat_dir && (ret = tracing_stat_init()))
 298                return ret;
 299
 300        session->file = tracefs_create_file(session->ts->name, 0644,
 301                                            stat_dir,
 302                                            session, &tracing_stat_fops);
 303        if (!session->file)
 304                return -ENOMEM;
 305        return 0;
 306}
 307
 308int register_stat_tracer(struct tracer_stat *trace)
 309{
 310        struct stat_session *session, *node;
 311        int ret = -EINVAL;
 312
 313        if (!trace)
 314                return -EINVAL;
 315
 316        if (!trace->stat_start || !trace->stat_next || !trace->stat_show)
 317                return -EINVAL;
 318
 319        /* Already registered? */
 320        mutex_lock(&all_stat_sessions_mutex);
 321        list_for_each_entry(node, &all_stat_sessions, session_list) {
 322                if (node->ts == trace)
 323                        goto out;
 324        }
 325
 326        ret = -ENOMEM;
 327        /* Init the session */
 328        session = kzalloc(sizeof(*session), GFP_KERNEL);
 329        if (!session)
 330                goto out;
 331
 332        session->ts = trace;
 333        INIT_LIST_HEAD(&session->session_list);
 334        mutex_init(&session->stat_mutex);
 335
 336        ret = init_stat_file(session);
 337        if (ret) {
 338                destroy_session(session);
 339                goto out;
 340        }
 341
 342        ret = 0;
 343        /* Register */
 344        list_add_tail(&session->session_list, &all_stat_sessions);
 345 out:
 346        mutex_unlock(&all_stat_sessions_mutex);
 347
 348        return ret;
 349}
 350
 351void unregister_stat_tracer(struct tracer_stat *trace)
 352{
 353        struct stat_session *node, *tmp;
 354
 355        mutex_lock(&all_stat_sessions_mutex);
 356        list_for_each_entry_safe(node, tmp, &all_stat_sessions, session_list) {
 357                if (node->ts == trace) {
 358                        list_del(&node->session_list);
 359                        destroy_session(node);
 360                        break;
 361                }
 362        }
 363        mutex_unlock(&all_stat_sessions_mutex);
 364}
 365