2008-12-24 12:24:12 +08:00
|
|
|
/*
|
|
|
|
* trace_output.c
|
|
|
|
*
|
|
|
|
* Copyright (C) 2008 Red Hat Inc, Steven Rostedt <srostedt@redhat.com>
|
|
|
|
*
|
|
|
|
*/
|
|
|
|
|
|
|
|
#include <linux/module.h>
|
|
|
|
#include <linux/mutex.h>
|
|
|
|
#include <linux/ftrace.h>
|
|
|
|
|
|
|
|
#include "trace_output.h"
|
|
|
|
|
|
|
|
/* must be a power of 2 */
|
|
|
|
#define EVENT_HASHSIZE 128
|
|
|
|
|
2013-03-11 15:14:03 +08:00
|
|
|
DECLARE_RWSEM(trace_event_sem);
|
2009-05-27 02:25:22 +08:00
|
|
|
|
2008-12-24 12:24:12 +08:00
|
|
|
static struct hlist_head event_hash[EVENT_HASHSIZE] __read_mostly;
|
|
|
|
|
|
|
|
static int next_event_type = __TRACE_LAST_TYPE + 1;
|
|
|
|
|
2009-12-07 22:11:39 +08:00
|
|
|
int trace_print_seq(struct seq_file *m, struct trace_seq *s)
|
2009-03-24 11:12:58 +08:00
|
|
|
{
|
|
|
|
int len = s->len >= PAGE_SIZE ? PAGE_SIZE - 1 : s->len;
|
2009-12-07 22:11:39 +08:00
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = seq_write(m, s->buffer, len);
|
2009-03-24 11:12:58 +08:00
|
|
|
|
2009-12-07 22:11:39 +08:00
|
|
|
/*
|
|
|
|
* Only reset this buffer if we successfully wrote to the
|
|
|
|
* seq_file buffer.
|
|
|
|
*/
|
|
|
|
if (!ret)
|
|
|
|
trace_seq_init(s);
|
2009-03-24 11:12:58 +08:00
|
|
|
|
2009-12-07 22:11:39 +08:00
|
|
|
return ret;
|
2009-03-24 11:12:58 +08:00
|
|
|
}
|
|
|
|
|
2013-03-09 10:02:34 +08:00
|
|
|
enum print_line_t trace_print_bputs_msg_only(struct trace_iterator *iter)
|
|
|
|
{
|
|
|
|
struct trace_seq *s = &iter->seq;
|
|
|
|
struct trace_entry *entry = iter->ent;
|
|
|
|
struct bputs_entry *field;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
trace_assign_type(field, entry);
|
|
|
|
|
|
|
|
ret = trace_seq_puts(s, field->str);
|
|
|
|
if (!ret)
|
|
|
|
return TRACE_TYPE_PARTIAL_LINE;
|
|
|
|
|
|
|
|
return TRACE_TYPE_HANDLED;
|
|
|
|
}
|
|
|
|
|
2009-03-20 00:20:38 +08:00
|
|
|
enum print_line_t trace_print_bprintk_msg_only(struct trace_iterator *iter)
|
|
|
|
{
|
|
|
|
struct trace_seq *s = &iter->seq;
|
|
|
|
struct trace_entry *entry = iter->ent;
|
|
|
|
struct bprint_entry *field;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
trace_assign_type(field, entry);
|
|
|
|
|
|
|
|
ret = trace_seq_bprintf(s, field->fmt, field->buf);
|
|
|
|
if (!ret)
|
|
|
|
return TRACE_TYPE_PARTIAL_LINE;
|
|
|
|
|
|
|
|
return TRACE_TYPE_HANDLED;
|
|
|
|
}
|
|
|
|
|
|
|
|
enum print_line_t trace_print_printk_msg_only(struct trace_iterator *iter)
|
|
|
|
{
|
|
|
|
struct trace_seq *s = &iter->seq;
|
|
|
|
struct trace_entry *entry = iter->ent;
|
|
|
|
struct print_entry *field;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
trace_assign_type(field, entry);
|
|
|
|
|
2013-07-15 16:32:44 +08:00
|
|
|
ret = trace_seq_puts(s, field->buf);
|
2009-03-20 00:20:38 +08:00
|
|
|
if (!ret)
|
|
|
|
return TRACE_TYPE_PARTIAL_LINE;
|
|
|
|
|
|
|
|
return TRACE_TYPE_HANDLED;
|
|
|
|
}
|
|
|
|
|
2008-12-24 12:24:12 +08:00
|
|
|
/**
|
|
|
|
* trace_seq_printf - sequence printing of trace information
|
|
|
|
* @s: trace sequence descriptor
|
|
|
|
* @fmt: printf format string
|
|
|
|
*
|
2009-10-24 07:36:17 +08:00
|
|
|
* It returns 0 if the trace oversizes the buffer's free
|
|
|
|
* space, 1 otherwise.
|
|
|
|
*
|
2008-12-24 12:24:12 +08:00
|
|
|
* The tracer may use either sequence operations or its own
|
|
|
|
* copy to user routines. To simplify formating of a trace
|
|
|
|
* trace_seq_printf is used to store strings into a special
|
|
|
|
* buffer (@s). Then the output may be either used by
|
|
|
|
* the sequencer or pulled into another buffer.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
trace_seq_printf(struct trace_seq *s, const char *fmt, ...)
|
|
|
|
{
|
|
|
|
int len = (PAGE_SIZE - 1) - s->len;
|
|
|
|
va_list ap;
|
|
|
|
int ret;
|
|
|
|
|
2009-11-25 23:10:14 +08:00
|
|
|
if (s->full || !len)
|
2008-12-24 12:24:12 +08:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
va_start(ap, fmt);
|
|
|
|
ret = vsnprintf(s->buffer + s->len, len, fmt, ap);
|
|
|
|
va_end(ap);
|
|
|
|
|
|
|
|
/* If we can't write it all, don't bother writing anything */
|
2009-11-25 23:10:14 +08:00
|
|
|
if (ret >= len) {
|
|
|
|
s->full = 1;
|
2008-12-24 12:24:12 +08:00
|
|
|
return 0;
|
2009-11-25 23:10:14 +08:00
|
|
|
}
|
2008-12-24 12:24:12 +08:00
|
|
|
|
|
|
|
s->len += ret;
|
|
|
|
|
2009-10-24 07:36:17 +08:00
|
|
|
return 1;
|
2008-12-24 12:24:12 +08:00
|
|
|
}
|
2009-04-11 06:12:50 +08:00
|
|
|
EXPORT_SYMBOL_GPL(trace_seq_printf);
|
2008-12-24 12:24:12 +08:00
|
|
|
|
2009-06-09 07:09:45 +08:00
|
|
|
/**
|
|
|
|
* trace_seq_vprintf - sequence printing of trace information
|
|
|
|
* @s: trace sequence descriptor
|
|
|
|
* @fmt: printf format string
|
|
|
|
*
|
|
|
|
* The tracer may use either sequence operations or its own
|
|
|
|
* copy to user routines. To simplify formating of a trace
|
|
|
|
* trace_seq_printf is used to store strings into a special
|
|
|
|
* buffer (@s). Then the output may be either used by
|
|
|
|
* the sequencer or pulled into another buffer.
|
|
|
|
*/
|
|
|
|
int
|
|
|
|
trace_seq_vprintf(struct trace_seq *s, const char *fmt, va_list args)
|
|
|
|
{
|
|
|
|
int len = (PAGE_SIZE - 1) - s->len;
|
|
|
|
int ret;
|
|
|
|
|
2009-11-25 23:10:14 +08:00
|
|
|
if (s->full || !len)
|
2009-06-09 07:09:45 +08:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
ret = vsnprintf(s->buffer + s->len, len, fmt, args);
|
|
|
|
|
|
|
|
/* If we can't write it all, don't bother writing anything */
|
2009-11-25 23:10:14 +08:00
|
|
|
if (ret >= len) {
|
|
|
|
s->full = 1;
|
2009-06-09 07:09:45 +08:00
|
|
|
return 0;
|
2009-11-25 23:10:14 +08:00
|
|
|
}
|
2009-06-09 07:09:45 +08:00
|
|
|
|
|
|
|
s->len += ret;
|
|
|
|
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(trace_seq_vprintf);
|
|
|
|
|
2009-03-07 00:21:49 +08:00
|
|
|
int trace_seq_bprintf(struct trace_seq *s, const char *fmt, const u32 *binary)
|
2009-03-07 00:21:47 +08:00
|
|
|
{
|
|
|
|
int len = (PAGE_SIZE - 1) - s->len;
|
|
|
|
int ret;
|
|
|
|
|
2009-11-25 23:10:14 +08:00
|
|
|
if (s->full || !len)
|
2009-03-07 00:21:47 +08:00
|
|
|
return 0;
|
|
|
|
|
|
|
|
ret = bstr_printf(s->buffer + s->len, len, fmt, binary);
|
|
|
|
|
|
|
|
/* If we can't write it all, don't bother writing anything */
|
2009-11-25 23:10:14 +08:00
|
|
|
if (ret >= len) {
|
|
|
|
s->full = 1;
|
2009-03-07 00:21:47 +08:00
|
|
|
return 0;
|
2009-11-25 23:10:14 +08:00
|
|
|
}
|
2009-03-07 00:21:47 +08:00
|
|
|
|
|
|
|
s->len += ret;
|
|
|
|
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
2008-12-24 12:24:12 +08:00
|
|
|
/**
|
|
|
|
* trace_seq_puts - trace sequence printing of simple string
|
|
|
|
* @s: trace sequence descriptor
|
|
|
|
* @str: simple string to record
|
|
|
|
*
|
|
|
|
* The tracer may use either the sequence operations or its own
|
|
|
|
* copy to user routines. This function records a simple string
|
|
|
|
* into a special buffer (@s) for later retrieval by a sequencer
|
|
|
|
* or other mechanism.
|
|
|
|
*/
|
|
|
|
int trace_seq_puts(struct trace_seq *s, const char *str)
|
|
|
|
{
|
|
|
|
int len = strlen(str);
|
|
|
|
|
2009-11-25 23:10:14 +08:00
|
|
|
if (s->full)
|
2008-12-24 12:24:12 +08:00
|
|
|
return 0;
|
|
|
|
|
2009-11-25 23:10:14 +08:00
|
|
|
if (len > ((PAGE_SIZE - 1) - s->len)) {
|
|
|
|
s->full = 1;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-12-24 12:24:12 +08:00
|
|
|
memcpy(s->buffer + s->len, str, len);
|
|
|
|
s->len += len;
|
|
|
|
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
|
|
|
int trace_seq_putc(struct trace_seq *s, unsigned char c)
|
|
|
|
{
|
2009-11-25 23:10:14 +08:00
|
|
|
if (s->full)
|
2008-12-24 12:24:12 +08:00
|
|
|
return 0;
|
|
|
|
|
2009-11-25 23:10:14 +08:00
|
|
|
if (s->len >= (PAGE_SIZE - 1)) {
|
|
|
|
s->full = 1;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-12-24 12:24:12 +08:00
|
|
|
s->buffer[s->len++] = c;
|
|
|
|
|
|
|
|
return 1;
|
|
|
|
}
|
2010-04-01 19:41:40 +08:00
|
|
|
EXPORT_SYMBOL(trace_seq_putc);
|
2008-12-24 12:24:12 +08:00
|
|
|
|
2009-03-27 10:21:00 +08:00
|
|
|
int trace_seq_putmem(struct trace_seq *s, const void *mem, size_t len)
|
2008-12-24 12:24:12 +08:00
|
|
|
{
|
2009-11-25 23:10:14 +08:00
|
|
|
if (s->full)
|
2008-12-24 12:24:12 +08:00
|
|
|
return 0;
|
|
|
|
|
2009-11-25 23:10:14 +08:00
|
|
|
if (len > ((PAGE_SIZE - 1) - s->len)) {
|
|
|
|
s->full = 1;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-12-24 12:24:12 +08:00
|
|
|
memcpy(s->buffer + s->len, mem, len);
|
|
|
|
s->len += len;
|
|
|
|
|
|
|
|
return len;
|
|
|
|
}
|
|
|
|
|
2009-03-27 10:21:00 +08:00
|
|
|
int trace_seq_putmem_hex(struct trace_seq *s, const void *mem, size_t len)
|
2008-12-24 12:24:12 +08:00
|
|
|
{
|
|
|
|
unsigned char hex[HEX_CHARS];
|
2009-03-27 10:21:00 +08:00
|
|
|
const unsigned char *data = mem;
|
2008-12-24 12:24:12 +08:00
|
|
|
int i, j;
|
|
|
|
|
2009-11-25 23:10:14 +08:00
|
|
|
if (s->full)
|
|
|
|
return 0;
|
|
|
|
|
2008-12-24 12:24:12 +08:00
|
|
|
#ifdef __BIG_ENDIAN
|
|
|
|
for (i = 0, j = 0; i < len; i++) {
|
|
|
|
#else
|
|
|
|
for (i = len-1, j = 0; i >= 0; i--) {
|
|
|
|
#endif
|
|
|
|
hex[j++] = hex_asc_hi(data[i]);
|
|
|
|
hex[j++] = hex_asc_lo(data[i]);
|
|
|
|
}
|
|
|
|
hex[j++] = ' ';
|
|
|
|
|
|
|
|
return trace_seq_putmem(s, hex, j);
|
|
|
|
}
|
|
|
|
|
2009-03-23 21:12:22 +08:00
|
|
|
void *trace_seq_reserve(struct trace_seq *s, size_t len)
|
|
|
|
{
|
|
|
|
void *ret;
|
|
|
|
|
2009-11-25 23:10:14 +08:00
|
|
|
if (s->full)
|
2010-01-25 00:03:50 +08:00
|
|
|
return NULL;
|
2009-11-25 23:10:14 +08:00
|
|
|
|
|
|
|
if (len > ((PAGE_SIZE - 1) - s->len)) {
|
|
|
|
s->full = 1;
|
2009-03-23 21:12:22 +08:00
|
|
|
return NULL;
|
2009-11-25 23:10:14 +08:00
|
|
|
}
|
2009-03-23 21:12:22 +08:00
|
|
|
|
|
|
|
ret = s->buffer + s->len;
|
|
|
|
s->len += len;
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2012-03-15 09:51:10 +08:00
|
|
|
int trace_seq_path(struct trace_seq *s, const struct path *path)
|
2008-12-24 12:24:12 +08:00
|
|
|
{
|
|
|
|
unsigned char *p;
|
|
|
|
|
2009-11-25 23:10:14 +08:00
|
|
|
if (s->full)
|
2008-12-24 12:24:12 +08:00
|
|
|
return 0;
|
2009-11-25 23:10:14 +08:00
|
|
|
|
|
|
|
if (s->len >= (PAGE_SIZE - 1)) {
|
|
|
|
s->full = 1;
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-12-24 12:24:12 +08:00
|
|
|
p = d_path(path, s->buffer + s->len, PAGE_SIZE - s->len);
|
|
|
|
if (!IS_ERR(p)) {
|
|
|
|
p = mangle_path(s->buffer + s->len, p, "\n");
|
|
|
|
if (p) {
|
|
|
|
s->len = p - s->buffer;
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
s->buffer[s->len++] = '?';
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
2009-11-25 23:10:14 +08:00
|
|
|
s->full = 1;
|
2008-12-24 12:24:12 +08:00
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2009-05-27 02:25:22 +08:00
|
|
|
const char *
|
|
|
|
ftrace_print_flags_seq(struct trace_seq *p, const char *delim,
|
|
|
|
unsigned long flags,
|
|
|
|
const struct trace_print_flags *flag_array)
|
|
|
|
{
|
|
|
|
unsigned long mask;
|
|
|
|
const char *str;
|
2009-06-03 21:52:03 +08:00
|
|
|
const char *ret = p->buffer + p->len;
|
2012-02-19 19:16:07 +08:00
|
|
|
int i, first = 1;
|
2009-05-27 02:25:22 +08:00
|
|
|
|
|
|
|
for (i = 0; flag_array[i].name && flags; i++) {
|
|
|
|
|
|
|
|
mask = flag_array[i].mask;
|
|
|
|
if ((flags & mask) != mask)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
str = flag_array[i].name;
|
|
|
|
flags &= ~mask;
|
2012-02-19 19:16:07 +08:00
|
|
|
if (!first && delim)
|
2009-05-27 02:25:22 +08:00
|
|
|
trace_seq_puts(p, delim);
|
2012-02-19 19:16:07 +08:00
|
|
|
else
|
|
|
|
first = 0;
|
2009-05-27 02:25:22 +08:00
|
|
|
trace_seq_puts(p, str);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* check for left over flags */
|
|
|
|
if (flags) {
|
2012-02-21 09:37:32 +08:00
|
|
|
if (!first && delim)
|
2009-05-27 02:25:22 +08:00
|
|
|
trace_seq_puts(p, delim);
|
|
|
|
trace_seq_printf(p, "0x%lx", flags);
|
|
|
|
}
|
|
|
|
|
|
|
|
trace_seq_putc(p, 0);
|
|
|
|
|
2009-06-03 21:52:03 +08:00
|
|
|
return ret;
|
2009-05-27 02:25:22 +08:00
|
|
|
}
|
2009-06-01 22:53:35 +08:00
|
|
|
EXPORT_SYMBOL(ftrace_print_flags_seq);
|
2009-05-27 02:25:22 +08:00
|
|
|
|
2009-05-21 07:21:47 +08:00
|
|
|
const char *
|
|
|
|
ftrace_print_symbols_seq(struct trace_seq *p, unsigned long val,
|
|
|
|
const struct trace_print_flags *symbol_array)
|
|
|
|
{
|
|
|
|
int i;
|
2009-06-03 21:52:03 +08:00
|
|
|
const char *ret = p->buffer + p->len;
|
2009-05-21 07:21:47 +08:00
|
|
|
|
|
|
|
for (i = 0; symbol_array[i].name; i++) {
|
|
|
|
|
|
|
|
if (val != symbol_array[i].mask)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
trace_seq_puts(p, symbol_array[i].name);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2012-02-21 09:37:32 +08:00
|
|
|
if (ret == (const char *)(p->buffer + p->len))
|
2009-05-21 07:21:47 +08:00
|
|
|
trace_seq_printf(p, "0x%lx", val);
|
|
|
|
|
|
|
|
trace_seq_putc(p, 0);
|
|
|
|
|
2009-06-03 21:52:03 +08:00
|
|
|
return ret;
|
2009-05-21 07:21:47 +08:00
|
|
|
}
|
2009-06-01 22:53:35 +08:00
|
|
|
EXPORT_SYMBOL(ftrace_print_symbols_seq);
|
2009-05-21 07:21:47 +08:00
|
|
|
|
2011-04-19 09:35:28 +08:00
|
|
|
#if BITS_PER_LONG == 32
|
|
|
|
const char *
|
|
|
|
ftrace_print_symbols_seq_u64(struct trace_seq *p, unsigned long long val,
|
|
|
|
const struct trace_print_flags_u64 *symbol_array)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
const char *ret = p->buffer + p->len;
|
|
|
|
|
|
|
|
for (i = 0; symbol_array[i].name; i++) {
|
|
|
|
|
|
|
|
if (val != symbol_array[i].mask)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
trace_seq_puts(p, symbol_array[i].name);
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2012-02-21 09:37:32 +08:00
|
|
|
if (ret == (const char *)(p->buffer + p->len))
|
2011-04-19 09:35:28 +08:00
|
|
|
trace_seq_printf(p, "0x%llx", val);
|
|
|
|
|
|
|
|
trace_seq_putc(p, 0);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(ftrace_print_symbols_seq_u64);
|
|
|
|
#endif
|
|
|
|
|
2010-04-01 19:40:58 +08:00
|
|
|
const char *
|
|
|
|
ftrace_print_hex_seq(struct trace_seq *p, const unsigned char *buf, int buf_len)
|
|
|
|
{
|
|
|
|
int i;
|
|
|
|
const char *ret = p->buffer + p->len;
|
|
|
|
|
|
|
|
for (i = 0; i < buf_len; i++)
|
|
|
|
trace_seq_printf(p, "%s%2.2x", i == 0 ? "" : " ", buf[i]);
|
|
|
|
|
|
|
|
trace_seq_putc(p, 0);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(ftrace_print_hex_seq);
|
|
|
|
|
2013-02-21 10:32:38 +08:00
|
|
|
int ftrace_raw_output_prep(struct trace_iterator *iter,
|
|
|
|
struct trace_event *trace_event)
|
|
|
|
{
|
|
|
|
struct ftrace_event_call *event;
|
|
|
|
struct trace_seq *s = &iter->seq;
|
|
|
|
struct trace_seq *p = &iter->tmp_seq;
|
|
|
|
struct trace_entry *entry;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
event = container_of(trace_event, struct ftrace_event_call, event);
|
|
|
|
entry = iter->ent;
|
|
|
|
|
|
|
|
if (entry->type != event->event.type) {
|
|
|
|
WARN_ON_ONCE(1);
|
|
|
|
return TRACE_TYPE_UNHANDLED;
|
|
|
|
}
|
|
|
|
|
|
|
|
trace_seq_init(p);
|
2014-04-09 05:26:21 +08:00
|
|
|
ret = trace_seq_printf(s, "%s: ", ftrace_event_name(event));
|
2013-02-21 10:32:38 +08:00
|
|
|
if (!ret)
|
|
|
|
return TRACE_TYPE_PARTIAL_LINE;
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL(ftrace_raw_output_prep);
|
|
|
|
|
2012-08-10 07:16:14 +08:00
|
|
|
static int ftrace_output_raw(struct trace_iterator *iter, char *name,
|
|
|
|
char *fmt, va_list ap)
|
|
|
|
{
|
|
|
|
struct trace_seq *s = &iter->seq;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
ret = trace_seq_printf(s, "%s: ", name);
|
|
|
|
if (!ret)
|
|
|
|
return TRACE_TYPE_PARTIAL_LINE;
|
|
|
|
|
|
|
|
ret = trace_seq_vprintf(s, fmt, ap);
|
|
|
|
|
|
|
|
if (!ret)
|
|
|
|
return TRACE_TYPE_PARTIAL_LINE;
|
|
|
|
|
|
|
|
return TRACE_TYPE_HANDLED;
|
|
|
|
}
|
|
|
|
|
|
|
|
int ftrace_output_call(struct trace_iterator *iter, char *name, char *fmt, ...)
|
|
|
|
{
|
|
|
|
va_list ap;
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
va_start(ap, fmt);
|
|
|
|
ret = ftrace_output_raw(iter, name, fmt, ap);
|
|
|
|
va_end(ap);
|
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
EXPORT_SYMBOL_GPL(ftrace_output_call);
|
|
|
|
|
2008-12-24 12:24:12 +08:00
|
|
|
#ifdef CONFIG_KRETPROBES
|
|
|
|
static inline const char *kretprobed(const char *name)
|
|
|
|
{
|
|
|
|
static const char tramp_name[] = "kretprobe_trampoline";
|
|
|
|
int size = sizeof(tramp_name);
|
|
|
|
|
|
|
|
if (strncmp(tramp_name, name, size) == 0)
|
|
|
|
return "[unknown/kretprobe'd]";
|
|
|
|
return name;
|
|
|
|
}
|
|
|
|
#else
|
|
|
|
static inline const char *kretprobed(const char *name)
|
|
|
|
{
|
|
|
|
return name;
|
|
|
|
}
|
|
|
|
#endif /* CONFIG_KRETPROBES */
|
|
|
|
|
|
|
|
static int
|
|
|
|
seq_print_sym_short(struct trace_seq *s, const char *fmt, unsigned long address)
|
|
|
|
{
|
|
|
|
#ifdef CONFIG_KALLSYMS
|
|
|
|
char str[KSYM_SYMBOL_LEN];
|
|
|
|
const char *name;
|
|
|
|
|
|
|
|
kallsyms_lookup(address, NULL, NULL, NULL, str);
|
|
|
|
|
|
|
|
name = kretprobed(str);
|
|
|
|
|
|
|
|
return trace_seq_printf(s, fmt, name);
|
|
|
|
#endif
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
static int
|
|
|
|
seq_print_sym_offset(struct trace_seq *s, const char *fmt,
|
|
|
|
unsigned long address)
|
|
|
|
{
|
|
|
|
#ifdef CONFIG_KALLSYMS
|
|
|
|
char str[KSYM_SYMBOL_LEN];
|
|
|
|
const char *name;
|
|
|
|
|
|
|
|
sprint_symbol(str, address);
|
|
|
|
name = kretprobed(str);
|
|
|
|
|
|
|
|
return trace_seq_printf(s, fmt, name);
|
|
|
|
#endif
|
|
|
|
return 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
#ifndef CONFIG_64BIT
|
|
|
|
# define IP_FMT "%08lx"
|
|
|
|
#else
|
|
|
|
# define IP_FMT "%016lx"
|
|
|
|
#endif
|
|
|
|
|
|
|
|
int seq_print_user_ip(struct trace_seq *s, struct mm_struct *mm,
|
|
|
|
unsigned long ip, unsigned long sym_flags)
|
|
|
|
{
|
|
|
|
struct file *file = NULL;
|
|
|
|
unsigned long vmstart = 0;
|
|
|
|
int ret = 1;
|
|
|
|
|
2009-11-25 23:10:14 +08:00
|
|
|
if (s->full)
|
|
|
|
return 0;
|
|
|
|
|
2008-12-24 12:24:12 +08:00
|
|
|
if (mm) {
|
|
|
|
const struct vm_area_struct *vma;
|
|
|
|
|
|
|
|
down_read(&mm->mmap_sem);
|
|
|
|
vma = find_vma(mm, ip);
|
|
|
|
if (vma) {
|
|
|
|
file = vma->vm_file;
|
|
|
|
vmstart = vma->vm_start;
|
|
|
|
}
|
|
|
|
if (file) {
|
|
|
|
ret = trace_seq_path(s, &file->f_path);
|
|
|
|
if (ret)
|
|
|
|
ret = trace_seq_printf(s, "[+0x%lx]",
|
|
|
|
ip - vmstart);
|
|
|
|
}
|
|
|
|
up_read(&mm->mmap_sem);
|
|
|
|
}
|
|
|
|
if (ret && ((sym_flags & TRACE_ITER_SYM_ADDR) || !file))
|
|
|
|
ret = trace_seq_printf(s, " <" IP_FMT ">", ip);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
seq_print_userip_objs(const struct userstack_entry *entry, struct trace_seq *s,
|
|
|
|
unsigned long sym_flags)
|
|
|
|
{
|
|
|
|
struct mm_struct *mm = NULL;
|
|
|
|
int ret = 1;
|
|
|
|
unsigned int i;
|
|
|
|
|
|
|
|
if (trace_flags & TRACE_ITER_SYM_USEROBJ) {
|
|
|
|
struct task_struct *task;
|
|
|
|
/*
|
|
|
|
* we do the lookup on the thread group leader,
|
|
|
|
* since individual threads might have already quit!
|
|
|
|
*/
|
|
|
|
rcu_read_lock();
|
2009-09-11 23:36:23 +08:00
|
|
|
task = find_task_by_vpid(entry->tgid);
|
2008-12-24 12:24:12 +08:00
|
|
|
if (task)
|
|
|
|
mm = get_task_mm(task);
|
|
|
|
rcu_read_unlock();
|
|
|
|
}
|
|
|
|
|
|
|
|
for (i = 0; i < FTRACE_STACK_ENTRIES; i++) {
|
|
|
|
unsigned long ip = entry->caller[i];
|
|
|
|
|
|
|
|
if (ip == ULONG_MAX || !ret)
|
|
|
|
break;
|
2009-06-03 16:01:30 +08:00
|
|
|
if (ret)
|
|
|
|
ret = trace_seq_puts(s, " => ");
|
2008-12-24 12:24:12 +08:00
|
|
|
if (!ip) {
|
|
|
|
if (ret)
|
|
|
|
ret = trace_seq_puts(s, "??");
|
2009-06-03 16:01:30 +08:00
|
|
|
if (ret)
|
2013-07-15 16:32:44 +08:00
|
|
|
ret = trace_seq_putc(s, '\n');
|
2008-12-24 12:24:12 +08:00
|
|
|
continue;
|
|
|
|
}
|
|
|
|
if (!ret)
|
|
|
|
break;
|
|
|
|
if (ret)
|
|
|
|
ret = seq_print_user_ip(s, mm, ip, sym_flags);
|
2013-07-15 16:32:44 +08:00
|
|
|
ret = trace_seq_putc(s, '\n');
|
2008-12-24 12:24:12 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
if (mm)
|
|
|
|
mmput(mm);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
|
|
|
int
|
|
|
|
seq_print_ip_sym(struct trace_seq *s, unsigned long ip, unsigned long sym_flags)
|
|
|
|
{
|
|
|
|
int ret;
|
|
|
|
|
|
|
|
if (!ip)
|
2013-07-15 16:32:44 +08:00
|
|
|
return trace_seq_putc(s, '0');
|
2008-12-24 12:24:12 +08:00
|
|
|
|
|
|
|
if (sym_flags & TRACE_ITER_SYM_OFFSET)
|
|
|
|
ret = seq_print_sym_offset(s, "%s", ip);
|
|
|
|
else
|
|
|
|
ret = seq_print_sym_short(s, "%s", ip);
|
|
|
|
|
|
|
|
if (!ret)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (sym_flags & TRACE_ITER_SYM_ADDR)
|
|
|
|
ret = trace_seq_printf(s, " <" IP_FMT ">", ip);
|
|
|
|
return ret;
|
|
|
|
}
|
|
|
|
|
2009-09-12 02:24:13 +08:00
|
|
|
/**
|
|
|
|
* trace_print_lat_fmt - print the irq, preempt and lockdep fields
|
|
|
|
* @s: trace seq struct to write to
|
|
|
|
* @entry: The trace entry field from the ring buffer
|
|
|
|
*
|
|
|
|
* Prints the generic fields of irqs off, in hard or softirq, preempt
|
2011-03-09 23:41:56 +08:00
|
|
|
* count.
|
2009-09-12 02:24:13 +08:00
|
|
|
*/
|
|
|
|
int trace_print_lat_fmt(struct trace_seq *s, struct trace_entry *entry)
|
2009-02-03 06:29:21 +08:00
|
|
|
{
|
2010-12-04 08:13:26 +08:00
|
|
|
char hardsoft_irq;
|
|
|
|
char need_resched;
|
|
|
|
char irqs_off;
|
|
|
|
int hardirq;
|
|
|
|
int softirq;
|
2009-09-12 01:55:35 +08:00
|
|
|
int ret;
|
2009-02-03 06:29:21 +08:00
|
|
|
|
|
|
|
hardirq = entry->flags & TRACE_FLAG_HARDIRQ;
|
|
|
|
softirq = entry->flags & TRACE_FLAG_SOFTIRQ;
|
2009-02-04 06:20:41 +08:00
|
|
|
|
2010-12-04 08:13:26 +08:00
|
|
|
irqs_off =
|
|
|
|
(entry->flags & TRACE_FLAG_IRQS_OFF) ? 'd' :
|
|
|
|
(entry->flags & TRACE_FLAG_IRQS_NOSUPPORT) ? 'X' :
|
|
|
|
'.';
|
2013-10-04 23:28:26 +08:00
|
|
|
|
|
|
|
switch (entry->flags & (TRACE_FLAG_NEED_RESCHED |
|
|
|
|
TRACE_FLAG_PREEMPT_RESCHED)) {
|
|
|
|
case TRACE_FLAG_NEED_RESCHED | TRACE_FLAG_PREEMPT_RESCHED:
|
|
|
|
need_resched = 'N';
|
|
|
|
break;
|
|
|
|
case TRACE_FLAG_NEED_RESCHED:
|
|
|
|
need_resched = 'n';
|
|
|
|
break;
|
|
|
|
case TRACE_FLAG_PREEMPT_RESCHED:
|
|
|
|
need_resched = 'p';
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
need_resched = '.';
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2010-12-04 08:13:26 +08:00
|
|
|
hardsoft_irq =
|
|
|
|
(hardirq && softirq) ? 'H' :
|
|
|
|
hardirq ? 'h' :
|
|
|
|
softirq ? 's' :
|
|
|
|
'.';
|
|
|
|
|
2009-09-12 02:24:13 +08:00
|
|
|
if (!trace_seq_printf(s, "%c%c%c",
|
2010-12-04 08:13:26 +08:00
|
|
|
irqs_off, need_resched, hardsoft_irq))
|
2009-02-04 06:20:41 +08:00
|
|
|
return 0;
|
2009-02-03 06:29:21 +08:00
|
|
|
|
2009-09-27 19:02:07 +08:00
|
|
|
if (entry->preempt_count)
|
|
|
|
ret = trace_seq_printf(s, "%x", entry->preempt_count);
|
2009-09-12 01:55:35 +08:00
|
|
|
else
|
2009-09-27 19:02:07 +08:00
|
|
|
ret = trace_seq_putc(s, '.');
|
|
|
|
|
2011-03-09 23:41:56 +08:00
|
|
|
return ret;
|
2009-02-03 06:29:21 +08:00
|
|
|
}
|
|
|
|
|
2009-09-12 02:24:13 +08:00
|
|
|
static int
|
|
|
|
lat_print_generic(struct trace_seq *s, struct trace_entry *entry, int cpu)
|
|
|
|
{
|
|
|
|
char comm[TASK_COMM_LEN];
|
|
|
|
|
|
|
|
trace_find_cmdline(entry->pid, comm);
|
|
|
|
|
|
|
|
if (!trace_seq_printf(s, "%8.8s-%-5d %3d",
|
|
|
|
comm, entry->pid, cpu))
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
return trace_print_lat_fmt(s, entry);
|
|
|
|
}
|
|
|
|
|
2012-11-14 04:18:22 +08:00
|
|
|
static unsigned long preempt_mark_thresh_us = 100;
|
2009-02-03 06:29:21 +08:00
|
|
|
|
2009-02-04 06:20:41 +08:00
|
|
|
static int
|
2012-11-14 04:18:22 +08:00
|
|
|
lat_print_timestamp(struct trace_iterator *iter, u64 next_ts)
|
2009-02-03 06:29:21 +08:00
|
|
|
{
|
2012-11-14 04:18:22 +08:00
|
|
|
unsigned long verbose = trace_flags & TRACE_ITER_VERBOSE;
|
|
|
|
unsigned long in_ns = iter->iter_flags & TRACE_FILE_TIME_IN_NS;
|
tracing: Consolidate max_tr into main trace_array structure
Currently, the way the latency tracers and snapshot feature works
is to have a separate trace_array called "max_tr" that holds the
snapshot buffer. For latency tracers, this snapshot buffer is used
to swap the running buffer with this buffer to save the current max
latency.
The only items needed for the max_tr is really just a copy of the buffer
itself, the per_cpu data pointers, the time_start timestamp that states
when the max latency was triggered, and the cpu that the max latency
was triggered on. All other fields in trace_array are unused by the
max_tr, making the max_tr mostly bloat.
This change removes the max_tr completely, and adds a new structure
called trace_buffer, that holds the buffer pointer, the per_cpu data
pointers, the time_start timestamp, and the cpu where the latency occurred.
The trace_array, now has two trace_buffers, one for the normal trace and
one for the max trace or snapshot. By doing this, not only do we remove
the bloat from the max_trace but the instances of traces can now use
their own snapshot feature and not have just the top level global_trace have
the snapshot feature and latency tracers for itself.
Signed-off-by: Steven Rostedt <rostedt@goodmis.org>
2013-03-05 22:24:35 +08:00
|
|
|
unsigned long long abs_ts = iter->ts - iter->trace_buffer->time_start;
|
2012-11-14 04:18:22 +08:00
|
|
|
unsigned long long rel_ts = next_ts - iter->ts;
|
|
|
|
struct trace_seq *s = &iter->seq;
|
|
|
|
|
|
|
|
if (in_ns) {
|
|
|
|
abs_ts = ns2usecs(abs_ts);
|
|
|
|
rel_ts = ns2usecs(rel_ts);
|
|
|
|
}
|
|
|
|
|
|
|
|
if (verbose && in_ns) {
|
|
|
|
unsigned long abs_usec = do_div(abs_ts, USEC_PER_MSEC);
|
|
|
|
unsigned long abs_msec = (unsigned long)abs_ts;
|
|
|
|
unsigned long rel_usec = do_div(rel_ts, USEC_PER_MSEC);
|
|
|
|
unsigned long rel_msec = (unsigned long)rel_ts;
|
|
|
|
|
|
|
|
return trace_seq_printf(
|
|
|
|
s, "[%08llx] %ld.%03ldms (+%ld.%03ldms): ",
|
|
|
|
ns2usecs(iter->ts),
|
|
|
|
abs_msec, abs_usec,
|
|
|
|
rel_msec, rel_usec);
|
|
|
|
} else if (verbose && !in_ns) {
|
|
|
|
return trace_seq_printf(
|
|
|
|
s, "[%016llx] %lld (+%lld): ",
|
|
|
|
iter->ts, abs_ts, rel_ts);
|
|
|
|
} else if (!verbose && in_ns) {
|
|
|
|
return trace_seq_printf(
|
|
|
|
s, " %4lldus%c: ",
|
|
|
|
abs_ts,
|
|
|
|
rel_ts > preempt_mark_thresh_us ? '!' :
|
|
|
|
rel_ts > 1 ? '+' : ' ');
|
|
|
|
} else { /* !verbose && !in_ns */
|
|
|
|
return trace_seq_printf(s, " %4lld: ", abs_ts);
|
|
|
|
}
|
2009-02-03 06:29:21 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
int trace_print_context(struct trace_iterator *iter)
|
|
|
|
{
|
|
|
|
struct trace_seq *s = &iter->seq;
|
|
|
|
struct trace_entry *entry = iter->ent;
|
2012-11-14 04:18:22 +08:00
|
|
|
unsigned long long t;
|
|
|
|
unsigned long secs, usec_rem;
|
2009-03-17 07:20:15 +08:00
|
|
|
char comm[TASK_COMM_LEN];
|
tracing: Add irq, preempt-count and need resched info to default trace output
People keep asking how to get the preempt count, irq, and need resched info
and we keep telling them to enable the latency format. Some developers think
that traces without this info is completely useless, and for a lot of tasks
it is useless.
The first option was to enable the latency trace as the default format, but
the header for the latency format is pretty useless for most tracers and
it also does the timestamp in straight microseconds from the time the trace
started. This is sometimes more difficult to read as the default trace is
seconds from the start of boot up.
Latency format:
# tracer: nop
#
# nop latency trace v1.1.5 on 3.2.0-rc1-test+
# --------------------------------------------------------------------
# latency: 0 us, #159771/64234230, CPU#1 | (M:preempt VP:0, KP:0, SP:0 HP:0 #P:4)
# -----------------
# | task: -0 (uid:0 nice:0 policy:0 rt_prio:0)
# -----------------
#
# _------=> CPU#
# / _-----=> irqs-off
# | / _----=> need-resched
# || / _---=> hardirq/softirq
# ||| / _--=> preempt-depth
# |||| / delay
# cmd pid ||||| time | caller
# \ / ||||| \ | /
migratio-6 0...2 41778231us+: rcu_note_context_switch <-__schedule
migratio-6 0...2 41778233us : trace_rcu_utilization <-rcu_note_context_switch
migratio-6 0...2 41778235us+: rcu_sched_qs <-rcu_note_context_switch
migratio-6 0d..2 41778236us+: rcu_preempt_qs <-rcu_note_context_switch
migratio-6 0...2 41778238us : trace_rcu_utilization <-rcu_note_context_switch
migratio-6 0...2 41778239us+: debug_lockdep_rcu_enabled <-__schedule
default format:
# tracer: nop
#
# TASK-PID CPU# TIMESTAMP FUNCTION
# | | | | |
migration/0-6 [000] 50.025810: rcu_note_context_switch <-__schedule
migration/0-6 [000] 50.025812: trace_rcu_utilization <-rcu_note_context_switch
migration/0-6 [000] 50.025813: rcu_sched_qs <-rcu_note_context_switch
migration/0-6 [000] 50.025815: rcu_preempt_qs <-rcu_note_context_switch
migration/0-6 [000] 50.025817: trace_rcu_utilization <-rcu_note_context_switch
migration/0-6 [000] 50.025818: debug_lockdep_rcu_enabled <-__schedule
migration/0-6 [000] 50.025820: debug_lockdep_rcu_enabled <-__schedule
The latency format header has latency information that is pretty meaningless
for most tracers. Although some of the header is useful, and we can add that
later to the default format as well.
What is really useful with the latency format is the irqs-off, need-resched
hard/softirq context and the preempt count.
This commit adds the option irq-info which is on by default that adds this
information:
# tracer: nop
#
# _-----=> irqs-off
# / _----=> need-resched
# | / _---=> hardirq/softirq
# || / _--=> preempt-depth
# ||| / delay
# TASK-PID CPU# |||| TIMESTAMP FUNCTION
# | | | |||| | |
<idle>-0 [000] d..2 49.309305: cpuidle_get_driver <-cpuidle_idle_call
<idle>-0 [000] d..2 49.309307: mwait_idle <-cpu_idle
<idle>-0 [000] d..2 49.309309: need_resched <-mwait_idle
<idle>-0 [000] d..2 49.309310: test_ti_thread_flag <-need_resched
<idle>-0 [000] d..2 49.309312: trace_power_start.constprop.13 <-mwait_idle
<idle>-0 [000] d..2 49.309313: trace_cpu_idle <-mwait_idle
<idle>-0 [000] d..2 49.309315: need_resched <-mwait_idle
If a user wants the old format, they can disable the 'irq-info' option:
# tracer: nop
#
# TASK-PID CPU# TIMESTAMP FUNCTION
# | | | | |
<idle>-0 [000] 49.309305: cpuidle_get_driver <-cpuidle_idle_call
<idle>-0 [000] 49.309307: mwait_idle <-cpu_idle
<idle>-0 [000] 49.309309: need_resched <-mwait_idle
<idle>-0 [000] 49.309310: test_ti_thread_flag <-need_resched
<idle>-0 [000] 49.309312: trace_power_start.constprop.13 <-mwait_idle
<idle>-0 [000] 49.309313: trace_cpu_idle <-mwait_idle
<idle>-0 [000] 49.309315: need_resched <-mwait_idle
Requested-by: Thomas Gleixner <tglx@linutronix.de>
Signed-off-by: Steven Rostedt <rostedt@goodmis.org>
2011-11-17 22:34:33 +08:00
|
|
|
int ret;
|
2009-03-17 07:20:15 +08:00
|
|
|
|
|
|
|
trace_find_cmdline(entry->pid, comm);
|
2009-02-03 06:29:21 +08:00
|
|
|
|
tracing: Add irq, preempt-count and need resched info to default trace output
People keep asking how to get the preempt count, irq, and need resched info
and we keep telling them to enable the latency format. Some developers think
that traces without this info is completely useless, and for a lot of tasks
it is useless.
The first option was to enable the latency trace as the default format, but
the header for the latency format is pretty useless for most tracers and
it also does the timestamp in straight microseconds from the time the trace
started. This is sometimes more difficult to read as the default trace is
seconds from the start of boot up.
Latency format:
# tracer: nop
#
# nop latency trace v1.1.5 on 3.2.0-rc1-test+
# --------------------------------------------------------------------
# latency: 0 us, #159771/64234230, CPU#1 | (M:preempt VP:0, KP:0, SP:0 HP:0 #P:4)
# -----------------
# | task: -0 (uid:0 nice:0 policy:0 rt_prio:0)
# -----------------
#
# _------=> CPU#
# / _-----=> irqs-off
# | / _----=> need-resched
# || / _---=> hardirq/softirq
# ||| / _--=> preempt-depth
# |||| / delay
# cmd pid ||||| time | caller
# \ / ||||| \ | /
migratio-6 0...2 41778231us+: rcu_note_context_switch <-__schedule
migratio-6 0...2 41778233us : trace_rcu_utilization <-rcu_note_context_switch
migratio-6 0...2 41778235us+: rcu_sched_qs <-rcu_note_context_switch
migratio-6 0d..2 41778236us+: rcu_preempt_qs <-rcu_note_context_switch
migratio-6 0...2 41778238us : trace_rcu_utilization <-rcu_note_context_switch
migratio-6 0...2 41778239us+: debug_lockdep_rcu_enabled <-__schedule
default format:
# tracer: nop
#
# TASK-PID CPU# TIMESTAMP FUNCTION
# | | | | |
migration/0-6 [000] 50.025810: rcu_note_context_switch <-__schedule
migration/0-6 [000] 50.025812: trace_rcu_utilization <-rcu_note_context_switch
migration/0-6 [000] 50.025813: rcu_sched_qs <-rcu_note_context_switch
migration/0-6 [000] 50.025815: rcu_preempt_qs <-rcu_note_context_switch
migration/0-6 [000] 50.025817: trace_rcu_utilization <-rcu_note_context_switch
migration/0-6 [000] 50.025818: debug_lockdep_rcu_enabled <-__schedule
migration/0-6 [000] 50.025820: debug_lockdep_rcu_enabled <-__schedule
The latency format header has latency information that is pretty meaningless
for most tracers. Although some of the header is useful, and we can add that
later to the default format as well.
What is really useful with the latency format is the irqs-off, need-resched
hard/softirq context and the preempt count.
This commit adds the option irq-info which is on by default that adds this
information:
# tracer: nop
#
# _-----=> irqs-off
# / _----=> need-resched
# | / _---=> hardirq/softirq
# || / _--=> preempt-depth
# ||| / delay
# TASK-PID CPU# |||| TIMESTAMP FUNCTION
# | | | |||| | |
<idle>-0 [000] d..2 49.309305: cpuidle_get_driver <-cpuidle_idle_call
<idle>-0 [000] d..2 49.309307: mwait_idle <-cpu_idle
<idle>-0 [000] d..2 49.309309: need_resched <-mwait_idle
<idle>-0 [000] d..2 49.309310: test_ti_thread_flag <-need_resched
<idle>-0 [000] d..2 49.309312: trace_power_start.constprop.13 <-mwait_idle
<idle>-0 [000] d..2 49.309313: trace_cpu_idle <-mwait_idle
<idle>-0 [000] d..2 49.309315: need_resched <-mwait_idle
If a user wants the old format, they can disable the 'irq-info' option:
# tracer: nop
#
# TASK-PID CPU# TIMESTAMP FUNCTION
# | | | | |
<idle>-0 [000] 49.309305: cpuidle_get_driver <-cpuidle_idle_call
<idle>-0 [000] 49.309307: mwait_idle <-cpu_idle
<idle>-0 [000] 49.309309: need_resched <-mwait_idle
<idle>-0 [000] 49.309310: test_ti_thread_flag <-need_resched
<idle>-0 [000] 49.309312: trace_power_start.constprop.13 <-mwait_idle
<idle>-0 [000] 49.309313: trace_cpu_idle <-mwait_idle
<idle>-0 [000] 49.309315: need_resched <-mwait_idle
Requested-by: Thomas Gleixner <tglx@linutronix.de>
Signed-off-by: Steven Rostedt <rostedt@goodmis.org>
2011-11-17 22:34:33 +08:00
|
|
|
ret = trace_seq_printf(s, "%16s-%-5d [%03d] ",
|
|
|
|
comm, entry->pid, iter->cpu);
|
|
|
|
if (!ret)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
if (trace_flags & TRACE_ITER_IRQ_INFO) {
|
|
|
|
ret = trace_print_lat_fmt(s, entry);
|
|
|
|
if (!ret)
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2012-11-14 04:18:22 +08:00
|
|
|
if (iter->iter_flags & TRACE_FILE_TIME_IN_NS) {
|
|
|
|
t = ns2usecs(iter->ts);
|
|
|
|
usec_rem = do_div(t, USEC_PER_SEC);
|
|
|
|
secs = (unsigned long)t;
|
|
|
|
return trace_seq_printf(s, " %5lu.%06lu: ", secs, usec_rem);
|
|
|
|
} else
|
|
|
|
return trace_seq_printf(s, " %12llu: ", iter->ts);
|
2009-02-03 06:29:21 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
int trace_print_lat_context(struct trace_iterator *iter)
|
|
|
|
{
|
|
|
|
u64 next_ts;
|
2009-02-04 06:20:41 +08:00
|
|
|
int ret;
|
2012-04-19 22:31:47 +08:00
|
|
|
/* trace_find_next_entry will reset ent_size */
|
|
|
|
int ent_size = iter->ent_size;
|
2009-02-03 06:29:21 +08:00
|
|
|
struct trace_seq *s = &iter->seq;
|
|
|
|
struct trace_entry *entry = iter->ent,
|
|
|
|
*next_entry = trace_find_next_entry(iter, NULL,
|
|
|
|
&next_ts);
|
|
|
|
unsigned long verbose = (trace_flags & TRACE_ITER_VERBOSE);
|
|
|
|
|
2012-04-19 22:31:47 +08:00
|
|
|
/* Restore the original ent_size */
|
|
|
|
iter->ent_size = ent_size;
|
|
|
|
|
2009-02-03 06:29:21 +08:00
|
|
|
if (!next_entry)
|
|
|
|
next_ts = iter->ts;
|
|
|
|
|
|
|
|
if (verbose) {
|
2009-03-17 07:20:15 +08:00
|
|
|
char comm[TASK_COMM_LEN];
|
|
|
|
|
|
|
|
trace_find_cmdline(entry->pid, comm);
|
|
|
|
|
2012-11-14 04:18:22 +08:00
|
|
|
ret = trace_seq_printf(
|
|
|
|
s, "%16s %5d %3d %d %08x %08lx ",
|
|
|
|
comm, entry->pid, iter->cpu, entry->flags,
|
|
|
|
entry->preempt_count, iter->idx);
|
2009-02-03 06:29:21 +08:00
|
|
|
} else {
|
2009-02-08 08:38:43 +08:00
|
|
|
ret = lat_print_generic(s, entry, iter->cpu);
|
2009-02-03 06:29:21 +08:00
|
|
|
}
|
|
|
|
|
2012-11-14 04:18:22 +08:00
|
|
|
if (ret)
|
|
|
|
ret = lat_print_timestamp(iter, next_ts);
|
|
|
|
|
2009-02-04 06:20:41 +08:00
|
|
|
return ret;
|
2009-02-03 06:29:21 +08:00
|
|
|
}
|
|
|
|
|
2008-12-24 12:24:13 +08:00
|
|
|
static const char state_to_char[] = TASK_STATE_TO_CHAR_STR;
|
|
|
|
|
|
|
|
static int task_state_char(unsigned long state)
|
|
|
|
{
|
|
|
|
int bit = state ? __ffs(state) + 1 : 0;
|
|
|
|
|
|
|
|
return bit < sizeof(state_to_char) - 1 ? state_to_char[bit] : '?';
|
|
|
|
}
|
|
|
|
|
2008-12-24 12:24:12 +08:00
|
|
|
/**
|
|
|
|
* ftrace_find_event - find a registered event
|
|
|
|
* @type: the type of event to look for
|
|
|
|
*
|
|
|
|
* Returns an event of type @type otherwise NULL
|
2009-05-18 19:35:34 +08:00
|
|
|
* Called with trace_event_read_lock() held.
|
2008-12-24 12:24:12 +08:00
|
|
|
*/
|
|
|
|
struct trace_event *ftrace_find_event(int type)
|
|
|
|
{
|
|
|
|
struct trace_event *event;
|
|
|
|
unsigned key;
|
|
|
|
|
|
|
|
key = type & (EVENT_HASHSIZE - 1);
|
|
|
|
|
hlist: drop the node parameter from iterators
I'm not sure why, but the hlist for each entry iterators were conceived
list_for_each_entry(pos, head, member)
The hlist ones were greedy and wanted an extra parameter:
hlist_for_each_entry(tpos, pos, head, member)
Why did they need an extra pos parameter? I'm not quite sure. Not only
they don't really need it, it also prevents the iterator from looking
exactly like the list iterator, which is unfortunate.
Besides the semantic patch, there was some manual work required:
- Fix up the actual hlist iterators in linux/list.h
- Fix up the declaration of other iterators based on the hlist ones.
- A very small amount of places were using the 'node' parameter, this
was modified to use 'obj->member' instead.
- Coccinelle didn't handle the hlist_for_each_entry_safe iterator
properly, so those had to be fixed up manually.
The semantic patch which is mostly the work of Peter Senna Tschudin is here:
@@
iterator name hlist_for_each_entry, hlist_for_each_entry_continue, hlist_for_each_entry_from, hlist_for_each_entry_rcu, hlist_for_each_entry_rcu_bh, hlist_for_each_entry_continue_rcu_bh, for_each_busy_worker, ax25_uid_for_each, ax25_for_each, inet_bind_bucket_for_each, sctp_for_each_hentry, sk_for_each, sk_for_each_rcu, sk_for_each_from, sk_for_each_safe, sk_for_each_bound, hlist_for_each_entry_safe, hlist_for_each_entry_continue_rcu, nr_neigh_for_each, nr_neigh_for_each_safe, nr_node_for_each, nr_node_for_each_safe, for_each_gfn_indirect_valid_sp, for_each_gfn_sp, for_each_host;
type T;
expression a,c,d,e;
identifier b;
statement S;
@@
-T b;
<+... when != b
(
hlist_for_each_entry(a,
- b,
c, d) S
|
hlist_for_each_entry_continue(a,
- b,
c) S
|
hlist_for_each_entry_from(a,
- b,
c) S
|
hlist_for_each_entry_rcu(a,
- b,
c, d) S
|
hlist_for_each_entry_rcu_bh(a,
- b,
c, d) S
|
hlist_for_each_entry_continue_rcu_bh(a,
- b,
c) S
|
for_each_busy_worker(a, c,
- b,
d) S
|
ax25_uid_for_each(a,
- b,
c) S
|
ax25_for_each(a,
- b,
c) S
|
inet_bind_bucket_for_each(a,
- b,
c) S
|
sctp_for_each_hentry(a,
- b,
c) S
|
sk_for_each(a,
- b,
c) S
|
sk_for_each_rcu(a,
- b,
c) S
|
sk_for_each_from
-(a, b)
+(a)
S
+ sk_for_each_from(a) S
|
sk_for_each_safe(a,
- b,
c, d) S
|
sk_for_each_bound(a,
- b,
c) S
|
hlist_for_each_entry_safe(a,
- b,
c, d, e) S
|
hlist_for_each_entry_continue_rcu(a,
- b,
c) S
|
nr_neigh_for_each(a,
- b,
c) S
|
nr_neigh_for_each_safe(a,
- b,
c, d) S
|
nr_node_for_each(a,
- b,
c) S
|
nr_node_for_each_safe(a,
- b,
c, d) S
|
- for_each_gfn_sp(a, c, d, b) S
+ for_each_gfn_sp(a, c, d) S
|
- for_each_gfn_indirect_valid_sp(a, c, d, b) S
+ for_each_gfn_indirect_valid_sp(a, c, d) S
|
for_each_host(a,
- b,
c) S
|
for_each_host_safe(a,
- b,
c, d) S
|
for_each_mesh_entry(a,
- b,
c, d) S
)
...+>
[akpm@linux-foundation.org: drop bogus change from net/ipv4/raw.c]
[akpm@linux-foundation.org: drop bogus hunk from net/ipv6/raw.c]
[akpm@linux-foundation.org: checkpatch fixes]
[akpm@linux-foundation.org: fix warnings]
[akpm@linux-foudnation.org: redo intrusive kvm changes]
Tested-by: Peter Senna Tschudin <peter.senna@gmail.com>
Acked-by: Paul E. McKenney <paulmck@linux.vnet.ibm.com>
Signed-off-by: Sasha Levin <sasha.levin@oracle.com>
Cc: Wu Fengguang <fengguang.wu@intel.com>
Cc: Marcelo Tosatti <mtosatti@redhat.com>
Cc: Gleb Natapov <gleb@redhat.com>
Signed-off-by: Andrew Morton <akpm@linux-foundation.org>
Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
2013-02-28 09:06:00 +08:00
|
|
|
hlist_for_each_entry(event, &event_hash[key], node) {
|
2008-12-24 12:24:12 +08:00
|
|
|
if (event->type == type)
|
|
|
|
return event;
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2009-04-25 00:20:52 +08:00
|
|
|
static LIST_HEAD(ftrace_event_list);
|
|
|
|
|
|
|
|
static int trace_search_list(struct list_head **list)
|
|
|
|
{
|
|
|
|
struct trace_event *e;
|
|
|
|
int last = __TRACE_LAST_TYPE;
|
|
|
|
|
|
|
|
if (list_empty(&ftrace_event_list)) {
|
|
|
|
*list = &ftrace_event_list;
|
|
|
|
return last + 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* We used up all possible max events,
|
|
|
|
* lets see if somebody freed one.
|
|
|
|
*/
|
|
|
|
list_for_each_entry(e, &ftrace_event_list, list) {
|
|
|
|
if (e->type != last + 1)
|
|
|
|
break;
|
|
|
|
last++;
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Did we used up all 65 thousand events??? */
|
|
|
|
if ((last + 1) > FTRACE_MAX_EVENT)
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
*list = &e->list;
|
|
|
|
return last + 1;
|
|
|
|
}
|
|
|
|
|
2009-05-18 19:35:34 +08:00
|
|
|
void trace_event_read_lock(void)
|
|
|
|
{
|
2013-03-11 15:14:03 +08:00
|
|
|
down_read(&trace_event_sem);
|
2009-05-18 19:35:34 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void trace_event_read_unlock(void)
|
|
|
|
{
|
2013-03-11 15:14:03 +08:00
|
|
|
up_read(&trace_event_sem);
|
2009-05-18 19:35:34 +08:00
|
|
|
}
|
|
|
|
|
2008-12-24 12:24:12 +08:00
|
|
|
/**
|
|
|
|
* register_ftrace_event - register output for an event type
|
|
|
|
* @event: the event type to register
|
|
|
|
*
|
|
|
|
* Event types are stored in a hash and this hash is used to
|
|
|
|
* find a way to print an event. If the @event->type is set
|
|
|
|
* then it will use that type, otherwise it will assign a
|
|
|
|
* type to use.
|
|
|
|
*
|
|
|
|
* If you assign your own type, please make sure it is added
|
|
|
|
* to the trace_type enum in trace.h, to avoid collisions
|
|
|
|
* with the dynamic types.
|
|
|
|
*
|
|
|
|
* Returns the event type number or zero on error.
|
|
|
|
*/
|
|
|
|
int register_ftrace_event(struct trace_event *event)
|
|
|
|
{
|
|
|
|
unsigned key;
|
|
|
|
int ret = 0;
|
|
|
|
|
2013-03-11 15:14:03 +08:00
|
|
|
down_write(&trace_event_sem);
|
2008-12-24 12:24:12 +08:00
|
|
|
|
2009-04-25 00:20:52 +08:00
|
|
|
if (WARN_ON(!event))
|
2009-03-20 03:26:14 +08:00
|
|
|
goto out;
|
|
|
|
|
2010-04-23 06:46:14 +08:00
|
|
|
if (WARN_ON(!event->funcs))
|
|
|
|
goto out;
|
|
|
|
|
2009-04-25 00:20:52 +08:00
|
|
|
INIT_LIST_HEAD(&event->list);
|
|
|
|
|
|
|
|
if (!event->type) {
|
2009-05-06 18:15:45 +08:00
|
|
|
struct list_head *list = NULL;
|
2009-04-25 00:20:52 +08:00
|
|
|
|
|
|
|
if (next_event_type > FTRACE_MAX_EVENT) {
|
|
|
|
|
|
|
|
event->type = trace_search_list(&list);
|
|
|
|
if (!event->type)
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
} else {
|
|
|
|
|
|
|
|
event->type = next_event_type++;
|
|
|
|
list = &ftrace_event_list;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (WARN_ON(ftrace_find_event(event->type)))
|
|
|
|
goto out;
|
|
|
|
|
|
|
|
list_add_tail(&event->list, list);
|
|
|
|
|
|
|
|
} else if (event->type > __TRACE_LAST_TYPE) {
|
2008-12-24 12:24:12 +08:00
|
|
|
printk(KERN_WARNING "Need to add type to trace.h\n");
|
|
|
|
WARN_ON(1);
|
|
|
|
goto out;
|
2009-04-25 00:20:52 +08:00
|
|
|
} else {
|
|
|
|
/* Is this event already used */
|
|
|
|
if (ftrace_find_event(event->type))
|
|
|
|
goto out;
|
|
|
|
}
|
2008-12-24 12:24:12 +08:00
|
|
|
|
2010-04-23 06:46:14 +08:00
|
|
|
if (event->funcs->trace == NULL)
|
|
|
|
event->funcs->trace = trace_nop_print;
|
|
|
|
if (event->funcs->raw == NULL)
|
|
|
|
event->funcs->raw = trace_nop_print;
|
|
|
|
if (event->funcs->hex == NULL)
|
|
|
|
event->funcs->hex = trace_nop_print;
|
|
|
|
if (event->funcs->binary == NULL)
|
|
|
|
event->funcs->binary = trace_nop_print;
|
2009-02-05 06:16:39 +08:00
|
|
|
|
2008-12-24 12:24:12 +08:00
|
|
|
key = event->type & (EVENT_HASHSIZE - 1);
|
|
|
|
|
2009-05-18 19:35:34 +08:00
|
|
|
hlist_add_head(&event->node, &event_hash[key]);
|
2008-12-24 12:24:12 +08:00
|
|
|
|
|
|
|
ret = event->type;
|
|
|
|
out:
|
2013-03-11 15:14:03 +08:00
|
|
|
up_write(&trace_event_sem);
|
2008-12-24 12:24:12 +08:00
|
|
|
|
|
|
|
return ret;
|
|
|
|
}
|
2009-04-11 06:12:50 +08:00
|
|
|
EXPORT_SYMBOL_GPL(register_ftrace_event);
|
2008-12-24 12:24:12 +08:00
|
|
|
|
2009-06-10 05:29:07 +08:00
|
|
|
/*
|
2013-03-11 15:14:03 +08:00
|
|
|
* Used by module code with the trace_event_sem held for write.
|
2009-06-10 05:29:07 +08:00
|
|
|
*/
|
|
|
|
int __unregister_ftrace_event(struct trace_event *event)
|
|
|
|
{
|
|
|
|
hlist_del(&event->node);
|
|
|
|
list_del(&event->list);
|
|
|
|
return 0;
|
|
|
|
}
|
|
|
|
|
2008-12-24 12:24:12 +08:00
|
|
|
/**
|
|
|
|
* unregister_ftrace_event - remove a no longer used event
|
|
|
|
* @event: the event to remove
|
|
|
|
*/
|
|
|
|
int unregister_ftrace_event(struct trace_event *event)
|
|
|
|
{
|
2013-03-11 15:14:03 +08:00
|
|
|
down_write(&trace_event_sem);
|
2009-06-10 05:29:07 +08:00
|
|
|
__unregister_ftrace_event(event);
|
2013-03-11 15:14:03 +08:00
|
|
|
up_write(&trace_event_sem);
|
2008-12-24 12:24:12 +08:00
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2009-04-11 06:12:50 +08:00
|
|
|
EXPORT_SYMBOL_GPL(unregister_ftrace_event);
|
2008-12-24 12:24:13 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Standard events
|
|
|
|
*/
|
|
|
|
|
2010-04-23 06:46:14 +08:00
|
|
|
enum print_line_t trace_nop_print(struct trace_iterator *iter, int flags,
|
|
|
|
struct trace_event *event)
|
2008-12-24 12:24:13 +08:00
|
|
|
{
|
2011-03-25 19:05:18 +08:00
|
|
|
if (!trace_seq_printf(&iter->seq, "type: %d\n", iter->ent->type))
|
|
|
|
return TRACE_TYPE_PARTIAL_LINE;
|
|
|
|
|
2009-02-04 06:20:41 +08:00
|
|
|
return TRACE_TYPE_HANDLED;
|
2008-12-24 12:24:13 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
/* TRACE_FN */
|
2010-04-23 06:46:14 +08:00
|
|
|
static enum print_line_t trace_fn_trace(struct trace_iterator *iter, int flags,
|
|
|
|
struct trace_event *event)
|
2008-12-24 12:24:13 +08:00
|
|
|
{
|
|
|
|
struct ftrace_entry *field;
|
2009-02-03 06:30:12 +08:00
|
|
|
struct trace_seq *s = &iter->seq;
|
2008-12-24 12:24:13 +08:00
|
|
|
|
2009-02-03 06:30:12 +08:00
|
|
|
trace_assign_type(field, iter->ent);
|
2008-12-24 12:24:13 +08:00
|
|
|
|
|
|
|
if (!seq_print_ip_sym(s, field->ip, flags))
|
|
|
|
goto partial;
|
|
|
|
|
|
|
|
if ((flags & TRACE_ITER_PRINT_PARENT) && field->parent_ip) {
|
2013-07-15 16:32:44 +08:00
|
|
|
if (!trace_seq_puts(s, " <-"))
|
2008-12-24 12:24:13 +08:00
|
|
|
goto partial;
|
|
|
|
if (!seq_print_ip_sym(s,
|
|
|
|
field->parent_ip,
|
|
|
|
flags))
|
|
|
|
goto partial;
|
|
|
|
}
|
2013-07-15 16:32:44 +08:00
|
|
|
if (!trace_seq_putc(s, '\n'))
|
2008-12-24 12:24:13 +08:00
|
|
|
goto partial;
|
|
|
|
|
2009-02-04 06:20:41 +08:00
|
|
|
return TRACE_TYPE_HANDLED;
|
2008-12-24 12:24:13 +08:00
|
|
|
|
|
|
|
partial:
|
|
|
|
return TRACE_TYPE_PARTIAL_LINE;
|
|
|
|
}
|
|
|
|
|
2010-04-23 06:46:14 +08:00
|
|
|
static enum print_line_t trace_fn_raw(struct trace_iterator *iter, int flags,
|
|
|
|
struct trace_event *event)
|
2008-12-24 12:24:13 +08:00
|
|
|
{
|
|
|
|
struct ftrace_entry *field;
|
|
|
|
|
2009-02-03 06:30:12 +08:00
|
|
|
trace_assign_type(field, iter->ent);
|
2008-12-24 12:24:13 +08:00
|
|
|
|
2009-02-03 06:30:12 +08:00
|
|
|
if (!trace_seq_printf(&iter->seq, "%lx %lx\n",
|
2009-01-15 18:05:40 +08:00
|
|
|
field->ip,
|
|
|
|
field->parent_ip))
|
2008-12-24 12:24:13 +08:00
|
|
|
return TRACE_TYPE_PARTIAL_LINE;
|
|
|
|
|
2009-02-04 06:20:41 +08:00
|
|
|
return TRACE_TYPE_HANDLED;
|
2008-12-24 12:24:13 +08:00
|
|
|
}
|
|
|
|
|
2010-04-23 06:46:14 +08:00
|
|
|
static enum print_line_t trace_fn_hex(struct trace_iterator *iter, int flags,
|
|
|
|
struct trace_event *event)
|
2008-12-24 12:24:13 +08:00
|
|
|
{
|
|
|
|
struct ftrace_entry *field;
|
2009-02-03 06:30:12 +08:00
|
|
|
struct trace_seq *s = &iter->seq;
|
2008-12-24 12:24:13 +08:00
|
|
|
|
2009-02-03 06:30:12 +08:00
|
|
|
trace_assign_type(field, iter->ent);
|
2008-12-24 12:24:13 +08:00
|
|
|
|
|
|
|
SEQ_PUT_HEX_FIELD_RET(s, field->ip);
|
|
|
|
SEQ_PUT_HEX_FIELD_RET(s, field->parent_ip);
|
|
|
|
|
2009-02-04 06:20:41 +08:00
|
|
|
return TRACE_TYPE_HANDLED;
|
2008-12-24 12:24:13 +08:00
|
|
|
}
|
|
|
|
|
2010-04-23 06:46:14 +08:00
|
|
|
static enum print_line_t trace_fn_bin(struct trace_iterator *iter, int flags,
|
|
|
|
struct trace_event *event)
|
2008-12-24 12:24:13 +08:00
|
|
|
{
|
|
|
|
struct ftrace_entry *field;
|
2009-02-03 06:30:12 +08:00
|
|
|
struct trace_seq *s = &iter->seq;
|
2008-12-24 12:24:13 +08:00
|
|
|
|
2009-02-03 06:30:12 +08:00
|
|
|
trace_assign_type(field, iter->ent);
|
2008-12-24 12:24:13 +08:00
|
|
|
|
|
|
|
SEQ_PUT_FIELD_RET(s, field->ip);
|
|
|
|
SEQ_PUT_FIELD_RET(s, field->parent_ip);
|
|
|
|
|
2009-02-04 06:20:41 +08:00
|
|
|
return TRACE_TYPE_HANDLED;
|
2008-12-24 12:24:13 +08:00
|
|
|
}
|
|
|
|
|
2010-04-23 06:46:14 +08:00
|
|
|
static struct trace_event_functions trace_fn_funcs = {
|
2008-12-24 12:24:13 +08:00
|
|
|
.trace = trace_fn_trace,
|
|
|
|
.raw = trace_fn_raw,
|
|
|
|
.hex = trace_fn_hex,
|
|
|
|
.binary = trace_fn_bin,
|
|
|
|
};
|
|
|
|
|
2010-04-23 06:46:14 +08:00
|
|
|
static struct trace_event trace_fn_event = {
|
|
|
|
.type = TRACE_FN,
|
|
|
|
.funcs = &trace_fn_funcs,
|
|
|
|
};
|
|
|
|
|
2008-12-24 12:24:13 +08:00
|
|
|
/* TRACE_CTX an TRACE_WAKE */
|
2009-02-04 08:05:50 +08:00
|
|
|
static enum print_line_t trace_ctxwake_print(struct trace_iterator *iter,
|
|
|
|
char *delim)
|
2008-12-24 12:24:13 +08:00
|
|
|
{
|
|
|
|
struct ctx_switch_entry *field;
|
2009-03-17 07:20:15 +08:00
|
|
|
char comm[TASK_COMM_LEN];
|
2008-12-24 12:24:13 +08:00
|
|
|
int S, T;
|
|
|
|
|
2009-03-17 07:20:15 +08:00
|
|
|
|
2009-02-03 06:30:12 +08:00
|
|
|
trace_assign_type(field, iter->ent);
|
2008-12-24 12:24:13 +08:00
|
|
|
|
|
|
|
T = task_state_char(field->next_state);
|
|
|
|
S = task_state_char(field->prev_state);
|
2009-03-17 07:20:15 +08:00
|
|
|
trace_find_cmdline(field->next_pid, comm);
|
2009-02-03 06:30:12 +08:00
|
|
|
if (!trace_seq_printf(&iter->seq,
|
|
|
|
" %5d:%3d:%c %s [%03d] %5d:%3d:%c %s\n",
|
2009-01-15 18:05:40 +08:00
|
|
|
field->prev_pid,
|
|
|
|
field->prev_prio,
|
|
|
|
S, delim,
|
|
|
|
field->next_cpu,
|
|
|
|
field->next_pid,
|
|
|
|
field->next_prio,
|
|
|
|
T, comm))
|
2008-12-24 12:24:13 +08:00
|
|
|
return TRACE_TYPE_PARTIAL_LINE;
|
|
|
|
|
2009-02-04 06:20:41 +08:00
|
|
|
return TRACE_TYPE_HANDLED;
|
2008-12-24 12:24:13 +08:00
|
|
|
}
|
|
|
|
|
2010-04-23 06:46:14 +08:00
|
|
|
static enum print_line_t trace_ctx_print(struct trace_iterator *iter, int flags,
|
|
|
|
struct trace_event *event)
|
2008-12-24 12:24:13 +08:00
|
|
|
{
|
2009-02-03 06:30:12 +08:00
|
|
|
return trace_ctxwake_print(iter, "==>");
|
2008-12-24 12:24:13 +08:00
|
|
|
}
|
|
|
|
|
2009-02-04 08:05:50 +08:00
|
|
|
static enum print_line_t trace_wake_print(struct trace_iterator *iter,
|
2010-04-23 06:46:14 +08:00
|
|
|
int flags, struct trace_event *event)
|
2008-12-24 12:24:13 +08:00
|
|
|
{
|
2009-02-03 06:30:12 +08:00
|
|
|
return trace_ctxwake_print(iter, " +");
|
2008-12-24 12:24:13 +08:00
|
|
|
}
|
|
|
|
|
2009-02-03 06:30:12 +08:00
|
|
|
static int trace_ctxwake_raw(struct trace_iterator *iter, char S)
|
2008-12-24 12:24:13 +08:00
|
|
|
{
|
|
|
|
struct ctx_switch_entry *field;
|
|
|
|
int T;
|
|
|
|
|
2009-02-03 06:30:12 +08:00
|
|
|
trace_assign_type(field, iter->ent);
|
2008-12-24 12:24:13 +08:00
|
|
|
|
|
|
|
if (!S)
|
2009-10-01 12:33:28 +08:00
|
|
|
S = task_state_char(field->prev_state);
|
2008-12-24 12:24:13 +08:00
|
|
|
T = task_state_char(field->next_state);
|
2009-02-03 06:30:12 +08:00
|
|
|
if (!trace_seq_printf(&iter->seq, "%d %d %c %d %d %d %c\n",
|
2009-01-15 18:05:40 +08:00
|
|
|
field->prev_pid,
|
|
|
|
field->prev_prio,
|
|
|
|
S,
|
|
|
|
field->next_cpu,
|
|
|
|
field->next_pid,
|
|
|
|
field->next_prio,
|
|
|
|
T))
|
2008-12-24 12:24:13 +08:00
|
|
|
return TRACE_TYPE_PARTIAL_LINE;
|
|
|
|
|
2009-02-04 06:20:41 +08:00
|
|
|
return TRACE_TYPE_HANDLED;
|
2008-12-24 12:24:13 +08:00
|
|
|
}
|
|
|
|
|
2010-04-23 06:46:14 +08:00
|
|
|
static enum print_line_t trace_ctx_raw(struct trace_iterator *iter, int flags,
|
|
|
|
struct trace_event *event)
|
2008-12-24 12:24:13 +08:00
|
|
|
{
|
2009-02-03 06:30:12 +08:00
|
|
|
return trace_ctxwake_raw(iter, 0);
|
2008-12-24 12:24:13 +08:00
|
|
|
}
|
|
|
|
|
2010-04-23 06:46:14 +08:00
|
|
|
static enum print_line_t trace_wake_raw(struct trace_iterator *iter, int flags,
|
|
|
|
struct trace_event *event)
|
2008-12-24 12:24:13 +08:00
|
|
|
{
|
2009-02-03 06:30:12 +08:00
|
|
|
return trace_ctxwake_raw(iter, '+');
|
2008-12-24 12:24:13 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
2009-02-03 06:30:12 +08:00
|
|
|
static int trace_ctxwake_hex(struct trace_iterator *iter, char S)
|
2008-12-24 12:24:13 +08:00
|
|
|
{
|
|
|
|
struct ctx_switch_entry *field;
|
2009-02-03 06:30:12 +08:00
|
|
|
struct trace_seq *s = &iter->seq;
|
2008-12-24 12:24:13 +08:00
|
|
|
int T;
|
|
|
|
|
2009-02-03 06:30:12 +08:00
|
|
|
trace_assign_type(field, iter->ent);
|
2008-12-24 12:24:13 +08:00
|
|
|
|
|
|
|
if (!S)
|
2009-10-01 12:33:28 +08:00
|
|
|
S = task_state_char(field->prev_state);
|
2008-12-24 12:24:13 +08:00
|
|
|
T = task_state_char(field->next_state);
|
|
|
|
|
|
|
|
SEQ_PUT_HEX_FIELD_RET(s, field->prev_pid);
|
|
|
|
SEQ_PUT_HEX_FIELD_RET(s, field->prev_prio);
|
|
|
|
SEQ_PUT_HEX_FIELD_RET(s, S);
|
|
|
|
SEQ_PUT_HEX_FIELD_RET(s, field->next_cpu);
|
|
|
|
SEQ_PUT_HEX_FIELD_RET(s, field->next_pid);
|
|
|
|
SEQ_PUT_HEX_FIELD_RET(s, field->next_prio);
|
|
|
|
SEQ_PUT_HEX_FIELD_RET(s, T);
|
|
|
|
|
2009-02-04 06:20:41 +08:00
|
|
|
return TRACE_TYPE_HANDLED;
|
2008-12-24 12:24:13 +08:00
|
|
|
}
|
|
|
|
|
2010-04-23 06:46:14 +08:00
|
|
|
static enum print_line_t trace_ctx_hex(struct trace_iterator *iter, int flags,
|
|
|
|
struct trace_event *event)
|
2008-12-24 12:24:13 +08:00
|
|
|
{
|
2009-02-03 06:30:12 +08:00
|
|
|
return trace_ctxwake_hex(iter, 0);
|
2008-12-24 12:24:13 +08:00
|
|
|
}
|
|
|
|
|
2010-04-23 06:46:14 +08:00
|
|
|
static enum print_line_t trace_wake_hex(struct trace_iterator *iter, int flags,
|
|
|
|
struct trace_event *event)
|
2008-12-24 12:24:13 +08:00
|
|
|
{
|
2009-02-03 06:30:12 +08:00
|
|
|
return trace_ctxwake_hex(iter, '+');
|
2008-12-24 12:24:13 +08:00
|
|
|
}
|
|
|
|
|
2009-02-04 08:05:50 +08:00
|
|
|
static enum print_line_t trace_ctxwake_bin(struct trace_iterator *iter,
|
2010-04-23 06:46:14 +08:00
|
|
|
int flags, struct trace_event *event)
|
2008-12-24 12:24:13 +08:00
|
|
|
{
|
|
|
|
struct ctx_switch_entry *field;
|
2009-02-03 06:30:12 +08:00
|
|
|
struct trace_seq *s = &iter->seq;
|
2008-12-24 12:24:13 +08:00
|
|
|
|
2009-02-03 06:30:12 +08:00
|
|
|
trace_assign_type(field, iter->ent);
|
2008-12-24 12:24:13 +08:00
|
|
|
|
|
|
|
SEQ_PUT_FIELD_RET(s, field->prev_pid);
|
|
|
|
SEQ_PUT_FIELD_RET(s, field->prev_prio);
|
|
|
|
SEQ_PUT_FIELD_RET(s, field->prev_state);
|
|
|
|
SEQ_PUT_FIELD_RET(s, field->next_pid);
|
|
|
|
SEQ_PUT_FIELD_RET(s, field->next_prio);
|
|
|
|
SEQ_PUT_FIELD_RET(s, field->next_state);
|
|
|
|
|
2009-02-04 06:20:41 +08:00
|
|
|
return TRACE_TYPE_HANDLED;
|
2008-12-24 12:24:13 +08:00
|
|
|
}
|
|
|
|
|
2010-04-23 06:46:14 +08:00
|
|
|
static struct trace_event_functions trace_ctx_funcs = {
|
2008-12-24 12:24:13 +08:00
|
|
|
.trace = trace_ctx_print,
|
|
|
|
.raw = trace_ctx_raw,
|
|
|
|
.hex = trace_ctx_hex,
|
|
|
|
.binary = trace_ctxwake_bin,
|
|
|
|
};
|
|
|
|
|
2010-04-23 06:46:14 +08:00
|
|
|
static struct trace_event trace_ctx_event = {
|
|
|
|
.type = TRACE_CTX,
|
|
|
|
.funcs = &trace_ctx_funcs,
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct trace_event_functions trace_wake_funcs = {
|
2008-12-24 12:24:13 +08:00
|
|
|
.trace = trace_wake_print,
|
|
|
|
.raw = trace_wake_raw,
|
|
|
|
.hex = trace_wake_hex,
|
|
|
|
.binary = trace_ctxwake_bin,
|
|
|
|
};
|
|
|
|
|
2010-04-23 06:46:14 +08:00
|
|
|
static struct trace_event trace_wake_event = {
|
|
|
|
.type = TRACE_WAKE,
|
|
|
|
.funcs = &trace_wake_funcs,
|
|
|
|
};
|
|
|
|
|
2008-12-24 12:24:13 +08:00
|
|
|
/* TRACE_STACK */
|
|
|
|
|
2009-02-04 08:05:50 +08:00
|
|
|
static enum print_line_t trace_stack_print(struct trace_iterator *iter,
|
2010-04-23 06:46:14 +08:00
|
|
|
int flags, struct trace_event *event)
|
2008-12-24 12:24:13 +08:00
|
|
|
{
|
|
|
|
struct stack_entry *field;
|
2009-02-03 06:30:12 +08:00
|
|
|
struct trace_seq *s = &iter->seq;
|
2011-07-15 04:36:53 +08:00
|
|
|
unsigned long *p;
|
|
|
|
unsigned long *end;
|
2008-12-24 12:24:13 +08:00
|
|
|
|
2009-02-03 06:30:12 +08:00
|
|
|
trace_assign_type(field, iter->ent);
|
2011-07-15 04:36:53 +08:00
|
|
|
end = (unsigned long *)((long)iter->ent + iter->ent_size);
|
2008-12-24 12:24:13 +08:00
|
|
|
|
2009-06-03 23:10:44 +08:00
|
|
|
if (!trace_seq_puts(s, "<stack trace>\n"))
|
2009-06-03 16:01:29 +08:00
|
|
|
goto partial;
|
2011-07-15 04:36:53 +08:00
|
|
|
|
|
|
|
for (p = field->caller; p && *p != ULONG_MAX && p < end; p++) {
|
2009-06-03 16:01:29 +08:00
|
|
|
if (!trace_seq_puts(s, " => "))
|
|
|
|
goto partial;
|
2008-12-24 12:24:13 +08:00
|
|
|
|
2011-07-15 04:36:53 +08:00
|
|
|
if (!seq_print_ip_sym(s, *p, flags))
|
2009-06-03 16:01:29 +08:00
|
|
|
goto partial;
|
2013-07-15 16:32:44 +08:00
|
|
|
if (!trace_seq_putc(s, '\n'))
|
2008-12-24 12:24:13 +08:00
|
|
|
goto partial;
|
|
|
|
}
|
|
|
|
|
2009-02-04 06:20:41 +08:00
|
|
|
return TRACE_TYPE_HANDLED;
|
2008-12-24 12:24:13 +08:00
|
|
|
|
|
|
|
partial:
|
|
|
|
return TRACE_TYPE_PARTIAL_LINE;
|
|
|
|
}
|
|
|
|
|
2010-04-23 06:46:14 +08:00
|
|
|
static struct trace_event_functions trace_stack_funcs = {
|
2008-12-24 12:24:13 +08:00
|
|
|
.trace = trace_stack_print,
|
|
|
|
};
|
|
|
|
|
2010-04-23 06:46:14 +08:00
|
|
|
static struct trace_event trace_stack_event = {
|
|
|
|
.type = TRACE_STACK,
|
|
|
|
.funcs = &trace_stack_funcs,
|
|
|
|
};
|
|
|
|
|
2008-12-24 12:24:13 +08:00
|
|
|
/* TRACE_USER_STACK */
|
2009-02-04 08:05:50 +08:00
|
|
|
static enum print_line_t trace_user_stack_print(struct trace_iterator *iter,
|
2010-04-23 06:46:14 +08:00
|
|
|
int flags, struct trace_event *event)
|
2008-12-24 12:24:13 +08:00
|
|
|
{
|
|
|
|
struct userstack_entry *field;
|
2009-02-03 06:30:12 +08:00
|
|
|
struct trace_seq *s = &iter->seq;
|
2008-12-24 12:24:13 +08:00
|
|
|
|
2009-02-03 06:30:12 +08:00
|
|
|
trace_assign_type(field, iter->ent);
|
2008-12-24 12:24:13 +08:00
|
|
|
|
2009-06-03 23:10:44 +08:00
|
|
|
if (!trace_seq_puts(s, "<user stack trace>\n"))
|
2008-12-24 12:24:13 +08:00
|
|
|
goto partial;
|
|
|
|
|
2009-06-03 16:01:30 +08:00
|
|
|
if (!seq_print_userip_objs(field, s, flags))
|
2008-12-24 12:24:13 +08:00
|
|
|
goto partial;
|
|
|
|
|
2009-02-04 06:20:41 +08:00
|
|
|
return TRACE_TYPE_HANDLED;
|
2008-12-24 12:24:13 +08:00
|
|
|
|
|
|
|
partial:
|
|
|
|
return TRACE_TYPE_PARTIAL_LINE;
|
|
|
|
}
|
|
|
|
|
2010-04-23 06:46:14 +08:00
|
|
|
static struct trace_event_functions trace_user_stack_funcs = {
|
2008-12-24 12:24:13 +08:00
|
|
|
.trace = trace_user_stack_print,
|
|
|
|
};
|
|
|
|
|
2010-04-23 06:46:14 +08:00
|
|
|
static struct trace_event trace_user_stack_event = {
|
|
|
|
.type = TRACE_USER_STACK,
|
|
|
|
.funcs = &trace_user_stack_funcs,
|
|
|
|
};
|
|
|
|
|
2013-03-09 10:02:34 +08:00
|
|
|
/* TRACE_BPUTS */
|
|
|
|
static enum print_line_t
|
|
|
|
trace_bputs_print(struct trace_iterator *iter, int flags,
|
|
|
|
struct trace_event *event)
|
|
|
|
{
|
|
|
|
struct trace_entry *entry = iter->ent;
|
|
|
|
struct trace_seq *s = &iter->seq;
|
|
|
|
struct bputs_entry *field;
|
|
|
|
|
|
|
|
trace_assign_type(field, entry);
|
|
|
|
|
|
|
|
if (!seq_print_ip_sym(s, field->ip, flags))
|
|
|
|
goto partial;
|
|
|
|
|
|
|
|
if (!trace_seq_puts(s, ": "))
|
|
|
|
goto partial;
|
|
|
|
|
|
|
|
if (!trace_seq_puts(s, field->str))
|
|
|
|
goto partial;
|
|
|
|
|
|
|
|
return TRACE_TYPE_HANDLED;
|
|
|
|
|
|
|
|
partial:
|
|
|
|
return TRACE_TYPE_PARTIAL_LINE;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static enum print_line_t
|
|
|
|
trace_bputs_raw(struct trace_iterator *iter, int flags,
|
|
|
|
struct trace_event *event)
|
|
|
|
{
|
|
|
|
struct bputs_entry *field;
|
|
|
|
struct trace_seq *s = &iter->seq;
|
|
|
|
|
|
|
|
trace_assign_type(field, iter->ent);
|
|
|
|
|
|
|
|
if (!trace_seq_printf(s, ": %lx : ", field->ip))
|
|
|
|
goto partial;
|
|
|
|
|
|
|
|
if (!trace_seq_puts(s, field->str))
|
|
|
|
goto partial;
|
|
|
|
|
|
|
|
return TRACE_TYPE_HANDLED;
|
|
|
|
|
|
|
|
partial:
|
|
|
|
return TRACE_TYPE_PARTIAL_LINE;
|
|
|
|
}
|
|
|
|
|
|
|
|
static struct trace_event_functions trace_bputs_funcs = {
|
|
|
|
.trace = trace_bputs_print,
|
|
|
|
.raw = trace_bputs_raw,
|
|
|
|
};
|
|
|
|
|
|
|
|
static struct trace_event trace_bputs_event = {
|
|
|
|
.type = TRACE_BPUTS,
|
|
|
|
.funcs = &trace_bputs_funcs,
|
|
|
|
};
|
|
|
|
|
2009-03-13 01:24:49 +08:00
|
|
|
/* TRACE_BPRINT */
|
2009-03-07 00:21:47 +08:00
|
|
|
static enum print_line_t
|
2010-04-23 06:46:14 +08:00
|
|
|
trace_bprint_print(struct trace_iterator *iter, int flags,
|
|
|
|
struct trace_event *event)
|
2009-03-07 00:21:47 +08:00
|
|
|
{
|
|
|
|
struct trace_entry *entry = iter->ent;
|
|
|
|
struct trace_seq *s = &iter->seq;
|
2009-03-13 01:24:49 +08:00
|
|
|
struct bprint_entry *field;
|
2009-03-07 00:21:47 +08:00
|
|
|
|
|
|
|
trace_assign_type(field, entry);
|
|
|
|
|
|
|
|
if (!seq_print_ip_sym(s, field->ip, flags))
|
|
|
|
goto partial;
|
|
|
|
|
|
|
|
if (!trace_seq_puts(s, ": "))
|
|
|
|
goto partial;
|
|
|
|
|
|
|
|
if (!trace_seq_bprintf(s, field->fmt, field->buf))
|
|
|
|
goto partial;
|
|
|
|
|
|
|
|
return TRACE_TYPE_HANDLED;
|
|
|
|
|
|
|
|
partial:
|
|
|
|
return TRACE_TYPE_PARTIAL_LINE;
|
|
|
|
}
|
|
|
|
|
2009-03-07 00:21:49 +08:00
|
|
|
|
2009-03-13 01:24:49 +08:00
|
|
|
static enum print_line_t
|
2010-04-23 06:46:14 +08:00
|
|
|
trace_bprint_raw(struct trace_iterator *iter, int flags,
|
|
|
|
struct trace_event *event)
|
2009-03-07 00:21:47 +08:00
|
|
|
{
|
2009-03-13 01:24:49 +08:00
|
|
|
struct bprint_entry *field;
|
2009-03-07 00:21:47 +08:00
|
|
|
struct trace_seq *s = &iter->seq;
|
|
|
|
|
2009-03-07 00:21:49 +08:00
|
|
|
trace_assign_type(field, iter->ent);
|
2009-03-07 00:21:47 +08:00
|
|
|
|
|
|
|
if (!trace_seq_printf(s, ": %lx : ", field->ip))
|
|
|
|
goto partial;
|
|
|
|
|
|
|
|
if (!trace_seq_bprintf(s, field->fmt, field->buf))
|
|
|
|
goto partial;
|
|
|
|
|
|
|
|
return TRACE_TYPE_HANDLED;
|
|
|
|
|
|
|
|
partial:
|
|
|
|
return TRACE_TYPE_PARTIAL_LINE;
|
|
|
|
}
|
|
|
|
|
2010-04-23 06:46:14 +08:00
|
|
|
static struct trace_event_functions trace_bprint_funcs = {
|
|
|
|
.trace = trace_bprint_print,
|
|
|
|
.raw = trace_bprint_raw,
|
|
|
|
};
|
2009-03-07 00:21:49 +08:00
|
|
|
|
2009-03-13 01:24:49 +08:00
|
|
|
static struct trace_event trace_bprint_event = {
|
|
|
|
.type = TRACE_BPRINT,
|
2010-04-23 06:46:14 +08:00
|
|
|
.funcs = &trace_bprint_funcs,
|
2009-03-13 01:24:49 +08:00
|
|
|
};
|
|
|
|
|
|
|
|
/* TRACE_PRINT */
|
|
|
|
static enum print_line_t trace_print_print(struct trace_iterator *iter,
|
2010-04-23 06:46:14 +08:00
|
|
|
int flags, struct trace_event *event)
|
2009-03-13 01:24:49 +08:00
|
|
|
{
|
|
|
|
struct print_entry *field;
|
|
|
|
struct trace_seq *s = &iter->seq;
|
|
|
|
|
|
|
|
trace_assign_type(field, iter->ent);
|
|
|
|
|
|
|
|
if (!seq_print_ip_sym(s, field->ip, flags))
|
|
|
|
goto partial;
|
|
|
|
|
|
|
|
if (!trace_seq_printf(s, ": %s", field->buf))
|
|
|
|
goto partial;
|
|
|
|
|
|
|
|
return TRACE_TYPE_HANDLED;
|
|
|
|
|
|
|
|
partial:
|
|
|
|
return TRACE_TYPE_PARTIAL_LINE;
|
|
|
|
}
|
|
|
|
|
2010-04-23 06:46:14 +08:00
|
|
|
static enum print_line_t trace_print_raw(struct trace_iterator *iter, int flags,
|
|
|
|
struct trace_event *event)
|
2009-03-13 01:24:49 +08:00
|
|
|
{
|
|
|
|
struct print_entry *field;
|
|
|
|
|
|
|
|
trace_assign_type(field, iter->ent);
|
|
|
|
|
|
|
|
if (!trace_seq_printf(&iter->seq, "# %lx %s", field->ip, field->buf))
|
|
|
|
goto partial;
|
|
|
|
|
|
|
|
return TRACE_TYPE_HANDLED;
|
|
|
|
|
|
|
|
partial:
|
|
|
|
return TRACE_TYPE_PARTIAL_LINE;
|
|
|
|
}
|
|
|
|
|
2010-04-23 06:46:14 +08:00
|
|
|
static struct trace_event_functions trace_print_funcs = {
|
2009-03-07 00:21:49 +08:00
|
|
|
.trace = trace_print_print,
|
|
|
|
.raw = trace_print_raw,
|
2009-03-07 00:21:47 +08:00
|
|
|
};
|
|
|
|
|
2010-04-23 06:46:14 +08:00
|
|
|
static struct trace_event trace_print_event = {
|
|
|
|
.type = TRACE_PRINT,
|
|
|
|
.funcs = &trace_print_funcs,
|
|
|
|
};
|
|
|
|
|
2009-03-13 01:24:49 +08:00
|
|
|
|
2008-12-24 12:24:13 +08:00
|
|
|
static struct trace_event *events[] __initdata = {
|
|
|
|
&trace_fn_event,
|
|
|
|
&trace_ctx_event,
|
|
|
|
&trace_wake_event,
|
|
|
|
&trace_stack_event,
|
|
|
|
&trace_user_stack_event,
|
2013-03-09 10:02:34 +08:00
|
|
|
&trace_bputs_event,
|
2009-03-13 01:24:49 +08:00
|
|
|
&trace_bprint_event,
|
2008-12-24 12:24:13 +08:00
|
|
|
&trace_print_event,
|
|
|
|
NULL
|
|
|
|
};
|
|
|
|
|
|
|
|
__init static int init_events(void)
|
|
|
|
{
|
|
|
|
struct trace_event *event;
|
|
|
|
int i, ret;
|
|
|
|
|
|
|
|
for (i = 0; events[i]; i++) {
|
|
|
|
event = events[i];
|
|
|
|
|
|
|
|
ret = register_ftrace_event(event);
|
|
|
|
if (!ret) {
|
|
|
|
printk(KERN_WARNING "event %d failed to register\n",
|
|
|
|
event->type);
|
|
|
|
WARN_ON_ONCE(1);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return 0;
|
|
|
|
}
|
2012-06-01 09:40:05 +08:00
|
|
|
early_initcall(init_events);
|