Merge branch 'perf/core' into perf/urgent, to pick up the latest fixes
Signed-off-by: Ingo Molnar <mingo@kernel.org>
This commit is contained in:
		
				commit
				
					
						cf230918cd
					
				
			
		
					 39 changed files with 1242 additions and 123 deletions
				
			
		|  | @ -705,6 +705,7 @@ enum perf_event_type { | ||||||
| 	 *	u32				min; | 	 *	u32				min; | ||||||
| 	 *	u64				ino; | 	 *	u64				ino; | ||||||
| 	 *	u64				ino_generation; | 	 *	u64				ino_generation; | ||||||
|  | 	 *	u32				prot, flags; | ||||||
| 	 *	char				filename[]; | 	 *	char				filename[]; | ||||||
| 	 * 	struct sample_id		sample_id; | 	 * 	struct sample_id		sample_id; | ||||||
| 	 * }; | 	 * }; | ||||||
|  |  | ||||||
|  | @ -40,6 +40,7 @@ | ||||||
| #include <linux/mm_types.h> | #include <linux/mm_types.h> | ||||||
| #include <linux/cgroup.h> | #include <linux/cgroup.h> | ||||||
| #include <linux/module.h> | #include <linux/module.h> | ||||||
|  | #include <linux/mman.h> | ||||||
| 
 | 
 | ||||||
| #include "internal.h" | #include "internal.h" | ||||||
| 
 | 
 | ||||||
|  | @ -5128,6 +5129,7 @@ struct perf_mmap_event { | ||||||
| 	int			maj, min; | 	int			maj, min; | ||||||
| 	u64			ino; | 	u64			ino; | ||||||
| 	u64			ino_generation; | 	u64			ino_generation; | ||||||
|  | 	u32			prot, flags; | ||||||
| 
 | 
 | ||||||
| 	struct { | 	struct { | ||||||
| 		struct perf_event_header	header; | 		struct perf_event_header	header; | ||||||
|  | @ -5169,6 +5171,8 @@ static void perf_event_mmap_output(struct perf_event *event, | ||||||
| 		mmap_event->event_id.header.size += sizeof(mmap_event->min); | 		mmap_event->event_id.header.size += sizeof(mmap_event->min); | ||||||
| 		mmap_event->event_id.header.size += sizeof(mmap_event->ino); | 		mmap_event->event_id.header.size += sizeof(mmap_event->ino); | ||||||
| 		mmap_event->event_id.header.size += sizeof(mmap_event->ino_generation); | 		mmap_event->event_id.header.size += sizeof(mmap_event->ino_generation); | ||||||
|  | 		mmap_event->event_id.header.size += sizeof(mmap_event->prot); | ||||||
|  | 		mmap_event->event_id.header.size += sizeof(mmap_event->flags); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	perf_event_header__init_id(&mmap_event->event_id.header, &sample, event); | 	perf_event_header__init_id(&mmap_event->event_id.header, &sample, event); | ||||||
|  | @ -5187,6 +5191,8 @@ static void perf_event_mmap_output(struct perf_event *event, | ||||||
| 		perf_output_put(&handle, mmap_event->min); | 		perf_output_put(&handle, mmap_event->min); | ||||||
| 		perf_output_put(&handle, mmap_event->ino); | 		perf_output_put(&handle, mmap_event->ino); | ||||||
| 		perf_output_put(&handle, mmap_event->ino_generation); | 		perf_output_put(&handle, mmap_event->ino_generation); | ||||||
|  | 		perf_output_put(&handle, mmap_event->prot); | ||||||
|  | 		perf_output_put(&handle, mmap_event->flags); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	__output_copy(&handle, mmap_event->file_name, | 	__output_copy(&handle, mmap_event->file_name, | ||||||
|  | @ -5205,6 +5211,7 @@ static void perf_event_mmap_event(struct perf_mmap_event *mmap_event) | ||||||
| 	struct file *file = vma->vm_file; | 	struct file *file = vma->vm_file; | ||||||
| 	int maj = 0, min = 0; | 	int maj = 0, min = 0; | ||||||
| 	u64 ino = 0, gen = 0; | 	u64 ino = 0, gen = 0; | ||||||
|  | 	u32 prot = 0, flags = 0; | ||||||
| 	unsigned int size; | 	unsigned int size; | ||||||
| 	char tmp[16]; | 	char tmp[16]; | ||||||
| 	char *buf = NULL; | 	char *buf = NULL; | ||||||
|  | @ -5235,6 +5242,28 @@ static void perf_event_mmap_event(struct perf_mmap_event *mmap_event) | ||||||
| 		gen = inode->i_generation; | 		gen = inode->i_generation; | ||||||
| 		maj = MAJOR(dev); | 		maj = MAJOR(dev); | ||||||
| 		min = MINOR(dev); | 		min = MINOR(dev); | ||||||
|  | 
 | ||||||
|  | 		if (vma->vm_flags & VM_READ) | ||||||
|  | 			prot |= PROT_READ; | ||||||
|  | 		if (vma->vm_flags & VM_WRITE) | ||||||
|  | 			prot |= PROT_WRITE; | ||||||
|  | 		if (vma->vm_flags & VM_EXEC) | ||||||
|  | 			prot |= PROT_EXEC; | ||||||
|  | 
 | ||||||
|  | 		if (vma->vm_flags & VM_MAYSHARE) | ||||||
|  | 			flags = MAP_SHARED; | ||||||
|  | 		else | ||||||
|  | 			flags = MAP_PRIVATE; | ||||||
|  | 
 | ||||||
|  | 		if (vma->vm_flags & VM_DENYWRITE) | ||||||
|  | 			flags |= MAP_DENYWRITE; | ||||||
|  | 		if (vma->vm_flags & VM_MAYEXEC) | ||||||
|  | 			flags |= MAP_EXECUTABLE; | ||||||
|  | 		if (vma->vm_flags & VM_LOCKED) | ||||||
|  | 			flags |= MAP_LOCKED; | ||||||
|  | 		if (vma->vm_flags & VM_HUGETLB) | ||||||
|  | 			flags |= MAP_HUGETLB; | ||||||
|  | 
 | ||||||
| 		goto got_name; | 		goto got_name; | ||||||
| 	} else { | 	} else { | ||||||
| 		name = (char *)arch_vma_name(vma); | 		name = (char *)arch_vma_name(vma); | ||||||
|  | @ -5275,6 +5304,8 @@ got_name: | ||||||
| 	mmap_event->min = min; | 	mmap_event->min = min; | ||||||
| 	mmap_event->ino = ino; | 	mmap_event->ino = ino; | ||||||
| 	mmap_event->ino_generation = gen; | 	mmap_event->ino_generation = gen; | ||||||
|  | 	mmap_event->prot = prot; | ||||||
|  | 	mmap_event->flags = flags; | ||||||
| 
 | 
 | ||||||
| 	if (!(vma->vm_flags & VM_EXEC)) | 	if (!(vma->vm_flags & VM_EXEC)) | ||||||
| 		mmap_event->event_id.header.misc |= PERF_RECORD_MISC_MMAP_DATA; | 		mmap_event->event_id.header.misc |= PERF_RECORD_MISC_MMAP_DATA; | ||||||
|  | @ -5315,6 +5346,8 @@ void perf_event_mmap(struct vm_area_struct *vma) | ||||||
| 		/* .min (attr_mmap2 only) */ | 		/* .min (attr_mmap2 only) */ | ||||||
| 		/* .ino (attr_mmap2 only) */ | 		/* .ino (attr_mmap2 only) */ | ||||||
| 		/* .ino_generation (attr_mmap2 only) */ | 		/* .ino_generation (attr_mmap2 only) */ | ||||||
|  | 		/* .prot (attr_mmap2 only) */ | ||||||
|  | 		/* .flags (attr_mmap2 only) */ | ||||||
| 	}; | 	}; | ||||||
| 
 | 
 | ||||||
| 	perf_event_mmap_event(&mmap_event); | 	perf_event_mmap_event(&mmap_event); | ||||||
|  | @ -6897,10 +6930,6 @@ static int perf_copy_attr(struct perf_event_attr __user *uattr, | ||||||
| 	if (ret) | 	if (ret) | ||||||
| 		return -EFAULT; | 		return -EFAULT; | ||||||
| 
 | 
 | ||||||
| 	/* disabled for now */ |  | ||||||
| 	if (attr->mmap2) |  | ||||||
| 		return -EINVAL; |  | ||||||
| 
 |  | ||||||
| 	if (attr->__reserved_1) | 	if (attr->__reserved_1) | ||||||
| 		return -EINVAL; | 		return -EINVAL; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -765,6 +765,9 @@ static void free_arg(struct print_arg *arg) | ||||||
| 	case PRINT_BSTRING: | 	case PRINT_BSTRING: | ||||||
| 		free(arg->string.string); | 		free(arg->string.string); | ||||||
| 		break; | 		break; | ||||||
|  | 	case PRINT_BITMASK: | ||||||
|  | 		free(arg->bitmask.bitmask); | ||||||
|  | 		break; | ||||||
| 	case PRINT_DYNAMIC_ARRAY: | 	case PRINT_DYNAMIC_ARRAY: | ||||||
| 		free(arg->dynarray.index); | 		free(arg->dynarray.index); | ||||||
| 		break; | 		break; | ||||||
|  | @ -2268,6 +2271,7 @@ static int arg_num_eval(struct print_arg *arg, long long *val) | ||||||
| 	case PRINT_FIELD ... PRINT_SYMBOL: | 	case PRINT_FIELD ... PRINT_SYMBOL: | ||||||
| 	case PRINT_STRING: | 	case PRINT_STRING: | ||||||
| 	case PRINT_BSTRING: | 	case PRINT_BSTRING: | ||||||
|  | 	case PRINT_BITMASK: | ||||||
| 	default: | 	default: | ||||||
| 		do_warning("invalid eval type %d", arg->type); | 		do_warning("invalid eval type %d", arg->type); | ||||||
| 		ret = 0; | 		ret = 0; | ||||||
|  | @ -2296,6 +2300,7 @@ static char *arg_eval (struct print_arg *arg) | ||||||
| 	case PRINT_FIELD ... PRINT_SYMBOL: | 	case PRINT_FIELD ... PRINT_SYMBOL: | ||||||
| 	case PRINT_STRING: | 	case PRINT_STRING: | ||||||
| 	case PRINT_BSTRING: | 	case PRINT_BSTRING: | ||||||
|  | 	case PRINT_BITMASK: | ||||||
| 	default: | 	default: | ||||||
| 		do_warning("invalid eval type %d", arg->type); | 		do_warning("invalid eval type %d", arg->type); | ||||||
| 		break; | 		break; | ||||||
|  | @ -2683,6 +2688,35 @@ process_str(struct event_format *event __maybe_unused, struct print_arg *arg, | ||||||
| 	return EVENT_ERROR; | 	return EVENT_ERROR; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static enum event_type | ||||||
|  | process_bitmask(struct event_format *event __maybe_unused, struct print_arg *arg, | ||||||
|  | 	    char **tok) | ||||||
|  | { | ||||||
|  | 	enum event_type type; | ||||||
|  | 	char *token; | ||||||
|  | 
 | ||||||
|  | 	if (read_expect_type(EVENT_ITEM, &token) < 0) | ||||||
|  | 		goto out_free; | ||||||
|  | 
 | ||||||
|  | 	arg->type = PRINT_BITMASK; | ||||||
|  | 	arg->bitmask.bitmask = token; | ||||||
|  | 	arg->bitmask.offset = -1; | ||||||
|  | 
 | ||||||
|  | 	if (read_expected(EVENT_DELIM, ")") < 0) | ||||||
|  | 		goto out_err; | ||||||
|  | 
 | ||||||
|  | 	type = read_token(&token); | ||||||
|  | 	*tok = token; | ||||||
|  | 
 | ||||||
|  | 	return type; | ||||||
|  | 
 | ||||||
|  |  out_free: | ||||||
|  | 	free_token(token); | ||||||
|  |  out_err: | ||||||
|  | 	*tok = NULL; | ||||||
|  | 	return EVENT_ERROR; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| static struct pevent_function_handler * | static struct pevent_function_handler * | ||||||
| find_func_handler(struct pevent *pevent, char *func_name) | find_func_handler(struct pevent *pevent, char *func_name) | ||||||
| { | { | ||||||
|  | @ -2797,6 +2831,10 @@ process_function(struct event_format *event, struct print_arg *arg, | ||||||
| 		free_token(token); | 		free_token(token); | ||||||
| 		return process_str(event, arg, tok); | 		return process_str(event, arg, tok); | ||||||
| 	} | 	} | ||||||
|  | 	if (strcmp(token, "__get_bitmask") == 0) { | ||||||
|  | 		free_token(token); | ||||||
|  | 		return process_bitmask(event, arg, tok); | ||||||
|  | 	} | ||||||
| 	if (strcmp(token, "__get_dynamic_array") == 0) { | 	if (strcmp(token, "__get_dynamic_array") == 0) { | ||||||
| 		free_token(token); | 		free_token(token); | ||||||
| 		return process_dynamic_array(event, arg, tok); | 		return process_dynamic_array(event, arg, tok); | ||||||
|  | @ -3324,6 +3362,7 @@ eval_num_arg(void *data, int size, struct event_format *event, struct print_arg | ||||||
| 		return eval_type(val, arg, 0); | 		return eval_type(val, arg, 0); | ||||||
| 	case PRINT_STRING: | 	case PRINT_STRING: | ||||||
| 	case PRINT_BSTRING: | 	case PRINT_BSTRING: | ||||||
|  | 	case PRINT_BITMASK: | ||||||
| 		return 0; | 		return 0; | ||||||
| 	case PRINT_FUNC: { | 	case PRINT_FUNC: { | ||||||
| 		struct trace_seq s; | 		struct trace_seq s; | ||||||
|  | @ -3556,6 +3595,60 @@ static void print_str_to_seq(struct trace_seq *s, const char *format, | ||||||
| 		trace_seq_printf(s, format, str); | 		trace_seq_printf(s, format, str); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static void print_bitmask_to_seq(struct pevent *pevent, | ||||||
|  | 				 struct trace_seq *s, const char *format, | ||||||
|  | 				 int len_arg, const void *data, int size) | ||||||
|  | { | ||||||
|  | 	int nr_bits = size * 8; | ||||||
|  | 	int str_size = (nr_bits + 3) / 4; | ||||||
|  | 	int len = 0; | ||||||
|  | 	char buf[3]; | ||||||
|  | 	char *str; | ||||||
|  | 	int index; | ||||||
|  | 	int i; | ||||||
|  | 
 | ||||||
|  | 	/*
 | ||||||
|  | 	 * The kernel likes to put in commas every 32 bits, we | ||||||
|  | 	 * can do the same. | ||||||
|  | 	 */ | ||||||
|  | 	str_size += (nr_bits - 1) / 32; | ||||||
|  | 
 | ||||||
|  | 	str = malloc(str_size + 1); | ||||||
|  | 	if (!str) { | ||||||
|  | 		do_warning("%s: not enough memory!", __func__); | ||||||
|  | 		return; | ||||||
|  | 	} | ||||||
|  | 	str[str_size] = 0; | ||||||
|  | 
 | ||||||
|  | 	/* Start out with -2 for the two chars per byte */ | ||||||
|  | 	for (i = str_size - 2; i >= 0; i -= 2) { | ||||||
|  | 		/*
 | ||||||
|  | 		 * data points to a bit mask of size bytes. | ||||||
|  | 		 * In the kernel, this is an array of long words, thus | ||||||
|  | 		 * endianess is very important. | ||||||
|  | 		 */ | ||||||
|  | 		if (pevent->file_bigendian) | ||||||
|  | 			index = size - (len + 1); | ||||||
|  | 		else | ||||||
|  | 			index = len; | ||||||
|  | 
 | ||||||
|  | 		snprintf(buf, 3, "%02x", *((unsigned char *)data + index)); | ||||||
|  | 		memcpy(str + i, buf, 2); | ||||||
|  | 		len++; | ||||||
|  | 		if (!(len & 3) && i > 0) { | ||||||
|  | 			i--; | ||||||
|  | 			str[i] = ','; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if (len_arg >= 0) | ||||||
|  | 		trace_seq_printf(s, format, len_arg, str); | ||||||
|  | 	else | ||||||
|  | 		trace_seq_printf(s, format, str); | ||||||
|  | 
 | ||||||
|  | 	free(str); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| static void print_str_arg(struct trace_seq *s, void *data, int size, | static void print_str_arg(struct trace_seq *s, void *data, int size, | ||||||
| 			  struct event_format *event, const char *format, | 			  struct event_format *event, const char *format, | ||||||
| 			  int len_arg, struct print_arg *arg) | 			  int len_arg, struct print_arg *arg) | ||||||
|  | @ -3691,6 +3784,23 @@ static void print_str_arg(struct trace_seq *s, void *data, int size, | ||||||
| 	case PRINT_BSTRING: | 	case PRINT_BSTRING: | ||||||
| 		print_str_to_seq(s, format, len_arg, arg->string.string); | 		print_str_to_seq(s, format, len_arg, arg->string.string); | ||||||
| 		break; | 		break; | ||||||
|  | 	case PRINT_BITMASK: { | ||||||
|  | 		int bitmask_offset; | ||||||
|  | 		int bitmask_size; | ||||||
|  | 
 | ||||||
|  | 		if (arg->bitmask.offset == -1) { | ||||||
|  | 			struct format_field *f; | ||||||
|  | 
 | ||||||
|  | 			f = pevent_find_any_field(event, arg->bitmask.bitmask); | ||||||
|  | 			arg->bitmask.offset = f->offset; | ||||||
|  | 		} | ||||||
|  | 		bitmask_offset = data2host4(pevent, data + arg->bitmask.offset); | ||||||
|  | 		bitmask_size = bitmask_offset >> 16; | ||||||
|  | 		bitmask_offset &= 0xffff; | ||||||
|  | 		print_bitmask_to_seq(pevent, s, format, len_arg, | ||||||
|  | 				     data + bitmask_offset, bitmask_size); | ||||||
|  | 		break; | ||||||
|  | 	} | ||||||
| 	case PRINT_OP: | 	case PRINT_OP: | ||||||
| 		/*
 | 		/*
 | ||||||
| 		 * The only op for string should be ? : | 		 * The only op for string should be ? : | ||||||
|  | @ -4822,6 +4932,9 @@ static void print_args(struct print_arg *args) | ||||||
| 	case PRINT_BSTRING: | 	case PRINT_BSTRING: | ||||||
| 		printf("__get_str(%s)", args->string.string); | 		printf("__get_str(%s)", args->string.string); | ||||||
| 		break; | 		break; | ||||||
|  | 	case PRINT_BITMASK: | ||||||
|  | 		printf("__get_bitmask(%s)", args->bitmask.bitmask); | ||||||
|  | 		break; | ||||||
| 	case PRINT_TYPE: | 	case PRINT_TYPE: | ||||||
| 		printf("(%s)", args->typecast.type); | 		printf("(%s)", args->typecast.type); | ||||||
| 		print_args(args->typecast.item); | 		print_args(args->typecast.item); | ||||||
|  |  | ||||||
|  | @ -107,8 +107,8 @@ typedef int (*pevent_event_handler_func)(struct trace_seq *s, | ||||||
| typedef int (*pevent_plugin_load_func)(struct pevent *pevent); | typedef int (*pevent_plugin_load_func)(struct pevent *pevent); | ||||||
| typedef int (*pevent_plugin_unload_func)(struct pevent *pevent); | typedef int (*pevent_plugin_unload_func)(struct pevent *pevent); | ||||||
| 
 | 
 | ||||||
| struct plugin_option { | struct pevent_plugin_option { | ||||||
| 	struct plugin_option		*next; | 	struct pevent_plugin_option	*next; | ||||||
| 	void				*handle; | 	void				*handle; | ||||||
| 	char				*file; | 	char				*file; | ||||||
| 	char				*name; | 	char				*name; | ||||||
|  | @ -135,7 +135,7 @@ struct plugin_option { | ||||||
|  * PEVENT_PLUGIN_OPTIONS:  (optional) |  * PEVENT_PLUGIN_OPTIONS:  (optional) | ||||||
|  *   Plugin options that can be set before loading |  *   Plugin options that can be set before loading | ||||||
|  * |  * | ||||||
|  *   struct plugin_option PEVENT_PLUGIN_OPTIONS[] = { |  *   struct pevent_plugin_option PEVENT_PLUGIN_OPTIONS[] = { | ||||||
|  *	{ |  *	{ | ||||||
|  *		.name = "option-name", |  *		.name = "option-name", | ||||||
|  *		.plugin_alias = "overide-file-name", (optional) |  *		.plugin_alias = "overide-file-name", (optional) | ||||||
|  | @ -208,6 +208,11 @@ struct print_arg_string { | ||||||
| 	int			offset; | 	int			offset; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | struct print_arg_bitmask { | ||||||
|  | 	char			*bitmask; | ||||||
|  | 	int			offset; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| struct print_arg_field { | struct print_arg_field { | ||||||
| 	char			*name; | 	char			*name; | ||||||
| 	struct format_field	*field; | 	struct format_field	*field; | ||||||
|  | @ -274,6 +279,7 @@ enum print_arg_type { | ||||||
| 	PRINT_DYNAMIC_ARRAY, | 	PRINT_DYNAMIC_ARRAY, | ||||||
| 	PRINT_OP, | 	PRINT_OP, | ||||||
| 	PRINT_FUNC, | 	PRINT_FUNC, | ||||||
|  | 	PRINT_BITMASK, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| struct print_arg { | struct print_arg { | ||||||
|  | @ -288,6 +294,7 @@ struct print_arg { | ||||||
| 		struct print_arg_hex		hex; | 		struct print_arg_hex		hex; | ||||||
| 		struct print_arg_func		func; | 		struct print_arg_func		func; | ||||||
| 		struct print_arg_string		string; | 		struct print_arg_string		string; | ||||||
|  | 		struct print_arg_bitmask	bitmask; | ||||||
| 		struct print_arg_op		op; | 		struct print_arg_op		op; | ||||||
| 		struct print_arg_dynarray	dynarray; | 		struct print_arg_dynarray	dynarray; | ||||||
| 	}; | 	}; | ||||||
|  | @ -354,6 +361,8 @@ enum pevent_func_arg_type { | ||||||
| 
 | 
 | ||||||
| enum pevent_flag { | enum pevent_flag { | ||||||
| 	PEVENT_NSEC_OUTPUT		= 1,	/* output in NSECS */ | 	PEVENT_NSEC_OUTPUT		= 1,	/* output in NSECS */ | ||||||
|  | 	PEVENT_DISABLE_SYS_PLUGINS	= 1 << 1, | ||||||
|  | 	PEVENT_DISABLE_PLUGINS		= 1 << 2, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| #define PEVENT_ERRORS 							      \ | #define PEVENT_ERRORS 							      \ | ||||||
|  | @ -410,9 +419,19 @@ enum pevent_errno { | ||||||
| 
 | 
 | ||||||
| struct plugin_list; | struct plugin_list; | ||||||
| 
 | 
 | ||||||
|  | #define INVALID_PLUGIN_LIST_OPTION	((char **)((unsigned long)-1)) | ||||||
|  | 
 | ||||||
| struct plugin_list *traceevent_load_plugins(struct pevent *pevent); | struct plugin_list *traceevent_load_plugins(struct pevent *pevent); | ||||||
| void traceevent_unload_plugins(struct plugin_list *plugin_list, | void traceevent_unload_plugins(struct plugin_list *plugin_list, | ||||||
| 			       struct pevent *pevent); | 			       struct pevent *pevent); | ||||||
|  | char **traceevent_plugin_list_options(void); | ||||||
|  | void traceevent_plugin_free_options_list(char **list); | ||||||
|  | int traceevent_plugin_add_options(const char *name, | ||||||
|  | 				  struct pevent_plugin_option *options); | ||||||
|  | void traceevent_plugin_remove_options(struct pevent_plugin_option *options); | ||||||
|  | void traceevent_print_plugins(struct trace_seq *s, | ||||||
|  | 			      const char *prefix, const char *suffix, | ||||||
|  | 			      const struct plugin_list *list); | ||||||
| 
 | 
 | ||||||
| struct cmdline; | struct cmdline; | ||||||
| struct cmdline_list; | struct cmdline_list; | ||||||
|  |  | ||||||
|  | @ -18,6 +18,7 @@ | ||||||
|  * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ |  * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ | ||||||
|  */ |  */ | ||||||
| 
 | 
 | ||||||
|  | #include <stdio.h> | ||||||
| #include <string.h> | #include <string.h> | ||||||
| #include <dlfcn.h> | #include <dlfcn.h> | ||||||
| #include <stdlib.h> | #include <stdlib.h> | ||||||
|  | @ -30,12 +31,207 @@ | ||||||
| 
 | 
 | ||||||
| #define LOCAL_PLUGIN_DIR ".traceevent/plugins" | #define LOCAL_PLUGIN_DIR ".traceevent/plugins" | ||||||
| 
 | 
 | ||||||
|  | static struct registered_plugin_options { | ||||||
|  | 	struct registered_plugin_options	*next; | ||||||
|  | 	struct pevent_plugin_option		*options; | ||||||
|  | } *registered_options; | ||||||
|  | 
 | ||||||
|  | static struct trace_plugin_options { | ||||||
|  | 	struct trace_plugin_options	*next; | ||||||
|  | 	char				*plugin; | ||||||
|  | 	char				*option; | ||||||
|  | 	char				*value; | ||||||
|  | } *trace_plugin_options; | ||||||
|  | 
 | ||||||
| struct plugin_list { | struct plugin_list { | ||||||
| 	struct plugin_list	*next; | 	struct plugin_list	*next; | ||||||
| 	char			*name; | 	char			*name; | ||||||
| 	void			*handle; | 	void			*handle; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | /**
 | ||||||
|  |  * traceevent_plugin_list_options - get list of plugin options | ||||||
|  |  * | ||||||
|  |  * Returns an array of char strings that list the currently registered | ||||||
|  |  * plugin options in the format of <plugin>:<option>. This list can be | ||||||
|  |  * used by toggling the option. | ||||||
|  |  * | ||||||
|  |  * Returns NULL if there's no options registered. On error it returns | ||||||
|  |  * INVALID_PLUGIN_LIST_OPTION | ||||||
|  |  * | ||||||
|  |  * Must be freed with traceevent_plugin_free_options_list(). | ||||||
|  |  */ | ||||||
|  | char **traceevent_plugin_list_options(void) | ||||||
|  | { | ||||||
|  | 	struct registered_plugin_options *reg; | ||||||
|  | 	struct pevent_plugin_option *op; | ||||||
|  | 	char **list = NULL; | ||||||
|  | 	char *name; | ||||||
|  | 	int count = 0; | ||||||
|  | 
 | ||||||
|  | 	for (reg = registered_options; reg; reg = reg->next) { | ||||||
|  | 		for (op = reg->options; op->name; op++) { | ||||||
|  | 			char *alias = op->plugin_alias ? op->plugin_alias : op->file; | ||||||
|  | 			char **temp = list; | ||||||
|  | 
 | ||||||
|  | 			name = malloc(strlen(op->name) + strlen(alias) + 2); | ||||||
|  | 			if (!name) | ||||||
|  | 				goto err; | ||||||
|  | 
 | ||||||
|  | 			sprintf(name, "%s:%s", alias, op->name); | ||||||
|  | 			list = realloc(list, count + 2); | ||||||
|  | 			if (!list) { | ||||||
|  | 				list = temp; | ||||||
|  | 				free(name); | ||||||
|  | 				goto err; | ||||||
|  | 			} | ||||||
|  | 			list[count++] = name; | ||||||
|  | 			list[count] = NULL; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 	return list; | ||||||
|  | 
 | ||||||
|  |  err: | ||||||
|  | 	while (--count >= 0) | ||||||
|  | 		free(list[count]); | ||||||
|  | 	free(list); | ||||||
|  | 
 | ||||||
|  | 	return INVALID_PLUGIN_LIST_OPTION; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | void traceevent_plugin_free_options_list(char **list) | ||||||
|  | { | ||||||
|  | 	int i; | ||||||
|  | 
 | ||||||
|  | 	if (!list) | ||||||
|  | 		return; | ||||||
|  | 
 | ||||||
|  | 	if (list == INVALID_PLUGIN_LIST_OPTION) | ||||||
|  | 		return; | ||||||
|  | 
 | ||||||
|  | 	for (i = 0; list[i]; i++) | ||||||
|  | 		free(list[i]); | ||||||
|  | 
 | ||||||
|  | 	free(list); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int | ||||||
|  | update_option(const char *file, struct pevent_plugin_option *option) | ||||||
|  | { | ||||||
|  | 	struct trace_plugin_options *op; | ||||||
|  | 	char *plugin; | ||||||
|  | 
 | ||||||
|  | 	if (option->plugin_alias) { | ||||||
|  | 		plugin = strdup(option->plugin_alias); | ||||||
|  | 		if (!plugin) | ||||||
|  | 			return -1; | ||||||
|  | 	} else { | ||||||
|  | 		char *p; | ||||||
|  | 		plugin = strdup(file); | ||||||
|  | 		if (!plugin) | ||||||
|  | 			return -1; | ||||||
|  | 		p = strstr(plugin, "."); | ||||||
|  | 		if (p) | ||||||
|  | 			*p = '\0'; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/* first look for named options */ | ||||||
|  | 	for (op = trace_plugin_options; op; op = op->next) { | ||||||
|  | 		if (!op->plugin) | ||||||
|  | 			continue; | ||||||
|  | 		if (strcmp(op->plugin, plugin) != 0) | ||||||
|  | 			continue; | ||||||
|  | 		if (strcmp(op->option, option->name) != 0) | ||||||
|  | 			continue; | ||||||
|  | 
 | ||||||
|  | 		option->value = op->value; | ||||||
|  | 		option->set ^= 1; | ||||||
|  | 		goto out; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/* first look for unnamed options */ | ||||||
|  | 	for (op = trace_plugin_options; op; op = op->next) { | ||||||
|  | 		if (op->plugin) | ||||||
|  | 			continue; | ||||||
|  | 		if (strcmp(op->option, option->name) != 0) | ||||||
|  | 			continue; | ||||||
|  | 
 | ||||||
|  | 		option->value = op->value; | ||||||
|  | 		option->set ^= 1; | ||||||
|  | 		break; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  |  out: | ||||||
|  | 	free(plugin); | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * traceevent_plugin_add_options - Add a set of options by a plugin | ||||||
|  |  * @name: The name of the plugin adding the options | ||||||
|  |  * @options: The set of options being loaded | ||||||
|  |  * | ||||||
|  |  * Sets the options with the values that have been added by user. | ||||||
|  |  */ | ||||||
|  | int traceevent_plugin_add_options(const char *name, | ||||||
|  | 				  struct pevent_plugin_option *options) | ||||||
|  | { | ||||||
|  | 	struct registered_plugin_options *reg; | ||||||
|  | 
 | ||||||
|  | 	reg = malloc(sizeof(*reg)); | ||||||
|  | 	if (!reg) | ||||||
|  | 		return -1; | ||||||
|  | 	reg->next = registered_options; | ||||||
|  | 	reg->options = options; | ||||||
|  | 	registered_options = reg; | ||||||
|  | 
 | ||||||
|  | 	while (options->name) { | ||||||
|  | 		update_option(name, options); | ||||||
|  | 		options++; | ||||||
|  | 	} | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * traceevent_plugin_remove_options - remove plugin options that were registered | ||||||
|  |  * @options: Options to removed that were registered with traceevent_plugin_add_options | ||||||
|  |  */ | ||||||
|  | void traceevent_plugin_remove_options(struct pevent_plugin_option *options) | ||||||
|  | { | ||||||
|  | 	struct registered_plugin_options **last; | ||||||
|  | 	struct registered_plugin_options *reg; | ||||||
|  | 
 | ||||||
|  | 	for (last = ®istered_options; *last; last = &(*last)->next) { | ||||||
|  | 		if ((*last)->options == options) { | ||||||
|  | 			reg = *last; | ||||||
|  | 			*last = reg->next; | ||||||
|  | 			free(reg); | ||||||
|  | 			return; | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * traceevent_print_plugins - print out the list of plugins loaded | ||||||
|  |  * @s: the trace_seq descripter to write to | ||||||
|  |  * @prefix: The prefix string to add before listing the option name | ||||||
|  |  * @suffix: The suffix string ot append after the option name | ||||||
|  |  * @list: The list of plugins (usually returned by traceevent_load_plugins() | ||||||
|  |  * | ||||||
|  |  * Writes to the trace_seq @s the list of plugins (files) that is | ||||||
|  |  * returned by traceevent_load_plugins(). Use @prefix and @suffix for formating: | ||||||
|  |  * @prefix = "  ", @suffix = "\n". | ||||||
|  |  */ | ||||||
|  | void traceevent_print_plugins(struct trace_seq *s, | ||||||
|  | 			      const char *prefix, const char *suffix, | ||||||
|  | 			      const struct plugin_list *list) | ||||||
|  | { | ||||||
|  | 	while (list) { | ||||||
|  | 		trace_seq_printf(s, "%s%s%s", prefix, list->name, suffix); | ||||||
|  | 		list = list->next; | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
| static void | static void | ||||||
| load_plugin(struct pevent *pevent, const char *path, | load_plugin(struct pevent *pevent, const char *path, | ||||||
| 	    const char *file, void *data) | 	    const char *file, void *data) | ||||||
|  | @ -148,12 +344,17 @@ load_plugins(struct pevent *pevent, const char *suffix, | ||||||
| 	char *path; | 	char *path; | ||||||
| 	char *envdir; | 	char *envdir; | ||||||
| 
 | 
 | ||||||
|  | 	if (pevent->flags & PEVENT_DISABLE_PLUGINS) | ||||||
|  | 		return; | ||||||
|  | 
 | ||||||
| 	/*
 | 	/*
 | ||||||
| 	 * If a system plugin directory was defined, | 	 * If a system plugin directory was defined, | ||||||
| 	 * check that first. | 	 * check that first. | ||||||
| 	 */ | 	 */ | ||||||
| #ifdef PLUGIN_DIR | #ifdef PLUGIN_DIR | ||||||
| 	load_plugins_dir(pevent, suffix, PLUGIN_DIR, load_plugin, data); | 	if (!(pevent->flags & PEVENT_DISABLE_SYS_PLUGINS)) | ||||||
|  | 		load_plugins_dir(pevent, suffix, PLUGIN_DIR, | ||||||
|  | 				 load_plugin, data); | ||||||
| #endif | #endif | ||||||
| 
 | 
 | ||||||
| 	/*
 | 	/*
 | ||||||
|  |  | ||||||
|  | @ -33,6 +33,29 @@ static int cpus = -1; | ||||||
| 
 | 
 | ||||||
| #define STK_BLK 10 | #define STK_BLK 10 | ||||||
| 
 | 
 | ||||||
|  | struct pevent_plugin_option plugin_options[] = | ||||||
|  | { | ||||||
|  | 	{ | ||||||
|  | 		.name = "parent", | ||||||
|  | 		.plugin_alias = "ftrace", | ||||||
|  | 		.description = | ||||||
|  | 		"Print parent of functions for function events", | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		.name = "indent", | ||||||
|  | 		.plugin_alias = "ftrace", | ||||||
|  | 		.description = | ||||||
|  | 		"Try to show function call indents, based on parents", | ||||||
|  | 		.set = 1, | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		.name = NULL, | ||||||
|  | 	} | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | static struct pevent_plugin_option *ftrace_parent = &plugin_options[0]; | ||||||
|  | static struct pevent_plugin_option *ftrace_indent = &plugin_options[1]; | ||||||
|  | 
 | ||||||
| static void add_child(struct func_stack *stack, const char *child, int pos) | static void add_child(struct func_stack *stack, const char *child, int pos) | ||||||
| { | { | ||||||
| 	int i; | 	int i; | ||||||
|  | @ -119,6 +142,7 @@ static int function_handler(struct trace_seq *s, struct pevent_record *record, | ||||||
| 
 | 
 | ||||||
| 	parent = pevent_find_function(pevent, pfunction); | 	parent = pevent_find_function(pevent, pfunction); | ||||||
| 
 | 
 | ||||||
|  | 	if (parent && ftrace_indent->set) | ||||||
| 		index = add_and_get_index(parent, func, record->cpu); | 		index = add_and_get_index(parent, func, record->cpu); | ||||||
| 
 | 
 | ||||||
| 	trace_seq_printf(s, "%*s", index*3, ""); | 	trace_seq_printf(s, "%*s", index*3, ""); | ||||||
|  | @ -128,11 +152,13 @@ static int function_handler(struct trace_seq *s, struct pevent_record *record, | ||||||
| 	else | 	else | ||||||
| 		trace_seq_printf(s, "0x%llx", function); | 		trace_seq_printf(s, "0x%llx", function); | ||||||
| 
 | 
 | ||||||
|  | 	if (ftrace_parent->set) { | ||||||
| 		trace_seq_printf(s, " <-- "); | 		trace_seq_printf(s, " <-- "); | ||||||
| 		if (parent) | 		if (parent) | ||||||
| 			trace_seq_printf(s, "%s", parent); | 			trace_seq_printf(s, "%s", parent); | ||||||
| 		else | 		else | ||||||
| 			trace_seq_printf(s, "0x%llx", pfunction); | 			trace_seq_printf(s, "0x%llx", pfunction); | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	return 0; | 	return 0; | ||||||
| } | } | ||||||
|  | @ -141,6 +167,9 @@ int PEVENT_PLUGIN_LOADER(struct pevent *pevent) | ||||||
| { | { | ||||||
| 	pevent_register_event_handler(pevent, -1, "ftrace", "function", | 	pevent_register_event_handler(pevent, -1, "ftrace", "function", | ||||||
| 				      function_handler, NULL); | 				      function_handler, NULL); | ||||||
|  | 
 | ||||||
|  | 	traceevent_plugin_add_options("ftrace", plugin_options); | ||||||
|  | 
 | ||||||
| 	return 0; | 	return 0; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -157,6 +186,8 @@ void PEVENT_PLUGIN_UNLOADER(struct pevent *pevent) | ||||||
| 		free(fstack[i].stack); | 		free(fstack[i].stack); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | 	traceevent_plugin_remove_options(plugin_options); | ||||||
|  | 
 | ||||||
| 	free(fstack); | 	free(fstack); | ||||||
| 	fstack = NULL; | 	fstack = NULL; | ||||||
| 	cpus = -1; | 	cpus = -1; | ||||||
|  |  | ||||||
|  | @ -117,6 +117,22 @@ OPTIONS | ||||||
| 	By default, every sort keys not specified in -F will be appended | 	By default, every sort keys not specified in -F will be appended | ||||||
| 	automatically. | 	automatically. | ||||||
| 
 | 
 | ||||||
|  | 	If --mem-mode option is used, following sort keys are also available | ||||||
|  | 	(incompatible with --branch-stack): | ||||||
|  | 	symbol_daddr, dso_daddr, locked, tlb, mem, snoop, dcacheline. | ||||||
|  | 
 | ||||||
|  | 	- symbol_daddr: name of data symbol being executed on at the time of sample | ||||||
|  | 	- dso_daddr: name of library or module containing the data being executed | ||||||
|  | 	on at the time of sample | ||||||
|  | 	- locked: whether the bus was locked at the time of sample | ||||||
|  | 	- tlb: type of tlb access for the data at the time of sample | ||||||
|  | 	- mem: type of memory access for the data at the time of sample | ||||||
|  | 	- snoop: type of snoop (if any) for the data at the time of sample | ||||||
|  | 	- dcacheline: the cacheline the data address is on at the time of sample | ||||||
|  | 
 | ||||||
|  | 	And default sort keys are changed to local_weight, mem, sym, dso, | ||||||
|  | 	symbol_daddr, dso_daddr, snoop, tlb, locked, see '--mem-mode'. | ||||||
|  | 
 | ||||||
| -p:: | -p:: | ||||||
| --parent=<regex>:: | --parent=<regex>:: | ||||||
|         A regex filter to identify parent. The parent is a caller of this |         A regex filter to identify parent. The parent is a caller of this | ||||||
|  | @ -260,6 +276,13 @@ OPTIONS | ||||||
| 	Demangle symbol names to human readable form. It's enabled by default, | 	Demangle symbol names to human readable form. It's enabled by default, | ||||||
| 	disable with --no-demangle. | 	disable with --no-demangle. | ||||||
| 
 | 
 | ||||||
|  | --mem-mode:: | ||||||
|  | 	Use the data addresses of samples in addition to instruction addresses | ||||||
|  | 	to build the histograms.  To generate meaningful output, the perf.data | ||||||
|  | 	file must have been obtained using perf record -d -W and using a | ||||||
|  | 	special event -e cpu/mem-loads/ or -e cpu/mem-stores/. See | ||||||
|  | 	'perf mem' for simpler access. | ||||||
|  | 
 | ||||||
| --percent-limit:: | --percent-limit:: | ||||||
| 	Do not show entries which have an overhead under that percent. | 	Do not show entries which have an overhead under that percent. | ||||||
| 	(Default: 0). | 	(Default: 0). | ||||||
|  |  | ||||||
|  | @ -43,27 +43,6 @@ TIMECHART OPTIONS | ||||||
| 
 | 
 | ||||||
| --symfs=<directory>:: | --symfs=<directory>:: | ||||||
|         Look for files with symbols relative to this directory. |         Look for files with symbols relative to this directory. | ||||||
| 
 |  | ||||||
| EXAMPLES |  | ||||||
| -------- |  | ||||||
| 
 |  | ||||||
| $ perf timechart record git pull |  | ||||||
| 
 |  | ||||||
|   [ perf record: Woken up 13 times to write data ] |  | ||||||
|   [ perf record: Captured and wrote 4.253 MB perf.data (~185801 samples) ] |  | ||||||
| 
 |  | ||||||
| $ perf timechart |  | ||||||
| 
 |  | ||||||
|   Written 10.2 seconds of trace to output.svg. |  | ||||||
| 
 |  | ||||||
| Record system-wide timechart: |  | ||||||
| 
 |  | ||||||
|   $ perf timechart record |  | ||||||
| 
 |  | ||||||
|   then generate timechart and highlight 'gcc' tasks: |  | ||||||
| 
 |  | ||||||
|   $ perf timechart --highlight gcc |  | ||||||
| 
 |  | ||||||
| -n:: | -n:: | ||||||
| --proc-num:: | --proc-num:: | ||||||
|         Print task info for at least given number of tasks. |         Print task info for at least given number of tasks. | ||||||
|  | @ -88,6 +67,26 @@ RECORD OPTIONS | ||||||
| --callchain:: | --callchain:: | ||||||
|         Do call-graph (stack chain/backtrace) recording |         Do call-graph (stack chain/backtrace) recording | ||||||
| 
 | 
 | ||||||
|  | EXAMPLES | ||||||
|  | -------- | ||||||
|  | 
 | ||||||
|  | $ perf timechart record git pull | ||||||
|  | 
 | ||||||
|  |   [ perf record: Woken up 13 times to write data ] | ||||||
|  |   [ perf record: Captured and wrote 4.253 MB perf.data (~185801 samples) ] | ||||||
|  | 
 | ||||||
|  | $ perf timechart | ||||||
|  | 
 | ||||||
|  |   Written 10.2 seconds of trace to output.svg. | ||||||
|  | 
 | ||||||
|  | Record system-wide timechart: | ||||||
|  | 
 | ||||||
|  |   $ perf timechart record | ||||||
|  | 
 | ||||||
|  |   then generate timechart and highlight 'gcc' tasks: | ||||||
|  | 
 | ||||||
|  |   $ perf timechart --highlight gcc | ||||||
|  | 
 | ||||||
| SEE ALSO | SEE ALSO | ||||||
| -------- | -------- | ||||||
| linkperf:perf-record[1] | linkperf:perf-record[1] | ||||||
|  |  | ||||||
|  | @ -819,15 +819,15 @@ TAG_FOLDERS= . ../lib/traceevent ../lib/api ../lib/symbol | ||||||
| TAG_FILES= ../../include/uapi/linux/perf_event.h | TAG_FILES= ../../include/uapi/linux/perf_event.h | ||||||
| 
 | 
 | ||||||
| TAGS: | TAGS: | ||||||
| 	$(RM) TAGS | 	$(QUIET_GEN)$(RM) TAGS; \ | ||||||
| 	$(FIND) $(TAG_FOLDERS) -name '*.[hcS]' -print | xargs etags -a $(TAG_FILES) | 	$(FIND) $(TAG_FOLDERS) -name '*.[hcS]' -print | xargs etags -a $(TAG_FILES) | ||||||
| 
 | 
 | ||||||
| tags: | tags: | ||||||
| 	$(RM) tags | 	$(QUIET_GEN)$(RM) tags; \ | ||||||
| 	$(FIND) $(TAG_FOLDERS) -name '*.[hcS]' -print | xargs ctags -a $(TAG_FILES) | 	$(FIND) $(TAG_FOLDERS) -name '*.[hcS]' -print | xargs ctags -a $(TAG_FILES) | ||||||
| 
 | 
 | ||||||
| cscope: | cscope: | ||||||
| 	$(RM) cscope* | 	$(QUIET_GEN)$(RM) cscope*; \ | ||||||
| 	$(FIND) $(TAG_FOLDERS) -name '*.[hcS]' -print | xargs cscope -b $(TAG_FILES) | 	$(FIND) $(TAG_FOLDERS) -name '*.[hcS]' -print | xargs cscope -b $(TAG_FILES) | ||||||
| 
 | 
 | ||||||
| ### Detect prefix changes | ### Detect prefix changes | ||||||
|  |  | ||||||
|  | @ -72,7 +72,7 @@ static int perf_event__repipe_attr(struct perf_tool *tool, | ||||||
| 	if (ret) | 	if (ret) | ||||||
| 		return ret; | 		return ret; | ||||||
| 
 | 
 | ||||||
| 	if (&inject->output.is_pipe) | 	if (!inject->output.is_pipe) | ||||||
| 		return 0; | 		return 0; | ||||||
| 
 | 
 | ||||||
| 	return perf_event__repipe_synth(tool, event); | 	return perf_event__repipe_synth(tool, event); | ||||||
|  |  | ||||||
|  | @ -288,6 +288,13 @@ static void cleanup_params(void) | ||||||
| 	memset(¶ms, 0, sizeof(params)); | 	memset(¶ms, 0, sizeof(params)); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static void pr_err_with_code(const char *msg, int err) | ||||||
|  | { | ||||||
|  | 	pr_err("%s", msg); | ||||||
|  | 	pr_debug(" Reason: %s (Code: %d)", strerror(-err), err); | ||||||
|  | 	pr_err("\n"); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| static int | static int | ||||||
| __cmd_probe(int argc, const char **argv, const char *prefix __maybe_unused) | __cmd_probe(int argc, const char **argv, const char *prefix __maybe_unused) | ||||||
| { | { | ||||||
|  | @ -379,7 +386,7 @@ __cmd_probe(int argc, const char **argv, const char *prefix __maybe_unused) | ||||||
| 		} | 		} | ||||||
| 		ret = parse_probe_event_argv(argc, argv); | 		ret = parse_probe_event_argv(argc, argv); | ||||||
| 		if (ret < 0) { | 		if (ret < 0) { | ||||||
| 			pr_err("  Error: Parse Error.  (%d)\n", ret); | 			pr_err_with_code("  Error: Command Parse Error.", ret); | ||||||
| 			return ret; | 			return ret; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | @ -419,8 +426,7 @@ __cmd_probe(int argc, const char **argv, const char *prefix __maybe_unused) | ||||||
| 		} | 		} | ||||||
| 		ret = show_perf_probe_events(); | 		ret = show_perf_probe_events(); | ||||||
| 		if (ret < 0) | 		if (ret < 0) | ||||||
| 			pr_err("  Error: Failed to show event list. (%d)\n", | 			pr_err_with_code("  Error: Failed to show event list.", ret); | ||||||
| 			       ret); |  | ||||||
| 		return ret; | 		return ret; | ||||||
| 	} | 	} | ||||||
| 	if (params.show_funcs) { | 	if (params.show_funcs) { | ||||||
|  | @ -445,8 +451,7 @@ __cmd_probe(int argc, const char **argv, const char *prefix __maybe_unused) | ||||||
| 		strfilter__delete(params.filter); | 		strfilter__delete(params.filter); | ||||||
| 		params.filter = NULL; | 		params.filter = NULL; | ||||||
| 		if (ret < 0) | 		if (ret < 0) | ||||||
| 			pr_err("  Error: Failed to show functions." | 			pr_err_with_code("  Error: Failed to show functions.", ret); | ||||||
| 			       " (%d)\n", ret); |  | ||||||
| 		return ret; | 		return ret; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -464,7 +469,7 @@ __cmd_probe(int argc, const char **argv, const char *prefix __maybe_unused) | ||||||
| 
 | 
 | ||||||
| 		ret = show_line_range(¶ms.line_range, params.target); | 		ret = show_line_range(¶ms.line_range, params.target); | ||||||
| 		if (ret < 0) | 		if (ret < 0) | ||||||
| 			pr_err("  Error: Failed to show lines. (%d)\n", ret); | 			pr_err_with_code("  Error: Failed to show lines.", ret); | ||||||
| 		return ret; | 		return ret; | ||||||
| 	} | 	} | ||||||
| 	if (params.show_vars) { | 	if (params.show_vars) { | ||||||
|  | @ -485,7 +490,7 @@ __cmd_probe(int argc, const char **argv, const char *prefix __maybe_unused) | ||||||
| 		strfilter__delete(params.filter); | 		strfilter__delete(params.filter); | ||||||
| 		params.filter = NULL; | 		params.filter = NULL; | ||||||
| 		if (ret < 0) | 		if (ret < 0) | ||||||
| 			pr_err("  Error: Failed to show vars. (%d)\n", ret); | 			pr_err_with_code("  Error: Failed to show vars.", ret); | ||||||
| 		return ret; | 		return ret; | ||||||
| 	} | 	} | ||||||
| #endif | #endif | ||||||
|  | @ -493,7 +498,7 @@ __cmd_probe(int argc, const char **argv, const char *prefix __maybe_unused) | ||||||
| 	if (params.dellist) { | 	if (params.dellist) { | ||||||
| 		ret = del_perf_probe_events(params.dellist); | 		ret = del_perf_probe_events(params.dellist); | ||||||
| 		if (ret < 0) { | 		if (ret < 0) { | ||||||
| 			pr_err("  Error: Failed to delete events. (%d)\n", ret); | 			pr_err_with_code("  Error: Failed to delete events.", ret); | ||||||
| 			return ret; | 			return ret; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  | @ -504,7 +509,7 @@ __cmd_probe(int argc, const char **argv, const char *prefix __maybe_unused) | ||||||
| 					    params.target, | 					    params.target, | ||||||
| 					    params.force_add); | 					    params.force_add); | ||||||
| 		if (ret < 0) { | 		if (ret < 0) { | ||||||
| 			pr_err("  Error: Failed to add events. (%d)\n", ret); | 			pr_err_with_code("  Error: Failed to add events.", ret); | ||||||
| 			return ret; | 			return ret; | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
|  |  | ||||||
|  | @ -299,7 +299,11 @@ else | ||||||
|       NO_LIBUNWIND := 1 |       NO_LIBUNWIND := 1 | ||||||
|       NO_LIBDW_DWARF_UNWIND := 1 |       NO_LIBDW_DWARF_UNWIND := 1 | ||||||
|     else |     else | ||||||
|       msg := $(error No gnu/libc-version.h found, please install glibc-dev[el]/glibc-static); |       ifneq ($(filter s% -static%,$(LDFLAGS),),) | ||||||
|  |         msg := $(error No static glibc found, please install glibc-static); | ||||||
|  |       else | ||||||
|  |         msg := $(error No gnu/libc-version.h found, please install glibc-dev[el]); | ||||||
|  |       endif | ||||||
|     endif |     endif | ||||||
|   else |   else | ||||||
|     ifndef NO_LIBDW_DWARF_UNWIND |     ifndef NO_LIBDW_DWARF_UNWIND | ||||||
|  |  | ||||||
|  | @ -458,6 +458,7 @@ int main(int argc, const char **argv) | ||||||
| 
 | 
 | ||||||
| 	/* The page_size is placed in util object. */ | 	/* The page_size is placed in util object. */ | ||||||
| 	page_size = sysconf(_SC_PAGE_SIZE); | 	page_size = sysconf(_SC_PAGE_SIZE); | ||||||
|  | 	cacheline_size = sysconf(_SC_LEVEL1_DCACHE_LINESIZE); | ||||||
| 
 | 
 | ||||||
| 	cmd = perf_extract_argv0_path(argv[0]); | 	cmd = perf_extract_argv0_path(argv[0]); | ||||||
| 	if (!cmd) | 	if (!cmd) | ||||||
|  |  | ||||||
|  | @ -3,6 +3,8 @@ | ||||||
|  * |  * | ||||||
|  * Builtin regression testing command: ever growing number of sanity tests |  * Builtin regression testing command: ever growing number of sanity tests | ||||||
|  */ |  */ | ||||||
|  | #include <unistd.h> | ||||||
|  | #include <string.h> | ||||||
| #include "builtin.h" | #include "builtin.h" | ||||||
| #include "intlist.h" | #include "intlist.h" | ||||||
| #include "tests.h" | #include "tests.h" | ||||||
|  | @ -50,9 +52,17 @@ static struct test { | ||||||
| 		.func = test__pmu, | 		.func = test__pmu, | ||||||
| 	}, | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		.desc = "Test dso data interface", | 		.desc = "Test dso data read", | ||||||
| 		.func = test__dso_data, | 		.func = test__dso_data, | ||||||
| 	}, | 	}, | ||||||
|  | 	{ | ||||||
|  | 		.desc = "Test dso data cache", | ||||||
|  | 		.func = test__dso_data_cache, | ||||||
|  | 	}, | ||||||
|  | 	{ | ||||||
|  | 		.desc = "Test dso data reopen", | ||||||
|  | 		.func = test__dso_data_reopen, | ||||||
|  | 	}, | ||||||
| 	{ | 	{ | ||||||
| 		.desc = "roundtrip evsel->name check", | 		.desc = "roundtrip evsel->name check", | ||||||
| 		.func = test__perf_evsel__roundtrip_name_test, | 		.func = test__perf_evsel__roundtrip_name_test, | ||||||
|  | @ -172,6 +182,34 @@ static bool perf_test__matches(int curr, int argc, const char *argv[]) | ||||||
| 	return false; | 	return false; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static int run_test(struct test *test) | ||||||
|  | { | ||||||
|  | 	int status, err = -1, child = fork(); | ||||||
|  | 
 | ||||||
|  | 	if (child < 0) { | ||||||
|  | 		pr_err("failed to fork test: %s\n", strerror(errno)); | ||||||
|  | 		return -1; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	if (!child) { | ||||||
|  | 		pr_debug("test child forked, pid %d\n", getpid()); | ||||||
|  | 		err = test->func(); | ||||||
|  | 		exit(err); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	wait(&status); | ||||||
|  | 
 | ||||||
|  | 	if (WIFEXITED(status)) { | ||||||
|  | 		err = WEXITSTATUS(status); | ||||||
|  | 		pr_debug("test child finished with %d\n", err); | ||||||
|  | 	} else if (WIFSIGNALED(status)) { | ||||||
|  | 		err = -1; | ||||||
|  | 		pr_debug("test child interrupted\n"); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return err; | ||||||
|  | } | ||||||
|  | 
 | ||||||
| static int __cmd_test(int argc, const char *argv[], struct intlist *skiplist) | static int __cmd_test(int argc, const char *argv[], struct intlist *skiplist) | ||||||
| { | { | ||||||
| 	int i = 0; | 	int i = 0; | ||||||
|  | @ -200,7 +238,7 @@ static int __cmd_test(int argc, const char *argv[], struct intlist *skiplist) | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		pr_debug("\n--- start ---\n"); | 		pr_debug("\n--- start ---\n"); | ||||||
| 		err = tests[curr].func(); | 		err = run_test(&tests[curr]); | ||||||
| 		pr_debug("---- end ----\n%s:", tests[curr].desc); | 		pr_debug("---- end ----\n%s:", tests[curr].desc); | ||||||
| 
 | 
 | ||||||
| 		switch (err) { | 		switch (err) { | ||||||
|  |  | ||||||
|  | @ -1,22 +1,27 @@ | ||||||
| #include "util.h" |  | ||||||
| 
 |  | ||||||
| #include <stdlib.h> | #include <stdlib.h> | ||||||
| #include <linux/types.h> | #include <linux/types.h> | ||||||
| #include <sys/stat.h> | #include <sys/stat.h> | ||||||
| #include <fcntl.h> | #include <fcntl.h> | ||||||
| #include <string.h> | #include <string.h> | ||||||
| 
 | #include <sys/time.h> | ||||||
|  | #include <sys/resource.h> | ||||||
|  | #include <api/fs/fs.h> | ||||||
|  | #include "util.h" | ||||||
| #include "machine.h" | #include "machine.h" | ||||||
| #include "symbol.h" | #include "symbol.h" | ||||||
| #include "tests.h" | #include "tests.h" | ||||||
| 
 | 
 | ||||||
| static char *test_file(int size) | static char *test_file(int size) | ||||||
| { | { | ||||||
| 	static char buf_templ[] = "/tmp/test-XXXXXX"; | #define TEMPL "/tmp/perf-test-XXXXXX" | ||||||
|  | 	static char buf_templ[sizeof(TEMPL)]; | ||||||
| 	char *templ = buf_templ; | 	char *templ = buf_templ; | ||||||
| 	int fd, i; | 	int fd, i; | ||||||
| 	unsigned char *buf; | 	unsigned char *buf; | ||||||
| 
 | 
 | ||||||
|  | 	strcpy(buf_templ, TEMPL); | ||||||
|  | #undef TEMPL | ||||||
|  | 
 | ||||||
| 	fd = mkstemp(templ); | 	fd = mkstemp(templ); | ||||||
| 	if (fd < 0) { | 	if (fd < 0) { | ||||||
| 		perror("mkstemp failed"); | 		perror("mkstemp failed"); | ||||||
|  | @ -150,3 +155,204 @@ int test__dso_data(void) | ||||||
| 	unlink(file); | 	unlink(file); | ||||||
| 	return 0; | 	return 0; | ||||||
| } | } | ||||||
|  | 
 | ||||||
|  | static long open_files_cnt(void) | ||||||
|  | { | ||||||
|  | 	char path[PATH_MAX]; | ||||||
|  | 	struct dirent *dent; | ||||||
|  | 	DIR *dir; | ||||||
|  | 	long nr = 0; | ||||||
|  | 
 | ||||||
|  | 	scnprintf(path, PATH_MAX, "%s/self/fd", procfs__mountpoint()); | ||||||
|  | 	pr_debug("fd path: %s\n", path); | ||||||
|  | 
 | ||||||
|  | 	dir = opendir(path); | ||||||
|  | 	TEST_ASSERT_VAL("failed to open fd directory", dir); | ||||||
|  | 
 | ||||||
|  | 	while ((dent = readdir(dir)) != NULL) { | ||||||
|  | 		if (!strcmp(dent->d_name, ".") || | ||||||
|  | 		    !strcmp(dent->d_name, "..")) | ||||||
|  | 			continue; | ||||||
|  | 
 | ||||||
|  | 		nr++; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	closedir(dir); | ||||||
|  | 	return nr - 1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static struct dso **dsos; | ||||||
|  | 
 | ||||||
|  | static int dsos__create(int cnt, int size) | ||||||
|  | { | ||||||
|  | 	int i; | ||||||
|  | 
 | ||||||
|  | 	dsos = malloc(sizeof(dsos) * cnt); | ||||||
|  | 	TEST_ASSERT_VAL("failed to alloc dsos array", dsos); | ||||||
|  | 
 | ||||||
|  | 	for (i = 0; i < cnt; i++) { | ||||||
|  | 		char *file; | ||||||
|  | 
 | ||||||
|  | 		file = test_file(size); | ||||||
|  | 		TEST_ASSERT_VAL("failed to get dso file", file); | ||||||
|  | 
 | ||||||
|  | 		dsos[i] = dso__new(file); | ||||||
|  | 		TEST_ASSERT_VAL("failed to get dso", dsos[i]); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void dsos__delete(int cnt) | ||||||
|  | { | ||||||
|  | 	int i; | ||||||
|  | 
 | ||||||
|  | 	for (i = 0; i < cnt; i++) { | ||||||
|  | 		struct dso *dso = dsos[i]; | ||||||
|  | 
 | ||||||
|  | 		unlink(dso->name); | ||||||
|  | 		dso__delete(dso); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	free(dsos); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int set_fd_limit(int n) | ||||||
|  | { | ||||||
|  | 	struct rlimit rlim; | ||||||
|  | 
 | ||||||
|  | 	if (getrlimit(RLIMIT_NOFILE, &rlim)) | ||||||
|  | 		return -1; | ||||||
|  | 
 | ||||||
|  | 	pr_debug("file limit %ld, new %d\n", (long) rlim.rlim_cur, n); | ||||||
|  | 
 | ||||||
|  | 	rlim.rlim_cur = n; | ||||||
|  | 	return setrlimit(RLIMIT_NOFILE, &rlim); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int test__dso_data_cache(void) | ||||||
|  | { | ||||||
|  | 	struct machine machine; | ||||||
|  | 	long nr_end, nr = open_files_cnt(); | ||||||
|  | 	int dso_cnt, limit, i, fd; | ||||||
|  | 
 | ||||||
|  | 	memset(&machine, 0, sizeof(machine)); | ||||||
|  | 
 | ||||||
|  | 	/* set as system limit */ | ||||||
|  | 	limit = nr * 4; | ||||||
|  | 	TEST_ASSERT_VAL("failed to set file limit", !set_fd_limit(limit)); | ||||||
|  | 
 | ||||||
|  | 	/* and this is now our dso open FDs limit + 1 extra */ | ||||||
|  | 	dso_cnt = limit / 2 + 1; | ||||||
|  | 	TEST_ASSERT_VAL("failed to create dsos\n", | ||||||
|  | 		!dsos__create(dso_cnt, TEST_FILE_SIZE)); | ||||||
|  | 
 | ||||||
|  | 	for (i = 0; i < (dso_cnt - 1); i++) { | ||||||
|  | 		struct dso *dso = dsos[i]; | ||||||
|  | 
 | ||||||
|  | 		/*
 | ||||||
|  | 		 * Open dsos via dso__data_fd or dso__data_read_offset. | ||||||
|  | 		 * Both opens the data file and keep it open. | ||||||
|  | 		 */ | ||||||
|  | 		if (i % 2) { | ||||||
|  | 			fd = dso__data_fd(dso, &machine); | ||||||
|  | 			TEST_ASSERT_VAL("failed to get fd", fd > 0); | ||||||
|  | 		} else { | ||||||
|  | 			#define BUFSIZE 10 | ||||||
|  | 			u8 buf[BUFSIZE]; | ||||||
|  | 			ssize_t n; | ||||||
|  | 
 | ||||||
|  | 			n = dso__data_read_offset(dso, &machine, 0, buf, BUFSIZE); | ||||||
|  | 			TEST_ASSERT_VAL("failed to read dso", n == BUFSIZE); | ||||||
|  | 		} | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	/* open +1 dso over the allowed limit */ | ||||||
|  | 	fd = dso__data_fd(dsos[i], &machine); | ||||||
|  | 	TEST_ASSERT_VAL("failed to get fd", fd > 0); | ||||||
|  | 
 | ||||||
|  | 	/* should force the first one to be closed */ | ||||||
|  | 	TEST_ASSERT_VAL("failed to close dsos[0]", dsos[0]->data.fd == -1); | ||||||
|  | 
 | ||||||
|  | 	/* cleanup everything */ | ||||||
|  | 	dsos__delete(dso_cnt); | ||||||
|  | 
 | ||||||
|  | 	/* Make sure we did not leak any file descriptor. */ | ||||||
|  | 	nr_end = open_files_cnt(); | ||||||
|  | 	pr_debug("nr start %ld, nr stop %ld\n", nr, nr_end); | ||||||
|  | 	TEST_ASSERT_VAL("failed leadking files", nr == nr_end); | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | int test__dso_data_reopen(void) | ||||||
|  | { | ||||||
|  | 	struct machine machine; | ||||||
|  | 	long nr_end, nr = open_files_cnt(); | ||||||
|  | 	int fd, fd_extra; | ||||||
|  | 
 | ||||||
|  | #define dso_0 (dsos[0]) | ||||||
|  | #define dso_1 (dsos[1]) | ||||||
|  | #define dso_2 (dsos[2]) | ||||||
|  | 
 | ||||||
|  | 	memset(&machine, 0, sizeof(machine)); | ||||||
|  | 
 | ||||||
|  | 	/*
 | ||||||
|  | 	 * Test scenario: | ||||||
|  | 	 * - create 3 dso objects | ||||||
|  | 	 * - set process file descriptor limit to current | ||||||
|  | 	 *   files count + 3 | ||||||
|  | 	 * - test that the first dso gets closed when we | ||||||
|  | 	 *   reach the files count limit | ||||||
|  | 	 */ | ||||||
|  | 
 | ||||||
|  | 	/* Make sure we are able to open 3 fds anyway */ | ||||||
|  | 	TEST_ASSERT_VAL("failed to set file limit", | ||||||
|  | 			!set_fd_limit((nr + 3))); | ||||||
|  | 
 | ||||||
|  | 	TEST_ASSERT_VAL("failed to create dsos\n", !dsos__create(3, TEST_FILE_SIZE)); | ||||||
|  | 
 | ||||||
|  | 	/* open dso_0 */ | ||||||
|  | 	fd = dso__data_fd(dso_0, &machine); | ||||||
|  | 	TEST_ASSERT_VAL("failed to get fd", fd > 0); | ||||||
|  | 
 | ||||||
|  | 	/* open dso_1 */ | ||||||
|  | 	fd = dso__data_fd(dso_1, &machine); | ||||||
|  | 	TEST_ASSERT_VAL("failed to get fd", fd > 0); | ||||||
|  | 
 | ||||||
|  | 	/*
 | ||||||
|  | 	 * open extra file descriptor and we just | ||||||
|  | 	 * reached the files count limit | ||||||
|  | 	 */ | ||||||
|  | 	fd_extra = open("/dev/null", O_RDONLY); | ||||||
|  | 	TEST_ASSERT_VAL("failed to open extra fd", fd_extra > 0); | ||||||
|  | 
 | ||||||
|  | 	/* open dso_2 */ | ||||||
|  | 	fd = dso__data_fd(dso_2, &machine); | ||||||
|  | 	TEST_ASSERT_VAL("failed to get fd", fd > 0); | ||||||
|  | 
 | ||||||
|  | 	/*
 | ||||||
|  | 	 * dso_0 should get closed, because we reached | ||||||
|  | 	 * the file descriptor limit | ||||||
|  | 	 */ | ||||||
|  | 	TEST_ASSERT_VAL("failed to close dso_0", dso_0->data.fd == -1); | ||||||
|  | 
 | ||||||
|  | 	/* open dso_0 */ | ||||||
|  | 	fd = dso__data_fd(dso_0, &machine); | ||||||
|  | 	TEST_ASSERT_VAL("failed to get fd", fd > 0); | ||||||
|  | 
 | ||||||
|  | 	/*
 | ||||||
|  | 	 * dso_1 should get closed, because we reached | ||||||
|  | 	 * the file descriptor limit | ||||||
|  | 	 */ | ||||||
|  | 	TEST_ASSERT_VAL("failed to close dso_1", dso_1->data.fd == -1); | ||||||
|  | 
 | ||||||
|  | 	/* cleanup everything */ | ||||||
|  | 	close(fd_extra); | ||||||
|  | 	dsos__delete(3); | ||||||
|  | 
 | ||||||
|  | 	/* Make sure we did not leak any file descriptor. */ | ||||||
|  | 	nr_end = open_files_cnt(); | ||||||
|  | 	pr_debug("nr start %ld, nr stop %ld\n", nr, nr_end); | ||||||
|  | 	TEST_ASSERT_VAL("failed leadking files", nr == nr_end); | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  |  | ||||||
|  | @ -15,7 +15,7 @@ static int mmap_handler(struct perf_tool *tool __maybe_unused, | ||||||
| 			struct perf_sample *sample __maybe_unused, | 			struct perf_sample *sample __maybe_unused, | ||||||
| 			struct machine *machine) | 			struct machine *machine) | ||||||
| { | { | ||||||
| 	return machine__process_mmap_event(machine, event, NULL); | 	return machine__process_mmap2_event(machine, event, NULL); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static int init_live_machine(struct machine *machine) | static int init_live_machine(struct machine *machine) | ||||||
|  |  | ||||||
|  | @ -205,8 +205,7 @@ $(run): | ||||||
| 	( eval $$cmd ) >> $@ 2>&1; \ | 	( eval $$cmd ) >> $@ 2>&1; \ | ||||||
| 	echo "  test: $(call test,$@)" >> $@ 2>&1; \ | 	echo "  test: $(call test,$@)" >> $@ 2>&1; \ | ||||||
| 	$(call test,$@) && \ | 	$(call test,$@) && \ | ||||||
| 	rm -f $@ \ | 	rm -rf $@ $$TMP_DEST || (cat $@ ; false) | ||||||
| 	rm -rf $$TMP_DEST |  | ||||||
| 
 | 
 | ||||||
| $(run_O): | $(run_O): | ||||||
| 	$(call clean) | 	$(call clean) | ||||||
|  | @ -217,9 +216,7 @@ $(run_O): | ||||||
| 	( eval $$cmd ) >> $@ 2>&1 && \ | 	( eval $$cmd ) >> $@ 2>&1 && \ | ||||||
| 	echo "  test: $(call test_O,$@)" >> $@ 2>&1; \ | 	echo "  test: $(call test_O,$@)" >> $@ 2>&1; \ | ||||||
| 	$(call test_O,$@) && \ | 	$(call test_O,$@) && \ | ||||||
| 	rm -f $@ && \ | 	rm -rf $@ $$TMP_O $$TMP_DEST || (cat $@ ; false) | ||||||
| 	rm -rf $$TMP_O \ |  | ||||||
| 	rm -rf $$TMP_DEST |  | ||||||
| 
 | 
 | ||||||
| tarpkg: | tarpkg: | ||||||
| 	@cmd="$(PERF)/tests/perf-targz-src-pkg $(PERF)"; \ | 	@cmd="$(PERF)/tests/perf-targz-src-pkg $(PERF)"; \ | ||||||
|  |  | ||||||
|  | @ -28,6 +28,8 @@ int test__syscall_open_tp_fields(void); | ||||||
| int test__pmu(void); | int test__pmu(void); | ||||||
| int test__attr(void); | int test__attr(void); | ||||||
| int test__dso_data(void); | int test__dso_data(void); | ||||||
|  | int test__dso_data_cache(void); | ||||||
|  | int test__dso_data_reopen(void); | ||||||
| int test__parse_events(void); | int test__parse_events(void); | ||||||
| int test__hists_link(void); | int test__hists_link(void); | ||||||
| int test__python_use(void); | int test__python_use(void); | ||||||
|  |  | ||||||
|  | @ -1,3 +1,6 @@ | ||||||
|  | #include <asm/bug.h> | ||||||
|  | #include <sys/time.h> | ||||||
|  | #include <sys/resource.h> | ||||||
| #include "symbol.h" | #include "symbol.h" | ||||||
| #include "dso.h" | #include "dso.h" | ||||||
| #include "machine.h" | #include "machine.h" | ||||||
|  | @ -136,7 +139,48 @@ int dso__read_binary_type_filename(const struct dso *dso, | ||||||
| 	return ret; | 	return ret; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static int open_dso(struct dso *dso, struct machine *machine) | /*
 | ||||||
|  |  * Global list of open DSOs and the counter. | ||||||
|  |  */ | ||||||
|  | static LIST_HEAD(dso__data_open); | ||||||
|  | static long dso__data_open_cnt; | ||||||
|  | 
 | ||||||
|  | static void dso__list_add(struct dso *dso) | ||||||
|  | { | ||||||
|  | 	list_add_tail(&dso->data.open_entry, &dso__data_open); | ||||||
|  | 	dso__data_open_cnt++; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void dso__list_del(struct dso *dso) | ||||||
|  | { | ||||||
|  | 	list_del(&dso->data.open_entry); | ||||||
|  | 	WARN_ONCE(dso__data_open_cnt <= 0, | ||||||
|  | 		  "DSO data fd counter out of bounds."); | ||||||
|  | 	dso__data_open_cnt--; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void close_first_dso(void); | ||||||
|  | 
 | ||||||
|  | static int do_open(char *name) | ||||||
|  | { | ||||||
|  | 	int fd; | ||||||
|  | 
 | ||||||
|  | 	do { | ||||||
|  | 		fd = open(name, O_RDONLY); | ||||||
|  | 		if (fd >= 0) | ||||||
|  | 			return fd; | ||||||
|  | 
 | ||||||
|  | 		pr_debug("dso open failed, mmap: %s\n", strerror(errno)); | ||||||
|  | 		if (!dso__data_open_cnt || errno != EMFILE) | ||||||
|  | 			break; | ||||||
|  | 
 | ||||||
|  | 		close_first_dso(); | ||||||
|  | 	} while (1); | ||||||
|  | 
 | ||||||
|  | 	return -1; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int __open_dso(struct dso *dso, struct machine *machine) | ||||||
| { | { | ||||||
| 	int fd; | 	int fd; | ||||||
| 	char *root_dir = (char *)""; | 	char *root_dir = (char *)""; | ||||||
|  | @ -154,11 +198,130 @@ static int open_dso(struct dso *dso, struct machine *machine) | ||||||
| 		return -EINVAL; | 		return -EINVAL; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	fd = open(name, O_RDONLY); | 	fd = do_open(name); | ||||||
| 	free(name); | 	free(name); | ||||||
| 	return fd; | 	return fd; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static void check_data_close(void); | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * dso_close - Open DSO data file | ||||||
|  |  * @dso: dso object | ||||||
|  |  * | ||||||
|  |  * Open @dso's data file descriptor and updates | ||||||
|  |  * list/count of open DSO objects. | ||||||
|  |  */ | ||||||
|  | static int open_dso(struct dso *dso, struct machine *machine) | ||||||
|  | { | ||||||
|  | 	int fd = __open_dso(dso, machine); | ||||||
|  | 
 | ||||||
|  | 	if (fd > 0) { | ||||||
|  | 		dso__list_add(dso); | ||||||
|  | 		/*
 | ||||||
|  | 		 * Check if we crossed the allowed number | ||||||
|  | 		 * of opened DSOs and close one if needed. | ||||||
|  | 		 */ | ||||||
|  | 		check_data_close(); | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return fd; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void close_data_fd(struct dso *dso) | ||||||
|  | { | ||||||
|  | 	if (dso->data.fd >= 0) { | ||||||
|  | 		close(dso->data.fd); | ||||||
|  | 		dso->data.fd = -1; | ||||||
|  | 		dso->data.file_size = 0; | ||||||
|  | 		dso__list_del(dso); | ||||||
|  | 	} | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * dso_close - Close DSO data file | ||||||
|  |  * @dso: dso object | ||||||
|  |  * | ||||||
|  |  * Close @dso's data file descriptor and updates | ||||||
|  |  * list/count of open DSO objects. | ||||||
|  |  */ | ||||||
|  | static void close_dso(struct dso *dso) | ||||||
|  | { | ||||||
|  | 	close_data_fd(dso); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static void close_first_dso(void) | ||||||
|  | { | ||||||
|  | 	struct dso *dso; | ||||||
|  | 
 | ||||||
|  | 	dso = list_first_entry(&dso__data_open, struct dso, data.open_entry); | ||||||
|  | 	close_dso(dso); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static rlim_t get_fd_limit(void) | ||||||
|  | { | ||||||
|  | 	struct rlimit l; | ||||||
|  | 	rlim_t limit = 0; | ||||||
|  | 
 | ||||||
|  | 	/* Allow half of the current open fd limit. */ | ||||||
|  | 	if (getrlimit(RLIMIT_NOFILE, &l) == 0) { | ||||||
|  | 		if (l.rlim_cur == RLIM_INFINITY) | ||||||
|  | 			limit = l.rlim_cur; | ||||||
|  | 		else | ||||||
|  | 			limit = l.rlim_cur / 2; | ||||||
|  | 	} else { | ||||||
|  | 		pr_err("failed to get fd limit\n"); | ||||||
|  | 		limit = 1; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return limit; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static bool may_cache_fd(void) | ||||||
|  | { | ||||||
|  | 	static rlim_t limit; | ||||||
|  | 
 | ||||||
|  | 	if (!limit) | ||||||
|  | 		limit = get_fd_limit(); | ||||||
|  | 
 | ||||||
|  | 	if (limit == RLIM_INFINITY) | ||||||
|  | 		return true; | ||||||
|  | 
 | ||||||
|  | 	return limit > (rlim_t) dso__data_open_cnt; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /*
 | ||||||
|  |  * Check and close LRU dso if we crossed allowed limit | ||||||
|  |  * for opened dso file descriptors. The limit is half | ||||||
|  |  * of the RLIMIT_NOFILE files opened. | ||||||
|  | */ | ||||||
|  | static void check_data_close(void) | ||||||
|  | { | ||||||
|  | 	bool cache_fd = may_cache_fd(); | ||||||
|  | 
 | ||||||
|  | 	if (!cache_fd) | ||||||
|  | 		close_first_dso(); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * dso__data_close - Close DSO data file | ||||||
|  |  * @dso: dso object | ||||||
|  |  * | ||||||
|  |  * External interface to close @dso's data file descriptor. | ||||||
|  |  */ | ||||||
|  | void dso__data_close(struct dso *dso) | ||||||
|  | { | ||||||
|  | 	close_dso(dso); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * dso__data_fd - Get dso's data file descriptor | ||||||
|  |  * @dso: dso object | ||||||
|  |  * @machine: machine object | ||||||
|  |  * | ||||||
|  |  * External interface to find dso's file, open it and | ||||||
|  |  * returns file descriptor. | ||||||
|  |  */ | ||||||
| int dso__data_fd(struct dso *dso, struct machine *machine) | int dso__data_fd(struct dso *dso, struct machine *machine) | ||||||
| { | { | ||||||
| 	enum dso_binary_type binary_type_data[] = { | 	enum dso_binary_type binary_type_data[] = { | ||||||
|  | @ -168,8 +331,13 @@ int dso__data_fd(struct dso *dso, struct machine *machine) | ||||||
| 	}; | 	}; | ||||||
| 	int i = 0; | 	int i = 0; | ||||||
| 
 | 
 | ||||||
| 	if (dso->binary_type != DSO_BINARY_TYPE__NOT_FOUND) | 	if (dso->data.fd >= 0) | ||||||
| 		return open_dso(dso, machine); | 		return dso->data.fd; | ||||||
|  | 
 | ||||||
|  | 	if (dso->binary_type != DSO_BINARY_TYPE__NOT_FOUND) { | ||||||
|  | 		dso->data.fd = open_dso(dso, machine); | ||||||
|  | 		return dso->data.fd; | ||||||
|  | 	} | ||||||
| 
 | 
 | ||||||
| 	do { | 	do { | ||||||
| 		int fd; | 		int fd; | ||||||
|  | @ -178,7 +346,7 @@ int dso__data_fd(struct dso *dso, struct machine *machine) | ||||||
| 
 | 
 | ||||||
| 		fd = open_dso(dso, machine); | 		fd = open_dso(dso, machine); | ||||||
| 		if (fd >= 0) | 		if (fd >= 0) | ||||||
| 			return fd; | 			return dso->data.fd = fd; | ||||||
| 
 | 
 | ||||||
| 	} while (dso->binary_type != DSO_BINARY_TYPE__NOT_FOUND); | 	} while (dso->binary_type != DSO_BINARY_TYPE__NOT_FOUND); | ||||||
| 
 | 
 | ||||||
|  | @ -260,16 +428,10 @@ dso_cache__memcpy(struct dso_cache *cache, u64 offset, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static ssize_t | static ssize_t | ||||||
| dso_cache__read(struct dso *dso, struct machine *machine, | dso_cache__read(struct dso *dso, u64 offset, u8 *data, ssize_t size) | ||||||
| 		 u64 offset, u8 *data, ssize_t size) |  | ||||||
| { | { | ||||||
| 	struct dso_cache *cache; | 	struct dso_cache *cache; | ||||||
| 	ssize_t ret; | 	ssize_t ret; | ||||||
| 	int fd; |  | ||||||
| 
 |  | ||||||
| 	fd = dso__data_fd(dso, machine); |  | ||||||
| 	if (fd < 0) |  | ||||||
| 		return -1; |  | ||||||
| 
 | 
 | ||||||
| 	do { | 	do { | ||||||
| 		u64 cache_offset; | 		u64 cache_offset; | ||||||
|  | @ -283,16 +445,16 @@ dso_cache__read(struct dso *dso, struct machine *machine, | ||||||
| 		cache_offset = offset & DSO__DATA_CACHE_MASK; | 		cache_offset = offset & DSO__DATA_CACHE_MASK; | ||||||
| 		ret = -EINVAL; | 		ret = -EINVAL; | ||||||
| 
 | 
 | ||||||
| 		if (-1 == lseek(fd, cache_offset, SEEK_SET)) | 		if (-1 == lseek(dso->data.fd, cache_offset, SEEK_SET)) | ||||||
| 			break; | 			break; | ||||||
| 
 | 
 | ||||||
| 		ret = read(fd, cache->data, DSO__DATA_CACHE_SIZE); | 		ret = read(dso->data.fd, cache->data, DSO__DATA_CACHE_SIZE); | ||||||
| 		if (ret <= 0) | 		if (ret <= 0) | ||||||
| 			break; | 			break; | ||||||
| 
 | 
 | ||||||
| 		cache->offset = cache_offset; | 		cache->offset = cache_offset; | ||||||
| 		cache->size   = ret; | 		cache->size   = ret; | ||||||
| 		dso_cache__insert(&dso->cache, cache); | 		dso_cache__insert(&dso->data.cache, cache); | ||||||
| 
 | 
 | ||||||
| 		ret = dso_cache__memcpy(cache, offset, data, size); | 		ret = dso_cache__memcpy(cache, offset, data, size); | ||||||
| 
 | 
 | ||||||
|  | @ -301,24 +463,27 @@ dso_cache__read(struct dso *dso, struct machine *machine, | ||||||
| 	if (ret <= 0) | 	if (ret <= 0) | ||||||
| 		free(cache); | 		free(cache); | ||||||
| 
 | 
 | ||||||
| 	close(fd); |  | ||||||
| 	return ret; | 	return ret; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static ssize_t dso_cache_read(struct dso *dso, struct machine *machine, | static ssize_t dso_cache_read(struct dso *dso, u64 offset, | ||||||
| 			      u64 offset, u8 *data, ssize_t size) | 			      u8 *data, ssize_t size) | ||||||
| { | { | ||||||
| 	struct dso_cache *cache; | 	struct dso_cache *cache; | ||||||
| 
 | 
 | ||||||
| 	cache = dso_cache__find(&dso->cache, offset); | 	cache = dso_cache__find(&dso->data.cache, offset); | ||||||
| 	if (cache) | 	if (cache) | ||||||
| 		return dso_cache__memcpy(cache, offset, data, size); | 		return dso_cache__memcpy(cache, offset, data, size); | ||||||
| 	else | 	else | ||||||
| 		return dso_cache__read(dso, machine, offset, data, size); | 		return dso_cache__read(dso, offset, data, size); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| ssize_t dso__data_read_offset(struct dso *dso, struct machine *machine, | /*
 | ||||||
| 			      u64 offset, u8 *data, ssize_t size) |  * Reads and caches dso data DSO__DATA_CACHE_SIZE size chunks | ||||||
|  |  * in the rb_tree. Any read to already cached data is served | ||||||
|  |  * by cached data. | ||||||
|  |  */ | ||||||
|  | static ssize_t cached_read(struct dso *dso, u64 offset, u8 *data, ssize_t size) | ||||||
| { | { | ||||||
| 	ssize_t r = 0; | 	ssize_t r = 0; | ||||||
| 	u8 *p = data; | 	u8 *p = data; | ||||||
|  | @ -326,7 +491,7 @@ ssize_t dso__data_read_offset(struct dso *dso, struct machine *machine, | ||||||
| 	do { | 	do { | ||||||
| 		ssize_t ret; | 		ssize_t ret; | ||||||
| 
 | 
 | ||||||
| 		ret = dso_cache_read(dso, machine, offset, p, size); | 		ret = dso_cache_read(dso, offset, p, size); | ||||||
| 		if (ret < 0) | 		if (ret < 0) | ||||||
| 			return ret; | 			return ret; | ||||||
| 
 | 
 | ||||||
|  | @ -346,6 +511,67 @@ ssize_t dso__data_read_offset(struct dso *dso, struct machine *machine, | ||||||
| 	return r; | 	return r; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static int data_file_size(struct dso *dso) | ||||||
|  | { | ||||||
|  | 	struct stat st; | ||||||
|  | 
 | ||||||
|  | 	if (!dso->data.file_size) { | ||||||
|  | 		if (fstat(dso->data.fd, &st)) { | ||||||
|  | 			pr_err("dso mmap failed, fstat: %s\n", strerror(errno)); | ||||||
|  | 			return -1; | ||||||
|  | 		} | ||||||
|  | 		dso->data.file_size = st.st_size; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static ssize_t data_read_offset(struct dso *dso, u64 offset, | ||||||
|  | 				u8 *data, ssize_t size) | ||||||
|  | { | ||||||
|  | 	if (data_file_size(dso)) | ||||||
|  | 		return -1; | ||||||
|  | 
 | ||||||
|  | 	/* Check the offset sanity. */ | ||||||
|  | 	if (offset > dso->data.file_size) | ||||||
|  | 		return -1; | ||||||
|  | 
 | ||||||
|  | 	if (offset + size < offset) | ||||||
|  | 		return -1; | ||||||
|  | 
 | ||||||
|  | 	return cached_read(dso, offset, data, size); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * dso__data_read_offset - Read data from dso file offset | ||||||
|  |  * @dso: dso object | ||||||
|  |  * @machine: machine object | ||||||
|  |  * @offset: file offset | ||||||
|  |  * @data: buffer to store data | ||||||
|  |  * @size: size of the @data buffer | ||||||
|  |  * | ||||||
|  |  * External interface to read data from dso file offset. Open | ||||||
|  |  * dso data file and use cached_read to get the data. | ||||||
|  |  */ | ||||||
|  | ssize_t dso__data_read_offset(struct dso *dso, struct machine *machine, | ||||||
|  | 			      u64 offset, u8 *data, ssize_t size) | ||||||
|  | { | ||||||
|  | 	if (dso__data_fd(dso, machine) < 0) | ||||||
|  | 		return -1; | ||||||
|  | 
 | ||||||
|  | 	return data_read_offset(dso, offset, data, size); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | /**
 | ||||||
|  |  * dso__data_read_addr - Read data from dso address | ||||||
|  |  * @dso: dso object | ||||||
|  |  * @machine: machine object | ||||||
|  |  * @add: virtual memory address | ||||||
|  |  * @data: buffer to store data | ||||||
|  |  * @size: size of the @data buffer | ||||||
|  |  * | ||||||
|  |  * External interface to read data from dso address. | ||||||
|  |  */ | ||||||
| ssize_t dso__data_read_addr(struct dso *dso, struct map *map, | ssize_t dso__data_read_addr(struct dso *dso, struct map *map, | ||||||
| 			    struct machine *machine, u64 addr, | 			    struct machine *machine, u64 addr, | ||||||
| 			    u8 *data, ssize_t size) | 			    u8 *data, ssize_t size) | ||||||
|  | @ -473,7 +699,8 @@ struct dso *dso__new(const char *name) | ||||||
| 		dso__set_short_name(dso, dso->name, false); | 		dso__set_short_name(dso, dso->name, false); | ||||||
| 		for (i = 0; i < MAP__NR_TYPES; ++i) | 		for (i = 0; i < MAP__NR_TYPES; ++i) | ||||||
| 			dso->symbols[i] = dso->symbol_names[i] = RB_ROOT; | 			dso->symbols[i] = dso->symbol_names[i] = RB_ROOT; | ||||||
| 		dso->cache = RB_ROOT; | 		dso->data.cache = RB_ROOT; | ||||||
|  | 		dso->data.fd = -1; | ||||||
| 		dso->symtab_type = DSO_BINARY_TYPE__NOT_FOUND; | 		dso->symtab_type = DSO_BINARY_TYPE__NOT_FOUND; | ||||||
| 		dso->binary_type = DSO_BINARY_TYPE__NOT_FOUND; | 		dso->binary_type = DSO_BINARY_TYPE__NOT_FOUND; | ||||||
| 		dso->loaded = 0; | 		dso->loaded = 0; | ||||||
|  | @ -485,6 +712,7 @@ struct dso *dso__new(const char *name) | ||||||
| 		dso->kernel = DSO_TYPE_USER; | 		dso->kernel = DSO_TYPE_USER; | ||||||
| 		dso->needs_swap = DSO_SWAP__UNSET; | 		dso->needs_swap = DSO_SWAP__UNSET; | ||||||
| 		INIT_LIST_HEAD(&dso->node); | 		INIT_LIST_HEAD(&dso->node); | ||||||
|  | 		INIT_LIST_HEAD(&dso->data.open_entry); | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	return dso; | 	return dso; | ||||||
|  | @ -506,7 +734,8 @@ void dso__delete(struct dso *dso) | ||||||
| 		dso->long_name_allocated = false; | 		dso->long_name_allocated = false; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	dso_cache__free(&dso->cache); | 	dso__data_close(dso); | ||||||
|  | 	dso_cache__free(&dso->data.cache); | ||||||
| 	dso__free_a2l(dso); | 	dso__free_a2l(dso); | ||||||
| 	zfree(&dso->symsrc_filename); | 	zfree(&dso->symsrc_filename); | ||||||
| 	free(dso); | 	free(dso); | ||||||
|  |  | ||||||
|  | @ -76,7 +76,6 @@ struct dso { | ||||||
| 	struct list_head node; | 	struct list_head node; | ||||||
| 	struct rb_root	 symbols[MAP__NR_TYPES]; | 	struct rb_root	 symbols[MAP__NR_TYPES]; | ||||||
| 	struct rb_root	 symbol_names[MAP__NR_TYPES]; | 	struct rb_root	 symbol_names[MAP__NR_TYPES]; | ||||||
| 	struct rb_root	 cache; |  | ||||||
| 	void		 *a2l; | 	void		 *a2l; | ||||||
| 	char		 *symsrc_filename; | 	char		 *symsrc_filename; | ||||||
| 	unsigned int	 a2l_fails; | 	unsigned int	 a2l_fails; | ||||||
|  | @ -99,6 +98,15 @@ struct dso { | ||||||
| 	const char	 *long_name; | 	const char	 *long_name; | ||||||
| 	u16		 long_name_len; | 	u16		 long_name_len; | ||||||
| 	u16		 short_name_len; | 	u16		 short_name_len; | ||||||
|  | 
 | ||||||
|  | 	/* dso data file */ | ||||||
|  | 	struct { | ||||||
|  | 		struct rb_root	 cache; | ||||||
|  | 		int		 fd; | ||||||
|  | 		size_t		 file_size; | ||||||
|  | 		struct list_head open_entry; | ||||||
|  | 	} data; | ||||||
|  | 
 | ||||||
| 	char		 name[0]; | 	char		 name[0]; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | @ -141,7 +149,47 @@ char dso__symtab_origin(const struct dso *dso); | ||||||
| int dso__read_binary_type_filename(const struct dso *dso, enum dso_binary_type type, | int dso__read_binary_type_filename(const struct dso *dso, enum dso_binary_type type, | ||||||
| 				   char *root_dir, char *filename, size_t size); | 				   char *root_dir, char *filename, size_t size); | ||||||
| 
 | 
 | ||||||
|  | /*
 | ||||||
|  |  * The dso__data_* external interface provides following functions: | ||||||
|  |  *   dso__data_fd | ||||||
|  |  *   dso__data_close | ||||||
|  |  *   dso__data_read_offset | ||||||
|  |  *   dso__data_read_addr | ||||||
|  |  * | ||||||
|  |  * Please refer to the dso.c object code for each function and | ||||||
|  |  * arguments documentation. Following text tries to explain the | ||||||
|  |  * dso file descriptor caching. | ||||||
|  |  * | ||||||
|  |  * The dso__data* interface allows caching of opened file descriptors | ||||||
|  |  * to speed up the dso data accesses. The idea is to leave the file | ||||||
|  |  * descriptor opened ideally for the whole life of the dso object. | ||||||
|  |  * | ||||||
|  |  * The current usage of the dso__data_* interface is as follows: | ||||||
|  |  * | ||||||
|  |  * Get DSO's fd: | ||||||
|  |  *   int fd = dso__data_fd(dso, machine); | ||||||
|  |  *   USE 'fd' SOMEHOW | ||||||
|  |  * | ||||||
|  |  * Read DSO's data: | ||||||
|  |  *   n = dso__data_read_offset(dso_0, &machine, 0, buf, BUFSIZE); | ||||||
|  |  *   n = dso__data_read_addr(dso_0, &machine, 0, buf, BUFSIZE); | ||||||
|  |  * | ||||||
|  |  * Eventually close DSO's fd: | ||||||
|  |  *   dso__data_close(dso); | ||||||
|  |  * | ||||||
|  |  * It is not necessary to close the DSO object data file. Each time new | ||||||
|  |  * DSO data file is opened, the limit (RLIMIT_NOFILE/2) is checked. Once | ||||||
|  |  * it is crossed, the oldest opened DSO object is closed. | ||||||
|  |  * | ||||||
|  |  * The dso__delete function calls close_dso function to ensure the | ||||||
|  |  * data file descriptor gets closed/unmapped before the dso object | ||||||
|  |  * is freed. | ||||||
|  |  * | ||||||
|  |  * TODO | ||||||
|  | */ | ||||||
| int dso__data_fd(struct dso *dso, struct machine *machine); | int dso__data_fd(struct dso *dso, struct machine *machine); | ||||||
|  | void dso__data_close(struct dso *dso); | ||||||
|  | 
 | ||||||
| ssize_t dso__data_read_offset(struct dso *dso, struct machine *machine, | ssize_t dso__data_read_offset(struct dso *dso, struct machine *machine, | ||||||
| 			      u64 offset, u8 *data, ssize_t size); | 			      u64 offset, u8 *data, ssize_t size); | ||||||
| ssize_t dso__data_read_addr(struct dso *dso, struct map *map, | ssize_t dso__data_read_addr(struct dso *dso, struct map *map, | ||||||
|  |  | ||||||
|  | @ -1,4 +1,5 @@ | ||||||
| #include <linux/types.h> | #include <linux/types.h> | ||||||
|  | #include <sys/mman.h> | ||||||
| #include "event.h" | #include "event.h" | ||||||
| #include "debug.h" | #include "debug.h" | ||||||
| #include "hist.h" | #include "hist.h" | ||||||
|  | @ -178,13 +179,14 @@ int perf_event__synthesize_mmap_events(struct perf_tool *tool, | ||||||
| 		return -1; | 		return -1; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	event->header.type = PERF_RECORD_MMAP; | 	event->header.type = PERF_RECORD_MMAP2; | ||||||
| 
 | 
 | ||||||
| 	while (1) { | 	while (1) { | ||||||
| 		char bf[BUFSIZ]; | 		char bf[BUFSIZ]; | ||||||
| 		char prot[5]; | 		char prot[5]; | ||||||
| 		char execname[PATH_MAX]; | 		char execname[PATH_MAX]; | ||||||
| 		char anonstr[] = "//anon"; | 		char anonstr[] = "//anon"; | ||||||
|  | 		unsigned int ino; | ||||||
| 		size_t size; | 		size_t size; | ||||||
| 		ssize_t n; | 		ssize_t n; | ||||||
| 
 | 
 | ||||||
|  | @ -195,15 +197,20 @@ int perf_event__synthesize_mmap_events(struct perf_tool *tool, | ||||||
| 		strcpy(execname, ""); | 		strcpy(execname, ""); | ||||||
| 
 | 
 | ||||||
| 		/* 00400000-0040c000 r-xp 00000000 fd:01 41038  /bin/cat */ | 		/* 00400000-0040c000 r-xp 00000000 fd:01 41038  /bin/cat */ | ||||||
| 		n = sscanf(bf, "%"PRIx64"-%"PRIx64" %s %"PRIx64" %*x:%*x %*u %s\n", | 		n = sscanf(bf, "%"PRIx64"-%"PRIx64" %s %"PRIx64" %x:%x %u %s\n", | ||||||
| 		       &event->mmap.start, &event->mmap.len, prot, | 		       &event->mmap2.start, &event->mmap2.len, prot, | ||||||
| 		       &event->mmap.pgoff, | 		       &event->mmap2.pgoff, &event->mmap2.maj, | ||||||
| 		       execname); | 		       &event->mmap2.min, | ||||||
|  | 		       &ino, execname); | ||||||
|  | 
 | ||||||
| 		/*
 | 		/*
 | ||||||
|  		 * Anon maps don't have the execname. |  		 * Anon maps don't have the execname. | ||||||
|  		 */ |  		 */ | ||||||
| 		if (n < 4) | 		if (n < 7) | ||||||
| 			continue; | 			continue; | ||||||
|  | 
 | ||||||
|  | 		event->mmap2.ino = (u64)ino; | ||||||
|  | 
 | ||||||
| 		/*
 | 		/*
 | ||||||
| 		 * Just like the kernel, see __perf_event_mmap in kernel/perf_event.c | 		 * Just like the kernel, see __perf_event_mmap in kernel/perf_event.c | ||||||
| 		 */ | 		 */ | ||||||
|  | @ -212,6 +219,21 @@ int perf_event__synthesize_mmap_events(struct perf_tool *tool, | ||||||
| 		else | 		else | ||||||
| 			event->header.misc = PERF_RECORD_MISC_GUEST_USER; | 			event->header.misc = PERF_RECORD_MISC_GUEST_USER; | ||||||
| 
 | 
 | ||||||
|  | 		/* map protection and flags bits */ | ||||||
|  | 		event->mmap2.prot = 0; | ||||||
|  | 		event->mmap2.flags = 0; | ||||||
|  | 		if (prot[0] == 'r') | ||||||
|  | 			event->mmap2.prot |= PROT_READ; | ||||||
|  | 		if (prot[1] == 'w') | ||||||
|  | 			event->mmap2.prot |= PROT_WRITE; | ||||||
|  | 		if (prot[2] == 'x') | ||||||
|  | 			event->mmap2.prot |= PROT_EXEC; | ||||||
|  | 
 | ||||||
|  | 		if (prot[3] == 's') | ||||||
|  | 			event->mmap2.flags |= MAP_SHARED; | ||||||
|  | 		else | ||||||
|  | 			event->mmap2.flags |= MAP_PRIVATE; | ||||||
|  | 
 | ||||||
| 		if (prot[2] != 'x') { | 		if (prot[2] != 'x') { | ||||||
| 			if (!mmap_data || prot[0] != 'r') | 			if (!mmap_data || prot[0] != 'r') | ||||||
| 				continue; | 				continue; | ||||||
|  | @ -223,15 +245,15 @@ int perf_event__synthesize_mmap_events(struct perf_tool *tool, | ||||||
| 			strcpy(execname, anonstr); | 			strcpy(execname, anonstr); | ||||||
| 
 | 
 | ||||||
| 		size = strlen(execname) + 1; | 		size = strlen(execname) + 1; | ||||||
| 		memcpy(event->mmap.filename, execname, size); | 		memcpy(event->mmap2.filename, execname, size); | ||||||
| 		size = PERF_ALIGN(size, sizeof(u64)); | 		size = PERF_ALIGN(size, sizeof(u64)); | ||||||
| 		event->mmap.len -= event->mmap.start; | 		event->mmap2.len -= event->mmap.start; | ||||||
| 		event->mmap.header.size = (sizeof(event->mmap) - | 		event->mmap2.header.size = (sizeof(event->mmap2) - | ||||||
| 					(sizeof(event->mmap.filename) - size)); | 					(sizeof(event->mmap2.filename) - size)); | ||||||
| 		memset(event->mmap.filename + size, 0, machine->id_hdr_size); | 		memset(event->mmap2.filename + size, 0, machine->id_hdr_size); | ||||||
| 		event->mmap.header.size += machine->id_hdr_size; | 		event->mmap2.header.size += machine->id_hdr_size; | ||||||
| 		event->mmap.pid = tgid; | 		event->mmap2.pid = tgid; | ||||||
| 		event->mmap.tid = pid; | 		event->mmap2.tid = pid; | ||||||
| 
 | 
 | ||||||
| 		if (process(tool, event, &synth_sample, machine) != 0) { | 		if (process(tool, event, &synth_sample, machine) != 0) { | ||||||
| 			rc = -1; | 			rc = -1; | ||||||
|  | @ -612,12 +634,15 @@ size_t perf_event__fprintf_mmap(union perf_event *event, FILE *fp) | ||||||
| size_t perf_event__fprintf_mmap2(union perf_event *event, FILE *fp) | size_t perf_event__fprintf_mmap2(union perf_event *event, FILE *fp) | ||||||
| { | { | ||||||
| 	return fprintf(fp, " %d/%d: [%#" PRIx64 "(%#" PRIx64 ") @ %#" PRIx64 | 	return fprintf(fp, " %d/%d: [%#" PRIx64 "(%#" PRIx64 ") @ %#" PRIx64 | ||||||
| 			   " %02x:%02x %"PRIu64" %"PRIu64"]: %c %s\n", | 			   " %02x:%02x %"PRIu64" %"PRIu64"]: %c%c%c%c %s\n", | ||||||
| 		       event->mmap2.pid, event->mmap2.tid, event->mmap2.start, | 		       event->mmap2.pid, event->mmap2.tid, event->mmap2.start, | ||||||
| 		       event->mmap2.len, event->mmap2.pgoff, event->mmap2.maj, | 		       event->mmap2.len, event->mmap2.pgoff, event->mmap2.maj, | ||||||
| 		       event->mmap2.min, event->mmap2.ino, | 		       event->mmap2.min, event->mmap2.ino, | ||||||
| 		       event->mmap2.ino_generation, | 		       event->mmap2.ino_generation, | ||||||
| 		       (event->header.misc & PERF_RECORD_MISC_MMAP_DATA) ? 'r' : 'x', | 		       (event->mmap2.prot & PROT_READ) ? 'r' : '-', | ||||||
|  | 		       (event->mmap2.prot & PROT_WRITE) ? 'w' : '-', | ||||||
|  | 		       (event->mmap2.prot & PROT_EXEC) ? 'x' : '-', | ||||||
|  | 		       (event->mmap2.flags & MAP_SHARED) ? 's' : 'p', | ||||||
| 		       event->mmap2.filename); | 		       event->mmap2.filename); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -7,6 +7,7 @@ | ||||||
| #include "../perf.h" | #include "../perf.h" | ||||||
| #include "map.h" | #include "map.h" | ||||||
| #include "build-id.h" | #include "build-id.h" | ||||||
|  | #include "perf_regs.h" | ||||||
| 
 | 
 | ||||||
| struct mmap_event { | struct mmap_event { | ||||||
| 	struct perf_event_header header; | 	struct perf_event_header header; | ||||||
|  | @ -27,6 +28,8 @@ struct mmap2_event { | ||||||
| 	u32 min; | 	u32 min; | ||||||
| 	u64 ino; | 	u64 ino; | ||||||
| 	u64 ino_generation; | 	u64 ino_generation; | ||||||
|  | 	u32 prot; | ||||||
|  | 	u32 flags; | ||||||
| 	char filename[PATH_MAX]; | 	char filename[PATH_MAX]; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | @ -87,6 +90,10 @@ struct regs_dump { | ||||||
| 	u64 abi; | 	u64 abi; | ||||||
| 	u64 mask; | 	u64 mask; | ||||||
| 	u64 *regs; | 	u64 *regs; | ||||||
|  | 
 | ||||||
|  | 	/* Cached values/mask filled by first register access. */ | ||||||
|  | 	u64 cache_regs[PERF_REGS_MAX]; | ||||||
|  | 	u64 cache_mask; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| struct stack_dump { | struct stack_dump { | ||||||
|  |  | ||||||
|  | @ -589,10 +589,10 @@ void perf_evsel__config(struct perf_evsel *evsel, struct record_opts *opts) | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	/*
 | 	/*
 | ||||||
| 	 * We default some events to a 1 default interval. But keep | 	 * We default some events to have a default interval. But keep | ||||||
| 	 * it a weak assumption overridable by the user. | 	 * it a weak assumption overridable by the user. | ||||||
| 	 */ | 	 */ | ||||||
| 	if (!attr->sample_period || (opts->user_freq != UINT_MAX && | 	if (!attr->sample_period || (opts->user_freq != UINT_MAX || | ||||||
| 				     opts->user_interval != ULLONG_MAX)) { | 				     opts->user_interval != ULLONG_MAX)) { | ||||||
| 		if (opts->freq) { | 		if (opts->freq) { | ||||||
| 			perf_evsel__set_sample_bit(evsel, PERIOD); | 			perf_evsel__set_sample_bit(evsel, PERIOD); | ||||||
|  | @ -659,6 +659,7 @@ void perf_evsel__config(struct perf_evsel *evsel, struct record_opts *opts) | ||||||
| 		perf_evsel__set_sample_bit(evsel, WEIGHT); | 		perf_evsel__set_sample_bit(evsel, WEIGHT); | ||||||
| 
 | 
 | ||||||
| 	attr->mmap  = track; | 	attr->mmap  = track; | ||||||
|  | 	attr->mmap2 = track && !perf_missing_features.mmap2; | ||||||
| 	attr->comm  = track; | 	attr->comm  = track; | ||||||
| 
 | 
 | ||||||
| 	if (opts->sample_transaction) | 	if (opts->sample_transaction) | ||||||
|  |  | ||||||
|  | @ -128,6 +128,8 @@ void hists__calc_col_len(struct hists *hists, struct hist_entry *h) | ||||||
| 			       + unresolved_col_width + 2; | 			       + unresolved_col_width + 2; | ||||||
| 			hists__new_col_len(hists, HISTC_MEM_DADDR_SYMBOL, | 			hists__new_col_len(hists, HISTC_MEM_DADDR_SYMBOL, | ||||||
| 					   symlen); | 					   symlen); | ||||||
|  | 			hists__new_col_len(hists, HISTC_MEM_DCACHELINE, | ||||||
|  | 					   symlen + 1); | ||||||
| 		} else { | 		} else { | ||||||
| 			symlen = unresolved_col_width + 4 + 2; | 			symlen = unresolved_col_width + 4 + 2; | ||||||
| 			hists__new_col_len(hists, HISTC_MEM_DADDR_SYMBOL, | 			hists__new_col_len(hists, HISTC_MEM_DADDR_SYMBOL, | ||||||
|  | @ -440,6 +442,7 @@ struct hist_entry *__hists__add_entry(struct hists *hists, | ||||||
| 			.sym	= al->sym, | 			.sym	= al->sym, | ||||||
| 		}, | 		}, | ||||||
| 		.cpu	 = al->cpu, | 		.cpu	 = al->cpu, | ||||||
|  | 		.cpumode = al->cpumode, | ||||||
| 		.ip	 = al->addr, | 		.ip	 = al->addr, | ||||||
| 		.level	 = al->level, | 		.level	 = al->level, | ||||||
| 		.stat = { | 		.stat = { | ||||||
|  |  | ||||||
|  | @ -72,6 +72,7 @@ enum hist_column { | ||||||
| 	HISTC_MEM_TLB, | 	HISTC_MEM_TLB, | ||||||
| 	HISTC_MEM_LVL, | 	HISTC_MEM_LVL, | ||||||
| 	HISTC_MEM_SNOOP, | 	HISTC_MEM_SNOOP, | ||||||
|  | 	HISTC_MEM_DCACHELINE, | ||||||
| 	HISTC_TRANSACTION, | 	HISTC_TRANSACTION, | ||||||
| 	HISTC_NR_COLS, /* Last entry */ | 	HISTC_NR_COLS, /* Last entry */ | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | @ -1060,6 +1060,8 @@ int machine__process_mmap2_event(struct machine *machine, | ||||||
| 			event->mmap2.pid, event->mmap2.maj, | 			event->mmap2.pid, event->mmap2.maj, | ||||||
| 			event->mmap2.min, event->mmap2.ino, | 			event->mmap2.min, event->mmap2.ino, | ||||||
| 			event->mmap2.ino_generation, | 			event->mmap2.ino_generation, | ||||||
|  | 			event->mmap2.prot, | ||||||
|  | 			event->mmap2.flags, | ||||||
| 			event->mmap2.filename, type); | 			event->mmap2.filename, type); | ||||||
| 
 | 
 | ||||||
| 	if (map == NULL) | 	if (map == NULL) | ||||||
|  | @ -1105,7 +1107,7 @@ int machine__process_mmap_event(struct machine *machine, union perf_event *event | ||||||
| 
 | 
 | ||||||
| 	map = map__new(&machine->user_dsos, event->mmap.start, | 	map = map__new(&machine->user_dsos, event->mmap.start, | ||||||
| 			event->mmap.len, event->mmap.pgoff, | 			event->mmap.len, event->mmap.pgoff, | ||||||
| 			event->mmap.pid, 0, 0, 0, 0, | 			event->mmap.pid, 0, 0, 0, 0, 0, 0, | ||||||
| 			event->mmap.filename, | 			event->mmap.filename, | ||||||
| 			type); | 			type); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -138,7 +138,7 @@ void map__init(struct map *map, enum map_type type, | ||||||
| 
 | 
 | ||||||
| struct map *map__new(struct list_head *dsos__list, u64 start, u64 len, | struct map *map__new(struct list_head *dsos__list, u64 start, u64 len, | ||||||
| 		     u64 pgoff, u32 pid, u32 d_maj, u32 d_min, u64 ino, | 		     u64 pgoff, u32 pid, u32 d_maj, u32 d_min, u64 ino, | ||||||
| 		     u64 ino_gen, char *filename, | 		     u64 ino_gen, u32 prot, u32 flags, char *filename, | ||||||
| 		     enum map_type type) | 		     enum map_type type) | ||||||
| { | { | ||||||
| 	struct map *map = malloc(sizeof(*map)); | 	struct map *map = malloc(sizeof(*map)); | ||||||
|  | @ -157,6 +157,8 @@ struct map *map__new(struct list_head *dsos__list, u64 start, u64 len, | ||||||
| 		map->min = d_min; | 		map->min = d_min; | ||||||
| 		map->ino = ino; | 		map->ino = ino; | ||||||
| 		map->ino_generation = ino_gen; | 		map->ino_generation = ino_gen; | ||||||
|  | 		map->prot = prot; | ||||||
|  | 		map->flags = flags; | ||||||
| 
 | 
 | ||||||
| 		if ((anon || no_dso) && type == MAP__FUNCTION) { | 		if ((anon || no_dso) && type == MAP__FUNCTION) { | ||||||
| 			snprintf(newfilename, sizeof(newfilename), "/tmp/perf-%d.map", pid); | 			snprintf(newfilename, sizeof(newfilename), "/tmp/perf-%d.map", pid); | ||||||
|  |  | ||||||
|  | @ -35,6 +35,8 @@ struct map { | ||||||
| 	bool			referenced; | 	bool			referenced; | ||||||
| 	bool			erange_warned; | 	bool			erange_warned; | ||||||
| 	u32			priv; | 	u32			priv; | ||||||
|  | 	u32			prot; | ||||||
|  | 	u32			flags; | ||||||
| 	u64			pgoff; | 	u64			pgoff; | ||||||
| 	u64			reloc; | 	u64			reloc; | ||||||
| 	u32			maj, min; /* only valid for MMAP2 record */ | 	u32			maj, min; /* only valid for MMAP2 record */ | ||||||
|  | @ -118,7 +120,7 @@ void map__init(struct map *map, enum map_type type, | ||||||
| 	       u64 start, u64 end, u64 pgoff, struct dso *dso); | 	       u64 start, u64 end, u64 pgoff, struct dso *dso); | ||||||
| struct map *map__new(struct list_head *dsos__list, u64 start, u64 len, | struct map *map__new(struct list_head *dsos__list, u64 start, u64 len, | ||||||
| 		     u64 pgoff, u32 pid, u32 d_maj, u32 d_min, u64 ino, | 		     u64 pgoff, u32 pid, u32 d_maj, u32 d_min, u64 ino, | ||||||
| 		     u64 ino_gen, | 		     u64 ino_gen, u32 prot, u32 flags, | ||||||
| 		     char *filename, enum map_type type); | 		     char *filename, enum map_type type); | ||||||
| struct map *map__new2(u64 start, struct dso *dso, enum map_type type); | struct map *map__new2(u64 start, struct dso *dso, enum map_type type); | ||||||
| void map__delete(struct map *map); | void map__delete(struct map *map); | ||||||
|  |  | ||||||
|  | @ -1,11 +1,15 @@ | ||||||
| #include <errno.h> | #include <errno.h> | ||||||
| #include "perf_regs.h" | #include "perf_regs.h" | ||||||
|  | #include "event.h" | ||||||
| 
 | 
 | ||||||
| int perf_reg_value(u64 *valp, struct regs_dump *regs, int id) | int perf_reg_value(u64 *valp, struct regs_dump *regs, int id) | ||||||
| { | { | ||||||
| 	int i, idx = 0; | 	int i, idx = 0; | ||||||
| 	u64 mask = regs->mask; | 	u64 mask = regs->mask; | ||||||
| 
 | 
 | ||||||
|  | 	if (regs->cache_mask & (1 << id)) | ||||||
|  | 		goto out; | ||||||
|  | 
 | ||||||
| 	if (!(mask & (1 << id))) | 	if (!(mask & (1 << id))) | ||||||
| 		return -EINVAL; | 		return -EINVAL; | ||||||
| 
 | 
 | ||||||
|  | @ -14,6 +18,10 @@ int perf_reg_value(u64 *valp, struct regs_dump *regs, int id) | ||||||
| 			idx++; | 			idx++; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| 	*valp = regs->regs[idx]; | 	regs->cache_mask |= (1 << id); | ||||||
|  | 	regs->cache_regs[id] = regs->regs[idx]; | ||||||
|  | 
 | ||||||
|  | out: | ||||||
|  | 	*valp = regs->cache_regs[id]; | ||||||
| 	return 0; | 	return 0; | ||||||
| } | } | ||||||
|  |  | ||||||
|  | @ -2,7 +2,8 @@ | ||||||
| #define __PERF_REGS_H | #define __PERF_REGS_H | ||||||
| 
 | 
 | ||||||
| #include <linux/types.h> | #include <linux/types.h> | ||||||
| #include "event.h" | 
 | ||||||
|  | struct regs_dump; | ||||||
| 
 | 
 | ||||||
| #ifdef HAVE_PERF_REGS_SUPPORT | #ifdef HAVE_PERF_REGS_SUPPORT | ||||||
| #include <perf_regs.h> | #include <perf_regs.h> | ||||||
|  | @ -11,6 +12,7 @@ int perf_reg_value(u64 *valp, struct regs_dump *regs, int id); | ||||||
| 
 | 
 | ||||||
| #else | #else | ||||||
| #define PERF_REGS_MASK	0 | #define PERF_REGS_MASK	0 | ||||||
|  | #define PERF_REGS_MAX	0 | ||||||
| 
 | 
 | ||||||
| static inline const char *perf_reg_name(int id __maybe_unused) | static inline const char *perf_reg_name(int id __maybe_unused) | ||||||
| { | { | ||||||
|  |  | ||||||
|  | @ -628,11 +628,11 @@ static int __show_line_range(struct line_range *lr, const char *module) | ||||||
| 
 | 
 | ||||||
| 	ret = debuginfo__find_line_range(dinfo, lr); | 	ret = debuginfo__find_line_range(dinfo, lr); | ||||||
| 	debuginfo__delete(dinfo); | 	debuginfo__delete(dinfo); | ||||||
| 	if (ret == 0) { | 	if (ret == 0 || ret == -ENOENT) { | ||||||
| 		pr_warning("Specified source line is not found.\n"); | 		pr_warning("Specified source line is not found.\n"); | ||||||
| 		return -ENOENT; | 		return -ENOENT; | ||||||
| 	} else if (ret < 0) { | 	} else if (ret < 0) { | ||||||
| 		pr_warning("Debuginfo analysis failed. (%d)\n", ret); | 		pr_warning("Debuginfo analysis failed.\n"); | ||||||
| 		return ret; | 		return ret; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -641,7 +641,7 @@ static int __show_line_range(struct line_range *lr, const char *module) | ||||||
| 	ret = get_real_path(tmp, lr->comp_dir, &lr->path); | 	ret = get_real_path(tmp, lr->comp_dir, &lr->path); | ||||||
| 	free(tmp);	/* Free old path */ | 	free(tmp);	/* Free old path */ | ||||||
| 	if (ret < 0) { | 	if (ret < 0) { | ||||||
| 		pr_warning("Failed to find source file. (%d)\n", ret); | 		pr_warning("Failed to find source file path.\n"); | ||||||
| 		return ret; | 		return ret; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
|  | @ -721,9 +721,14 @@ static int show_available_vars_at(struct debuginfo *dinfo, | ||||||
| 	ret = debuginfo__find_available_vars_at(dinfo, pev, &vls, | 	ret = debuginfo__find_available_vars_at(dinfo, pev, &vls, | ||||||
| 						max_vls, externs); | 						max_vls, externs); | ||||||
| 	if (ret <= 0) { | 	if (ret <= 0) { | ||||||
| 		pr_err("Failed to find variables at %s (%d)\n", buf, ret); | 		if (ret == 0 || ret == -ENOENT) { | ||||||
|  | 			pr_err("Failed to find the address of %s\n", buf); | ||||||
|  | 			ret = -ENOENT; | ||||||
|  | 		} else | ||||||
|  | 			pr_warning("Debuginfo analysis failed.\n"); | ||||||
| 		goto end; | 		goto end; | ||||||
| 	} | 	} | ||||||
|  | 
 | ||||||
| 	/* Some variables are found */ | 	/* Some variables are found */ | ||||||
| 	fprintf(stdout, "Available variables at %s\n", buf); | 	fprintf(stdout, "Available variables at %s\n", buf); | ||||||
| 	for (i = 0; i < ret; i++) { | 	for (i = 0; i < ret; i++) { | ||||||
|  |  | ||||||
|  | @ -573,14 +573,13 @@ static int find_variable(Dwarf_Die *sc_die, struct probe_finder *pf) | ||||||
| 	if (!die_find_variable_at(sc_die, pf->pvar->var, pf->addr, &vr_die)) { | 	if (!die_find_variable_at(sc_die, pf->pvar->var, pf->addr, &vr_die)) { | ||||||
| 		/* Search again in global variables */ | 		/* Search again in global variables */ | ||||||
| 		if (!die_find_variable_at(&pf->cu_die, pf->pvar->var, 0, &vr_die)) | 		if (!die_find_variable_at(&pf->cu_die, pf->pvar->var, 0, &vr_die)) | ||||||
|  | 			pr_warning("Failed to find '%s' in this function.\n", | ||||||
|  | 				   pf->pvar->var); | ||||||
| 			ret = -ENOENT; | 			ret = -ENOENT; | ||||||
| 	} | 	} | ||||||
| 	if (ret >= 0) | 	if (ret >= 0) | ||||||
| 		ret = convert_variable(&vr_die, pf); | 		ret = convert_variable(&vr_die, pf); | ||||||
| 
 | 
 | ||||||
| 	if (ret < 0) |  | ||||||
| 		pr_warning("Failed to find '%s' in this function.\n", |  | ||||||
| 			   pf->pvar->var); |  | ||||||
| 	return ret; | 	return ret; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | @ -1281,7 +1280,11 @@ out: | ||||||
| 	return ret; | 	return ret; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| /* Find available variables at given probe point */ | /*
 | ||||||
|  |  * Find available variables at given probe point | ||||||
|  |  * Return the number of found probe points. Return 0 if there is no | ||||||
|  |  * matched probe point. Return <0 if an error occurs. | ||||||
|  |  */ | ||||||
| int debuginfo__find_available_vars_at(struct debuginfo *dbg, | int debuginfo__find_available_vars_at(struct debuginfo *dbg, | ||||||
| 				      struct perf_probe_event *pev, | 				      struct perf_probe_event *pev, | ||||||
| 				      struct variable_list **vls, | 				      struct variable_list **vls, | ||||||
|  |  | ||||||
|  | @ -215,6 +215,7 @@ static void define_event_symbols(struct event_format *event, | ||||||
| 	case PRINT_BSTRING: | 	case PRINT_BSTRING: | ||||||
| 	case PRINT_DYNAMIC_ARRAY: | 	case PRINT_DYNAMIC_ARRAY: | ||||||
| 	case PRINT_STRING: | 	case PRINT_STRING: | ||||||
|  | 	case PRINT_BITMASK: | ||||||
| 		break; | 		break; | ||||||
| 	case PRINT_TYPE: | 	case PRINT_TYPE: | ||||||
| 		define_event_symbols(event, ev_name, args->typecast.item); | 		define_event_symbols(event, ev_name, args->typecast.item); | ||||||
|  |  | ||||||
|  | @ -197,6 +197,7 @@ static void define_event_symbols(struct event_format *event, | ||||||
| 	case PRINT_BSTRING: | 	case PRINT_BSTRING: | ||||||
| 	case PRINT_DYNAMIC_ARRAY: | 	case PRINT_DYNAMIC_ARRAY: | ||||||
| 	case PRINT_FUNC: | 	case PRINT_FUNC: | ||||||
|  | 	case PRINT_BITMASK: | ||||||
| 		/* we should warn... */ | 		/* we should warn... */ | ||||||
| 		return; | 		return; | ||||||
| 	} | 	} | ||||||
|  | @ -622,6 +623,7 @@ static int python_generate_script(struct pevent *pevent, const char *outfile) | ||||||
| 			fprintf(ofp, "%s=", f->name); | 			fprintf(ofp, "%s=", f->name); | ||||||
| 			if (f->flags & FIELD_IS_STRING || | 			if (f->flags & FIELD_IS_STRING || | ||||||
| 			    f->flags & FIELD_IS_FLAG || | 			    f->flags & FIELD_IS_FLAG || | ||||||
|  | 			    f->flags & FIELD_IS_ARRAY || | ||||||
| 			    f->flags & FIELD_IS_SYMBOLIC) | 			    f->flags & FIELD_IS_SYMBOLIC) | ||||||
| 				fprintf(ofp, "%%s"); | 				fprintf(ofp, "%%s"); | ||||||
| 			else if (f->flags & FIELD_IS_SIGNED) | 			else if (f->flags & FIELD_IS_SIGNED) | ||||||
|  |  | ||||||
|  | @ -1,3 +1,4 @@ | ||||||
|  | #include <sys/mman.h> | ||||||
| #include "sort.h" | #include "sort.h" | ||||||
| #include "hist.h" | #include "hist.h" | ||||||
| #include "comm.h" | #include "comm.h" | ||||||
|  | @ -784,6 +785,104 @@ static int hist_entry__snoop_snprintf(struct hist_entry *he, char *bf, | ||||||
| 	return repsep_snprintf(bf, size, "%-*s", width, out); | 	return repsep_snprintf(bf, size, "%-*s", width, out); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
|  | static inline  u64 cl_address(u64 address) | ||||||
|  | { | ||||||
|  | 	/* return the cacheline of the address */ | ||||||
|  | 	return (address & ~(cacheline_size - 1)); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int64_t | ||||||
|  | sort__dcacheline_cmp(struct hist_entry *left, struct hist_entry *right) | ||||||
|  | { | ||||||
|  | 	u64 l, r; | ||||||
|  | 	struct map *l_map, *r_map; | ||||||
|  | 
 | ||||||
|  | 	if (!left->mem_info)  return -1; | ||||||
|  | 	if (!right->mem_info) return 1; | ||||||
|  | 
 | ||||||
|  | 	/* group event types together */ | ||||||
|  | 	if (left->cpumode > right->cpumode) return -1; | ||||||
|  | 	if (left->cpumode < right->cpumode) return 1; | ||||||
|  | 
 | ||||||
|  | 	l_map = left->mem_info->daddr.map; | ||||||
|  | 	r_map = right->mem_info->daddr.map; | ||||||
|  | 
 | ||||||
|  | 	/* if both are NULL, jump to sort on al_addr instead */ | ||||||
|  | 	if (!l_map && !r_map) | ||||||
|  | 		goto addr; | ||||||
|  | 
 | ||||||
|  | 	if (!l_map) return -1; | ||||||
|  | 	if (!r_map) return 1; | ||||||
|  | 
 | ||||||
|  | 	if (l_map->maj > r_map->maj) return -1; | ||||||
|  | 	if (l_map->maj < r_map->maj) return 1; | ||||||
|  | 
 | ||||||
|  | 	if (l_map->min > r_map->min) return -1; | ||||||
|  | 	if (l_map->min < r_map->min) return 1; | ||||||
|  | 
 | ||||||
|  | 	if (l_map->ino > r_map->ino) return -1; | ||||||
|  | 	if (l_map->ino < r_map->ino) return 1; | ||||||
|  | 
 | ||||||
|  | 	if (l_map->ino_generation > r_map->ino_generation) return -1; | ||||||
|  | 	if (l_map->ino_generation < r_map->ino_generation) return 1; | ||||||
|  | 
 | ||||||
|  | 	/*
 | ||||||
|  | 	 * Addresses with no major/minor numbers are assumed to be | ||||||
|  | 	 * anonymous in userspace.  Sort those on pid then address. | ||||||
|  | 	 * | ||||||
|  | 	 * The kernel and non-zero major/minor mapped areas are | ||||||
|  | 	 * assumed to be unity mapped.  Sort those on address. | ||||||
|  | 	 */ | ||||||
|  | 
 | ||||||
|  | 	if ((left->cpumode != PERF_RECORD_MISC_KERNEL) && | ||||||
|  | 	    (!(l_map->flags & MAP_SHARED)) && | ||||||
|  | 	    !l_map->maj && !l_map->min && !l_map->ino && | ||||||
|  | 	    !l_map->ino_generation) { | ||||||
|  | 		/* userspace anonymous */ | ||||||
|  | 
 | ||||||
|  | 		if (left->thread->pid_ > right->thread->pid_) return -1; | ||||||
|  | 		if (left->thread->pid_ < right->thread->pid_) return 1; | ||||||
|  | 	} | ||||||
|  | 
 | ||||||
|  | addr: | ||||||
|  | 	/* al_addr does all the right addr - start + offset calculations */ | ||||||
|  | 	l = cl_address(left->mem_info->daddr.al_addr); | ||||||
|  | 	r = cl_address(right->mem_info->daddr.al_addr); | ||||||
|  | 
 | ||||||
|  | 	if (l > r) return -1; | ||||||
|  | 	if (l < r) return 1; | ||||||
|  | 
 | ||||||
|  | 	return 0; | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | static int hist_entry__dcacheline_snprintf(struct hist_entry *he, char *bf, | ||||||
|  | 					  size_t size, unsigned int width) | ||||||
|  | { | ||||||
|  | 
 | ||||||
|  | 	uint64_t addr = 0; | ||||||
|  | 	struct map *map = NULL; | ||||||
|  | 	struct symbol *sym = NULL; | ||||||
|  | 	char level = he->level; | ||||||
|  | 
 | ||||||
|  | 	if (he->mem_info) { | ||||||
|  | 		addr = cl_address(he->mem_info->daddr.al_addr); | ||||||
|  | 		map = he->mem_info->daddr.map; | ||||||
|  | 		sym = he->mem_info->daddr.sym; | ||||||
|  | 
 | ||||||
|  | 		/* print [s] for shared data mmaps */ | ||||||
|  | 		if ((he->cpumode != PERF_RECORD_MISC_KERNEL) && | ||||||
|  | 		     map && (map->type == MAP__VARIABLE) && | ||||||
|  | 		    (map->flags & MAP_SHARED) && | ||||||
|  | 		    (map->maj || map->min || map->ino || | ||||||
|  | 		     map->ino_generation)) | ||||||
|  | 			level = 's'; | ||||||
|  | 		else if (!map) | ||||||
|  | 			level = 'X'; | ||||||
|  | 	} | ||||||
|  | 	return _hist_entry__sym_snprintf(map, sym, addr, level, bf, size, | ||||||
|  | 					 width); | ||||||
|  | } | ||||||
|  | 
 | ||||||
| struct sort_entry sort_mispredict = { | struct sort_entry sort_mispredict = { | ||||||
| 	.se_header	= "Branch Mispredicted", | 	.se_header	= "Branch Mispredicted", | ||||||
| 	.se_cmp		= sort__mispredict_cmp, | 	.se_cmp		= sort__mispredict_cmp, | ||||||
|  | @ -876,6 +975,13 @@ struct sort_entry sort_mem_snoop = { | ||||||
| 	.se_width_idx	= HISTC_MEM_SNOOP, | 	.se_width_idx	= HISTC_MEM_SNOOP, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | struct sort_entry sort_mem_dcacheline = { | ||||||
|  | 	.se_header	= "Data Cacheline", | ||||||
|  | 	.se_cmp		= sort__dcacheline_cmp, | ||||||
|  | 	.se_snprintf	= hist_entry__dcacheline_snprintf, | ||||||
|  | 	.se_width_idx	= HISTC_MEM_DCACHELINE, | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| static int64_t | static int64_t | ||||||
| sort__abort_cmp(struct hist_entry *left, struct hist_entry *right) | sort__abort_cmp(struct hist_entry *left, struct hist_entry *right) | ||||||
| { | { | ||||||
|  | @ -1043,6 +1149,7 @@ static struct sort_dimension memory_sort_dimensions[] = { | ||||||
| 	DIM(SORT_MEM_TLB, "tlb", sort_mem_tlb), | 	DIM(SORT_MEM_TLB, "tlb", sort_mem_tlb), | ||||||
| 	DIM(SORT_MEM_LVL, "mem", sort_mem_lvl), | 	DIM(SORT_MEM_LVL, "mem", sort_mem_lvl), | ||||||
| 	DIM(SORT_MEM_SNOOP, "snoop", sort_mem_snoop), | 	DIM(SORT_MEM_SNOOP, "snoop", sort_mem_snoop), | ||||||
|  | 	DIM(SORT_MEM_DCACHELINE, "dcacheline", sort_mem_dcacheline), | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| #undef DIM | #undef DIM | ||||||
|  |  | ||||||
|  | @ -89,6 +89,7 @@ struct hist_entry { | ||||||
| 	u64			ip; | 	u64			ip; | ||||||
| 	u64			transaction; | 	u64			transaction; | ||||||
| 	s32			cpu; | 	s32			cpu; | ||||||
|  | 	u8			cpumode; | ||||||
| 
 | 
 | ||||||
| 	struct hist_entry_diff	diff; | 	struct hist_entry_diff	diff; | ||||||
| 
 | 
 | ||||||
|  | @ -185,6 +186,7 @@ enum sort_type { | ||||||
| 	SORT_MEM_TLB, | 	SORT_MEM_TLB, | ||||||
| 	SORT_MEM_LVL, | 	SORT_MEM_LVL, | ||||||
| 	SORT_MEM_SNOOP, | 	SORT_MEM_SNOOP, | ||||||
|  | 	SORT_MEM_DCACHELINE, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| /*
 | /*
 | ||||||
|  |  | ||||||
|  | @ -250,7 +250,6 @@ static int read_unwind_spec_eh_frame(struct dso *dso, struct machine *machine, | ||||||
| 
 | 
 | ||||||
| 	/* Check the .eh_frame section for unwinding info */ | 	/* Check the .eh_frame section for unwinding info */ | ||||||
| 	offset = elf_section_offset(fd, ".eh_frame_hdr"); | 	offset = elf_section_offset(fd, ".eh_frame_hdr"); | ||||||
| 	close(fd); |  | ||||||
| 
 | 
 | ||||||
| 	if (offset) | 	if (offset) | ||||||
| 		ret = unwind_spec_ehframe(dso, machine, offset, | 		ret = unwind_spec_ehframe(dso, machine, offset, | ||||||
|  | @ -271,7 +270,6 @@ static int read_unwind_spec_debug_frame(struct dso *dso, | ||||||
| 
 | 
 | ||||||
| 	/* Check the .debug_frame section for unwinding info */ | 	/* Check the .debug_frame section for unwinding info */ | ||||||
| 	*offset = elf_section_offset(fd, ".debug_frame"); | 	*offset = elf_section_offset(fd, ".debug_frame"); | ||||||
| 	close(fd); |  | ||||||
| 
 | 
 | ||||||
| 	if (*offset) | 	if (*offset) | ||||||
| 		return 0; | 		return 0; | ||||||
|  |  | ||||||
|  | @ -17,6 +17,7 @@ | ||||||
|  * XXX We need to find a better place for these things... |  * XXX We need to find a better place for these things... | ||||||
|  */ |  */ | ||||||
| unsigned int page_size; | unsigned int page_size; | ||||||
|  | int cacheline_size; | ||||||
| 
 | 
 | ||||||
| bool test_attr__enabled; | bool test_attr__enabled; | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
|  | @ -304,6 +304,7 @@ char *rtrim(char *s); | ||||||
| void dump_stack(void); | void dump_stack(void); | ||||||
| 
 | 
 | ||||||
| extern unsigned int page_size; | extern unsigned int page_size; | ||||||
|  | extern int cacheline_size; | ||||||
| 
 | 
 | ||||||
| void get_term_dimensions(struct winsize *ws); | void get_term_dimensions(struct winsize *ws); | ||||||
| 
 | 
 | ||||||
|  |  | ||||||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 Ingo Molnar
				Ingo Molnar