diff options
Diffstat (limited to 'tools/perf/builtin-top.c')
| -rw-r--r-- | tools/perf/builtin-top.c | 623 | 
1 files changed, 553 insertions, 70 deletions
diff --git a/tools/perf/builtin-top.c b/tools/perf/builtin-top.c index cf0d21f1ae1..7de28ce9ca2 100644 --- a/tools/perf/builtin-top.c +++ b/tools/perf/builtin-top.c @@ -23,7 +23,7 @@  #include "util/symbol.h"  #include "util/color.h"  #include "util/util.h" -#include "util/rbtree.h" +#include <linux/rbtree.h>  #include "util/parse-options.h"  #include "util/parse-events.h" @@ -31,6 +31,8 @@  #include <fcntl.h>  #include <stdio.h> +#include <termios.h> +#include <unistd.h>  #include <errno.h>  #include <time.h> @@ -54,10 +56,11 @@ static int			system_wide			=  0;  static int			default_interval		= 100000; -static u64			count_filter			=  5; +static int			count_filter			=  5;  static int			print_entries			= 15;  static int			target_pid			= -1; +static int			inherit				=  0;  static int			profile_cpu			= -1;  static int			nr_cpus				=  0;  static unsigned int		realtime_prio			=  0; @@ -66,16 +69,30 @@ static unsigned int		page_size;  static unsigned int		mmap_pages			= 16;  static int			freq				=  0;  static int			verbose				=  0; - -static char			*sym_filter; -static unsigned long		filter_start; -static unsigned long		filter_end; +static char			*vmlinux			=  NULL;  static int			delay_secs			=  2;  static int			zero;  static int			dump_symtab;  /* + * Source + */ + +struct source_line { +	u64			eip; +	unsigned long		count[MAX_COUNTERS]; +	char			*line; +	struct source_line	*next; +}; + +static char			*sym_filter			=  NULL; +struct sym_entry		*sym_filter_entry		=  NULL; +static int			sym_pcnt_filter			=  5; +static int			sym_counter			=  0; +static int			display_weighted		= -1; + +/*   * Symbols   */ @@ -89,9 +106,237 @@ struct sym_entry {  	unsigned long		snap_count;  	double			weight;  	int			skip; +	struct source_line	*source; +	struct source_line	*lines; +	struct source_line	**lines_tail; +	pthread_mutex_t		source_lock;  }; -struct sym_entry		*sym_filter_entry; +/* + * Source functions + */ + +static void parse_source(struct sym_entry *syme) +{ +	struct symbol *sym; +	struct module *module; +	struct section *section = NULL; +	FILE *file; +	char command[PATH_MAX*2], *path = vmlinux; +	u64 start, end, len; + +	if (!syme) +		return; + +	if (syme->lines) { +		pthread_mutex_lock(&syme->source_lock); +		goto out_assign; +	} + +	sym = (struct symbol *)(syme + 1); +	module = sym->module; + +	if (module) +		path = module->path; +	if (!path) +		return; + +	start = sym->obj_start; +	if (!start) +		start = sym->start; + +	if (module) { +		section = module->sections->find_section(module->sections, ".text"); +		if (section) +			start -= section->vma; +	} + +	end = start + sym->end - sym->start + 1; +	len = sym->end - sym->start; + +	sprintf(command, "objdump --start-address=0x%016Lx --stop-address=0x%016Lx -dS %s", start, end, path); + +	file = popen(command, "r"); +	if (!file) +		return; + +	pthread_mutex_lock(&syme->source_lock); +	syme->lines_tail = &syme->lines; +	while (!feof(file)) { +		struct source_line *src; +		size_t dummy = 0; +		char *c; + +		src = malloc(sizeof(struct source_line)); +		assert(src != NULL); +		memset(src, 0, sizeof(struct source_line)); + +		if (getline(&src->line, &dummy, file) < 0) +			break; +		if (!src->line) +			break; + +		c = strchr(src->line, '\n'); +		if (c) +			*c = 0; + +		src->next = NULL; +		*syme->lines_tail = src; +		syme->lines_tail = &src->next; + +		if (strlen(src->line)>8 && src->line[8] == ':') { +			src->eip = strtoull(src->line, NULL, 16); +			if (section) +				src->eip += section->vma; +		} +		if (strlen(src->line)>8 && src->line[16] == ':') { +			src->eip = strtoull(src->line, NULL, 16); +			if (section) +				src->eip += section->vma; +		} +	} +	pclose(file); +out_assign: +	sym_filter_entry = syme; +	pthread_mutex_unlock(&syme->source_lock); +} + +static void __zero_source_counters(struct sym_entry *syme) +{ +	int i; +	struct source_line *line; + +	line = syme->lines; +	while (line) { +		for (i = 0; i < nr_counters; i++) +			line->count[i] = 0; +		line = line->next; +	} +} + +static void record_precise_ip(struct sym_entry *syme, int counter, u64 ip) +{ +	struct source_line *line; + +	if (syme != sym_filter_entry) +		return; + +	if (pthread_mutex_trylock(&syme->source_lock)) +		return; + +	if (!syme->source) +		goto out_unlock; + +	for (line = syme->lines; line; line = line->next) { +		if (line->eip == ip) { +			line->count[counter]++; +			break; +		} +		if (line->eip > ip) +			break; +	} +out_unlock: +	pthread_mutex_unlock(&syme->source_lock); +} + +static void lookup_sym_source(struct sym_entry *syme) +{ +	struct symbol *symbol = (struct symbol *)(syme + 1); +	struct source_line *line; +	char pattern[PATH_MAX]; +	char *idx; + +	sprintf(pattern, "<%s>:", symbol->name); + +	if (symbol->module) { +		idx = strstr(pattern, "\t"); +		if (idx) +			*idx = 0; +	} + +	pthread_mutex_lock(&syme->source_lock); +	for (line = syme->lines; line; line = line->next) { +		if (strstr(line->line, pattern)) { +			syme->source = line; +			break; +		} +	} +	pthread_mutex_unlock(&syme->source_lock); +} + +static void show_lines(struct source_line *queue, int count, int total) +{ +	int i; +	struct source_line *line; + +	line = queue; +	for (i = 0; i < count; i++) { +		float pcnt = 100.0*(float)line->count[sym_counter]/(float)total; + +		printf("%8li %4.1f%%\t%s\n", line->count[sym_counter], pcnt, line->line); +		line = line->next; +	} +} + +#define TRACE_COUNT     3 + +static void show_details(struct sym_entry *syme) +{ +	struct symbol *symbol; +	struct source_line *line; +	struct source_line *line_queue = NULL; +	int displayed = 0; +	int line_queue_count = 0, total = 0, more = 0; + +	if (!syme) +		return; + +	if (!syme->source) +		lookup_sym_source(syme); + +	if (!syme->source) +		return; + +	symbol = (struct symbol *)(syme + 1); +	printf("Showing %s for %s\n", event_name(sym_counter), symbol->name); +	printf("  Events  Pcnt (>=%d%%)\n", sym_pcnt_filter); + +	pthread_mutex_lock(&syme->source_lock); +	line = syme->source; +	while (line) { +		total += line->count[sym_counter]; +		line = line->next; +	} + +	line = syme->source; +	while (line) { +		float pcnt = 0.0; + +		if (!line_queue_count) +			line_queue = line; +		line_queue_count++; + +		if (line->count[sym_counter]) +			pcnt = 100.0 * line->count[sym_counter] / (float)total; +		if (pcnt >= (float)sym_pcnt_filter) { +			if (displayed <= print_entries) +				show_lines(line_queue, line_queue_count, total); +			else more++; +			displayed += line_queue_count; +			line_queue_count = 0; +			line_queue = NULL; +		} else if (line_queue_count > TRACE_COUNT) { +			line_queue = line_queue->next; +			line_queue_count--; +		} + +		line->count[sym_counter] = zero ? 0 : line->count[sym_counter] * 7 / 8; +		line = line->next; +	} +	pthread_mutex_unlock(&syme->source_lock); +	if (more) +		printf("%d lines not displayed, maybe increase display entries [e]\n", more); +}  struct dso			*kernel_dso; @@ -110,6 +355,9 @@ static double sym_weight(const struct sym_entry *sym)  	double weight = sym->snap_count;  	int counter; +	if (!display_weighted) +		return weight; +  	for (counter = 1; counter < nr_counters-1; counter++)  		weight *= sym->count[counter]; @@ -157,7 +405,7 @@ static void rb_insert_active_sym(struct rb_root *tree, struct sym_entry *se)  static void print_sym_table(void)  {  	int printed = 0, j; -	int counter; +	int counter, snap = !display_weighted ? sym_counter : 0;  	float samples_per_sec = samples/delay_secs;  	float ksamples_per_sec = (samples-userspace_samples)/delay_secs;  	float sum_ksamples = 0.0; @@ -173,7 +421,7 @@ static void print_sym_table(void)  	pthread_mutex_unlock(&active_symbols_lock);  	list_for_each_entry_safe_from(syme, n, &active_symbols, node) { -		syme->snap_count = syme->count[0]; +		syme->snap_count = syme->count[snap];  		if (syme->snap_count != 0) {  			syme->weight = sym_weight(syme);  			rb_insert_active_sym(&tmp, syme); @@ -193,7 +441,7 @@ static void print_sym_table(void)  		samples_per_sec,  		100.0 - (100.0*((samples_per_sec-ksamples_per_sec)/samples_per_sec))); -	if (nr_counters == 1) { +	if (nr_counters == 1 || !display_weighted) {  		printf("%Ld", (u64)attrs[0].sample_period);  		if (freq)  			printf("Hz "); @@ -201,7 +449,9 @@ static void print_sym_table(void)  			printf(" ");  	} -	for (counter = 0; counter < nr_counters; counter++) { +	if (!display_weighted) +		printf("%s", event_name(sym_counter)); +	else for (counter = 0; counter < nr_counters; counter++) {  		if (counter)  			printf("/"); @@ -226,6 +476,11 @@ static void print_sym_table(void)  	printf("------------------------------------------------------------------------------\n\n"); +	if (sym_filter_entry) { +		show_details(sym_filter_entry); +		return; +	} +  	if (nr_counters == 1)  		printf("             samples    pcnt");  	else @@ -238,59 +493,300 @@ static void print_sym_table(void)  	for (nd = rb_first(&tmp); nd; nd = rb_next(nd)) {  		struct sym_entry *syme = rb_entry(nd, struct sym_entry, rb_node);  		struct symbol *sym = (struct symbol *)(syme + 1); -		char *color = PERF_COLOR_NORMAL;  		double pcnt; -		if (++printed > print_entries || syme->snap_count < count_filter) +		if (++printed > print_entries || (int)syme->snap_count < count_filter)  			continue;  		pcnt = 100.0 - (100.0 * ((sum_ksamples - syme->snap_count) /  					 sum_ksamples)); -		/* -		 * We color high-overhead entries in red, mid-overhead -		 * entries in green - and keep the low overhead places -		 * normal: -		 */ -		if (pcnt >= 5.0) { -			color = PERF_COLOR_RED; -		} else { -			if (pcnt >= 0.5) -				color = PERF_COLOR_GREEN; -		} - -		if (nr_counters == 1) +		if (nr_counters == 1 || !display_weighted)  			printf("%20.2f - ", syme->weight);  		else  			printf("%9.1f %10ld - ", syme->weight, syme->snap_count); -		color_fprintf(stdout, color, "%4.1f%%", pcnt); -		printf(" - %016llx : %s\n", sym->start, sym->name); +		percent_color_fprintf(stdout, "%4.1f%%", pcnt); +		printf(" - %016llx : %s", sym->start, sym->name); +		if (sym->module) +			printf("\t[%s]", sym->module->name); +		printf("\n"); +	} +} + +static void prompt_integer(int *target, const char *msg) +{ +	char *buf = malloc(0), *p; +	size_t dummy = 0; +	int tmp; + +	fprintf(stdout, "\n%s: ", msg); +	if (getline(&buf, &dummy, stdin) < 0) +		return; + +	p = strchr(buf, '\n'); +	if (p) +		*p = 0; + +	p = buf; +	while(*p) { +		if (!isdigit(*p)) +			goto out_free; +		p++; +	} +	tmp = strtoul(buf, NULL, 10); +	*target = tmp; +out_free: +	free(buf); +} + +static void prompt_percent(int *target, const char *msg) +{ +	int tmp = 0; + +	prompt_integer(&tmp, msg); +	if (tmp >= 0 && tmp <= 100) +		*target = tmp; +} + +static void prompt_symbol(struct sym_entry **target, const char *msg) +{ +	char *buf = malloc(0), *p; +	struct sym_entry *syme = *target, *n, *found = NULL; +	size_t dummy = 0; + +	/* zero counters of active symbol */ +	if (syme) { +		pthread_mutex_lock(&syme->source_lock); +		__zero_source_counters(syme); +		*target = NULL; +		pthread_mutex_unlock(&syme->source_lock); +	} + +	fprintf(stdout, "\n%s: ", msg); +	if (getline(&buf, &dummy, stdin) < 0) +		goto out_free; + +	p = strchr(buf, '\n'); +	if (p) +		*p = 0; + +	pthread_mutex_lock(&active_symbols_lock); +	syme = list_entry(active_symbols.next, struct sym_entry, node); +	pthread_mutex_unlock(&active_symbols_lock); + +	list_for_each_entry_safe_from(syme, n, &active_symbols, node) { +		struct symbol *sym = (struct symbol *)(syme + 1); + +		if (!strcmp(buf, sym->name)) { +			found = syme; +			break; +		} +	} + +	if (!found) { +		fprintf(stderr, "Sorry, %s is not active.\n", sym_filter); +		sleep(1); +		return; +	} else +		parse_source(found); + +out_free: +	free(buf); +} + +static void print_mapped_keys(void) +{ +	char *name = NULL; + +	if (sym_filter_entry) { +		struct symbol *sym = (struct symbol *)(sym_filter_entry+1); +		name = sym->name; +	} + +	fprintf(stdout, "\nMapped keys:\n"); +	fprintf(stdout, "\t[d]     display refresh delay.             \t(%d)\n", delay_secs); +	fprintf(stdout, "\t[e]     display entries (lines).           \t(%d)\n", print_entries); + +	if (nr_counters > 1) +		fprintf(stdout, "\t[E]     active event counter.              \t(%s)\n", event_name(sym_counter)); + +	fprintf(stdout, "\t[f]     profile display filter (count).    \t(%d)\n", count_filter); + +	if (vmlinux) { +		fprintf(stdout, "\t[F]     annotate display filter (percent). \t(%d%%)\n", sym_pcnt_filter); +		fprintf(stdout, "\t[s]     annotate symbol.                   \t(%s)\n", name?: "NULL"); +		fprintf(stdout, "\t[S]     stop annotation.\n"); +	} + +	if (nr_counters > 1) +		fprintf(stdout, "\t[w]     toggle display weighted/count[E]r. \t(%d)\n", display_weighted ? 1 : 0); + +	fprintf(stdout, "\t[z]     toggle sample zeroing.             \t(%d)\n", zero ? 1 : 0); +	fprintf(stdout, "\t[qQ]    quit.\n"); +} + +static int key_mapped(int c) +{ +	switch (c) { +		case 'd': +		case 'e': +		case 'f': +		case 'z': +		case 'q': +		case 'Q': +			return 1; +		case 'E': +		case 'w': +			return nr_counters > 1 ? 1 : 0; +		case 'F': +		case 's': +		case 'S': +			return vmlinux ? 1 : 0; +	} + +	return 0; +} + +static void handle_keypress(int c) +{ +	if (!key_mapped(c)) { +		struct pollfd stdin_poll = { .fd = 0, .events = POLLIN }; +		struct termios tc, save; + +		print_mapped_keys(); +		fprintf(stdout, "\nEnter selection, or unmapped key to continue: "); +		fflush(stdout); + +		tcgetattr(0, &save); +		tc = save; +		tc.c_lflag &= ~(ICANON | ECHO); +		tc.c_cc[VMIN] = 0; +		tc.c_cc[VTIME] = 0; +		tcsetattr(0, TCSANOW, &tc); + +		poll(&stdin_poll, 1, -1); +		c = getc(stdin); + +		tcsetattr(0, TCSAFLUSH, &save); +		if (!key_mapped(c)) +			return; +	} + +	switch (c) { +		case 'd': +			prompt_integer(&delay_secs, "Enter display delay"); +			break; +		case 'e': +			prompt_integer(&print_entries, "Enter display entries (lines)"); +			break; +		case 'E': +			if (nr_counters > 1) { +				int i; + +				fprintf(stderr, "\nAvailable events:"); +				for (i = 0; i < nr_counters; i++) +					fprintf(stderr, "\n\t%d %s", i, event_name(i)); + +				prompt_integer(&sym_counter, "Enter details event counter"); + +				if (sym_counter >= nr_counters) { +					fprintf(stderr, "Sorry, no such event, using %s.\n", event_name(0)); +					sym_counter = 0; +					sleep(1); +				} +			} else sym_counter = 0; +			break; +		case 'f': +			prompt_integer(&count_filter, "Enter display event count filter"); +			break; +		case 'F': +			prompt_percent(&sym_pcnt_filter, "Enter details display event filter (percent)"); +			break; +		case 'q': +		case 'Q': +			printf("exiting.\n"); +			exit(0); +		case 's': +			prompt_symbol(&sym_filter_entry, "Enter details symbol"); +			break; +		case 'S': +			if (!sym_filter_entry) +				break; +			else { +				struct sym_entry *syme = sym_filter_entry; + +				pthread_mutex_lock(&syme->source_lock); +				sym_filter_entry = NULL; +				__zero_source_counters(syme); +				pthread_mutex_unlock(&syme->source_lock); +			} +			break; +		case 'w': +			display_weighted = ~display_weighted; +			break; +		case 'z': +			zero = ~zero; +			break;  	}  } -static void *display_thread(void *arg) +static void *display_thread(void *arg __used)  {  	struct pollfd stdin_poll = { .fd = 0, .events = POLLIN }; -	int delay_msecs = delay_secs * 1000; +	struct termios tc, save; +	int delay_msecs, c; + +	tcgetattr(0, &save); +	tc = save; +	tc.c_lflag &= ~(ICANON | ECHO); +	tc.c_cc[VMIN] = 0; +	tc.c_cc[VTIME] = 0; -	printf("PerfTop refresh period: %d seconds\n", delay_secs); +repeat: +	delay_msecs = delay_secs * 1000; +	tcsetattr(0, TCSANOW, &tc); +	/* trash return*/ +	getc(stdin);  	do {  		print_sym_table();  	} while (!poll(&stdin_poll, 1, delay_msecs) == 1); -	printf("key pressed - exiting.\n"); -	exit(0); +	c = getc(stdin); +	tcsetattr(0, TCSAFLUSH, &save); + +	handle_keypress(c); +	goto repeat;  	return NULL;  } +/* Tag samples to be skipped. */ +static const char *skip_symbols[] = { +	"default_idle", +	"cpu_idle", +	"enter_idle", +	"exit_idle", +	"mwait_idle", +	"mwait_idle_with_hints", +	"ppc64_runlatch_off", +	"pseries_dedicated_idle_sleep", +	NULL +}; +  static int symbol_filter(struct dso *self, struct symbol *sym)  { -	static int filter_match;  	struct sym_entry *syme;  	const char *name = sym->name; +	int i; + +	/* +	 * ppc64 uses function descriptors and appends a '.' to the +	 * start of every instruction address. Remove it. +	 */ +	if (name[0] == '.') +		name++;  	if (!strcmp(name, "_text") ||  	    !strcmp(name, "_etext") || @@ -302,37 +798,17 @@ static int symbol_filter(struct dso *self, struct symbol *sym)  		return 1;  	syme = dso__sym_priv(self, sym); -	/* Tag samples to be skipped. */ -	if (!strcmp("default_idle", name) || -	    !strcmp("cpu_idle", name) || -	    !strcmp("enter_idle", name) || -	    !strcmp("exit_idle", name) || -	    !strcmp("mwait_idle", name)) -		syme->skip = 1; +	pthread_mutex_init(&syme->source_lock, NULL); +	if (!sym_filter_entry && sym_filter && !strcmp(name, sym_filter)) +		sym_filter_entry = syme; -	if (filter_match == 1) { -		filter_end = sym->start; -		filter_match = -1; -		if (filter_end - filter_start > 10000) { -			fprintf(stderr, -				"hm, too large filter symbol <%s> - skipping.\n", -				sym_filter); -			fprintf(stderr, "symbol filter start: %016lx\n", -				filter_start); -			fprintf(stderr, "                end: %016lx\n", -				filter_end); -			filter_end = filter_start = 0; -			sym_filter = NULL; -			sleep(1); +	for (i = 0; skip_symbols[i]; i++) { +		if (!strcmp(skip_symbols[i], name)) { +			syme->skip = 1; +			break;  		}  	} -	if (filter_match == 0 && sym_filter && !strcmp(name, sym_filter)) { -		filter_match = 1; -		filter_start = sym->start; -	} - -  	return 0;  } @@ -340,12 +816,13 @@ static int parse_symbols(void)  {  	struct rb_node *node;  	struct symbol  *sym; +	int modules = vmlinux ? 1 : 0;  	kernel_dso = dso__new("[kernel]", sizeof(struct sym_entry));  	if (kernel_dso == NULL)  		return -1; -	if (dso__load_kernel(kernel_dso, NULL, symbol_filter, 1) != 0) +	if (dso__load_kernel(kernel_dso, vmlinux, symbol_filter, verbose, modules) <= 0)  		goto out_delete_dso;  	node = rb_first(&kernel_dso->syms); @@ -367,8 +844,6 @@ out_delete_dso:  	return -1;  } -#define TRACE_COUNT     3 -  /*   * Binary search in the histogram table and record the hit:   */ @@ -381,6 +856,7 @@ static void record_ip(u64 ip, int counter)  		if (!syme->skip) {  			syme->count[counter]++; +			record_precise_ip(syme, counter, ip);  			pthread_mutex_lock(&active_symbols_lock);  			if (list_empty(&syme->node) || !syme->node.next)  				__list_insert_active_sym(syme); @@ -407,7 +883,7 @@ static void process_event(u64 ip, int counter, int user)  struct mmap_data {  	int			counter;  	void			*base; -	unsigned int		mask; +	int			mask;  	unsigned int		prev;  }; @@ -538,7 +1014,7 @@ int group_fd;  static void start_counter(int i, int counter)  {  	struct perf_counter_attr *attr; -	unsigned int cpu; +	int cpu;  	cpu = profile_cpu;  	if (target_pid == -1 && profile_cpu == -1) @@ -548,6 +1024,7 @@ static void start_counter(int i, int counter)  	attr->sample_type	= PERF_SAMPLE_IP | PERF_SAMPLE_TID;  	attr->freq		= freq; +	attr->inherit		= (cpu < 0) && inherit;  try_again:  	fd[i][counter] = sys_perf_counter_open(attr, target_pid, cpu, group_fd, 0); @@ -661,6 +1138,7 @@ static const struct option options[] = {  			    "system-wide collection from all CPUs"),  	OPT_INTEGER('C', "CPU", &profile_cpu,  		    "CPU to profile on"), +	OPT_STRING('k', "vmlinux", &vmlinux, "file", "vmlinux pathname"),  	OPT_INTEGER('m', "mmap-pages", &mmap_pages,  		    "number of mmap data pages"),  	OPT_INTEGER('r', "realtime", &realtime_prio, @@ -673,9 +1151,11 @@ static const struct option options[] = {  		    "only display functions with more events than this"),  	OPT_BOOLEAN('g', "group", &group,  			    "put the counters into a counter group"), -	OPT_STRING('s', "sym-filter", &sym_filter, "pattern", -		    "only display symbols matchig this pattern"), -	OPT_BOOLEAN('z', "zero", &group, +	OPT_BOOLEAN('i', "inherit", &inherit, +		    "child tasks inherit counters"), +	OPT_STRING('s', "sym-annotate", &sym_filter, "symbol name", +		    "symbol to annotate - requires -k option"), +	OPT_BOOLEAN('z', "zero", &zero,  		    "zero history across updates"),  	OPT_INTEGER('F', "freq", &freq,  		    "profile at this frequency"), @@ -686,10 +1166,12 @@ static const struct option options[] = {  	OPT_END()  }; -int cmd_top(int argc, const char **argv, const char *prefix) +int cmd_top(int argc, const char **argv, const char *prefix __used)  {  	int counter; +	symbol__init(); +  	page_size = sysconf(_SC_PAGE_SIZE);  	argc = parse_options(argc, argv, options, top_usage, 0); @@ -715,6 +1197,7 @@ int cmd_top(int argc, const char **argv, const char *prefix)  		delay_secs = 1;  	parse_symbols(); +	parse_source(sym_filter_entry);  	/*  	 * Fill in the ones not specifically initialized via -c:  |