mirror of
https://github.com/torvalds/linux.git
synced 2025-12-07 20:06:24 +00:00
Pull tracing updates from Steven Rostedt:
- Extend tracing option mask to 64 bits
The trace options were defined by a 32 bit variable. This limits the
tracing instances to have a total of 32 different options. As that
limit has been hit, and more options are being added, increase the
option mask to a 64 bit number, doubling the number of options
available.
As this is required for the kprobe topic branches as well as the
tracing topic branch, a separate branch was created and merged into
both.
- Make trace_user_fault_read() available for the rest of tracing
The function trace_user_fault_read() is used by trace_marker file
read to allow reading user space to be done fast and without locking
or allocations. Make this available so that the system call trace
events can use it too.
- Have system call trace events read user space values
Now that the system call trace events callbacks are called in a
faultable context, take advantage of this and read the user space
buffers for various system calls. For example, show the path name of
the openat system call instead of just showing the pointer to that
path name in user space. Also show the contents of the buffer of the
write system call. Several system call trace events are updated to
make tracing into a light weight strace tool for all applications in
the system.
- Update perf system call tracing to do the same
- And a config and syscall_user_buf_size file to control the size of
the buffer
Limit the amount of data that can be read from user space. The
default size is 63 bytes but that can be expanded to 165 bytes.
- Allow the persistent ring buffer to print system calls normally
The persistent ring buffer prints trace events by their type and
ignores the print_fmt. This is because the print_fmt may change from
kernel to kernel. As the system call output is fixed by the system
call ABI itself, there's no reason to limit that. This makes reading
the system call events in the persistent ring buffer much nicer and
easier to understand.
- Add options to show text offset to function profiler
The function profiler that counts the number of times a function is
hit currently lists all functions by its name and offset. But this
becomes ambiguous when there are several functions with the same
name.
Add a tracing option that changes the output to be that of
'_text+offset' instead. Now a user space tool can use this
information to map the '_text+offset' to the unique function it is
counting.
- Report bad dynamic event command
If a bad command is passed to the dynamic_events file, report it
properly in the error log.
- Clean up tracer options
Clean up the tracer option code a bit, by removing some useless code
and also using switch statements instead of a series of if
statements.
- Have tracing options be instance specific
Tracers can have their own options (function tracer, irqsoff tracer,
function graph tracer, etc). But now that the same tracer can be
enabled in multiple trace instances, their options are still global.
The API is per instance, thus changing one affects other instances.
This isn't even consistent, as the option take affect differently
depending on when an tracer started in an instance. Make the options
for instances only affect the instance it is changed under.
- Optimize pid_list lock contention
Whenever the pid_list is read, it uses a spin lock. This happens at
every sched switch. Taking the lock at sched switch can be removed by
instead using a seqlock counter.
- Clean up the trace trigger structures
The trigger code uses two different structures to implement a single
tigger. This was due to trying to reuse code for the two different
types of triggers (always on trigger, and count limited trigger). But
by adding a single field to one structure, the other structure could
be absorbed into the first structure making he code easier to
understand.
- Create a bulk garbage collector for trace triggers
If user space has triggers for several hundreds of events and then
removes them, it can take several seconds to complete. This is
because each removal calls tracepoint_synchronize_unregister() that
can take hundreds of milliseconds to complete.
Instead, create a helper thread that will do the clean up. When a
trigger is removed, it will create the kthread if it isn't already
created, and then add the trigger to a llist. The kthread will take
the items off the llist, call tracepoint_synchronize_unregister(),
and then remove the items it took off. It will then check if there's
more items to free before sleeping.
This makes user space removing all these triggers to finish in less
than a second.
- Allow function tracing of some of the tracing infrastructure code
Because the tracing code can cause recursion issues if it is traced
by the function tracer the entire tracing directory disables function
tracing. But not all of tracing causes issues if it is traced.
Namely, the event tracing code. Add a config that enables some of the
tracing code to be traced to help in debugging it. Note, when this is
enabled, it does add noise to general function tracing, especially if
events are enabled as well (which is a common case).
- Add boot-time backup instance for persistent buffer
The persistent ring buffer is used mostly for kernel crash analysis
in the field. One issue is that if there's a crash, the data in the
persistent ring buffer must be read before tracing can begin using
it. This slows down the boot process. Once tracing starts in the
persistent ring buffer, the old data must be freed and the addresses
no longer match and old events can't be in the buffer with new
events.
Create a way to create a backup buffer that copies the persistent
ring buffer at boot up. Then after a crash, the always on tracer can
begin immediately as well as the normal boot process while the crash
analysis tooling uses the backup buffer. After the backup buffer is
finished being read, it can be removed.
- Enable function graph args and return address options at the same
time
Currently the when reading of arguments in the function graph tracer
is enabled, the option to record the parent function in the entry
event can not be enabled. Update the code so that it can.
- Add new struct_offset() helper macro
Add a new macro that takes a pointer to a structure and a name of one
of its members and it will return the offset of that member. This
allows the ring buffer code to simplify the following:
From: size = struct_size(entry, buf, cnt - sizeof(entry->id));
To: size = struct_offset(entry, id) + cnt;
There should be other simplifications that this macro can help out
with as well
* tag 'trace-v6.19' of git://git.kernel.org/pub/scm/linux/kernel/git/trace/linux-trace: (42 commits)
overflow: Introduce struct_offset() to get offset of member
function_graph: Enable funcgraph-args and funcgraph-retaddr to work simultaneously
tracing: Add boot-time backup of persistent ring buffer
ftrace: Allow tracing of some of the tracing code
tracing: Use strim() in trigger_process_regex() instead of skip_spaces()
tracing: Add bulk garbage collection of freeing event_trigger_data
tracing: Remove unneeded event_mutex lock in event_trigger_regex_release()
tracing: Merge struct event_trigger_ops into struct event_command
tracing: Remove get_trigger_ops() and add count_func() from trigger ops
tracing: Show the tracer options in boot-time created instance
ftrace: Avoid redundant initialization in register_ftrace_direct
tracing: Remove unused variable in tracing_trace_options_show()
fgraph: Make fgraph_no_sleep_time signed
tracing: Convert function graph set_flags() to use a switch() statement
tracing: Have function graph tracer option sleep-time be per instance
tracing: Move graph-time out of function graph options
tracing: Have function graph tracer option funcgraph-irqs be per instance
trace/pid_list: optimize pid_list->lock contention
tracing: Have function graph tracer define options per instance
tracing: Have function tracer define options per instance
...
1897 lines
43 KiB
C
1897 lines
43 KiB
C
// SPDX-License-Identifier: GPL-2.0
|
|
/*
|
|
* trace_output.c
|
|
*
|
|
* Copyright (C) 2008 Red Hat Inc, Steven Rostedt <srostedt@redhat.com>
|
|
*
|
|
*/
|
|
#include "trace.h"
|
|
#include <linux/module.h>
|
|
#include <linux/mutex.h>
|
|
#include <linux/ftrace.h>
|
|
#include <linux/kprobes.h>
|
|
#include <linux/sched/clock.h>
|
|
#include <linux/sched/mm.h>
|
|
#include <linux/idr.h>
|
|
#include <linux/btf.h>
|
|
#include <linux/bpf.h>
|
|
#include <linux/hashtable.h>
|
|
|
|
#include "trace_output.h"
|
|
#include "trace_btf.h"
|
|
|
|
/* 2^7 = 128 */
|
|
#define EVENT_HASH_BITS 7
|
|
|
|
DECLARE_RWSEM(trace_event_sem);
|
|
|
|
static DEFINE_HASHTABLE(event_hash, EVENT_HASH_BITS);
|
|
|
|
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;
|
|
|
|
trace_assign_type(field, entry);
|
|
|
|
trace_seq_puts(s, field->str);
|
|
|
|
return trace_handle_return(s);
|
|
}
|
|
|
|
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;
|
|
|
|
trace_assign_type(field, entry);
|
|
|
|
trace_seq_bprintf(s, field->fmt, field->buf);
|
|
|
|
return trace_handle_return(s);
|
|
}
|
|
|
|
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;
|
|
|
|
trace_assign_type(field, entry);
|
|
|
|
trace_seq_puts(s, field->buf);
|
|
|
|
return trace_handle_return(s);
|
|
}
|
|
|
|
const char *
|
|
trace_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;
|
|
const char *ret = trace_seq_buffer_ptr(p);
|
|
int i, first = 1;
|
|
|
|
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;
|
|
if (!first && delim)
|
|
trace_seq_puts(p, delim);
|
|
else
|
|
first = 0;
|
|
trace_seq_puts(p, str);
|
|
}
|
|
|
|
/* check for left over flags */
|
|
if (flags) {
|
|
if (!first && delim)
|
|
trace_seq_puts(p, delim);
|
|
trace_seq_printf(p, "0x%lx", flags);
|
|
}
|
|
|
|
trace_seq_putc(p, 0);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(trace_print_flags_seq);
|
|
|
|
const char *
|
|
trace_print_symbols_seq(struct trace_seq *p, unsigned long val,
|
|
const struct trace_print_flags *symbol_array)
|
|
{
|
|
int i;
|
|
const char *ret = trace_seq_buffer_ptr(p);
|
|
|
|
for (i = 0; symbol_array[i].name; i++) {
|
|
|
|
if (val != symbol_array[i].mask)
|
|
continue;
|
|
|
|
trace_seq_puts(p, symbol_array[i].name);
|
|
break;
|
|
}
|
|
|
|
if (ret == (const char *)(trace_seq_buffer_ptr(p)))
|
|
trace_seq_printf(p, "0x%lx", val);
|
|
|
|
trace_seq_putc(p, 0);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(trace_print_symbols_seq);
|
|
|
|
#if BITS_PER_LONG == 32
|
|
const char *
|
|
trace_print_flags_seq_u64(struct trace_seq *p, const char *delim,
|
|
unsigned long long flags,
|
|
const struct trace_print_flags_u64 *flag_array)
|
|
{
|
|
unsigned long long mask;
|
|
const char *str;
|
|
const char *ret = trace_seq_buffer_ptr(p);
|
|
int i, first = 1;
|
|
|
|
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;
|
|
if (!first && delim)
|
|
trace_seq_puts(p, delim);
|
|
else
|
|
first = 0;
|
|
trace_seq_puts(p, str);
|
|
}
|
|
|
|
/* check for left over flags */
|
|
if (flags) {
|
|
if (!first && delim)
|
|
trace_seq_puts(p, delim);
|
|
trace_seq_printf(p, "0x%llx", flags);
|
|
}
|
|
|
|
trace_seq_putc(p, 0);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(trace_print_flags_seq_u64);
|
|
|
|
const char *
|
|
trace_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 = trace_seq_buffer_ptr(p);
|
|
|
|
for (i = 0; symbol_array[i].name; i++) {
|
|
|
|
if (val != symbol_array[i].mask)
|
|
continue;
|
|
|
|
trace_seq_puts(p, symbol_array[i].name);
|
|
break;
|
|
}
|
|
|
|
if (ret == (const char *)(trace_seq_buffer_ptr(p)))
|
|
trace_seq_printf(p, "0x%llx", val);
|
|
|
|
trace_seq_putc(p, 0);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(trace_print_symbols_seq_u64);
|
|
#endif
|
|
|
|
const char *
|
|
trace_print_bitmask_seq(struct trace_seq *p, void *bitmask_ptr,
|
|
unsigned int bitmask_size)
|
|
{
|
|
const char *ret = trace_seq_buffer_ptr(p);
|
|
|
|
trace_seq_bitmask(p, bitmask_ptr, bitmask_size * 8);
|
|
trace_seq_putc(p, 0);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(trace_print_bitmask_seq);
|
|
|
|
/**
|
|
* trace_print_hex_seq - print buffer as hex sequence
|
|
* @p: trace seq struct to write to
|
|
* @buf: The buffer to print
|
|
* @buf_len: Length of @buf in bytes
|
|
* @concatenate: Print @buf as single hex string or with spacing
|
|
*
|
|
* Prints the passed buffer as a hex sequence either as a whole,
|
|
* single hex string if @concatenate is true or with spacing after
|
|
* each byte in case @concatenate is false.
|
|
*/
|
|
const char *
|
|
trace_print_hex_seq(struct trace_seq *p, const unsigned char *buf, int buf_len,
|
|
bool concatenate)
|
|
{
|
|
int i;
|
|
const char *ret = trace_seq_buffer_ptr(p);
|
|
const char *fmt = concatenate ? "%*phN" : "%*ph";
|
|
|
|
for (i = 0; i < buf_len; i += 16) {
|
|
if (!concatenate && i != 0)
|
|
trace_seq_putc(p, ' ');
|
|
trace_seq_printf(p, fmt, min(buf_len - i, 16), &buf[i]);
|
|
}
|
|
trace_seq_putc(p, 0);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(trace_print_hex_seq);
|
|
|
|
const char *
|
|
trace_print_array_seq(struct trace_seq *p, const void *buf, int count,
|
|
size_t el_size)
|
|
{
|
|
const char *ret = trace_seq_buffer_ptr(p);
|
|
const char *prefix = "";
|
|
void *ptr = (void *)buf;
|
|
size_t buf_len = count * el_size;
|
|
|
|
trace_seq_putc(p, '{');
|
|
|
|
while (ptr < buf + buf_len) {
|
|
switch (el_size) {
|
|
case 1:
|
|
trace_seq_printf(p, "%s0x%x", prefix,
|
|
*(u8 *)ptr);
|
|
break;
|
|
case 2:
|
|
trace_seq_printf(p, "%s0x%x", prefix,
|
|
*(u16 *)ptr);
|
|
break;
|
|
case 4:
|
|
trace_seq_printf(p, "%s0x%x", prefix,
|
|
*(u32 *)ptr);
|
|
break;
|
|
case 8:
|
|
trace_seq_printf(p, "%s0x%llx", prefix,
|
|
*(u64 *)ptr);
|
|
break;
|
|
default:
|
|
trace_seq_printf(p, "BAD SIZE:%zu 0x%x", el_size,
|
|
*(u8 *)ptr);
|
|
el_size = 1;
|
|
}
|
|
prefix = ",";
|
|
ptr += el_size;
|
|
}
|
|
|
|
trace_seq_putc(p, '}');
|
|
trace_seq_putc(p, 0);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(trace_print_array_seq);
|
|
|
|
const char *
|
|
trace_print_hex_dump_seq(struct trace_seq *p, const char *prefix_str,
|
|
int prefix_type, int rowsize, int groupsize,
|
|
const void *buf, size_t len, bool ascii)
|
|
{
|
|
const char *ret = trace_seq_buffer_ptr(p);
|
|
|
|
trace_seq_putc(p, '\n');
|
|
trace_seq_hex_dump(p, prefix_str, prefix_type,
|
|
rowsize, groupsize, buf, len, ascii);
|
|
trace_seq_putc(p, 0);
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL(trace_print_hex_dump_seq);
|
|
|
|
int trace_raw_output_prep(struct trace_iterator *iter,
|
|
struct trace_event *trace_event)
|
|
{
|
|
struct trace_event_call *event;
|
|
struct trace_seq *s = &iter->seq;
|
|
struct trace_seq *p = &iter->tmp_seq;
|
|
struct trace_entry *entry;
|
|
|
|
event = container_of(trace_event, struct trace_event_call, event);
|
|
entry = iter->ent;
|
|
|
|
if (entry->type != event->event.type) {
|
|
WARN_ON_ONCE(1);
|
|
return TRACE_TYPE_UNHANDLED;
|
|
}
|
|
|
|
trace_seq_init(p);
|
|
trace_seq_printf(s, "%s: ", trace_event_name(event));
|
|
|
|
return trace_handle_return(s);
|
|
}
|
|
EXPORT_SYMBOL(trace_raw_output_prep);
|
|
|
|
void trace_event_printf(struct trace_iterator *iter, const char *fmt, ...)
|
|
{
|
|
struct trace_seq *s = &iter->seq;
|
|
va_list ap;
|
|
|
|
if (ignore_event(iter))
|
|
return;
|
|
|
|
va_start(ap, fmt);
|
|
trace_seq_vprintf(s, trace_event_format(iter, fmt), ap);
|
|
va_end(ap);
|
|
}
|
|
EXPORT_SYMBOL(trace_event_printf);
|
|
|
|
static __printf(3, 0)
|
|
int trace_output_raw(struct trace_iterator *iter, char *name,
|
|
char *fmt, va_list ap)
|
|
{
|
|
struct trace_seq *s = &iter->seq;
|
|
|
|
trace_seq_printf(s, "%s: ", name);
|
|
trace_seq_vprintf(s, trace_event_format(iter, fmt), ap);
|
|
|
|
return trace_handle_return(s);
|
|
}
|
|
|
|
int trace_output_call(struct trace_iterator *iter, char *name, char *fmt, ...)
|
|
{
|
|
va_list ap;
|
|
int ret;
|
|
|
|
va_start(ap, fmt);
|
|
ret = trace_output_raw(iter, name, fmt, ap);
|
|
va_end(ap);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(trace_output_call);
|
|
|
|
static inline const char *kretprobed(const char *name, unsigned long addr)
|
|
{
|
|
if (is_kretprobe_trampoline(addr))
|
|
return "[unknown/kretprobe'd]";
|
|
return name;
|
|
}
|
|
|
|
void
|
|
trace_seq_print_sym(struct trace_seq *s, unsigned long address, bool offset)
|
|
{
|
|
#ifdef CONFIG_KALLSYMS
|
|
char str[KSYM_SYMBOL_LEN];
|
|
const char *name;
|
|
|
|
if (offset)
|
|
sprint_symbol(str, address);
|
|
else
|
|
kallsyms_lookup(address, NULL, NULL, NULL, str);
|
|
name = kretprobed(str, address);
|
|
|
|
if (name && strlen(name)) {
|
|
trace_seq_puts(s, name);
|
|
return;
|
|
}
|
|
#endif
|
|
trace_seq_printf(s, "0x%08lx", address);
|
|
}
|
|
|
|
#ifndef CONFIG_64BIT
|
|
# define IP_FMT "%08lx"
|
|
#else
|
|
# define IP_FMT "%016lx"
|
|
#endif
|
|
|
|
static 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;
|
|
|
|
if (s->full)
|
|
return 0;
|
|
|
|
if (mm) {
|
|
const struct vm_area_struct *vma;
|
|
|
|
mmap_read_lock(mm);
|
|
vma = find_vma(mm, ip);
|
|
if (vma) {
|
|
file = vma->vm_file;
|
|
vmstart = vma->vm_start;
|
|
}
|
|
if (file) {
|
|
ret = trace_seq_path(s, file_user_path(file));
|
|
if (ret)
|
|
trace_seq_printf(s, "[+0x%lx]",
|
|
ip - vmstart);
|
|
}
|
|
mmap_read_unlock(mm);
|
|
}
|
|
if (ret && ((sym_flags & TRACE_ITER(SYM_ADDR)) || !file))
|
|
trace_seq_printf(s, " <" IP_FMT ">", ip);
|
|
return !trace_seq_has_overflowed(s);
|
|
}
|
|
|
|
int
|
|
seq_print_ip_sym(struct trace_seq *s, unsigned long ip, unsigned long sym_flags)
|
|
{
|
|
if (!ip) {
|
|
trace_seq_putc(s, '0');
|
|
goto out;
|
|
}
|
|
|
|
trace_seq_print_sym(s, ip, sym_flags & TRACE_ITER(SYM_OFFSET));
|
|
|
|
if (sym_flags & TRACE_ITER(SYM_ADDR))
|
|
trace_seq_printf(s, " <" IP_FMT ">", ip);
|
|
|
|
out:
|
|
return !trace_seq_has_overflowed(s);
|
|
}
|
|
|
|
/**
|
|
* 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
|
|
* count.
|
|
*/
|
|
int trace_print_lat_fmt(struct trace_seq *s, struct trace_entry *entry)
|
|
{
|
|
char hardsoft_irq;
|
|
char need_resched;
|
|
char irqs_off;
|
|
int hardirq;
|
|
int softirq;
|
|
int bh_off;
|
|
int nmi;
|
|
|
|
nmi = entry->flags & TRACE_FLAG_NMI;
|
|
hardirq = entry->flags & TRACE_FLAG_HARDIRQ;
|
|
softirq = entry->flags & TRACE_FLAG_SOFTIRQ;
|
|
bh_off = entry->flags & TRACE_FLAG_BH_OFF;
|
|
|
|
irqs_off =
|
|
(entry->flags & TRACE_FLAG_IRQS_OFF && bh_off) ? 'D' :
|
|
(entry->flags & TRACE_FLAG_IRQS_OFF) ? 'd' :
|
|
bh_off ? 'b' :
|
|
'.';
|
|
|
|
switch (entry->flags & (TRACE_FLAG_NEED_RESCHED | TRACE_FLAG_NEED_RESCHED_LAZY |
|
|
TRACE_FLAG_PREEMPT_RESCHED)) {
|
|
case TRACE_FLAG_NEED_RESCHED | TRACE_FLAG_NEED_RESCHED_LAZY | TRACE_FLAG_PREEMPT_RESCHED:
|
|
need_resched = 'B';
|
|
break;
|
|
case TRACE_FLAG_NEED_RESCHED | TRACE_FLAG_PREEMPT_RESCHED:
|
|
need_resched = 'N';
|
|
break;
|
|
case TRACE_FLAG_NEED_RESCHED_LAZY | TRACE_FLAG_PREEMPT_RESCHED:
|
|
need_resched = 'L';
|
|
break;
|
|
case TRACE_FLAG_NEED_RESCHED | TRACE_FLAG_NEED_RESCHED_LAZY:
|
|
need_resched = 'b';
|
|
break;
|
|
case TRACE_FLAG_NEED_RESCHED:
|
|
need_resched = 'n';
|
|
break;
|
|
case TRACE_FLAG_PREEMPT_RESCHED:
|
|
need_resched = 'p';
|
|
break;
|
|
case TRACE_FLAG_NEED_RESCHED_LAZY:
|
|
need_resched = 'l';
|
|
break;
|
|
default:
|
|
need_resched = '.';
|
|
break;
|
|
}
|
|
|
|
hardsoft_irq =
|
|
(nmi && hardirq) ? 'Z' :
|
|
nmi ? 'z' :
|
|
(hardirq && softirq) ? 'H' :
|
|
hardirq ? 'h' :
|
|
softirq ? 's' :
|
|
'.' ;
|
|
|
|
trace_seq_printf(s, "%c%c%c",
|
|
irqs_off, need_resched, hardsoft_irq);
|
|
|
|
if (entry->preempt_count & 0xf)
|
|
trace_seq_printf(s, "%x", entry->preempt_count & 0xf);
|
|
else
|
|
trace_seq_putc(s, '.');
|
|
|
|
if (entry->preempt_count & 0xf0)
|
|
trace_seq_printf(s, "%x", entry->preempt_count >> 4);
|
|
else
|
|
trace_seq_putc(s, '.');
|
|
|
|
return !trace_seq_has_overflowed(s);
|
|
}
|
|
|
|
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);
|
|
|
|
trace_seq_printf(s, "%8.8s-%-7d %3d",
|
|
comm, entry->pid, cpu);
|
|
|
|
return trace_print_lat_fmt(s, entry);
|
|
}
|
|
|
|
#undef MARK
|
|
#define MARK(v, s) {.val = v, .sym = s}
|
|
/* trace overhead mark */
|
|
static const struct trace_mark {
|
|
unsigned long long val; /* unit: nsec */
|
|
char sym;
|
|
} mark[] = {
|
|
MARK(1000000000ULL , '$'), /* 1 sec */
|
|
MARK(100000000ULL , '@'), /* 100 msec */
|
|
MARK(10000000ULL , '*'), /* 10 msec */
|
|
MARK(1000000ULL , '#'), /* 1000 usecs */
|
|
MARK(100000ULL , '!'), /* 100 usecs */
|
|
MARK(10000ULL , '+'), /* 10 usecs */
|
|
};
|
|
#undef MARK
|
|
|
|
char trace_find_mark(unsigned long long d)
|
|
{
|
|
int i;
|
|
int size = ARRAY_SIZE(mark);
|
|
|
|
for (i = 0; i < size; i++) {
|
|
if (d > mark[i].val)
|
|
break;
|
|
}
|
|
|
|
return (i == size) ? ' ' : mark[i].sym;
|
|
}
|
|
|
|
static int
|
|
lat_print_timestamp(struct trace_iterator *iter, u64 next_ts)
|
|
{
|
|
struct trace_array *tr = iter->tr;
|
|
unsigned long verbose = tr->trace_flags & TRACE_ITER(VERBOSE);
|
|
unsigned long in_ns = iter->iter_flags & TRACE_FILE_TIME_IN_NS;
|
|
unsigned long long abs_ts = iter->ts - iter->array_buffer->time_start;
|
|
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;
|
|
|
|
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) {
|
|
trace_seq_printf(
|
|
s, "[%016llx] %lld (+%lld): ",
|
|
iter->ts, abs_ts, rel_ts);
|
|
|
|
} else if (!verbose && in_ns) {
|
|
trace_seq_printf(
|
|
s, " %4lldus%c: ",
|
|
abs_ts,
|
|
trace_find_mark(rel_ts * NSEC_PER_USEC));
|
|
|
|
} else { /* !verbose && !in_ns */
|
|
trace_seq_printf(s, " %4lld: ", abs_ts);
|
|
}
|
|
|
|
return !trace_seq_has_overflowed(s);
|
|
}
|
|
|
|
static void trace_print_time(struct trace_seq *s, struct trace_iterator *iter,
|
|
unsigned long long ts)
|
|
{
|
|
unsigned long secs, usec_rem;
|
|
unsigned long long t;
|
|
|
|
if (iter->iter_flags & TRACE_FILE_TIME_IN_NS) {
|
|
t = ns2usecs(ts);
|
|
usec_rem = do_div(t, USEC_PER_SEC);
|
|
secs = (unsigned long)t;
|
|
trace_seq_printf(s, " %5lu.%06lu", secs, usec_rem);
|
|
} else
|
|
trace_seq_printf(s, " %12llu", ts);
|
|
}
|
|
|
|
int trace_print_context(struct trace_iterator *iter)
|
|
{
|
|
struct trace_array *tr = iter->tr;
|
|
struct trace_seq *s = &iter->seq;
|
|
struct trace_entry *entry = iter->ent;
|
|
char comm[TASK_COMM_LEN];
|
|
|
|
trace_find_cmdline(entry->pid, comm);
|
|
|
|
trace_seq_printf(s, "%16s-%-7d ", comm, entry->pid);
|
|
|
|
if (tr->trace_flags & TRACE_ITER(RECORD_TGID)) {
|
|
unsigned int tgid = trace_find_tgid(entry->pid);
|
|
|
|
if (!tgid)
|
|
trace_seq_printf(s, "(-------) ");
|
|
else
|
|
trace_seq_printf(s, "(%7d) ", tgid);
|
|
}
|
|
|
|
trace_seq_printf(s, "[%03d] ", iter->cpu);
|
|
|
|
if (tr->trace_flags & TRACE_ITER(IRQ_INFO))
|
|
trace_print_lat_fmt(s, entry);
|
|
|
|
trace_print_time(s, iter, iter->ts);
|
|
trace_seq_puts(s, ": ");
|
|
|
|
return !trace_seq_has_overflowed(s);
|
|
}
|
|
|
|
int trace_print_lat_context(struct trace_iterator *iter)
|
|
{
|
|
struct trace_entry *entry, *next_entry;
|
|
struct trace_array *tr = iter->tr;
|
|
struct trace_seq *s = &iter->seq;
|
|
unsigned long verbose = (tr->trace_flags & TRACE_ITER(VERBOSE));
|
|
u64 next_ts;
|
|
|
|
next_entry = trace_find_next_entry(iter, NULL, &next_ts);
|
|
if (!next_entry)
|
|
next_ts = iter->ts;
|
|
|
|
/* trace_find_next_entry() may change iter->ent */
|
|
entry = iter->ent;
|
|
|
|
if (verbose) {
|
|
char comm[TASK_COMM_LEN];
|
|
|
|
trace_find_cmdline(entry->pid, comm);
|
|
|
|
trace_seq_printf(
|
|
s, "%16s %7d %3d %d %08x %08lx ",
|
|
comm, entry->pid, iter->cpu, entry->flags,
|
|
entry->preempt_count & 0xf, iter->idx);
|
|
} else {
|
|
lat_print_generic(s, entry, iter->cpu);
|
|
}
|
|
|
|
lat_print_timestamp(iter, next_ts);
|
|
|
|
return !trace_seq_has_overflowed(s);
|
|
}
|
|
|
|
#ifdef CONFIG_FUNCTION_TRACE_ARGS
|
|
void print_function_args(struct trace_seq *s, unsigned long *args,
|
|
unsigned long func)
|
|
{
|
|
const struct btf_param *param;
|
|
const struct btf_type *t;
|
|
const char *param_name;
|
|
char name[KSYM_NAME_LEN];
|
|
unsigned long arg;
|
|
struct btf *btf;
|
|
s32 tid, nr = 0;
|
|
int a, p, x;
|
|
u16 encode;
|
|
|
|
trace_seq_printf(s, "(");
|
|
|
|
if (!args)
|
|
goto out;
|
|
if (lookup_symbol_name(func, name))
|
|
goto out;
|
|
|
|
/* TODO: Pass module name here too */
|
|
t = btf_find_func_proto(name, &btf);
|
|
if (IS_ERR_OR_NULL(t))
|
|
goto out;
|
|
|
|
param = btf_get_func_param(t, &nr);
|
|
if (!param)
|
|
goto out_put;
|
|
|
|
for (a = 0, p = 0; p < nr; a++, p++) {
|
|
if (p)
|
|
trace_seq_puts(s, ", ");
|
|
|
|
/* This only prints what the arch allows (6 args by default) */
|
|
if (a == FTRACE_REGS_MAX_ARGS) {
|
|
trace_seq_puts(s, "...");
|
|
break;
|
|
}
|
|
|
|
arg = args[a];
|
|
|
|
param_name = btf_name_by_offset(btf, param[p].name_off);
|
|
if (param_name)
|
|
trace_seq_printf(s, "%s=", param_name);
|
|
t = btf_type_skip_modifiers(btf, param[p].type, &tid);
|
|
|
|
switch (t ? BTF_INFO_KIND(t->info) : BTF_KIND_UNKN) {
|
|
case BTF_KIND_UNKN:
|
|
trace_seq_putc(s, '?');
|
|
/* Still print unknown type values */
|
|
fallthrough;
|
|
case BTF_KIND_PTR:
|
|
trace_seq_printf(s, "0x%lx", arg);
|
|
break;
|
|
case BTF_KIND_INT:
|
|
encode = btf_int_encoding(t);
|
|
/* Print unsigned ints as hex */
|
|
if (encode & BTF_INT_SIGNED)
|
|
trace_seq_printf(s, "%ld", arg);
|
|
else
|
|
trace_seq_printf(s, "0x%lx", arg);
|
|
break;
|
|
case BTF_KIND_ENUM:
|
|
trace_seq_printf(s, "%ld", arg);
|
|
break;
|
|
default:
|
|
/* This does not handle complex arguments */
|
|
trace_seq_printf(s, "(%s)[0x%lx", btf_type_str(t), arg);
|
|
for (x = sizeof(long); x < t->size; x += sizeof(long)) {
|
|
trace_seq_putc(s, ':');
|
|
if (++a == FTRACE_REGS_MAX_ARGS) {
|
|
trace_seq_puts(s, "...]");
|
|
goto out_put;
|
|
}
|
|
trace_seq_printf(s, "0x%lx", args[a]);
|
|
}
|
|
trace_seq_putc(s, ']');
|
|
break;
|
|
}
|
|
}
|
|
out_put:
|
|
btf_put(btf);
|
|
out:
|
|
trace_seq_printf(s, ")");
|
|
}
|
|
#endif
|
|
|
|
/**
|
|
* ftrace_find_event - find a registered event
|
|
* @type: the type of event to look for
|
|
*
|
|
* Returns an event of type @type otherwise NULL
|
|
* Called with trace_event_read_lock() held.
|
|
*/
|
|
struct trace_event *ftrace_find_event(int type)
|
|
{
|
|
struct trace_event *event;
|
|
|
|
hash_for_each_possible(event_hash, event, node, type) {
|
|
if (event->type == type)
|
|
return event;
|
|
}
|
|
|
|
return NULL;
|
|
}
|
|
|
|
static DEFINE_IDA(trace_event_ida);
|
|
|
|
static void free_trace_event_type(int type)
|
|
{
|
|
if (type >= __TRACE_LAST_TYPE)
|
|
ida_free(&trace_event_ida, type);
|
|
}
|
|
|
|
static int alloc_trace_event_type(void)
|
|
{
|
|
int next;
|
|
|
|
/* Skip static defined type numbers */
|
|
next = ida_alloc_range(&trace_event_ida, __TRACE_LAST_TYPE,
|
|
TRACE_EVENT_TYPE_MAX, GFP_KERNEL);
|
|
if (next < 0)
|
|
return 0;
|
|
return next;
|
|
}
|
|
|
|
void trace_event_read_lock(void)
|
|
{
|
|
down_read(&trace_event_sem);
|
|
}
|
|
|
|
void trace_event_read_unlock(void)
|
|
{
|
|
up_read(&trace_event_sem);
|
|
}
|
|
|
|
/**
|
|
* register_trace_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_trace_event(struct trace_event *event)
|
|
{
|
|
int ret = 0;
|
|
|
|
down_write(&trace_event_sem);
|
|
|
|
if (WARN_ON(!event))
|
|
goto out;
|
|
|
|
if (WARN_ON(!event->funcs))
|
|
goto out;
|
|
|
|
if (!event->type) {
|
|
event->type = alloc_trace_event_type();
|
|
if (!event->type)
|
|
goto out;
|
|
} else if (WARN(event->type > __TRACE_LAST_TYPE,
|
|
"Need to add type to trace.h")) {
|
|
goto out;
|
|
} else {
|
|
/* Is this event already used */
|
|
if (ftrace_find_event(event->type))
|
|
goto out;
|
|
}
|
|
|
|
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;
|
|
|
|
hash_add(event_hash, &event->node, event->type);
|
|
|
|
ret = event->type;
|
|
out:
|
|
up_write(&trace_event_sem);
|
|
|
|
return ret;
|
|
}
|
|
EXPORT_SYMBOL_GPL(register_trace_event);
|
|
|
|
/*
|
|
* Used by module code with the trace_event_sem held for write.
|
|
*/
|
|
int __unregister_trace_event(struct trace_event *event)
|
|
{
|
|
hash_del(&event->node);
|
|
free_trace_event_type(event->type);
|
|
return 0;
|
|
}
|
|
|
|
/**
|
|
* unregister_trace_event - remove a no longer used event
|
|
* @event: the event to remove
|
|
*/
|
|
int unregister_trace_event(struct trace_event *event)
|
|
{
|
|
down_write(&trace_event_sem);
|
|
__unregister_trace_event(event);
|
|
up_write(&trace_event_sem);
|
|
|
|
return 0;
|
|
}
|
|
EXPORT_SYMBOL_GPL(unregister_trace_event);
|
|
|
|
/*
|
|
* Standard events
|
|
*/
|
|
|
|
static void print_array(struct trace_iterator *iter, void *pos,
|
|
struct ftrace_event_field *field)
|
|
{
|
|
int offset;
|
|
int len;
|
|
int i;
|
|
|
|
offset = *(int *)pos & 0xffff;
|
|
len = *(int *)pos >> 16;
|
|
|
|
if (field)
|
|
offset += field->offset + sizeof(int);
|
|
|
|
if (offset + len > iter->ent_size) {
|
|
trace_seq_puts(&iter->seq, "<OVERFLOW>");
|
|
return;
|
|
}
|
|
|
|
pos = (void *)iter->ent + offset;
|
|
|
|
for (i = 0; i < len; i++, pos++) {
|
|
if (i)
|
|
trace_seq_putc(&iter->seq, ',');
|
|
trace_seq_printf(&iter->seq, "%02x", *(unsigned char *)pos);
|
|
}
|
|
}
|
|
|
|
static void print_fields(struct trace_iterator *iter, struct trace_event_call *call,
|
|
struct list_head *head)
|
|
{
|
|
struct ftrace_event_field *field;
|
|
struct trace_array *tr = iter->tr;
|
|
unsigned long long laddr;
|
|
unsigned long addr;
|
|
int offset;
|
|
int len;
|
|
int ret;
|
|
int i;
|
|
void *pos;
|
|
char *str;
|
|
|
|
list_for_each_entry_reverse(field, head, link) {
|
|
trace_seq_printf(&iter->seq, " %s=", field->name);
|
|
if (field->offset + field->size > iter->ent_size) {
|
|
trace_seq_puts(&iter->seq, "<OVERFLOW>");
|
|
continue;
|
|
}
|
|
pos = (void *)iter->ent + field->offset;
|
|
|
|
switch (field->filter_type) {
|
|
case FILTER_COMM:
|
|
case FILTER_STATIC_STRING:
|
|
trace_seq_printf(&iter->seq, "%.*s", field->size, (char *)pos);
|
|
break;
|
|
case FILTER_RDYN_STRING:
|
|
case FILTER_DYN_STRING:
|
|
offset = *(int *)pos & 0xffff;
|
|
len = *(int *)pos >> 16;
|
|
|
|
if (field->filter_type == FILTER_RDYN_STRING)
|
|
offset += field->offset + sizeof(int);
|
|
|
|
if (offset + len > iter->ent_size) {
|
|
trace_seq_puts(&iter->seq, "<OVERFLOW>");
|
|
break;
|
|
}
|
|
str = (char *)iter->ent + offset;
|
|
/* Check if there's any non printable strings */
|
|
for (i = 0; i < len; i++) {
|
|
if (str[i] && !(isascii(str[i]) && isprint(str[i])))
|
|
break;
|
|
}
|
|
if (i < len) {
|
|
for (i = 0; i < len; i++) {
|
|
if (isascii(str[i]) && isprint(str[i]))
|
|
trace_seq_putc(&iter->seq, str[i]);
|
|
else
|
|
trace_seq_putc(&iter->seq, '.');
|
|
}
|
|
trace_seq_puts(&iter->seq, " (");
|
|
for (i = 0; i < len; i++) {
|
|
if (i)
|
|
trace_seq_putc(&iter->seq, ':');
|
|
trace_seq_printf(&iter->seq, "%02x", str[i]);
|
|
}
|
|
trace_seq_putc(&iter->seq, ')');
|
|
} else {
|
|
trace_seq_printf(&iter->seq, "%.*s", len, str);
|
|
}
|
|
break;
|
|
case FILTER_PTR_STRING:
|
|
if (!iter->fmt_size)
|
|
trace_iter_expand_format(iter);
|
|
addr = trace_adjust_address(tr, *(unsigned long *)pos);
|
|
ret = strncpy_from_kernel_nofault(iter->fmt, (void *)addr,
|
|
iter->fmt_size);
|
|
if (ret < 0)
|
|
trace_seq_printf(&iter->seq, "(0x%px)", pos);
|
|
else
|
|
trace_seq_printf(&iter->seq, "(0x%px:%s)",
|
|
pos, iter->fmt);
|
|
break;
|
|
case FILTER_TRACE_FN:
|
|
addr = trace_adjust_address(tr, *(unsigned long *)pos);
|
|
trace_seq_printf(&iter->seq, "%pS", (void *)addr);
|
|
break;
|
|
case FILTER_CPU:
|
|
case FILTER_OTHER:
|
|
switch (field->size) {
|
|
case 1:
|
|
if (isprint(*(char *)pos)) {
|
|
trace_seq_printf(&iter->seq, "'%c'",
|
|
*(unsigned char *)pos);
|
|
}
|
|
trace_seq_printf(&iter->seq, "(%d)",
|
|
*(unsigned char *)pos);
|
|
break;
|
|
case 2:
|
|
trace_seq_printf(&iter->seq, "0x%x (%d)",
|
|
*(unsigned short *)pos,
|
|
*(unsigned short *)pos);
|
|
break;
|
|
case 4:
|
|
/* dynamic array info is 4 bytes */
|
|
if (strstr(field->type, "__data_loc")) {
|
|
print_array(iter, pos, NULL);
|
|
break;
|
|
}
|
|
|
|
if (strstr(field->type, "__rel_loc")) {
|
|
print_array(iter, pos, field);
|
|
break;
|
|
}
|
|
|
|
addr = *(unsigned int *)pos;
|
|
|
|
/* Some fields reference offset from _stext. */
|
|
if (!strcmp(field->name, "caller_offs") ||
|
|
!strcmp(field->name, "parent_offs")) {
|
|
unsigned long ip;
|
|
|
|
ip = addr + (unsigned long)_stext;
|
|
ip = trace_adjust_address(tr, ip);
|
|
trace_seq_printf(&iter->seq, "%pS ", (void *)ip);
|
|
}
|
|
|
|
if (sizeof(long) == 4) {
|
|
addr = trace_adjust_address(tr, addr);
|
|
trace_seq_printf(&iter->seq, "%pS (%d)",
|
|
(void *)addr, (int)addr);
|
|
} else {
|
|
trace_seq_printf(&iter->seq, "0x%x (%d)",
|
|
(unsigned int)addr, (int)addr);
|
|
}
|
|
break;
|
|
case 8:
|
|
laddr = *(unsigned long long *)pos;
|
|
if (sizeof(long) == 8) {
|
|
laddr = trace_adjust_address(tr, (unsigned long)laddr);
|
|
trace_seq_printf(&iter->seq, "%pS (%lld)",
|
|
(void *)(long)laddr, laddr);
|
|
} else {
|
|
trace_seq_printf(&iter->seq, "0x%llx (%lld)", laddr, laddr);
|
|
}
|
|
break;
|
|
default:
|
|
trace_seq_puts(&iter->seq, "<INVALID-SIZE>");
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
trace_seq_puts(&iter->seq, "<INVALID-TYPE>");
|
|
}
|
|
}
|
|
trace_seq_putc(&iter->seq, '\n');
|
|
}
|
|
|
|
enum print_line_t print_event_fields(struct trace_iterator *iter,
|
|
struct trace_event *event)
|
|
{
|
|
struct trace_event_call *call;
|
|
struct list_head *head;
|
|
|
|
lockdep_assert_held_read(&trace_event_sem);
|
|
|
|
/* ftrace defined events have separate call structures */
|
|
if (event->type <= __TRACE_LAST_TYPE) {
|
|
bool found = false;
|
|
|
|
list_for_each_entry(call, &ftrace_events, list) {
|
|
if (call->event.type == event->type) {
|
|
found = true;
|
|
break;
|
|
}
|
|
/* No need to search all events */
|
|
if (call->event.type > __TRACE_LAST_TYPE)
|
|
break;
|
|
}
|
|
if (!found) {
|
|
trace_seq_printf(&iter->seq, "UNKNOWN TYPE %d\n", event->type);
|
|
goto out;
|
|
}
|
|
} else {
|
|
call = container_of(event, struct trace_event_call, event);
|
|
}
|
|
head = trace_get_fields(call);
|
|
|
|
trace_seq_printf(&iter->seq, "%s:", trace_event_name(call));
|
|
|
|
if (head && !list_empty(head))
|
|
print_fields(iter, call, head);
|
|
else
|
|
trace_seq_puts(&iter->seq, "No fields found\n");
|
|
|
|
out:
|
|
return trace_handle_return(&iter->seq);
|
|
}
|
|
|
|
enum print_line_t trace_nop_print(struct trace_iterator *iter, int flags,
|
|
struct trace_event *event)
|
|
{
|
|
trace_seq_printf(&iter->seq, "type: %d\n", iter->ent->type);
|
|
|
|
return trace_handle_return(&iter->seq);
|
|
}
|
|
|
|
static void print_fn_trace(struct trace_seq *s, unsigned long ip,
|
|
unsigned long parent_ip, unsigned long *args,
|
|
struct trace_array *tr, int flags)
|
|
{
|
|
ip = trace_adjust_address(tr, ip);
|
|
parent_ip = trace_adjust_address(tr, parent_ip);
|
|
|
|
seq_print_ip_sym(s, ip, flags);
|
|
if (args)
|
|
print_function_args(s, args, ip);
|
|
|
|
if ((flags & TRACE_ITER(PRINT_PARENT)) && parent_ip) {
|
|
trace_seq_puts(s, " <-");
|
|
seq_print_ip_sym(s, parent_ip, flags);
|
|
}
|
|
}
|
|
|
|
/* TRACE_FN */
|
|
static enum print_line_t trace_fn_trace(struct trace_iterator *iter, int flags,
|
|
struct trace_event *event)
|
|
{
|
|
struct ftrace_entry *field;
|
|
struct trace_seq *s = &iter->seq;
|
|
unsigned long *args;
|
|
int args_size;
|
|
|
|
trace_assign_type(field, iter->ent);
|
|
|
|
args_size = iter->ent_size - offsetof(struct ftrace_entry, args);
|
|
if (args_size >= FTRACE_REGS_MAX_ARGS * sizeof(long))
|
|
args = field->args;
|
|
else
|
|
args = NULL;
|
|
|
|
print_fn_trace(s, field->ip, field->parent_ip, args, iter->tr, flags);
|
|
trace_seq_putc(s, '\n');
|
|
|
|
return trace_handle_return(s);
|
|
}
|
|
|
|
static enum print_line_t trace_fn_raw(struct trace_iterator *iter, int flags,
|
|
struct trace_event *event)
|
|
{
|
|
struct ftrace_entry *field;
|
|
|
|
trace_assign_type(field, iter->ent);
|
|
|
|
trace_seq_printf(&iter->seq, "%lx %lx\n",
|
|
field->ip,
|
|
field->parent_ip);
|
|
|
|
return trace_handle_return(&iter->seq);
|
|
}
|
|
|
|
static enum print_line_t trace_fn_hex(struct trace_iterator *iter, int flags,
|
|
struct trace_event *event)
|
|
{
|
|
struct ftrace_entry *field;
|
|
struct trace_seq *s = &iter->seq;
|
|
|
|
trace_assign_type(field, iter->ent);
|
|
|
|
SEQ_PUT_HEX_FIELD(s, field->ip);
|
|
SEQ_PUT_HEX_FIELD(s, field->parent_ip);
|
|
|
|
return trace_handle_return(s);
|
|
}
|
|
|
|
static enum print_line_t trace_fn_bin(struct trace_iterator *iter, int flags,
|
|
struct trace_event *event)
|
|
{
|
|
struct ftrace_entry *field;
|
|
struct trace_seq *s = &iter->seq;
|
|
|
|
trace_assign_type(field, iter->ent);
|
|
|
|
SEQ_PUT_FIELD(s, field->ip);
|
|
SEQ_PUT_FIELD(s, field->parent_ip);
|
|
|
|
return trace_handle_return(s);
|
|
}
|
|
|
|
static struct trace_event_functions trace_fn_funcs = {
|
|
.trace = trace_fn_trace,
|
|
.raw = trace_fn_raw,
|
|
.hex = trace_fn_hex,
|
|
.binary = trace_fn_bin,
|
|
};
|
|
|
|
static struct trace_event trace_fn_event = {
|
|
.type = TRACE_FN,
|
|
.funcs = &trace_fn_funcs,
|
|
};
|
|
|
|
/* TRACE_CTX an TRACE_WAKE */
|
|
static enum print_line_t trace_ctxwake_print(struct trace_iterator *iter,
|
|
char *delim)
|
|
{
|
|
struct ctx_switch_entry *field;
|
|
char comm[TASK_COMM_LEN];
|
|
int S, T;
|
|
|
|
|
|
trace_assign_type(field, iter->ent);
|
|
|
|
T = task_index_to_char(field->next_state);
|
|
S = task_index_to_char(field->prev_state);
|
|
trace_find_cmdline(field->next_pid, comm);
|
|
trace_seq_printf(&iter->seq,
|
|
" %7d:%3d:%c %s [%03d] %7d:%3d:%c %s\n",
|
|
field->prev_pid,
|
|
field->prev_prio,
|
|
S, delim,
|
|
field->next_cpu,
|
|
field->next_pid,
|
|
field->next_prio,
|
|
T, comm);
|
|
|
|
return trace_handle_return(&iter->seq);
|
|
}
|
|
|
|
static enum print_line_t trace_ctx_print(struct trace_iterator *iter, int flags,
|
|
struct trace_event *event)
|
|
{
|
|
return trace_ctxwake_print(iter, "==>");
|
|
}
|
|
|
|
static enum print_line_t trace_wake_print(struct trace_iterator *iter,
|
|
int flags, struct trace_event *event)
|
|
{
|
|
return trace_ctxwake_print(iter, " +");
|
|
}
|
|
|
|
static int trace_ctxwake_raw(struct trace_iterator *iter, char S)
|
|
{
|
|
struct ctx_switch_entry *field;
|
|
int T;
|
|
|
|
trace_assign_type(field, iter->ent);
|
|
|
|
if (!S)
|
|
S = task_index_to_char(field->prev_state);
|
|
T = task_index_to_char(field->next_state);
|
|
trace_seq_printf(&iter->seq, "%d %d %c %d %d %d %c\n",
|
|
field->prev_pid,
|
|
field->prev_prio,
|
|
S,
|
|
field->next_cpu,
|
|
field->next_pid,
|
|
field->next_prio,
|
|
T);
|
|
|
|
return trace_handle_return(&iter->seq);
|
|
}
|
|
|
|
static enum print_line_t trace_ctx_raw(struct trace_iterator *iter, int flags,
|
|
struct trace_event *event)
|
|
{
|
|
return trace_ctxwake_raw(iter, 0);
|
|
}
|
|
|
|
static enum print_line_t trace_wake_raw(struct trace_iterator *iter, int flags,
|
|
struct trace_event *event)
|
|
{
|
|
return trace_ctxwake_raw(iter, '+');
|
|
}
|
|
|
|
|
|
static int trace_ctxwake_hex(struct trace_iterator *iter, char S)
|
|
{
|
|
struct ctx_switch_entry *field;
|
|
struct trace_seq *s = &iter->seq;
|
|
int T;
|
|
|
|
trace_assign_type(field, iter->ent);
|
|
|
|
if (!S)
|
|
S = task_index_to_char(field->prev_state);
|
|
T = task_index_to_char(field->next_state);
|
|
|
|
SEQ_PUT_HEX_FIELD(s, field->prev_pid);
|
|
SEQ_PUT_HEX_FIELD(s, field->prev_prio);
|
|
SEQ_PUT_HEX_FIELD(s, S);
|
|
SEQ_PUT_HEX_FIELD(s, field->next_cpu);
|
|
SEQ_PUT_HEX_FIELD(s, field->next_pid);
|
|
SEQ_PUT_HEX_FIELD(s, field->next_prio);
|
|
SEQ_PUT_HEX_FIELD(s, T);
|
|
|
|
return trace_handle_return(s);
|
|
}
|
|
|
|
static enum print_line_t trace_ctx_hex(struct trace_iterator *iter, int flags,
|
|
struct trace_event *event)
|
|
{
|
|
return trace_ctxwake_hex(iter, 0);
|
|
}
|
|
|
|
static enum print_line_t trace_wake_hex(struct trace_iterator *iter, int flags,
|
|
struct trace_event *event)
|
|
{
|
|
return trace_ctxwake_hex(iter, '+');
|
|
}
|
|
|
|
static enum print_line_t trace_ctxwake_bin(struct trace_iterator *iter,
|
|
int flags, struct trace_event *event)
|
|
{
|
|
struct ctx_switch_entry *field;
|
|
struct trace_seq *s = &iter->seq;
|
|
|
|
trace_assign_type(field, iter->ent);
|
|
|
|
SEQ_PUT_FIELD(s, field->prev_pid);
|
|
SEQ_PUT_FIELD(s, field->prev_prio);
|
|
SEQ_PUT_FIELD(s, field->prev_state);
|
|
SEQ_PUT_FIELD(s, field->next_cpu);
|
|
SEQ_PUT_FIELD(s, field->next_pid);
|
|
SEQ_PUT_FIELD(s, field->next_prio);
|
|
SEQ_PUT_FIELD(s, field->next_state);
|
|
|
|
return trace_handle_return(s);
|
|
}
|
|
|
|
static struct trace_event_functions trace_ctx_funcs = {
|
|
.trace = trace_ctx_print,
|
|
.raw = trace_ctx_raw,
|
|
.hex = trace_ctx_hex,
|
|
.binary = trace_ctxwake_bin,
|
|
};
|
|
|
|
static struct trace_event trace_ctx_event = {
|
|
.type = TRACE_CTX,
|
|
.funcs = &trace_ctx_funcs,
|
|
};
|
|
|
|
static struct trace_event_functions trace_wake_funcs = {
|
|
.trace = trace_wake_print,
|
|
.raw = trace_wake_raw,
|
|
.hex = trace_wake_hex,
|
|
.binary = trace_ctxwake_bin,
|
|
};
|
|
|
|
static struct trace_event trace_wake_event = {
|
|
.type = TRACE_WAKE,
|
|
.funcs = &trace_wake_funcs,
|
|
};
|
|
|
|
/* TRACE_STACK */
|
|
|
|
static enum print_line_t trace_stack_print(struct trace_iterator *iter,
|
|
int flags, struct trace_event *event)
|
|
{
|
|
struct stack_entry *field;
|
|
struct trace_seq *s = &iter->seq;
|
|
unsigned long *p;
|
|
unsigned long *end;
|
|
|
|
trace_assign_type(field, iter->ent);
|
|
end = (unsigned long *)((long)iter->ent + iter->ent_size);
|
|
|
|
trace_seq_puts(s, "<stack trace>\n");
|
|
|
|
for (p = field->caller; p && p < end && *p != ULONG_MAX; p++) {
|
|
|
|
if (trace_seq_has_overflowed(s))
|
|
break;
|
|
|
|
trace_seq_puts(s, " => ");
|
|
if ((*p) == FTRACE_TRAMPOLINE_MARKER) {
|
|
trace_seq_puts(s, "[FTRACE TRAMPOLINE]\n");
|
|
continue;
|
|
}
|
|
seq_print_ip_sym(s, trace_adjust_address(iter->tr, *p), flags);
|
|
trace_seq_putc(s, '\n');
|
|
}
|
|
|
|
return trace_handle_return(s);
|
|
}
|
|
|
|
static struct trace_event_functions trace_stack_funcs = {
|
|
.trace = trace_stack_print,
|
|
};
|
|
|
|
static struct trace_event trace_stack_event = {
|
|
.type = TRACE_STACK,
|
|
.funcs = &trace_stack_funcs,
|
|
};
|
|
|
|
/* TRACE_USER_STACK */
|
|
static enum print_line_t trace_user_stack_print(struct trace_iterator *iter,
|
|
int flags, struct trace_event *event)
|
|
{
|
|
struct trace_array *tr = iter->tr;
|
|
struct userstack_entry *field;
|
|
struct trace_seq *s = &iter->seq;
|
|
struct mm_struct *mm = NULL;
|
|
unsigned int i;
|
|
|
|
trace_assign_type(field, iter->ent);
|
|
|
|
trace_seq_puts(s, "<user stack trace>\n");
|
|
|
|
if (tr->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();
|
|
task = find_task_by_vpid(field->tgid);
|
|
if (task)
|
|
mm = get_task_mm(task);
|
|
rcu_read_unlock();
|
|
}
|
|
|
|
for (i = 0; i < FTRACE_STACK_ENTRIES; i++) {
|
|
unsigned long ip = field->caller[i];
|
|
|
|
if (!ip || trace_seq_has_overflowed(s))
|
|
break;
|
|
|
|
trace_seq_puts(s, " => ");
|
|
seq_print_user_ip(s, mm, ip, flags);
|
|
trace_seq_putc(s, '\n');
|
|
}
|
|
|
|
if (mm)
|
|
mmput(mm);
|
|
|
|
return trace_handle_return(s);
|
|
}
|
|
|
|
static struct trace_event_functions trace_user_stack_funcs = {
|
|
.trace = trace_user_stack_print,
|
|
};
|
|
|
|
static struct trace_event trace_user_stack_event = {
|
|
.type = TRACE_USER_STACK,
|
|
.funcs = &trace_user_stack_funcs,
|
|
};
|
|
|
|
/* TRACE_HWLAT */
|
|
static enum print_line_t
|
|
trace_hwlat_print(struct trace_iterator *iter, int flags,
|
|
struct trace_event *event)
|
|
{
|
|
struct trace_entry *entry = iter->ent;
|
|
struct trace_seq *s = &iter->seq;
|
|
struct hwlat_entry *field;
|
|
|
|
trace_assign_type(field, entry);
|
|
|
|
trace_seq_printf(s, "#%-5u inner/outer(us): %4llu/%-5llu ts:%ptSp count:%d",
|
|
field->seqnum,
|
|
field->duration,
|
|
field->outer_duration,
|
|
&field->timestamp,
|
|
field->count);
|
|
|
|
if (field->nmi_count) {
|
|
/*
|
|
* The generic sched_clock() is not NMI safe, thus
|
|
* we only record the count and not the time.
|
|
*/
|
|
if (!IS_ENABLED(CONFIG_GENERIC_SCHED_CLOCK))
|
|
trace_seq_printf(s, " nmi-total:%llu",
|
|
field->nmi_total_ts);
|
|
trace_seq_printf(s, " nmi-count:%u",
|
|
field->nmi_count);
|
|
}
|
|
|
|
trace_seq_putc(s, '\n');
|
|
|
|
return trace_handle_return(s);
|
|
}
|
|
|
|
static enum print_line_t
|
|
trace_hwlat_raw(struct trace_iterator *iter, int flags,
|
|
struct trace_event *event)
|
|
{
|
|
struct hwlat_entry *field;
|
|
struct trace_seq *s = &iter->seq;
|
|
|
|
trace_assign_type(field, iter->ent);
|
|
|
|
trace_seq_printf(s, "%llu %lld %lld %09ld %u\n",
|
|
field->duration,
|
|
field->outer_duration,
|
|
(long long)field->timestamp.tv_sec,
|
|
field->timestamp.tv_nsec,
|
|
field->seqnum);
|
|
|
|
return trace_handle_return(s);
|
|
}
|
|
|
|
static struct trace_event_functions trace_hwlat_funcs = {
|
|
.trace = trace_hwlat_print,
|
|
.raw = trace_hwlat_raw,
|
|
};
|
|
|
|
static struct trace_event trace_hwlat_event = {
|
|
.type = TRACE_HWLAT,
|
|
.funcs = &trace_hwlat_funcs,
|
|
};
|
|
|
|
/* TRACE_OSNOISE */
|
|
static enum print_line_t
|
|
trace_osnoise_print(struct trace_iterator *iter, int flags,
|
|
struct trace_event *event)
|
|
{
|
|
struct trace_entry *entry = iter->ent;
|
|
struct trace_seq *s = &iter->seq;
|
|
struct osnoise_entry *field;
|
|
u64 ratio, ratio_dec;
|
|
u64 net_runtime;
|
|
|
|
trace_assign_type(field, entry);
|
|
|
|
/*
|
|
* compute the available % of cpu time.
|
|
*/
|
|
net_runtime = field->runtime - field->noise;
|
|
ratio = net_runtime * 10000000;
|
|
do_div(ratio, field->runtime);
|
|
ratio_dec = do_div(ratio, 100000);
|
|
|
|
trace_seq_printf(s, "%llu %10llu %3llu.%05llu %7llu",
|
|
field->runtime,
|
|
field->noise,
|
|
ratio, ratio_dec,
|
|
field->max_sample);
|
|
|
|
trace_seq_printf(s, " %6u", field->hw_count);
|
|
trace_seq_printf(s, " %6u", field->nmi_count);
|
|
trace_seq_printf(s, " %6u", field->irq_count);
|
|
trace_seq_printf(s, " %6u", field->softirq_count);
|
|
trace_seq_printf(s, " %6u", field->thread_count);
|
|
|
|
trace_seq_putc(s, '\n');
|
|
|
|
return trace_handle_return(s);
|
|
}
|
|
|
|
static enum print_line_t
|
|
trace_osnoise_raw(struct trace_iterator *iter, int flags,
|
|
struct trace_event *event)
|
|
{
|
|
struct osnoise_entry *field;
|
|
struct trace_seq *s = &iter->seq;
|
|
|
|
trace_assign_type(field, iter->ent);
|
|
|
|
trace_seq_printf(s, "%lld %llu %llu %u %u %u %u %u\n",
|
|
field->runtime,
|
|
field->noise,
|
|
field->max_sample,
|
|
field->hw_count,
|
|
field->nmi_count,
|
|
field->irq_count,
|
|
field->softirq_count,
|
|
field->thread_count);
|
|
|
|
return trace_handle_return(s);
|
|
}
|
|
|
|
static struct trace_event_functions trace_osnoise_funcs = {
|
|
.trace = trace_osnoise_print,
|
|
.raw = trace_osnoise_raw,
|
|
};
|
|
|
|
static struct trace_event trace_osnoise_event = {
|
|
.type = TRACE_OSNOISE,
|
|
.funcs = &trace_osnoise_funcs,
|
|
};
|
|
|
|
/* TRACE_TIMERLAT */
|
|
|
|
static char *timerlat_lat_context[] = {"irq", "thread", "user-ret"};
|
|
static enum print_line_t
|
|
trace_timerlat_print(struct trace_iterator *iter, int flags,
|
|
struct trace_event *event)
|
|
{
|
|
struct trace_entry *entry = iter->ent;
|
|
struct trace_seq *s = &iter->seq;
|
|
struct timerlat_entry *field;
|
|
|
|
trace_assign_type(field, entry);
|
|
|
|
trace_seq_printf(s, "#%-5u context %6s timer_latency %9llu ns\n",
|
|
field->seqnum,
|
|
timerlat_lat_context[field->context],
|
|
field->timer_latency);
|
|
|
|
return trace_handle_return(s);
|
|
}
|
|
|
|
static enum print_line_t
|
|
trace_timerlat_raw(struct trace_iterator *iter, int flags,
|
|
struct trace_event *event)
|
|
{
|
|
struct timerlat_entry *field;
|
|
struct trace_seq *s = &iter->seq;
|
|
|
|
trace_assign_type(field, iter->ent);
|
|
|
|
trace_seq_printf(s, "%u %d %llu\n",
|
|
field->seqnum,
|
|
field->context,
|
|
field->timer_latency);
|
|
|
|
return trace_handle_return(s);
|
|
}
|
|
|
|
static struct trace_event_functions trace_timerlat_funcs = {
|
|
.trace = trace_timerlat_print,
|
|
.raw = trace_timerlat_raw,
|
|
};
|
|
|
|
static struct trace_event trace_timerlat_event = {
|
|
.type = TRACE_TIMERLAT,
|
|
.funcs = &trace_timerlat_funcs,
|
|
};
|
|
|
|
/* 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);
|
|
|
|
seq_print_ip_sym(s, field->ip, flags);
|
|
trace_seq_puts(s, ": ");
|
|
trace_seq_puts(s, field->str);
|
|
|
|
return trace_handle_return(s);
|
|
}
|
|
|
|
|
|
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);
|
|
|
|
trace_seq_printf(s, ": %lx : ", field->ip);
|
|
trace_seq_puts(s, field->str);
|
|
|
|
return trace_handle_return(s);
|
|
}
|
|
|
|
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,
|
|
};
|
|
|
|
/* TRACE_BPRINT */
|
|
static enum print_line_t
|
|
trace_bprint_print(struct trace_iterator *iter, int flags,
|
|
struct trace_event *event)
|
|
{
|
|
struct trace_entry *entry = iter->ent;
|
|
struct trace_seq *s = &iter->seq;
|
|
struct bprint_entry *field;
|
|
|
|
trace_assign_type(field, entry);
|
|
|
|
seq_print_ip_sym(s, field->ip, flags);
|
|
trace_seq_puts(s, ": ");
|
|
trace_seq_bprintf(s, field->fmt, field->buf);
|
|
|
|
return trace_handle_return(s);
|
|
}
|
|
|
|
|
|
static enum print_line_t
|
|
trace_bprint_raw(struct trace_iterator *iter, int flags,
|
|
struct trace_event *event)
|
|
{
|
|
struct bprint_entry *field;
|
|
struct trace_seq *s = &iter->seq;
|
|
|
|
trace_assign_type(field, iter->ent);
|
|
|
|
trace_seq_printf(s, ": %lx : ", field->ip);
|
|
trace_seq_bprintf(s, field->fmt, field->buf);
|
|
|
|
return trace_handle_return(s);
|
|
}
|
|
|
|
static struct trace_event_functions trace_bprint_funcs = {
|
|
.trace = trace_bprint_print,
|
|
.raw = trace_bprint_raw,
|
|
};
|
|
|
|
static struct trace_event trace_bprint_event = {
|
|
.type = TRACE_BPRINT,
|
|
.funcs = &trace_bprint_funcs,
|
|
};
|
|
|
|
/* TRACE_PRINT */
|
|
static enum print_line_t trace_print_print(struct trace_iterator *iter,
|
|
int flags, struct trace_event *event)
|
|
{
|
|
struct print_entry *field;
|
|
struct trace_seq *s = &iter->seq;
|
|
unsigned long ip;
|
|
|
|
trace_assign_type(field, iter->ent);
|
|
|
|
ip = trace_adjust_address(iter->tr, field->ip);
|
|
|
|
seq_print_ip_sym(s, ip, flags);
|
|
trace_seq_printf(s, ": %s", field->buf);
|
|
|
|
return trace_handle_return(s);
|
|
}
|
|
|
|
static enum print_line_t trace_print_raw(struct trace_iterator *iter, int flags,
|
|
struct trace_event *event)
|
|
{
|
|
struct print_entry *field;
|
|
|
|
trace_assign_type(field, iter->ent);
|
|
|
|
trace_seq_printf(&iter->seq, "# %lx %s", field->ip, field->buf);
|
|
|
|
return trace_handle_return(&iter->seq);
|
|
}
|
|
|
|
static struct trace_event_functions trace_print_funcs = {
|
|
.trace = trace_print_print,
|
|
.raw = trace_print_raw,
|
|
};
|
|
|
|
static struct trace_event trace_print_event = {
|
|
.type = TRACE_PRINT,
|
|
.funcs = &trace_print_funcs,
|
|
};
|
|
|
|
static enum print_line_t trace_raw_data(struct trace_iterator *iter, int flags,
|
|
struct trace_event *event)
|
|
{
|
|
struct raw_data_entry *field;
|
|
int i;
|
|
|
|
trace_assign_type(field, iter->ent);
|
|
|
|
trace_seq_printf(&iter->seq, "# %x buf:", field->id);
|
|
|
|
for (i = 0; i < iter->ent_size - offsetof(struct raw_data_entry, buf); i++)
|
|
trace_seq_printf(&iter->seq, " %02x",
|
|
(unsigned char)field->buf[i]);
|
|
|
|
trace_seq_putc(&iter->seq, '\n');
|
|
|
|
return trace_handle_return(&iter->seq);
|
|
}
|
|
|
|
static struct trace_event_functions trace_raw_data_funcs = {
|
|
.trace = trace_raw_data,
|
|
.raw = trace_raw_data,
|
|
};
|
|
|
|
static struct trace_event trace_raw_data_event = {
|
|
.type = TRACE_RAW_DATA,
|
|
.funcs = &trace_raw_data_funcs,
|
|
};
|
|
|
|
static enum print_line_t
|
|
trace_func_repeats_raw(struct trace_iterator *iter, int flags,
|
|
struct trace_event *event)
|
|
{
|
|
struct func_repeats_entry *field;
|
|
struct trace_seq *s = &iter->seq;
|
|
|
|
trace_assign_type(field, iter->ent);
|
|
|
|
trace_seq_printf(s, "%lu %lu %u %llu\n",
|
|
field->ip,
|
|
field->parent_ip,
|
|
field->count,
|
|
FUNC_REPEATS_GET_DELTA_TS(field));
|
|
|
|
return trace_handle_return(s);
|
|
}
|
|
|
|
static enum print_line_t
|
|
trace_func_repeats_print(struct trace_iterator *iter, int flags,
|
|
struct trace_event *event)
|
|
{
|
|
struct func_repeats_entry *field;
|
|
struct trace_seq *s = &iter->seq;
|
|
|
|
trace_assign_type(field, iter->ent);
|
|
|
|
print_fn_trace(s, field->ip, field->parent_ip, NULL, iter->tr, flags);
|
|
trace_seq_printf(s, " (repeats: %u, last_ts:", field->count);
|
|
trace_print_time(s, iter,
|
|
iter->ts - FUNC_REPEATS_GET_DELTA_TS(field));
|
|
trace_seq_puts(s, ")\n");
|
|
|
|
return trace_handle_return(s);
|
|
}
|
|
|
|
static struct trace_event_functions trace_func_repeats_funcs = {
|
|
.trace = trace_func_repeats_print,
|
|
.raw = trace_func_repeats_raw,
|
|
};
|
|
|
|
static struct trace_event trace_func_repeats_event = {
|
|
.type = TRACE_FUNC_REPEATS,
|
|
.funcs = &trace_func_repeats_funcs,
|
|
};
|
|
|
|
static struct trace_event *events[] __initdata = {
|
|
&trace_fn_event,
|
|
&trace_ctx_event,
|
|
&trace_wake_event,
|
|
&trace_stack_event,
|
|
&trace_user_stack_event,
|
|
&trace_bputs_event,
|
|
&trace_bprint_event,
|
|
&trace_print_event,
|
|
&trace_hwlat_event,
|
|
&trace_osnoise_event,
|
|
&trace_timerlat_event,
|
|
&trace_raw_data_event,
|
|
&trace_func_repeats_event,
|
|
NULL
|
|
};
|
|
|
|
__init int init_events(void)
|
|
{
|
|
struct trace_event *event;
|
|
int i, ret;
|
|
|
|
for (i = 0; events[i]; i++) {
|
|
event = events[i];
|
|
ret = register_trace_event(event);
|
|
WARN_ONCE(!ret, "event %d failed to register", event->type);
|
|
}
|
|
|
|
return 0;
|
|
}
|