Skip to content

Commit

Permalink
perf probe: Support --line option to show probable source-code lines
Browse files Browse the repository at this point in the history
Add --line option to support showing probable source-code lines.

  perf probe --line SRC:LN[-LN|+NUM]
   or
  perf probe --line FUNC[:LN[-LN|+NUM]]

This option shows source-code with line number if the line can
be probed. Lines without line number (and blue color) means that
the line can not be probed, because debuginfo doesn't have the
information of those lines.

The argument specifies the range of lines, "source.c:100-120"
shows lines between 100th to l20th in source.c file. And
"func:10+20" shows 20 lines from 10th line of func function.

e.g.
 # ./perf probe --line kernel/sched.c:1080
 <kernel/sched.c:1080>
          *
          * called with rq->lock held and irqs disabled
          */
         static void hrtick_start(struct rq *rq, u64 delay)
         {
                struct hrtimer *timer = &rq->hrtick_timer;
   1086         ktime_t time = ktime_add_ns(timer->base->get_time(), delay);

                hrtimer_set_expires(timer, time);

   1090         if (rq == this_rq()) {
   1091                 hrtimer_restart(timer);
   1092         } else if (!rq->hrtick_csd_pending) {
   1093                 __smp_call_function_single(cpu_of(rq), &rq->hrtick_csd,
   1094                 rq->hrtick_csd_pending = 1;

If you specifying function name, this shows function-relative
line number.

 # ./perf probe --line schedule
 <schedule:0>
         asmlinkage void __sched schedule(void)
      1  {
                struct task_struct *prev, *next;
                unsigned long *switch_count;
                struct rq *rq;
                int cpu;

         need_resched:
                preempt_disable();
      9         cpu = smp_processor_id();
     10         rq = cpu_rq(cpu);
     11         rcu_sched_qs(cpu);
     12         prev = rq->curr;
     13         switch_count = &prev->nivcsw;

Signed-off-by: Masami Hiramatsu <mhiramat@redhat.com>
Cc: Frederic Weisbecker <fweisbec@gmail.com>
Cc: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: systemtap <systemtap@sources.redhat.com>
Cc: DLE <dle-develop@lists.sourceforge.net>
Cc: Frederic Weisbecker <fweisbec@gmail.com>
Cc: Paul Mackerras <paulus@samba.org>
Cc: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Peter Zijlstra <peterz@infradead.org>
Cc: Mike Galbraith <efault@gmx.de>
LKML-Reference: <20100106144534.27218.77939.stgit@dhcp-100-2-132.bos.redhat.com>
Signed-off-by: Ingo Molnar <mingo@elte.hu>
  • Loading branch information
Masami Hiramatsu authored and Ingo Molnar committed Jan 13, 2010
1 parent 6964cd2 commit 631c9de
Show file tree
Hide file tree
Showing 6 changed files with 402 additions and 18 deletions.
20 changes: 20 additions & 0 deletions tools/perf/Documentation/perf-probe.txt
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ or
'perf probe' [options] --del='[GROUP:]EVENT' [...]
or
'perf probe' --list
or
'perf probe' --line='FUNC[:RLN[+NUM|:RLN2]]|SRC:ALN[+NUM|:ALN2]'

DESCRIPTION
-----------
Expand Down Expand Up @@ -45,6 +47,11 @@ OPTIONS
--list::
List up current probe events.

-L::
--line=::
Show source code lines which can be probed. This needs an argument
which specifies a range of the source code.

PROBE SYNTAX
------------
Probe points are defined by following syntax.
Expand All @@ -56,6 +63,19 @@ Probe points are defined by following syntax.
It is also possible to specify a probe point by the source line number by using 'SRC:ALN' syntax, where 'SRC' is the source file path and 'ALN' is the line number.
'ARG' specifies the arguments of this probe point. You can use the name of local variable, or kprobe-tracer argument format (e.g. $retval, %ax, etc).

LINE SYNTAX
-----------
Line range is descripted by following syntax.

"FUNC[:RLN[+NUM|:RLN2]]|SRC:ALN[+NUM|:ALN2]"

FUNC specifies the function name of showing lines. 'RLN' is the start line
number from function entry line, and 'RLN2' is the end line number. As same as
probe syntax, 'SRC' means the source file path, 'ALN' is start line number,
and 'ALN2' is end line number in the file. It is also possible to specify how
many lines to show by using 'NUM'.
So, "source.c:100-120" shows lines between 100th to l20th in source.c file. And "func:10+20" shows 20 lines from 10th line of func function.

SEE ALSO
--------
linkperf:perf-trace[1], linkperf:perf-record[1]
76 changes: 63 additions & 13 deletions tools/perf/builtin-probe.c
Original file line number Diff line number Diff line change
Expand Up @@ -55,11 +55,13 @@ static struct {
bool need_dwarf;
bool list_events;
bool force_add;
bool show_lines;
int nr_probe;
struct probe_point probes[MAX_PROBES];
struct strlist *dellist;
struct perf_session *psession;
struct map *kmap;
struct line_range line_range;
} session;


Expand Down Expand Up @@ -116,6 +118,15 @@ static int opt_del_probe_event(const struct option *opt __used,
return 0;
}

static int opt_show_lines(const struct option *opt __used,
const char *str, int unset __used)
{
if (str)
parse_line_range_desc(str, &session.line_range);
INIT_LIST_HEAD(&session.line_range.line_list);
session.show_lines = true;
return 0;
}
/* Currently just checking function name from symbol map */
static void evaluate_probe_point(struct probe_point *pp)
{
Expand Down Expand Up @@ -144,6 +155,7 @@ static const char * const probe_usage[] = {
"perf probe [<options>] --add 'PROBEDEF' [--add 'PROBEDEF' ...]",
"perf probe [<options>] --del '[GROUP:]EVENT' ...",
"perf probe --list",
"perf probe --line 'LINEDESC'",
NULL
};

Expand Down Expand Up @@ -182,9 +194,32 @@ static const struct option options[] = {
opt_add_probe_event),
OPT_BOOLEAN('f', "force", &session.force_add, "forcibly add events"
" with existing name"),
#ifndef NO_LIBDWARF
OPT_CALLBACK('L', "line", NULL,
"FUNC[:RLN[+NUM|:RLN2]]|SRC:ALN[+NUM|:ALN2]",
"Show source code lines.", opt_show_lines),
#endif
OPT_END()
};

/* Initialize symbol maps for vmlinux */
static void init_vmlinux(void)
{
symbol_conf.sort_by_name = true;
if (symbol_conf.vmlinux_name == NULL)
symbol_conf.try_vmlinux_path = true;
else
pr_debug("Use vmlinux: %s\n", symbol_conf.vmlinux_name);
if (symbol__init() < 0)
die("Failed to init symbol map.");
session.psession = perf_session__new(NULL, O_WRONLY, false);
if (session.psession == NULL)
die("Failed to init perf_session.");
session.kmap = session.psession->vmlinux_maps[MAP__FUNCTION];
if (!session.kmap)
die("Could not find kernel map.\n");
}

int cmd_probe(int argc, const char **argv, const char *prefix __used)
{
int i, ret;
Expand All @@ -203,7 +238,8 @@ int cmd_probe(int argc, const char **argv, const char *prefix __used)
parse_probe_event_argv(argc, argv);
}

if ((!session.nr_probe && !session.dellist && !session.list_events))
if ((!session.nr_probe && !session.dellist && !session.list_events &&
!session.show_lines))
usage_with_options(probe_usage, options);

if (debugfs_valid_mountpoint(debugfs_path) < 0)
Expand All @@ -215,29 +251,43 @@ int cmd_probe(int argc, const char **argv, const char *prefix __used)
" --add/--del.\n");
usage_with_options(probe_usage, options);
}
if (session.show_lines) {
pr_warning(" Error: Don't use --list with --line.\n");
usage_with_options(probe_usage, options);
}
show_perf_probe_events();
return 0;
}

#ifndef NO_LIBDWARF
if (session.show_lines) {
if (session.nr_probe != 0 || session.dellist) {
pr_warning(" Error: Don't use --line with"
" --add/--del.\n");
usage_with_options(probe_usage, options);
}
init_vmlinux();
fd = open_vmlinux();
if (fd < 0)
die("Could not open debuginfo file.");
ret = find_line_range(fd, &session.line_range);
if (ret <= 0)
die("Source line is not found.\n");
close(fd);
show_line_range(&session.line_range);
return 0;
}
#endif

if (session.dellist) {
del_trace_kprobe_events(session.dellist);
strlist__delete(session.dellist);
if (session.nr_probe == 0)
return 0;
}

/* Initialize symbol maps for vmlinux */
symbol_conf.sort_by_name = true;
if (symbol_conf.vmlinux_name == NULL)
symbol_conf.try_vmlinux_path = true;
if (symbol__init() < 0)
die("Failed to init symbol map.");
session.psession = perf_session__new(NULL, O_WRONLY, false);
if (session.psession == NULL)
die("Failed to init perf_session.");
session.kmap = session.psession->vmlinux_maps[MAP__FUNCTION];
if (!session.kmap)
die("Could not find kernel map.\n");
/* Add probes */
init_vmlinux();

if (session.need_dwarf)
#ifdef NO_LIBDWARF
Expand Down
100 changes: 100 additions & 0 deletions tools/perf/util/probe-event.c
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
#include "strlist.h"
#include "debug.h"
#include "cache.h"
#include "color.h"
#include "parse-events.h" /* For debugfs_path */
#include "probe-event.h"

Expand All @@ -63,6 +64,42 @@ static int e_snprintf(char *str, size_t size, const char *format, ...)
return ret;
}

void parse_line_range_desc(const char *arg, struct line_range *lr)
{
const char *ptr;
char *tmp;
/*
* <Syntax>
* SRC:SLN[+NUM|-ELN]
* FUNC[:SLN[+NUM|-ELN]]
*/
ptr = strchr(arg, ':');
if (ptr) {
lr->start = (unsigned int)strtoul(ptr + 1, &tmp, 0);
if (*tmp == '+')
lr->end = lr->start + (unsigned int)strtoul(tmp + 1,
&tmp, 0);
else if (*tmp == '-')
lr->end = (unsigned int)strtoul(tmp + 1, &tmp, 0);
else
lr->end = 0;
pr_debug("Line range is %u to %u\n", lr->start, lr->end);
if (lr->end && lr->start > lr->end)
semantic_error("Start line must be smaller"
" than end line.");
if (*tmp != '\0')
semantic_error("Tailing with invalid character '%d'.",
*tmp);
tmp = strndup(arg, (ptr - arg));
} else
tmp = strdup(arg);

if (strchr(tmp, '.'))
lr->file = tmp;
else
lr->function = tmp;
}

/* Check the name is good for event/group */
static bool check_event_name(const char *name)
{
Expand Down Expand Up @@ -678,3 +715,66 @@ void del_trace_kprobe_events(struct strlist *dellist)
close(fd);
}

#define LINEBUF_SIZE 256

static void show_one_line(FILE *fp, unsigned int l, bool skip, bool show_num)
{
char buf[LINEBUF_SIZE];
const char *color = PERF_COLOR_BLUE;

if (fgets(buf, LINEBUF_SIZE, fp) == NULL)
goto error;
if (!skip) {
if (show_num)
fprintf(stdout, "%7u %s", l, buf);
else
color_fprintf(stdout, color, " %s", buf);
}

while (strlen(buf) == LINEBUF_SIZE - 1 &&
buf[LINEBUF_SIZE - 2] != '\n') {
if (fgets(buf, LINEBUF_SIZE, fp) == NULL)
goto error;
if (!skip) {
if (show_num)
fprintf(stdout, "%s", buf);
else
color_fprintf(stdout, color, "%s", buf);
}
}
return;
error:
if (feof(fp))
die("Source file is shorter than expected.");
else
die("File read error: %s", strerror(errno));
}

void show_line_range(struct line_range *lr)
{
unsigned int l = 1;
struct line_node *ln;
FILE *fp;

setup_pager();

if (lr->function)
fprintf(stdout, "<%s:%d>\n", lr->function,
lr->start - lr->offset);
else
fprintf(stdout, "<%s:%d>\n", lr->file, lr->start);

fp = fopen(lr->path, "r");
if (fp == NULL)
die("Failed to open %s: %s", lr->path, strerror(errno));
/* Skip to starting line number */
while (l < lr->start)
show_one_line(fp, l++, true, false);

list_for_each_entry(ln, &lr->line_list, list) {
while (ln->line > l)
show_one_line(fp, (l++) - lr->offset, false, false);
show_one_line(fp, (l++) - lr->offset, false, true);
}
fclose(fp);
}
2 changes: 2 additions & 0 deletions tools/perf/util/probe-event.h
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
#include "probe-finder.h"
#include "strlist.h"

extern void parse_line_range_desc(const char *arg, struct line_range *lr);
extern void parse_perf_probe_event(const char *str, struct probe_point *pp,
bool *need_dwarf);
extern int synthesize_perf_probe_point(struct probe_point *pp);
Expand All @@ -15,6 +16,7 @@ extern void add_trace_kprobe_events(struct probe_point *probes, int nr_probes,
bool force_add);
extern void del_trace_kprobe_events(struct strlist *dellist);
extern void show_perf_probe_events(void);
extern void show_line_range(struct line_range *lr);

/* Maximum index number of event-name postfix */
#define MAX_EVENT_INDEX 1024
Expand Down
Loading

0 comments on commit 631c9de

Please sign in to comment.