diff options
Diffstat (limited to 'tools')
113 files changed, 7551 insertions, 1668 deletions
diff --git a/tools/lib/lk/Makefile b/tools/lib/lk/Makefile index 280dd8205430..3dba0a4aebbf 100644 --- a/tools/lib/lk/Makefile +++ b/tools/lib/lk/Makefile @@ -3,21 +3,6 @@ include ../../scripts/Makefile.include CC = $(CROSS_COMPILE)gcc AR = $(CROSS_COMPILE)ar -# Makefiles suck: This macro sets a default value of $(2) for the -# variable named by $(1), unless the variable has been set by -# environment or command line. This is necessary for CC and AR -# because make sets default values, so the simpler ?= approach -# won't work as expected. -define allow-override - $(if $(or $(findstring environment,$(origin $(1))),\ - $(findstring command line,$(origin $(1)))),,\ - $(eval $(1) = $(2))) -endef - -# Allow setting CC and AR, or setting CROSS_COMPILE as a prefix. -$(call allow-override,CC,$(CROSS_COMPILE)gcc) -$(call allow-override,AR,$(CROSS_COMPILE)ar) - # guard against environment variables LIB_H= LIB_OBJS= diff --git a/tools/lib/traceevent/Makefile b/tools/lib/traceevent/Makefile index 0b0a90787db6..ca6cb779876a 100644 --- a/tools/lib/traceevent/Makefile +++ b/tools/lib/traceevent/Makefile @@ -39,13 +39,8 @@ bindir_relative = bin bindir = $(prefix)/$(bindir_relative) man_dir = $(prefix)/share/man man_dir_SQ = '$(subst ','\'',$(man_dir))' -html_install = $(prefix)/share/kernelshark/html -html_install_SQ = '$(subst ','\'',$(html_install))' -img_install = $(prefix)/share/kernelshark/html/images -img_install_SQ = '$(subst ','\'',$(img_install))' -export man_dir man_dir_SQ html_install html_install_SQ INSTALL -export img_install img_install_SQ +export man_dir man_dir_SQ INSTALL export DESTDIR DESTDIR_SQ # copy a bit from Linux kbuild @@ -65,7 +60,7 @@ ifeq ($(BUILD_SRC),) ifneq ($(BUILD_OUTPUT),) define build_output - $(if $(VERBOSE:1=),@)$(MAKE) -C $(BUILD_OUTPUT) \ + $(if $(VERBOSE:1=),@)+$(MAKE) -C $(BUILD_OUTPUT) \ BUILD_SRC=$(CURDIR) -f $(CURDIR)/Makefile $1 endef @@ -76,10 +71,7 @@ $(if $(BUILD_OUTPUT),, \ all: sub-make -gui: force - $(call build_output, all_cmd) - -$(filter-out gui,$(MAKECMDGOALS)): sub-make +$(MAKECMDGOALS): sub-make sub-make: force $(call build_output, $(MAKECMDGOALS)) @@ -189,6 +181,7 @@ $(obj)/%.o: $(src)/%.c $(Q)$(call do_compile) PEVENT_LIB_OBJS = event-parse.o trace-seq.o parse-filter.o parse-utils.o +PEVENT_LIB_OBJS += kbuffer-parse.o ALL_OBJS = $(PEVENT_LIB_OBJS) @@ -258,9 +251,6 @@ define check_deps $(RM) $@.$$$$ endef -$(gui_deps): ks_version.h -$(non_gui_deps): tc_version.h - $(all_deps): .%.d: $(src)/%.c $(Q)$(call check_deps) @@ -300,7 +290,7 @@ define do_install $(INSTALL) $1 '$(DESTDIR_SQ)$2' endef -install_lib: all_cmd install_plugins install_python +install_lib: all_cmd $(Q)$(call do_install,$(LIB_FILE),$(bindir_SQ)) install: install_lib diff --git a/tools/lib/traceevent/event-parse.c b/tools/lib/traceevent/event-parse.c index 82b0606dcb8a..d1c2a6a4cd32 100644 --- a/tools/lib/traceevent/event-parse.c +++ b/tools/lib/traceevent/event-parse.c @@ -5450,10 +5450,9 @@ int pevent_register_print_function(struct pevent *pevent, * If @id is >= 0, then it is used to find the event. * else @sys_name and @event_name are used. */ -int pevent_register_event_handler(struct pevent *pevent, - int id, char *sys_name, char *event_name, - pevent_event_handler_func func, - void *context) +int pevent_register_event_handler(struct pevent *pevent, int id, + const char *sys_name, const char *event_name, + pevent_event_handler_func func, void *context) { struct event_format *event; struct event_handler *handle; diff --git a/tools/lib/traceevent/event-parse.h b/tools/lib/traceevent/event-parse.h index 7be7e89533e4..c37b2026d04a 100644 --- a/tools/lib/traceevent/event-parse.h +++ b/tools/lib/traceevent/event-parse.h @@ -69,6 +69,7 @@ struct trace_seq { }; void trace_seq_init(struct trace_seq *s); +void trace_seq_reset(struct trace_seq *s); void trace_seq_destroy(struct trace_seq *s); extern int trace_seq_printf(struct trace_seq *s, const char *fmt, ...) @@ -399,6 +400,7 @@ struct pevent { int cpus; int long_size; + int page_size; struct cmdline *cmdlines; struct cmdline_list *cmdlist; @@ -561,7 +563,8 @@ int pevent_print_num_field(struct trace_seq *s, const char *fmt, struct event_format *event, const char *name, struct pevent_record *record, int err); -int pevent_register_event_handler(struct pevent *pevent, int id, char *sys_name, char *event_name, +int pevent_register_event_handler(struct pevent *pevent, int id, + const char *sys_name, const char *event_name, pevent_event_handler_func func, void *context); int pevent_register_print_function(struct pevent *pevent, pevent_func_handler func, @@ -619,6 +622,16 @@ static inline void pevent_set_long_size(struct pevent *pevent, int long_size) pevent->long_size = long_size; } +static inline int pevent_get_page_size(struct pevent *pevent) +{ + return pevent->page_size; +} + +static inline void pevent_set_page_size(struct pevent *pevent, int _page_size) +{ + pevent->page_size = _page_size; +} + static inline int pevent_is_file_bigendian(struct pevent *pevent) { return pevent->file_bigendian; diff --git a/tools/lib/traceevent/kbuffer-parse.c b/tools/lib/traceevent/kbuffer-parse.c new file mode 100644 index 000000000000..dcc665228c71 --- /dev/null +++ b/tools/lib/traceevent/kbuffer-parse.c @@ -0,0 +1,732 @@ +/* + * Copyright (C) 2009, 2010 Red Hat Inc, Steven Rostedt <srostedt@redhat.com> + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License (not later!) + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ +#include <stdio.h> +#include <stdlib.h> +#include <string.h> + +#include "kbuffer.h" + +#define MISSING_EVENTS (1 << 31) +#define MISSING_STORED (1 << 30) + +#define COMMIT_MASK ((1 << 27) - 1) + +enum { + KBUFFER_FL_HOST_BIG_ENDIAN = (1<<0), + KBUFFER_FL_BIG_ENDIAN = (1<<1), + KBUFFER_FL_LONG_8 = (1<<2), + KBUFFER_FL_OLD_FORMAT = (1<<3), +}; + +#define ENDIAN_MASK (KBUFFER_FL_HOST_BIG_ENDIAN | KBUFFER_FL_BIG_ENDIAN) + +/** kbuffer + * @timestamp - timestamp of current event + * @lost_events - # of lost events between this subbuffer and previous + * @flags - special flags of the kbuffer + * @subbuffer - pointer to the sub-buffer page + * @data - pointer to the start of data on the sub-buffer page + * @index - index from @data to the @curr event data + * @curr - offset from @data to the start of current event + * (includes metadata) + * @next - offset from @data to the start of next event + * @size - The size of data on @data + * @start - The offset from @subbuffer where @data lives + * + * @read_4 - Function to read 4 raw bytes (may swap) + * @read_8 - Function to read 8 raw bytes (may swap) + * @read_long - Function to read a long word (4 or 8 bytes with needed swap) + */ +struct kbuffer { + unsigned long long timestamp; + long long lost_events; + unsigned long flags; + void *subbuffer; + void *data; + unsigned int index; + unsigned int curr; + unsigned int next; + unsigned int size; + unsigned int start; + + unsigned int (*read_4)(void *ptr); + unsigned long long (*read_8)(void *ptr); + unsigned long long (*read_long)(struct kbuffer *kbuf, void *ptr); + int (*next_event)(struct kbuffer *kbuf); +}; + +static void *zmalloc(size_t size) +{ + return calloc(1, size); +} + +static int host_is_bigendian(void) +{ + unsigned char str[] = { 0x1, 0x2, 0x3, 0x4 }; + unsigned int *ptr; + + ptr = (unsigned int *)str; + return *ptr == 0x01020304; +} + +static int do_swap(struct kbuffer *kbuf) +{ + return ((kbuf->flags & KBUFFER_FL_HOST_BIG_ENDIAN) + kbuf->flags) & + ENDIAN_MASK; +} + +static unsigned long long __read_8(void *ptr) +{ + unsigned long long data = *(unsigned long long *)ptr; + + return data; +} + +static unsigned long long __read_8_sw(void *ptr) +{ + unsigned long long data = *(unsigned long long *)ptr; + unsigned long long swap; + + swap = ((data & 0xffULL) << 56) | + ((data & (0xffULL << 8)) << 40) | + ((data & (0xffULL << 16)) << 24) | + ((data & (0xffULL << 24)) << 8) | + ((data & (0xffULL << 32)) >> 8) | + ((data & (0xffULL << 40)) >> 24) | + ((data & (0xffULL << 48)) >> 40) | + ((data & (0xffULL << 56)) >> 56); + + return swap; +} + +static unsigned int __read_4(void *ptr) +{ + unsigned int data = *(unsigned int *)ptr; + + return data; +} + +static unsigned int __read_4_sw(void *ptr) +{ + unsigned int data = *(unsigned int *)ptr; + unsigned int swap; + + swap = ((data & 0xffULL) << 24) | + ((data & (0xffULL << 8)) << 8) | + ((data & (0xffULL << 16)) >> 8) | + ((data & (0xffULL << 24)) >> 24); + + return swap; +} + +static unsigned long long read_8(struct kbuffer *kbuf, void *ptr) +{ + return kbuf->read_8(ptr); +} + +static unsigned int read_4(struct kbuffer *kbuf, void *ptr) +{ + return kbuf->read_4(ptr); +} + +static unsigned long long __read_long_8(struct kbuffer *kbuf, void *ptr) +{ + return kbuf->read_8(ptr); +} + +static unsigned long long __read_long_4(struct kbuffer *kbuf, void *ptr) +{ + return kbuf->read_4(ptr); +} + +static unsigned long long read_long(struct kbuffer *kbuf, void *ptr) +{ + return kbuf->read_long(kbuf, ptr); +} + +static int calc_index(struct kbuffer *kbuf, void *ptr) +{ + return (unsigned long)ptr - (unsigned long)kbuf->data; +} + +static int __next_event(struct kbuffer *kbuf); + +/** + * kbuffer_alloc - allocat a new kbuffer + * @size; enum to denote size of word + * @endian: enum to denote endianness + * + * Allocates and returns a new kbuffer. + */ +struct kbuffer * +kbuffer_alloc(enum kbuffer_long_size size, enum kbuffer_endian endian) +{ + struct kbuffer *kbuf; + int flags = 0; + + switch (size) { + case KBUFFER_LSIZE_4: + break; + case KBUFFER_LSIZE_8: + flags |= KBUFFER_FL_LONG_8; + break; + default: + return NULL; + } + + switch (endian) { + case KBUFFER_ENDIAN_LITTLE: + break; + case KBUFFER_ENDIAN_BIG: + flags |= KBUFFER_FL_BIG_ENDIAN; + break; + default: + return NULL; + } + + kbuf = zmalloc(sizeof(*kbuf)); + if (!kbuf) + return NULL; + + kbuf->flags = flags; + + if (host_is_bigendian()) + kbuf->flags |= KBUFFER_FL_HOST_BIG_ENDIAN; + + if (do_swap(kbuf)) { + kbuf->read_8 = __read_8_sw; + kbuf->read_4 = __read_4_sw; + } else { + kbuf->read_8 = __read_8; + kbuf->read_4 = __read_4; + } + + if (kbuf->flags & KBUFFER_FL_LONG_8) + kbuf->read_long = __read_long_8; + else + kbuf->read_long = __read_long_4; + + /* May be changed by kbuffer_set_old_format() */ + kbuf->next_event = __next_event; + + return kbuf; +} + +/** kbuffer_free - free an allocated kbuffer + * @kbuf: The kbuffer to free + * + * Can take NULL as a parameter. + */ +void kbuffer_free(struct kbuffer *kbuf) +{ + free(kbuf); +} + +static unsigned int type4host(struct kbuffer *kbuf, + unsigned int type_len_ts) +{ + if (kbuf->flags & KBUFFER_FL_BIG_ENDIAN) + return (type_len_ts >> 29) & 3; + else + return type_len_ts & 3; +} + +static unsigned int len4host(struct kbuffer *kbuf, + unsigned int type_len_ts) +{ + if (kbuf->flags & KBUFFER_FL_BIG_ENDIAN) + return (type_len_ts >> 27) & 7; + else + return (type_len_ts >> 2) & 7; +} + +static unsigned int type_len4host(struct kbuffer *kbuf, + unsigned int type_len_ts) +{ + if (kbuf->flags & KBUFFER_FL_BIG_ENDIAN) + return (type_len_ts >> 27) & ((1 << 5) - 1); + else + return type_len_ts & ((1 << 5) - 1); +} + +static unsigned int ts4host(struct kbuffer *kbuf, + unsigned int type_len_ts) +{ + if (kbuf->flags & KBUFFER_FL_BIG_ENDIAN) + return type_len_ts & ((1 << 27) - 1); + else + return type_len_ts >> 5; +} + +/* + * Linux 2.6.30 and earlier (not much ealier) had a different + * ring buffer format. It should be obsolete, but we handle it anyway. + */ +enum old_ring_buffer_type { + OLD_RINGBUF_TYPE_PADDING, + OLD_RINGBUF_TYPE_TIME_EXTEND, + OLD_RINGBUF_TYPE_TIME_STAMP, + OLD_RINGBUF_TYPE_DATA, +}; + +static unsigned int old_update_pointers(struct kbuffer *kbuf) +{ + unsigned long long extend; + unsigned int type_len_ts; + unsigned int type; + unsigned int len; + unsigned int delta; + unsigned int length; + void *ptr = kbuf->data + kbuf->curr; + + type_len_ts = read_4(kbuf, ptr); + ptr += 4; + + type = type4host(kbuf, type_len_ts); + len = len4host(kbuf, type_len_ts); + delta = ts4host(kbuf, type_len_ts); + + switch (type) { + case OLD_RINGBUF_TYPE_PADDING: + kbuf->next = kbuf->size; + return 0; + + case OLD_RINGBUF_TYPE_TIME_EXTEND: + extend = read_4(kbuf, ptr); + extend <<= TS_SHIFT; + extend += delta; + delta = extend; + ptr += 4; + break; + + case OLD_RINGBUF_TYPE_TIME_STAMP: + /* should never happen! */ + kbuf->curr = kbuf->size; + kbuf->next = kbuf->size; + kbuf->index = kbuf->size; + return -1; + default: + if (len) + length = len * 4; + else { + length = read_4(kbuf, ptr); + length -= 4; + ptr += 4; + } + break; + } + + kbuf->timestamp += delta; + kbuf->index = calc_index(kbuf, ptr); + kbuf->next = kbuf->index + length; + + return type; +} + +static int __old_next_event(struct kbuffer *kbuf) +{ + int type; + + do { + kbuf->curr = kbuf->next; + if (kbuf->next >= kbuf->size) + return -1; + type = old_update_pointers(kbuf); + } while (type == OLD_RINGBUF_TYPE_TIME_EXTEND || type == OLD_RINGBUF_TYPE_PADDING); + + return 0; +} + +static unsigned int +translate_data(struct kbuffer *kbuf, void *data, void **rptr, + unsigned long long *delta, int *length) +{ + unsigned long long extend; + unsigned int type_len_ts; + unsigned int type_len; + + type_len_ts = read_4(kbuf, data); + data += 4; + + type_len = type_len4host(kbuf, type_len_ts); + *delta = ts4host(kbuf, type_len_ts); + + switch (type_len) { + case KBUFFER_TYPE_PADDING: + *length = read_4(kbuf, data); + data += *length; + break; + + case KBUFFER_TYPE_TIME_EXTEND: + extend = read_4(kbuf, data); + data += 4; + extend <<= TS_SHIFT; + extend += *delta; + *delta = extend; + *length = 0; + break; + + case KBUFFER_TYPE_TIME_STAMP: + data += 12; + *length = 0; + break; + case 0: + *length = read_4(kbuf, data) - 4; + *length = (*length + 3) & ~3; + data += 4; + break; + default: + *length = type_len * 4; + break; + } + + *rptr = data; + + return type_len; +} + +static unsigned int update_pointers(struct kbuffer *kbuf) +{ + unsigned long long delta; + unsigned int type_len; + int length; + void *ptr = kbuf->data + kbuf->curr; + + type_len = translate_data(kbuf, ptr, &ptr, &delta, &length); + + kbuf->timestamp += delta; + kbuf->index = calc_index(kbuf, ptr); + kbuf->next = kbuf->index + length; + + return type_len; +} + +/** + * kbuffer_translate_data - read raw data to get a record + * @swap: Set to 1 if bytes in words need to be swapped when read + * @data: The raw data to read + * @size: Address to store the size of the event data. + * + * Returns a pointer to the event data. To determine the entire + * record size (record metadata + data) just add the difference between + * @data and the returned value to @size. + */ +void *kbuffer_translate_data(int swap, void *data, unsigned int *size) +{ + unsigned long long delta; + struct kbuffer kbuf; + int type_len; + int length; + void *ptr; + + if (swap) { + kbuf.read_8 = __read_8_sw; + kbuf.read_4 = __read_4_sw; + kbuf.flags = host_is_bigendian() ? 0 : KBUFFER_FL_BIG_ENDIAN; + } else { + kbuf.read_8 = __read_8; + kbuf.read_4 = __read_4; + kbuf.flags = host_is_bigendian() ? KBUFFER_FL_BIG_ENDIAN: 0; + } + + type_len = translate_data(&kbuf, data, &ptr, &delta, &length); + switch (type_len) { + case KBUFFER_TYPE_PADDING: + case KBUFFER_TYPE_TIME_EXTEND: + case KBUFFER_TYPE_TIME_STAMP: + return NULL; + }; + + *size = length; + + return ptr; +} + +static int __next_event(struct kbuffer *kbuf) +{ + int type; + + do { + kbuf->curr = kbuf->next; + if (kbuf->next >= kbuf->size) + return -1; + type = update_pointers(kbuf); + } while (type == KBUFFER_TYPE_TIME_EXTEND || type == KBUFFER_TYPE_PADDING); + + return 0; +} + +static int next_event(struct kbuffer *kbuf) +{ + return kbuf->next_event(kbuf); +} + +/** + * kbuffer_next_event - increment the current pointer + * @kbuf: The kbuffer to read + * @ts: Address to store the next record's timestamp (may be NULL to ignore) + * + * Increments the pointers into the subbuffer of the kbuffer to point to the + * next event so that the next kbuffer_read_event() will return a + * new event. + * + * Returns the data of the next event if a new event exists on the subbuffer, + * NULL otherwise. + */ +void *kbuffer_next_event(struct kbuffer *kbuf, unsigned long long *ts) +{ + int ret; + + if (!kbuf || !kbuf->subbuffer) + return NULL; + + ret = next_event(kbuf); + if (ret < 0) + return NULL; + + if (ts) + *ts = kbuf->timestamp; + + return kbuf->data + kbuf->index; +} + +/** + * kbuffer_load_subbuffer - load a new subbuffer into the kbuffer + * @kbuf: The kbuffer to load + * @subbuffer: The subbuffer to load into @kbuf. + * + * Load a new subbuffer (page) into @kbuf. This will reset all + * the pointers and update the @kbuf timestamp. The next read will + * return the first event on @subbuffer. + * + * Returns 0 on succes, -1 otherwise. + */ +int kbuffer_load_subbuffer(struct kbuffer *kbuf, void *subbuffer) +{ + unsigned long long flags; + void *ptr = subbuffer; + + if (!kbuf || !subbuffer) + return -1; + + kbuf->subbuffer = subbuffer; + + kbuf->timestamp = read_8(kbuf, ptr); + ptr += 8; + + kbuf->curr = 0; + + if (kbuf->flags & KBUFFER_FL_LONG_8) + kbuf->start = 16; + else + kbuf->start = 12; + + kbuf->data = subbuffer + kbuf->start; + + flags = read_long(kbuf, ptr); + kbuf->size = (unsigned int)flags & COMMIT_MASK; + + if (flags & MISSING_EVENTS) { + if (flags & MISSING_STORED) { + ptr = kbuf->data + kbuf->size; + kbuf->lost_events = read_long(kbuf, ptr); + } else + kbuf->lost_events = -1; + } else + kbuf->lost_events = 0; + + kbuf->index = 0; + kbuf->next = 0; + + next_event(kbuf); + + return 0; +} + +/** + * kbuffer_read_event - read the next event in the kbuffer subbuffer + * @kbuf: The kbuffer to read from + * @ts: The address to store the timestamp of the event (may be NULL to ignore) + * + * Returns a pointer to the data part of the current event. + * NULL if no event is left on the subbuffer. + */ +void *kbuffer_read_event(struct kbuffer *kbuf, unsigned long long *ts) +{ + if (!kbuf || !kbuf->subbuffer) + return NULL; + + if (kbuf->curr >= kbuf->size) + return NULL; + + if (ts) + *ts = kbuf->timestamp; + return kbuf->data + kbuf->index; +} + +/** + * kbuffer_timestamp - Return the timestamp of the current event + * @kbuf: The kbuffer to read from + * + * Returns the timestamp of the current (next) event. + */ +unsigned long long kbuffer_timestamp(struct kbuffer *kbuf) +{ + return kbuf->timestamp; +} + +/** + * kbuffer_read_at_offset - read the event that is at offset + * @kbuf: The kbuffer to read from + * @offset: The offset into the subbuffer + * @ts: The address to store the timestamp of the event (may be NULL to ignore) + * + * The @offset must be an index from the @kbuf subbuffer beginning. + * If @offset is bigger than the stored subbuffer, NULL will be returned. + * + * Returns the data of the record that is at @offset. Note, @offset does + * not need to be the start of the record, the offset just needs to be + * in the record (or beginning of it). + * + * Note, the kbuf timestamp and pointers are updated to the + * returned record. That is, kbuffer_read_event() will return the same + * data and timestamp, and kbuffer_next_event() will increment from + * this record. + */ +void *kbuffer_read_at_offset(struct kbuffer *kbuf, int offset, + unsigned long long *ts) +{ + void *data; + + if (offset < kbuf->start) + offset = 0; + else + offset -= kbuf->start; + + /* Reset the buffer */ + kbuffer_load_subbuffer(kbuf, kbuf->subbuffer); + + while (kbuf->curr < offset) { + data = kbuffer_next_event(kbuf, ts); + if (!data) + break; + } + + return data; +} + +/** + * kbuffer_subbuffer_size - the size of the loaded subbuffer + * @kbuf: The kbuffer to read from + * + * Returns the size of the subbuffer. Note, this size is + * where the last event resides. The stored subbuffer may actually be + * bigger due to padding and such. + */ +int kbuffer_subbuffer_size(struct kbuffer *kbuf) +{ + return kbuf->size; +} + +/** + * kbuffer_curr_index - Return the index of the record + * @kbuf: The kbuffer to read from + * + * Returns the index from the start of the data part of + * the subbuffer to the current location. Note this is not + * from the start of the subbuffer. An index of zero will + * point to the first record. Use kbuffer_curr_offset() for + * the actually offset (that can be used by kbuffer_read_at_offset()) + */ +int kbuffer_curr_index(struct kbuffer *kbuf) +{ + return kbuf->curr; +} + +/** + * kbuffer_curr_offset - Return the offset of the record + * @kbuf: The kbuffer to read from + * + * Returns the offset from the start of the subbuffer to the + * current location. + */ +int kbuffer_curr_offset(struct kbuffer *kbuf) +{ + return kbuf->curr + kbuf->start; +} + +/** + * kbuffer_event_size - return the size of the event data + * @kbuf: The kbuffer to read + * + * Returns the size of the event data (the payload not counting + * the meta data of the record) of the current event. + */ +int kbuffer_event_size(struct kbuffer *kbuf) +{ + return kbuf->next - kbuf->index; +} + +/** + * kbuffer_curr_size - return the size of the entire record + * @kbuf: The kbuffer to read + * + * Returns the size of the entire record (meta data and payload) + * of the current event. + */ +int kbuffer_curr_size(struct kbuffer *kbuf) +{ + return kbuf->next - kbuf->curr; +} + +/** + * kbuffer_missed_events - return the # of missed events from last event. + * @kbuf: The kbuffer to read from + * + * Returns the # of missed events (if recorded) before the current + * event. Note, only events on the beginning of a subbuffer can + * have missed events, all other events within the buffer will be + * zero. + */ +int kbuffer_missed_events(struct kbuffer *kbuf) +{ + /* Only the first event can have missed events */ + if (kbuf->curr) + return 0; + + return kbuf->lost_events; +} + +/** + * kbuffer_set_old_forma - set the kbuffer to use the old format parsing + * @kbuf: The kbuffer to set + * + * This is obsolete (or should be). The first kernels to use the + * new ring buffer had a slightly different ring buffer format + * (2.6.30 and earlier). It is still somewhat supported by kbuffer, + * but should not be counted on in the future. + */ +void kbuffer_set_old_format(struct kbuffer *kbuf) +{ + kbuf->flags |= KBUFFER_FL_OLD_FORMAT; + + kbuf->next_event = __old_next_event; +} diff --git a/tools/lib/traceevent/kbuffer.h b/tools/lib/traceevent/kbuffer.h new file mode 100644 index 000000000000..c831f64b17a0 --- /dev/null +++ b/tools/lib/traceevent/kbuffer.h @@ -0,0 +1,67 @@ +/* + * Copyright (C) 2012 Red Hat Inc, Steven Rostedt <srostedt@redhat.com> + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * This program is free software; you can redistribute it and/or + * modify it under the terms of the GNU Lesser General Public + * License as published by the Free Software Foundation; + * version 2.1 of the License (not later!) + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU Lesser General Public License for more details. + * + * You should have received a copy of the GNU Lesser General Public + * License along with this program; if not, write to the Free Software + * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + * + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + */ +#ifndef _KBUFFER_H +#define _KBUFFER_H + +#ifndef TS_SHIFT +#define TS_SHIFT 27 +#endif + +enum kbuffer_endian { + KBUFFER_ENDIAN_BIG, + KBUFFER_ENDIAN_LITTLE, +}; + +enum kbuffer_long_size { + KBUFFER_LSIZE_4, + KBUFFER_LSIZE_8, +}; + +enum { + KBUFFER_TYPE_PADDING = 29, + KBUFFER_TYPE_TIME_EXTEND = 30, + KBUFFER_TYPE_TIME_STAMP = 31, +}; + +struct kbuffer; + +struct kbuffer *kbuffer_alloc(enum kbuffer_long_size size, enum kbuffer_endian endian); +void kbuffer_free(struct kbuffer *kbuf); +int kbuffer_load_subbuffer(struct kbuffer *kbuf, void *subbuffer); +void *kbuffer_read_event(struct kbuffer *kbuf, unsigned long long *ts); +void *kbuffer_next_event(struct kbuffer *kbuf, unsigned long long *ts); +unsigned long long kbuffer_timestamp(struct kbuffer *kbuf); + +void *kbuffer_translate_data(int swap, void *data, unsigned int *size); + +void *kbuffer_read_at_offset(struct kbuffer *kbuf, int offset, unsigned long long *ts); + +int kbuffer_curr_index(struct kbuffer *kbuf); + +int kbuffer_curr_offset(struct kbuffer *kbuf); +int kbuffer_curr_size(struct kbuffer *kbuf); +int kbuffer_event_size(struct kbuffer *kbuf); +int kbuffer_missed_events(struct kbuffer *kbuf); +int kbuffer_subbuffer_size(struct kbuffer *kbuf); + +void kbuffer_set_old_format(struct kbuffer *kbuf); + +#endif /* _K_BUFFER_H */ diff --git a/tools/lib/traceevent/trace-seq.c b/tools/lib/traceevent/trace-seq.c index a57db805136a..d7f2e68bc5b9 100644 --- a/tools/lib/traceevent/trace-seq.c +++ b/tools/lib/traceevent/trace-seq.c @@ -49,6 +49,19 @@ void trace_seq_init(struct trace_seq *s) } /** + * trace_seq_reset - re-initialize the trace_seq structure + * @s: a pointer to the trace_seq structure to reset + */ +void trace_seq_reset(struct trace_seq *s) +{ + if (!s) + return; + TRACE_SEQ_CHECK(s); + s->len = 0; + s->readpos = 0; +} + +/** * trace_seq_destroy - free up memory of a trace_seq * @s: a pointer to the trace_seq to free the buffer * diff --git a/tools/perf/Documentation/perf-diff.txt b/tools/perf/Documentation/perf-diff.txt index 5b3123d5721f..fdfceee0ffd0 100644 --- a/tools/perf/Documentation/perf-diff.txt +++ b/tools/perf/Documentation/perf-diff.txt @@ -3,17 +3,17 @@ perf-diff(1) NAME ---- -perf-diff - Read two perf.data files and display the differential profile +perf-diff - Read perf.data files and display the differential profile SYNOPSIS -------- [verse] -'perf diff' [oldfile] [newfile] +'perf diff' [baseline file] [data file1] [[data file2] ... ] DESCRIPTION ----------- -This command displays the performance difference amongst two perf.data files -captured via perf record. +This command displays the performance difference amongst two or more perf.data +files captured via perf record. If no parameters are passed it will assume perf.data.old and perf.data. @@ -75,8 +75,6 @@ OPTIONS -c:: --compute:: Differential computation selection - delta,ratio,wdiff (default is delta). - If '+' is specified as a first character, the output is sorted based - on the computation results. See COMPARISON METHODS section for more info. -p:: @@ -87,6 +85,63 @@ OPTIONS --formula:: Show formula for given computation. +-o:: +--order:: + Specify compute sorting column number. + +COMPARISON +---------- +The comparison is governed by the baseline file. The baseline perf.data +file is iterated for samples. All other perf.data files specified on +the command line are searched for the baseline sample pair. If the pair +is found, specified computation is made and result is displayed. + +All samples from non-baseline perf.data files, that do not match any +baseline entry, are displayed with empty space within baseline column +and possible computation results (delta) in their related column. + +Example files samples: +- file A with samples f1, f2, f3, f4, f6 +- file B with samples f2, f4, f5 +- file C with samples f1, f2, f5 + +Example output: + x - computation takes place for pair + b - baseline sample percentage + +- perf diff A B C + + baseline/A compute/B compute/C samples + --------------------------------------- + b x f1 + b x x f2 + b f3 + b x f4 + b f6 + x x f5 + +- perf diff B A C + + baseline/B compute/A compute/C samples + --------------------------------------- + b x x f2 + b x f4 + b x f5 + x x f1 + x f3 + x f6 + +- perf diff C B A + + baseline/C compute/B compute/A samples + --------------------------------------- + b x f1 + b x x f2 + b x f5 + x f3 + x x f4 + x f6 + COMPARISON METHODS ------------------ delta @@ -96,7 +151,7 @@ If specified the 'Delta' column is displayed with value 'd' computed as: d = A->period_percent - B->period_percent with: - - A/B being matching hist entry from first/second file specified + - A/B being matching hist entry from data/baseline file specified (or perf.data/perf.data.old) respectively. - period_percent being the % of the hist entry period value within @@ -109,24 +164,26 @@ If specified the 'Ratio' column is displayed with value 'r' computed as: r = A->period / B->period with: - - A/B being matching hist entry from first/second file specified + - A/B being matching hist entry from data/baseline file specified (or perf.data/perf.data.old) respectively. - period being the hist entry period value -wdiff -~~~~~ +wdiff:WEIGHT-B,WEIGHT-A +~~~~~~~~~~~~~~~~~~~~~~~ If specified the 'Weighted diff' column is displayed with value 'd' computed as: d = B->period * WEIGHT-A - A->period * WEIGHT-B - - A/B being matching hist entry from first/second file specified + - A/B being matching hist entry from data/baseline file specified (or perf.data/perf.data.old) respectively. - period being the hist entry period value - WEIGHT-A/WEIGHT-B being user suplied weights in the the '-c' option behind ':' separator like '-c wdiff:1,2'. + - WIEGHT-A being the weight of the data file + - WIEGHT-B being the weight of the baseline data file SEE ALSO -------- diff --git a/tools/perf/Documentation/perf-kvm.txt b/tools/perf/Documentation/perf-kvm.txt index 326f2cb333cb..ac84db2d2334 100644 --- a/tools/perf/Documentation/perf-kvm.txt +++ b/tools/perf/Documentation/perf-kvm.txt @@ -13,6 +13,7 @@ SYNOPSIS {top|record|report|diff|buildid-list} 'perf kvm' [--host] [--guest] [--guestkallsyms=<path> --guestmodules=<path> | --guestvmlinux=<path>] {top|record|report|diff|buildid-list|stat} +'perf kvm stat [record|report|live] [<options>] DESCRIPTION ----------- @@ -50,6 +51,10 @@ There are a couple of variants of perf kvm: 'perf kvm stat report' reports statistical data which includes events handled time, samples, and so on. + 'perf kvm stat live' reports statistical data in a live mode (similar to + record + report but with statistical data updated live at a given display + rate). + OPTIONS ------- -i:: @@ -85,13 +90,50 @@ STAT REPORT OPTIONS --vcpu=<value>:: analyze events which occures on this vcpu. (default: all vcpus) ---events=<value>:: - events to be analyzed. Possible values: vmexit, mmio, ioport. +--event=<value>:: + event to be analyzed. Possible values: vmexit, mmio, ioport. (default: vmexit) -k:: --key=<value>:: Sorting key. Possible values: sample (default, sort by samples number), time (sort by average time). +-p:: +--pid=:: + Analyze events only for given process ID(s) (comma separated list). + +STAT LIVE OPTIONS +----------------- +-d:: +--display:: + Time in seconds between display updates + +-m:: +--mmap-pages=:: + Number of mmap data pages. Must be a power of two. + +-a:: +--all-cpus:: + System-wide collection from all CPUs. + +-p:: +--pid=:: + Analyze events only for given process ID(s) (comma separated list). + +--vcpu=<value>:: + analyze events which occures on this vcpu. (default: all vcpus) + + +--event=<value>:: + event to be analyzed. Possible values: vmexit, mmio, ioport. + (default: vmexit) + +-k:: +--key=<value>:: + Sorting key. Possible values: sample (default, sort by samples + number), time (sort by average time). + +--duration=<value>:: + Show events other than HLT that take longer than duration usecs. SEE ALSO -------- diff --git a/tools/perf/Documentation/perf-list.txt b/tools/perf/Documentation/perf-list.txt index d1e39dc8c810..6fce6a622206 100644 --- a/tools/perf/Documentation/perf-list.txt +++ b/tools/perf/Documentation/perf-list.txt @@ -8,7 +8,7 @@ perf-list - List all symbolic event types SYNOPSIS -------- [verse] -'perf list' [hw|sw|cache|tracepoint|event_glob] +'perf list' [hw|sw|cache|tracepoint|pmu|event_glob] DESCRIPTION ----------- @@ -29,6 +29,8 @@ counted. The following modifiers exist: G - guest counting (in KVM guests) H - host counting (not in KVM guests) p - precise level + S - read sample value (PERF_SAMPLE_READ) + D - pin the event to the PMU The 'p' modifier can be used for specifying how precise the instruction address should be. The 'p' modifier can be specified multiple times: @@ -104,6 +106,8 @@ To limit the list use: 'subsys_glob:event_glob' to filter by tracepoint subsystems such as sched, block, etc. +. 'pmu' to print the kernel supplied PMU events. + . If none of the above is matched, it will apply the supplied glob to all events, printing the ones that match. diff --git a/tools/perf/Documentation/perf-report.txt b/tools/perf/Documentation/perf-report.txt index 66dab7410c1d..2b8097ee39d8 100644 --- a/tools/perf/Documentation/perf-report.txt +++ b/tools/perf/Documentation/perf-report.txt @@ -115,7 +115,7 @@ OPTIONS --dump-raw-trace:: Dump raw trace in ASCII. --g [type,min[,limit],order]:: +-g [type,min[,limit],order[,key]]:: --call-graph:: Display call chains using type, min percent threshold, optional print limit and order. @@ -129,12 +129,21 @@ OPTIONS - callee: callee based call graph. - caller: inverted caller based call graph. - Default: fractal,0.5,callee. + key can be: + - function: compare on functions + - address: compare on individual code addresses + + Default: fractal,0.5,callee,function. -G:: --inverted:: alias for inverted caller based call graph. +--ignore-callees=<regex>:: + Ignore callees of the function(s) matching the given regex. + This has the effect of collecting the callers of each such + function into one place in the call-graph tree. + --pretty=<key>:: Pretty printing style. key: normal, raw diff --git a/tools/perf/Documentation/perf-stat.txt b/tools/perf/Documentation/perf-stat.txt index 2fe87fb558f0..73c9759005a3 100644 --- a/tools/perf/Documentation/perf-stat.txt +++ b/tools/perf/Documentation/perf-stat.txt @@ -132,6 +132,11 @@ is a useful mode to detect imbalance between physical cores. To enable this mod use --per-core in addition to -a. (system-wide). The output includes the core number and the number of online logical processors on that physical processor. +-D msecs:: +--initial-delay msecs:: +After starting the program, wait msecs before measuring. This is useful to +filter out the startup phase of the program, which is often very different. + EXAMPLES -------- diff --git a/tools/perf/Documentation/perf-top.txt b/tools/perf/Documentation/perf-top.txt index 7fdd1909e376..58d6598a9686 100644 --- a/tools/perf/Documentation/perf-top.txt +++ b/tools/perf/Documentation/perf-top.txt @@ -155,6 +155,11 @@ Default is to monitor all CPUS. Default: fractal,0.5,callee. +--ignore-callees=<regex>:: + Ignore callees of the function(s) matching the given regex. + This has the effect of collecting the callers of each such + function into one place in the call-graph tree. + --percent-limit:: Do not show entries which have an overhead under that percent. (Default: 0). diff --git a/tools/perf/Documentation/perf-trace.txt b/tools/perf/Documentation/perf-trace.txt index 68718ccdd178..daccd2c0a48f 100644 --- a/tools/perf/Documentation/perf-trace.txt +++ b/tools/perf/Documentation/perf-trace.txt @@ -23,25 +23,45 @@ analysis phases. OPTIONS ------- +-a:: --all-cpus:: System-wide collection from all CPUs. +-e:: +--expr:: + List of events to show, currently only syscall names. + Prefixing with ! shows all syscalls but the ones specified. You may + need to escape it. + +-o:: +--output=:: + Output file name. + -p:: --pid=:: Record events on existing process ID (comma separated list). +-t:: --tid=:: Record events on existing thread ID (comma separated list). +-u:: --uid=:: Record events in threads owned by uid. Name or number. +-v:: +--verbose=:: + Verbosity level. + +-i:: --no-inherit:: Child tasks do not inherit counters. +-m:: --mmap-pages=:: Number of mmap data pages. Must be a power of two. +-C:: --cpu:: Collect samples only on the list of CPUs provided. Multiple CPUs can be provided as a comma-separated list with no space: 0,1. Ranges of CPUs are specified with -: 0-2. @@ -54,6 +74,10 @@ the thread executes on the designated CPUs. Default is to monitor all CPUs. --sched: Accrue thread runtime and provide a summary at the end of the session. +-i +--input + Process events from a given perf data file. + SEE ALSO -------- linkperf:perf-record[1], linkperf:perf-script[1] diff --git a/tools/perf/Makefile b/tools/perf/Makefile index 641fccddb249..c5dc1ad1b8d7 100644 --- a/tools/perf/Makefile +++ b/tools/perf/Makefile @@ -124,7 +124,7 @@ strip-libs = $(filter-out -l%,$(1)) ifneq ($(OUTPUT),) TE_PATH=$(OUTPUT) ifneq ($(subdir),) - LK_PATH=$(objtree)/lib/lk/ + LK_PATH=$(OUTPUT)/../lib/lk/ else LK_PATH=$(OUTPUT) endif @@ -281,7 +281,7 @@ LIB_H += util/cpumap.h LIB_H += util/top.h LIB_H += $(ARCH_INCLUDE) LIB_H += util/cgroup.h -LIB_H += $(TRACE_EVENT_DIR)event-parse.h +LIB_H += $(LIB_INCLUDE)traceevent/event-parse.h LIB_H += util/target.h LIB_H += util/rblist.h LIB_H += util/intlist.h @@ -360,6 +360,7 @@ LIB_OBJS += $(OUTPUT)util/rblist.o LIB_OBJS += $(OUTPUT)util/intlist.o LIB_OBJS += $(OUTPUT)util/vdso.o LIB_OBJS += $(OUTPUT)util/stat.o +LIB_OBJS += $(OUTPUT)util/record.o LIB_OBJS += $(OUTPUT)ui/setup.o LIB_OBJS += $(OUTPUT)ui/helpline.o @@ -389,6 +390,10 @@ LIB_OBJS += $(OUTPUT)tests/bp_signal.o LIB_OBJS += $(OUTPUT)tests/bp_signal_overflow.o LIB_OBJS += $(OUTPUT)tests/task-exit.o LIB_OBJS += $(OUTPUT)tests/sw-clock.o +ifeq ($(ARCH),x86) +LIB_OBJS += $(OUTPUT)tests/perf-time-to-tsc.o +endif +LIB_OBJS += $(OUTPUT)tests/code-reading.o BUILTIN_OBJS += $(OUTPUT)builtin-annotate.o BUILTIN_OBJS += $(OUTPUT)builtin-bench.o @@ -434,6 +439,7 @@ PERFLIBS = $(LIB_FILE) $(LIBLK) $(LIBTRACEEVENT) ifneq ($(OUTPUT),) CFLAGS += -I$(OUTPUT) endif +LIB_OBJS += $(OUTPUT)tests/sample-parsing.o ifdef NO_LIBELF EXTLIBS := $(filter-out -lelf,$(EXTLIBS)) @@ -459,6 +465,7 @@ endif # NO_LIBELF ifndef NO_LIBUNWIND LIB_OBJS += $(OUTPUT)util/unwind.o endif +LIB_OBJS += $(OUTPUT)tests/keep-tracking.o ifndef NO_LIBAUDIT BUILTIN_OBJS += $(OUTPUT)builtin-trace.o @@ -631,10 +638,10 @@ $(OUTPUT)util/parse-events.o: util/parse-events.c $(OUTPUT)PERF-CFLAGS $(QUIET_CC)$(CC) -o $@ -c $(CFLAGS) -Wno-redundant-decls $< $(OUTPUT)util/scripting-engines/trace-event-perl.o: util/scripting-engines/trace-event-perl.c $(OUTPUT)PERF-CFLAGS - $(QUIET_CC)$(CC) -o $@ -c $(CFLAGS) $(PERL_EMBED_CCOPTS) -Wno-redundant-decls -Wno-strict-prototypes -Wno-unused-parameter -Wno-shadow $< + $(QUIET_CC)$(CC) -o $@ -c $(CFLAGS) $(PERL_EMBED_CCOPTS) -Wno-redundant-decls -Wno-strict-prototypes -Wno-unused-parameter -Wno-shadow -Wno-undef -Wno-switch-default $< $(OUTPUT)scripts/perl/Perf-Trace-Util/Context.o: scripts/perl/Perf-Trace-Util/Context.c $(OUTPUT)PERF-CFLAGS - $(QUIET_CC)$(CC) -o $@ -c $(CFLAGS) $(PERL_EMBED_CCOPTS) -Wno-redundant-decls -Wno-strict-prototypes -Wno-unused-parameter -Wno-nested-externs $< + $(QUIET_CC)$(CC) -o $@ -c $(CFLAGS) $(PERL_EMBED_CCOPTS) -Wno-redundant-decls -Wno-strict-prototypes -Wno-unused-parameter -Wno-nested-externs -Wno-undef -Wno-switch-default $< $(OUTPUT)util/scripting-engines/trace-event-python.o: util/scripting-engines/trace-event-python.c $(OUTPUT)PERF-CFLAGS $(QUIET_CC)$(CC) -o $@ -c $(CFLAGS) $(PYTHON_EMBED_CCOPTS) -Wno-redundant-decls -Wno-strict-prototypes -Wno-unused-parameter -Wno-shadow $< @@ -762,17 +769,21 @@ check: $(OUTPUT)common-cmds.h install-bin: all $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(bindir_SQ)' $(INSTALL) $(OUTPUT)perf '$(DESTDIR_SQ)$(bindir_SQ)' + $(INSTALL) $(OUTPUT)perf-archive -t '$(DESTDIR_SQ)$(perfexec_instdir_SQ)' +ifndef NO_LIBPERL $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(perfexec_instdir_SQ)/scripts/perl/Perf-Trace-Util/lib/Perf/Trace' $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(perfexec_instdir_SQ)/scripts/perl/bin' - $(INSTALL) $(OUTPUT)perf-archive -t '$(DESTDIR_SQ)$(perfexec_instdir_SQ)' $(INSTALL) scripts/perl/Perf-Trace-Util/lib/Perf/Trace/* -t '$(DESTDIR_SQ)$(perfexec_instdir_SQ)/scripts/perl/Perf-Trace-Util/lib/Perf/Trace' $(INSTALL) scripts/perl/*.pl -t '$(DESTDIR_SQ)$(perfexec_instdir_SQ)/scripts/perl' $(INSTALL) scripts/perl/bin/* -t '$(DESTDIR_SQ)$(perfexec_instdir_SQ)/scripts/perl/bin' +endif +ifndef NO_LIBPYTHON $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(perfexec_instdir_SQ)/scripts/python/Perf-Trace-Util/lib/Perf/Trace' $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(perfexec_instdir_SQ)/scripts/python/bin' $(INSTALL) scripts/python/Perf-Trace-Util/lib/Perf/Trace/* -t '$(DESTDIR_SQ)$(perfexec_instdir_SQ)/scripts/python/Perf-Trace-Util/lib/Perf/Trace' $(INSTALL) scripts/python/*.py -t '$(DESTDIR_SQ)$(perfexec_instdir_SQ)/scripts/python' $(INSTALL) scripts/python/bin/* -t '$(DESTDIR_SQ)$(perfexec_instdir_SQ)/scripts/python/bin' +endif $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(sysconfdir_SQ)/bash_completion.d' $(INSTALL) bash_completion '$(DESTDIR_SQ)$(sysconfdir_SQ)/bash_completion.d/perf' $(INSTALL) -d -m 755 '$(DESTDIR_SQ)$(perfexec_instdir_SQ)/tests' diff --git a/tools/perf/arch/x86/Makefile b/tools/perf/arch/x86/Makefile index 815841c04eb2..8801fe02f206 100644 --- a/tools/perf/arch/x86/Makefile +++ b/tools/perf/arch/x86/Makefile @@ -6,3 +6,5 @@ ifndef NO_LIBUNWIND LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/unwind.o endif LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/header.o +LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/tsc.o +LIB_H += arch/$(ARCH)/util/tsc.h diff --git a/tools/perf/arch/x86/util/tsc.c b/tools/perf/arch/x86/util/tsc.c new file mode 100644 index 000000000000..9570c2b0f83c --- /dev/null +++ b/tools/perf/arch/x86/util/tsc.c @@ -0,0 +1,59 @@ +#include <stdbool.h> +#include <errno.h> + +#include <linux/perf_event.h> + +#include "../../perf.h" +#include "../../util/types.h" +#include "../../util/debug.h" +#include "tsc.h" + +u64 perf_time_to_tsc(u64 ns, struct perf_tsc_conversion *tc) +{ + u64 t, quot, rem; + + t = ns - tc->time_zero; + quot = t / tc->time_mult; + rem = t % tc->time_mult; + return (quot << tc->time_shift) + + (rem << tc->time_shift) / tc->time_mult; +} + +u64 tsc_to_perf_time(u64 cyc, struct perf_tsc_conversion *tc) +{ + u64 quot, rem; + + quot = cyc >> tc->time_shift; + rem = cyc & ((1 << tc->time_shift) - 1); + return tc->time_zero + quot * tc->time_mult + + ((rem * tc->time_mult) >> tc->time_shift); +} + +int perf_read_tsc_conversion(const struct perf_event_mmap_page *pc, + struct perf_tsc_conversion *tc) +{ + bool cap_usr_time_zero; + u32 seq; + int i = 0; + + while (1) { + seq = pc->lock; + rmb(); + tc->time_mult = pc->time_mult; + tc->time_shift = pc->time_shift; + tc->time_zero = pc->time_zero; + cap_usr_time_zero = pc->cap_usr_time_zero; + rmb(); + if (pc->lock == seq && !(seq & 1)) + break; + if (++i > 10000) { + pr_debug("failed to get perf_event_mmap_page lock\n"); + return -EINVAL; + } + } + + if (!cap_usr_time_zero) + return -EOPNOTSUPP; + + return 0; +} diff --git a/tools/perf/arch/x86/util/tsc.h b/tools/perf/arch/x86/util/tsc.h new file mode 100644 index 000000000000..a24dec81c795 --- /dev/null +++ b/tools/perf/arch/x86/util/tsc.h @@ -0,0 +1,20 @@ +#ifndef TOOLS_PERF_ARCH_X86_UTIL_TSC_H__ +#define TOOLS_PERF_ARCH_X86_UTIL_TSC_H__ + +#include "../../util/types.h" + +struct perf_tsc_conversion { + u16 time_shift; + u32 time_mult; + u64 time_zero; +}; + +struct perf_event_mmap_page; + +int perf_read_tsc_conversion(const struct perf_event_mmap_page *pc, + struct perf_tsc_conversion *tc); + +u64 perf_time_to_tsc(u64 ns, struct perf_tsc_conversion *tc); +u64 tsc_to_perf_time(u64 cyc, struct perf_tsc_conversion *tc); + +#endif /* TOOLS_PERF_ARCH_X86_UTIL_TSC_H__ */ diff --git a/tools/perf/bench/mem-memcpy.c b/tools/perf/bench/mem-memcpy.c index 25fd3f1966f1..8cdca43016b2 100644 --- a/tools/perf/bench/mem-memcpy.c +++ b/tools/perf/bench/mem-memcpy.c @@ -117,6 +117,8 @@ static void alloc_mem(void **dst, void **src, size_t length) *src = zalloc(length); if (!*src) die("memory allocation failed - maybe length is too large?\n"); + /* Make sure to always replace the zero pages even if MMAP_THRESH is crossed */ + memset(*src, 0, length); } static u64 do_memcpy_cycle(memcpy_t fn, size_t len, bool prefault) diff --git a/tools/perf/builtin-annotate.c b/tools/perf/builtin-annotate.c index db491e9a812b..f988d380c52f 100644 --- a/tools/perf/builtin-annotate.c +++ b/tools/perf/builtin-annotate.c @@ -90,8 +90,7 @@ static int process_sample_event(struct perf_tool *tool, struct perf_annotate *ann = container_of(tool, struct perf_annotate, tool); struct addr_location al; - if (perf_event__preprocess_sample(event, machine, &al, sample, - symbol__annotate_init) < 0) { + if (perf_event__preprocess_sample(event, machine, &al, sample) < 0) { pr_warning("problem processing %d event, skipping it.\n", event->header.type); return -1; @@ -195,6 +194,8 @@ static int __cmd_annotate(struct perf_annotate *ann) if (session == NULL) return -ENOMEM; + machines__set_symbol_filter(&session->machines, symbol__annotate_init); + if (ann->cpu_list) { ret = perf_session__cpu_bitmap(session, ann->cpu_list, ann->cpu_bitmap); diff --git a/tools/perf/builtin-diff.c b/tools/perf/builtin-diff.c index 0aac5f3e594d..f28799e94f2a 100644 --- a/tools/perf/builtin-diff.c +++ b/tools/perf/builtin-diff.c @@ -18,15 +18,53 @@ #include "util/util.h" #include <stdlib.h> +#include <math.h> -static char const *input_old = "perf.data.old", - *input_new = "perf.data"; -static char diff__default_sort_order[] = "dso,symbol"; -static bool force; +/* Diff command specific HPP columns. */ +enum { + PERF_HPP_DIFF__BASELINE, + PERF_HPP_DIFF__PERIOD, + PERF_HPP_DIFF__PERIOD_BASELINE, + PERF_HPP_DIFF__DELTA, + PERF_HPP_DIFF__RATIO, + PERF_HPP_DIFF__WEIGHTED_DIFF, + PERF_HPP_DIFF__FORMULA, + + PERF_HPP_DIFF__MAX_INDEX +}; + +struct diff_hpp_fmt { + struct perf_hpp_fmt fmt; + int idx; + char *header; + int header_width; +}; + +struct data__file { + struct perf_session *session; + const char *file; + int idx; + struct hists *hists; + struct diff_hpp_fmt fmt[PERF_HPP_DIFF__MAX_INDEX]; +}; + +static struct data__file *data__files; +static int data__files_cnt; + +#define data__for_each_file_start(i, d, s) \ + for (i = s, d = &data__files[s]; \ + i < data__files_cnt; \ + i++, d = &data__files[i]) + +#define data__for_each_file(i, d) data__for_each_file_start(i, d, 0) +#define data__for_each_file_new(i, d) data__for_each_file_start(i, d, 1) + +static char diff__default_sort_order[] = "dso,symbol"; +static bool force; static bool show_period; static bool show_formula; static bool show_baseline_only; -static bool sort_compute; +static unsigned int sort_compute; static s64 compute_wdiff_w1; static s64 compute_wdiff_w2; @@ -46,6 +84,47 @@ const char *compute_names[COMPUTE_MAX] = { static int compute; +static int compute_2_hpp[COMPUTE_MAX] = { + [COMPUTE_DELTA] = PERF_HPP_DIFF__DELTA, + [COMPUTE_RATIO] = PERF_HPP_DIFF__RATIO, + [COMPUTE_WEIGHTED_DIFF] = PERF_HPP_DIFF__WEIGHTED_DIFF, +}; + +#define MAX_COL_WIDTH 70 + +static struct header_column { + const char *name; + int width; +} columns[PERF_HPP_DIFF__MAX_INDEX] = { + [PERF_HPP_DIFF__BASELINE] = { + .name = "Baseline", + }, + [PERF_HPP_DIFF__PERIOD] = { + .name = "Period", + .width = 14, + }, + [PERF_HPP_DIFF__PERIOD_BASELINE] = { + .name = "Base period", + .width = 14, + }, + [PERF_HPP_DIFF__DELTA] = { + .name = "Delta", + .width = 7, + }, + [PERF_HPP_DIFF__RATIO] = { + .name = "Ratio", + .width = 14, + }, + [PERF_HPP_DIFF__WEIGHTED_DIFF] = { + .name = "Weighted diff", + .width = 14, + }, + [PERF_HPP_DIFF__FORMULA] = { + .name = "Formula", + .width = MAX_COL_WIDTH, + } +}; + static int setup_compute_opt_wdiff(char *opt) { char *w1_str = opt; @@ -109,13 +188,6 @@ static int setup_compute(const struct option *opt, const char *str, return 0; } - if (*str == '+') { - sort_compute = true; - cstr = (char *) ++str; - if (!*str) - return 0; - } - option = strchr(str, ':'); if (option) { unsigned len = option++ - str; @@ -145,42 +217,42 @@ static int setup_compute(const struct option *opt, const char *str, return -EINVAL; } -double perf_diff__period_percent(struct hist_entry *he, u64 period) +static double period_percent(struct hist_entry *he, u64 period) { u64 total = he->hists->stats.total_period; return (period * 100.0) / total; } -double perf_diff__compute_delta(struct hist_entry *he, struct hist_entry *pair) +static double compute_delta(struct hist_entry *he, struct hist_entry *pair) { - double new_percent = perf_diff__period_percent(he, he->stat.period); - double old_percent = perf_diff__period_percent(pair, pair->stat.period); + double old_percent = period_percent(he, he->stat.period); + double new_percent = period_percent(pair, pair->stat.period); - he->diff.period_ratio_delta = new_percent - old_percent; - he->diff.computed = true; - return he->diff.period_ratio_delta; + pair->diff.period_ratio_delta = new_percent - old_percent; + pair->diff.computed = true; + return pair->diff.period_ratio_delta; } -double perf_diff__compute_ratio(struct hist_entry *he, struct hist_entry *pair) +static double compute_ratio(struct hist_entry *he, struct hist_entry *pair) { - double new_period = he->stat.period; - double old_period = pair->stat.period; + double old_period = he->stat.period ?: 1; + double new_period = pair->stat.period; - he->diff.computed = true; - he->diff.period_ratio = new_period / old_period; - return he->diff.period_ratio; + pair->diff.computed = true; + pair->diff.period_ratio = new_period / old_period; + return pair->diff.period_ratio; } -s64 perf_diff__compute_wdiff(struct hist_entry *he, struct hist_entry *pair) +static s64 compute_wdiff(struct hist_entry *he, struct hist_entry *pair) { - u64 new_period = he->stat.period; - u64 old_period = pair->stat.period; + u64 old_period = he->stat.period; + u64 new_period = pair->stat.period; - he->diff.computed = true; - he->diff.wdiff = new_period * compute_wdiff_w2 - - old_period * compute_wdiff_w1; + pair->diff.computed = true; + pair->diff.wdiff = new_period * compute_wdiff_w2 - + old_period * compute_wdiff_w1; - return he->diff.wdiff; + return pair->diff.wdiff; } static int formula_delta(struct hist_entry *he, struct hist_entry *pair, @@ -189,15 +261,15 @@ static int formula_delta(struct hist_entry *he, struct hist_entry *pair, return scnprintf(buf, size, "(%" PRIu64 " * 100 / %" PRIu64 ") - " "(%" PRIu64 " * 100 / %" PRIu64 ")", - he->stat.period, he->hists->stats.total_period, - pair->stat.period, pair->hists->stats.total_period); + pair->stat.period, pair->hists->stats.total_period, + he->stat.period, he->hists->stats.total_period); } static int formula_ratio(struct hist_entry *he, struct hist_entry *pair, char *buf, size_t size) { - double new_period = he->stat.period; - double old_period = pair->stat.period; + double old_period = he->stat.period; + double new_period = pair->stat.period; return scnprintf(buf, size, "%.0F / %.0F", new_period, old_period); } @@ -205,16 +277,16 @@ static int formula_ratio(struct hist_entry *he, struct hist_entry *pair, static int formula_wdiff(struct hist_entry *he, struct hist_entry *pair, char *buf, size_t size) { - u64 new_period = he->stat.period; - u64 old_period = pair->stat.period; + u64 old_period = he->stat.period; + u64 new_period = pair->stat.period; return scnprintf(buf, size, "(%" PRIu64 " * " "%" PRId64 ") - (%" PRIu64 " * " "%" PRId64 ")", new_period, compute_wdiff_w2, old_period, compute_wdiff_w1); } -int perf_diff__formula(struct hist_entry *he, struct hist_entry *pair, - char *buf, size_t size) +static int formula_fprintf(struct hist_entry *he, struct hist_entry *pair, + char *buf, size_t size) { switch (compute) { case COMPUTE_DELTA: @@ -247,7 +319,7 @@ static int diff__process_sample_event(struct perf_tool *tool __maybe_unused, { struct addr_location al; - if (perf_event__preprocess_sample(event, machine, &al, sample, NULL) < 0) { + if (perf_event__preprocess_sample(event, machine, &al, sample) < 0) { pr_warning("problem processing %d event, skipping it.\n", event->header.type); return -1; @@ -299,6 +371,29 @@ static void perf_evlist__collapse_resort(struct perf_evlist *evlist) } } +static struct hist_entry* +get_pair_data(struct hist_entry *he, struct data__file *d) +{ + if (hist_entry__has_pairs(he)) { + struct hist_entry *pair; + + list_for_each_entry(pair, &he->pairs.head, pairs.node) + if (pair->hists == d->hists) + return pair; + } + + return NULL; +} + +static struct hist_entry* +get_pair_fmt(struct hist_entry *he, struct diff_hpp_fmt *dfmt) +{ + void *ptr = dfmt - dfmt->idx; + struct data__file *d = container_of(ptr, struct data__file, fmt); + + return get_pair_data(he, d); +} + static void hists__baseline_only(struct hists *hists) { struct rb_root *root; @@ -333,22 +428,24 @@ static void hists__precompute(struct hists *hists) next = rb_first(root); while (next != NULL) { - struct hist_entry *he = rb_entry(next, struct hist_entry, rb_node_in); - struct hist_entry *pair = hist_entry__next_pair(he); + struct hist_entry *he, *pair; + he = rb_entry(next, struct hist_entry, rb_node_in); next = rb_next(&he->rb_node_in); + + pair = get_pair_data(he, &data__files[sort_compute]); if (!pair) continue; switch (compute) { case COMPUTE_DELTA: - perf_diff__compute_delta(he, pair); + compute_delta(he, pair); break; case COMPUTE_RATIO: - perf_diff__compute_ratio(he, pair); + compute_ratio(he, pair); break; case COMPUTE_WEIGHTED_DIFF: - perf_diff__compute_wdiff(he, pair); + compute_wdiff(he, pair); break; default: BUG_ON(1); @@ -367,7 +464,7 @@ static int64_t cmp_doubles(double l, double r) } static int64_t -hist_entry__cmp_compute(struct hist_entry *left, struct hist_entry *right, +__hist_entry__cmp_compute(struct hist_entry *left, struct hist_entry *right, int c) { switch (c) { @@ -399,6 +496,36 @@ hist_entry__cmp_compute(struct hist_entry *left, struct hist_entry *right, return 0; } +static int64_t +hist_entry__cmp_compute(struct hist_entry *left, struct hist_entry *right, + int c) +{ + bool pairs_left = hist_entry__has_pairs(left); + bool pairs_right = hist_entry__has_pairs(right); + struct hist_entry *p_right, *p_left; + + if (!pairs_left && !pairs_right) + return 0; + + if (!pairs_left || !pairs_right) + return pairs_left ? -1 : 1; + + p_left = get_pair_data(left, &data__files[sort_compute]); + p_right = get_pair_data(right, &data__files[sort_compute]); + + if (!p_left && !p_right) + return 0; + + if (!p_left || !p_right) + return p_left ? -1 : 1; + + /* + * We have 2 entries of same kind, let's + * make the data comparison. + */ + return __hist_entry__cmp_compute(p_left, p_right, c); +} + static void insert_hist_entry_by_compute(struct rb_root *root, struct hist_entry *he, int c) @@ -448,75 +575,121 @@ static void hists__compute_resort(struct hists *hists) } } -static void hists__process(struct hists *old, struct hists *new) +static void hists__process(struct hists *hists) { - hists__match(new, old); - if (show_baseline_only) - hists__baseline_only(new); - else - hists__link(new, old); + hists__baseline_only(hists); if (sort_compute) { - hists__precompute(new); - hists__compute_resort(new); + hists__precompute(hists); + hists__compute_resort(hists); } else { - hists__output_resort(new); + hists__output_resort(hists); } - hists__fprintf(new, true, 0, 0, 0, stdout); + hists__fprintf(hists, true, 0, 0, 0, stdout); } -static int __cmd_diff(void) +static void data__fprintf(void) { - int ret, i; -#define older (session[0]) -#define newer (session[1]) - struct perf_session *session[2]; - struct perf_evlist *evlist_new, *evlist_old; - struct perf_evsel *evsel; + struct data__file *d; + int i; + + fprintf(stdout, "# Data files:\n"); + + data__for_each_file(i, d) + fprintf(stdout, "# [%d] %s %s\n", + d->idx, d->file, + !d->idx ? "(Baseline)" : ""); + + fprintf(stdout, "#\n"); +} + +static void data_process(void) +{ + struct perf_evlist *evlist_base = data__files[0].session->evlist; + struct perf_evsel *evsel_base; bool first = true; - older = perf_session__new(input_old, O_RDONLY, force, false, - &tool); - newer = perf_session__new(input_new, O_RDONLY, force, false, - &tool); - if (session[0] == NULL || session[1] == NULL) - return -ENOMEM; + list_for_each_entry(evsel_base, &evlist_base->entries, node) { + struct data__file *d; + int i; - for (i = 0; i < 2; ++i) { - ret = perf_session__process_events(session[i], &tool); - if (ret) - goto out_delete; - } + data__for_each_file_new(i, d) { + struct perf_evlist *evlist = d->session->evlist; + struct perf_evsel *evsel; - evlist_old = older->evlist; - evlist_new = newer->evlist; + evsel = evsel_match(evsel_base, evlist); + if (!evsel) + continue; - perf_evlist__collapse_resort(evlist_old); - perf_evlist__collapse_resort(evlist_new); + d->hists = &evsel->hists; - list_for_each_entry(evsel, &evlist_new->entries, node) { - struct perf_evsel *evsel_old; + hists__match(&evsel_base->hists, &evsel->hists); - evsel_old = evsel_match(evsel, evlist_old); - if (!evsel_old) - continue; + if (!show_baseline_only) + hists__link(&evsel_base->hists, + &evsel->hists); + } fprintf(stdout, "%s# Event '%s'\n#\n", first ? "" : "\n", - perf_evsel__name(evsel)); + perf_evsel__name(evsel_base)); first = false; - hists__process(&evsel_old->hists, &evsel->hists); + if (verbose || data__files_cnt > 2) + data__fprintf(); + + hists__process(&evsel_base->hists); + } +} + +static void data__free(struct data__file *d) +{ + int col; + + for (col = 0; col < PERF_HPP_DIFF__MAX_INDEX; col++) { + struct diff_hpp_fmt *fmt = &d->fmt[col]; + + free(fmt->header); } +} -out_delete: - for (i = 0; i < 2; ++i) - perf_session__delete(session[i]); +static int __cmd_diff(void) +{ + struct data__file *d; + int ret = -EINVAL, i; + + data__for_each_file(i, d) { + d->session = perf_session__new(d->file, O_RDONLY, force, + false, &tool); + if (!d->session) { + pr_err("Failed to open %s\n", d->file); + ret = -ENOMEM; + goto out_delete; + } + + ret = perf_session__process_events(d->session, &tool); + if (ret) { + pr_err("Failed to process %s\n", d->file); + goto out_delete; + } + + perf_evlist__collapse_resort(d->session->evlist); + } + + data_process(); + + out_delete: + data__for_each_file(i, d) { + if (d->session) + perf_session__delete(d->session); + + data__free(d); + } + + free(data__files); return ret; -#undef older -#undef newer } static const char * const diff_usage[] = { @@ -555,61 +728,310 @@ static const struct option options[] = { "columns '.' is reserved."), OPT_STRING(0, "symfs", &symbol_conf.symfs, "directory", "Look for files with symbols relative to this directory"), + OPT_UINTEGER('o', "order", &sort_compute, "Specify compute sorting."), OPT_END() }; -static void ui_init(void) +static double baseline_percent(struct hist_entry *he) { - /* - * Display baseline/delta/ratio - * formula/periods columns. - */ - perf_hpp__column_enable(PERF_HPP__BASELINE); + struct hists *hists = he->hists; + return 100.0 * he->stat.period / hists->stats.total_period; +} - switch (compute) { - case COMPUTE_DELTA: - perf_hpp__column_enable(PERF_HPP__DELTA); +static int hpp__color_baseline(struct perf_hpp_fmt *fmt, + struct perf_hpp *hpp, struct hist_entry *he) +{ + struct diff_hpp_fmt *dfmt = + container_of(fmt, struct diff_hpp_fmt, fmt); + double percent = baseline_percent(he); + char pfmt[20] = " "; + + if (!he->dummy) { + scnprintf(pfmt, 20, "%%%d.2f%%%%", dfmt->header_width - 1); + return percent_color_snprintf(hpp->buf, hpp->size, + pfmt, percent); + } else + return scnprintf(hpp->buf, hpp->size, "%*s", + dfmt->header_width, pfmt); +} + +static int hpp__entry_baseline(struct hist_entry *he, char *buf, size_t size) +{ + double percent = baseline_percent(he); + const char *fmt = symbol_conf.field_sep ? "%.2f" : "%6.2f%%"; + int ret = 0; + + if (!he->dummy) + ret = scnprintf(buf, size, fmt, percent); + + return ret; +} + +static void +hpp__entry_unpair(struct hist_entry *he, int idx, char *buf, size_t size) +{ + switch (idx) { + case PERF_HPP_DIFF__PERIOD_BASELINE: + scnprintf(buf, size, "%" PRIu64, he->stat.period); break; - case COMPUTE_RATIO: - perf_hpp__column_enable(PERF_HPP__RATIO); + + default: break; - case COMPUTE_WEIGHTED_DIFF: - perf_hpp__column_enable(PERF_HPP__WEIGHTED_DIFF); + } +} + +static void +hpp__entry_pair(struct hist_entry *he, struct hist_entry *pair, + int idx, char *buf, size_t size) +{ + double diff; + double ratio; + s64 wdiff; + + switch (idx) { + case PERF_HPP_DIFF__DELTA: + if (pair->diff.computed) + diff = pair->diff.period_ratio_delta; + else + diff = compute_delta(he, pair); + + if (fabs(diff) >= 0.01) + scnprintf(buf, size, "%+4.2F%%", diff); + break; + + case PERF_HPP_DIFF__RATIO: + /* No point for ratio number if we are dummy.. */ + if (he->dummy) + break; + + if (pair->diff.computed) + ratio = pair->diff.period_ratio; + else + ratio = compute_ratio(he, pair); + + if (ratio > 0.0) + scnprintf(buf, size, "%14.6F", ratio); + break; + + case PERF_HPP_DIFF__WEIGHTED_DIFF: + /* No point for wdiff number if we are dummy.. */ + if (he->dummy) + break; + + if (pair->diff.computed) + wdiff = pair->diff.wdiff; + else + wdiff = compute_wdiff(he, pair); + + if (wdiff != 0) + scnprintf(buf, size, "%14ld", wdiff); + break; + + case PERF_HPP_DIFF__FORMULA: + formula_fprintf(he, pair, buf, size); + break; + + case PERF_HPP_DIFF__PERIOD: + scnprintf(buf, size, "%" PRIu64, pair->stat.period); break; + default: BUG_ON(1); }; +} + +static void +__hpp__entry_global(struct hist_entry *he, struct diff_hpp_fmt *dfmt, + char *buf, size_t size) +{ + struct hist_entry *pair = get_pair_fmt(he, dfmt); + int idx = dfmt->idx; + + /* baseline is special */ + if (idx == PERF_HPP_DIFF__BASELINE) + hpp__entry_baseline(he, buf, size); + else { + if (pair) + hpp__entry_pair(he, pair, idx, buf, size); + else + hpp__entry_unpair(he, idx, buf, size); + } +} + +static int hpp__entry_global(struct perf_hpp_fmt *_fmt, struct perf_hpp *hpp, + struct hist_entry *he) +{ + struct diff_hpp_fmt *dfmt = + container_of(_fmt, struct diff_hpp_fmt, fmt); + char buf[MAX_COL_WIDTH] = " "; + + __hpp__entry_global(he, dfmt, buf, MAX_COL_WIDTH); + + if (symbol_conf.field_sep) + return scnprintf(hpp->buf, hpp->size, "%s", buf); + else + return scnprintf(hpp->buf, hpp->size, "%*s", + dfmt->header_width, buf); +} + +static int hpp__header(struct perf_hpp_fmt *fmt, + struct perf_hpp *hpp) +{ + struct diff_hpp_fmt *dfmt = + container_of(fmt, struct diff_hpp_fmt, fmt); - if (show_formula) - perf_hpp__column_enable(PERF_HPP__FORMULA); + BUG_ON(!dfmt->header); + return scnprintf(hpp->buf, hpp->size, dfmt->header); +} - if (show_period) { - perf_hpp__column_enable(PERF_HPP__PERIOD); - perf_hpp__column_enable(PERF_HPP__PERIOD_BASELINE); +static int hpp__width(struct perf_hpp_fmt *fmt, + struct perf_hpp *hpp __maybe_unused) +{ + struct diff_hpp_fmt *dfmt = + container_of(fmt, struct diff_hpp_fmt, fmt); + + BUG_ON(dfmt->header_width <= 0); + return dfmt->header_width; +} + +static void init_header(struct data__file *d, struct diff_hpp_fmt *dfmt) +{ +#define MAX_HEADER_NAME 100 + char buf_indent[MAX_HEADER_NAME]; + char buf[MAX_HEADER_NAME]; + const char *header = NULL; + int width = 0; + + BUG_ON(dfmt->idx >= PERF_HPP_DIFF__MAX_INDEX); + header = columns[dfmt->idx].name; + width = columns[dfmt->idx].width; + + /* Only our defined HPP fmts should appear here. */ + BUG_ON(!header); + + if (data__files_cnt > 2) + scnprintf(buf, MAX_HEADER_NAME, "%s/%d", header, d->idx); + +#define NAME (data__files_cnt > 2 ? buf : header) + dfmt->header_width = width; + width = (int) strlen(NAME); + if (dfmt->header_width < width) + dfmt->header_width = width; + + scnprintf(buf_indent, MAX_HEADER_NAME, "%*s", + dfmt->header_width, NAME); + + dfmt->header = strdup(buf_indent); +#undef MAX_HEADER_NAME +#undef NAME +} + +static void data__hpp_register(struct data__file *d, int idx) +{ + struct diff_hpp_fmt *dfmt = &d->fmt[idx]; + struct perf_hpp_fmt *fmt = &dfmt->fmt; + + dfmt->idx = idx; + + fmt->header = hpp__header; + fmt->width = hpp__width; + fmt->entry = hpp__entry_global; + + /* TODO more colors */ + if (idx == PERF_HPP_DIFF__BASELINE) + fmt->color = hpp__color_baseline; + + init_header(d, dfmt); + perf_hpp__column_register(fmt); +} + +static void ui_init(void) +{ + struct data__file *d; + int i; + + data__for_each_file(i, d) { + + /* + * Baseline or compute realted columns: + * + * PERF_HPP_DIFF__BASELINE + * PERF_HPP_DIFF__DELTA + * PERF_HPP_DIFF__RATIO + * PERF_HPP_DIFF__WEIGHTED_DIFF + */ + data__hpp_register(d, i ? compute_2_hpp[compute] : + PERF_HPP_DIFF__BASELINE); + + /* + * And the rest: + * + * PERF_HPP_DIFF__FORMULA + * PERF_HPP_DIFF__PERIOD + * PERF_HPP_DIFF__PERIOD_BASELINE + */ + if (show_formula && i) + data__hpp_register(d, PERF_HPP_DIFF__FORMULA); + + if (show_period) + data__hpp_register(d, i ? PERF_HPP_DIFF__PERIOD : + PERF_HPP_DIFF__PERIOD_BASELINE); } } -int cmd_diff(int argc, const char **argv, const char *prefix __maybe_unused) +static int data_init(int argc, const char **argv) { - sort_order = diff__default_sort_order; - argc = parse_options(argc, argv, options, diff_usage, 0); + struct data__file *d; + static const char *defaults[] = { + "perf.data.old", + "perf.data", + }; + bool use_default = true; + int i; + + data__files_cnt = 2; + if (argc) { - if (argc > 2) - usage_with_options(diff_usage, options); - if (argc == 2) { - input_old = argv[0]; - input_new = argv[1]; - } else - input_new = argv[0]; + if (argc == 1) + defaults[1] = argv[0]; + else { + data__files_cnt = argc; + use_default = false; + } } else if (symbol_conf.default_guest_vmlinux_name || symbol_conf.default_guest_kallsyms) { - input_old = "perf.data.host"; - input_new = "perf.data.guest"; + defaults[0] = "perf.data.host"; + defaults[1] = "perf.data.guest"; } + if (sort_compute >= (unsigned int) data__files_cnt) { + pr_err("Order option out of limit.\n"); + return -EINVAL; + } + + data__files = zalloc(sizeof(*data__files) * data__files_cnt); + if (!data__files) + return -ENOMEM; + + data__for_each_file(i, d) { + d->file = use_default ? defaults[i] : argv[i]; + d->idx = i; + } + + return 0; +} + +int cmd_diff(int argc, const char **argv, const char *prefix __maybe_unused) +{ + sort_order = diff__default_sort_order; + argc = parse_options(argc, argv, options, diff_usage, 0); + if (symbol__init() < 0) return -1; + if (data_init(argc, argv) < 0) + return -1; + ui_init(); if (setup_sorting() < 0) diff --git a/tools/perf/builtin-inject.c b/tools/perf/builtin-inject.c index 84ad6abe4258..9b336fdb6f71 100644 --- a/tools/perf/builtin-inject.c +++ b/tools/perf/builtin-inject.c @@ -38,8 +38,7 @@ struct event_entry { }; static int perf_event__repipe_synth(struct perf_tool *tool, - union perf_event *event, - struct machine *machine __maybe_unused) + union perf_event *event) { struct perf_inject *inject = container_of(tool, struct perf_inject, tool); uint32_t size; @@ -65,39 +64,28 @@ static int perf_event__repipe_op2_synth(struct perf_tool *tool, struct perf_session *session __maybe_unused) { - return perf_event__repipe_synth(tool, event, NULL); + return perf_event__repipe_synth(tool, event); } -static int perf_event__repipe_event_type_synth(struct perf_tool *tool, - union perf_event *event) -{ - return perf_event__repipe_synth(tool, event, NULL); -} - -static int perf_event__repipe_tracing_data_synth(union perf_event *event, - struct perf_session *session - __maybe_unused) -{ - return perf_event__repipe_synth(NULL, event, NULL); -} - -static int perf_event__repipe_attr(union perf_event *event, - struct perf_evlist **pevlist __maybe_unused) +static int perf_event__repipe_attr(struct perf_tool *tool, + union perf_event *event, + struct perf_evlist **pevlist) { int ret; - ret = perf_event__process_attr(event, pevlist); + + ret = perf_event__process_attr(tool, event, pevlist); if (ret) return ret; - return perf_event__repipe_synth(NULL, event, NULL); + return perf_event__repipe_synth(tool, event); } static int perf_event__repipe(struct perf_tool *tool, union perf_event *event, struct perf_sample *sample __maybe_unused, - struct machine *machine) + struct machine *machine __maybe_unused) { - return perf_event__repipe_synth(tool, event, machine); + return perf_event__repipe_synth(tool, event); } typedef int (*inject_handler)(struct perf_tool *tool, @@ -119,7 +107,7 @@ static int perf_event__repipe_sample(struct perf_tool *tool, build_id__mark_dso_hit(tool, event, sample, evsel, machine); - return perf_event__repipe_synth(tool, event, machine); + return perf_event__repipe_synth(tool, event); } static int perf_event__repipe_mmap(struct perf_tool *tool, @@ -148,13 +136,14 @@ static int perf_event__repipe_fork(struct perf_tool *tool, return err; } -static int perf_event__repipe_tracing_data(union perf_event *event, +static int perf_event__repipe_tracing_data(struct perf_tool *tool, + union perf_event *event, struct perf_session *session) { int err; - perf_event__repipe_synth(NULL, event, NULL); - err = perf_event__process_tracing_data(event, session); + perf_event__repipe_synth(tool, event); + err = perf_event__process_tracing_data(tool, event, session); return err; } @@ -209,7 +198,7 @@ static int perf_event__inject_buildid(struct perf_tool *tool, cpumode = event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK; - thread = machine__findnew_thread(machine, event->ip.pid); + thread = machine__findnew_thread(machine, sample->pid, sample->pid); if (thread == NULL) { pr_err("problem processing %d event, skipping it.\n", event->header.type); @@ -217,7 +206,7 @@ static int perf_event__inject_buildid(struct perf_tool *tool, } thread__find_addr_map(thread, machine, cpumode, MAP__FUNCTION, - event->ip.ip, &al); + sample->ip, &al); if (al.map != NULL) { if (!al.map->dso->hit) { @@ -312,7 +301,9 @@ found: sample_sw.period = sample->period; sample_sw.time = sample->time; perf_event__synthesize_sample(event_sw, evsel->attr.sample_type, - &sample_sw, false); + evsel->attr.sample_regs_user, + evsel->attr.read_format, &sample_sw, + false); build_id__mark_dso_hit(tool, event_sw, &sample_sw, evsel, machine); return perf_event__repipe(tool, event_sw, &sample_sw, machine); } @@ -407,8 +398,8 @@ int cmd_inject(int argc, const char **argv, const char *prefix __maybe_unused) .throttle = perf_event__repipe, .unthrottle = perf_event__repipe, .attr = perf_event__repipe_attr, - .event_type = perf_event__repipe_event_type_synth, - .tracing_data = perf_event__repipe_tracing_data_synth, + .tracing_data = perf_event__repipe_op2_synth, + .finished_round = perf_event__repipe_op2_synth, .build_id = perf_event__repipe_op2_synth, }, .input_name = "-", diff --git a/tools/perf/builtin-kmem.c b/tools/perf/builtin-kmem.c index 0259502638b4..c2dff9cb1f2c 100644 --- a/tools/perf/builtin-kmem.c +++ b/tools/perf/builtin-kmem.c @@ -305,7 +305,8 @@ static int process_sample_event(struct perf_tool *tool __maybe_unused, struct perf_evsel *evsel, struct machine *machine) { - struct thread *thread = machine__findnew_thread(machine, event->ip.pid); + struct thread *thread = machine__findnew_thread(machine, sample->pid, + sample->pid); if (thread == NULL) { pr_debug("problem processing %d event, skipping it.\n", @@ -313,7 +314,7 @@ static int process_sample_event(struct perf_tool *tool __maybe_unused, return -1; } - dump_printf(" ... thread: %s:%d\n", thread->comm, thread->pid); + dump_printf(" ... thread: %s:%d\n", thread->comm, thread->tid); if (evsel->handler.func != NULL) { tracepoint_handler f = evsel->handler.func; diff --git a/tools/perf/builtin-kvm.c b/tools/perf/builtin-kvm.c index 24b78aecc928..47b35407c2f2 100644 --- a/tools/perf/builtin-kvm.c +++ b/tools/perf/builtin-kvm.c @@ -2,22 +2,26 @@ #include "perf.h" #include "util/evsel.h" +#include "util/evlist.h" #include "util/util.h" #include "util/cache.h" #include "util/symbol.h" #include "util/thread.h" #include "util/header.h" #include "util/session.h" - +#include "util/intlist.h" #include "util/parse-options.h" #include "util/trace-event.h" #include "util/debug.h" #include <lk/debugfs.h> #include "util/tool.h" #include "util/stat.h" +#include "util/top.h" #include <sys/prctl.h> +#include <sys/timerfd.h> +#include <termios.h> #include <semaphore.h> #include <pthread.h> #include <math.h> @@ -82,6 +86,8 @@ struct exit_reasons_table { struct perf_kvm_stat { struct perf_tool tool; + struct perf_record_opts opts; + struct perf_evlist *evlist; struct perf_session *session; const char *file_name; @@ -96,10 +102,20 @@ struct perf_kvm_stat { struct kvm_events_ops *events_ops; key_cmp_fun compare; struct list_head kvm_events_cache[EVENTS_CACHE_SIZE]; + u64 total_time; u64 total_count; + u64 lost_events; + u64 duration; + + const char *pid_str; + struct intlist *pid_list; struct rb_root result; + + int timerfd; + unsigned int display_time; + bool live; }; @@ -320,6 +336,28 @@ static void init_kvm_event_record(struct perf_kvm_stat *kvm) INIT_LIST_HEAD(&kvm->kvm_events_cache[i]); } +static void clear_events_cache_stats(struct list_head *kvm_events_cache) +{ + struct list_head *head; + struct kvm_event *event; + unsigned int i; + int j; + + for (i = 0; i < EVENTS_CACHE_SIZE; i++) { + head = &kvm_events_cache[i]; + list_for_each_entry(event, head, hash_entry) { + /* reset stats for event */ + event->total.time = 0; + init_stats(&event->total.stats); + + for (j = 0; j < event->max_vcpu; ++j) { + event->vcpu[j].time = 0; + init_stats(&event->vcpu[j].stats); + } + } + } +} + static int kvm_events_hash_fn(u64 key) { return key & (EVENTS_CACHE_SIZE - 1); @@ -436,7 +474,7 @@ static bool update_kvm_event(struct kvm_event *event, int vcpu_id, static bool handle_end_event(struct perf_kvm_stat *kvm, struct vcpu_event_record *vcpu_record, struct event_key *key, - u64 timestamp) + struct perf_sample *sample) { struct kvm_event *event; u64 time_begin, time_diff; @@ -472,9 +510,25 @@ static bool handle_end_event(struct perf_kvm_stat *kvm, vcpu_record->last_event = NULL; vcpu_record->start_time = 0; - BUG_ON(timestamp < time_begin); + /* seems to happen once in a while during live mode */ + if (sample->time < time_begin) { + pr_debug("End time before begin time; skipping event.\n"); + return true; + } + + time_diff = sample->time - time_begin; + + if (kvm->duration && time_diff > kvm->duration) { + char decode[32]; + + kvm->events_ops->decode_key(kvm, &event->key, decode); + if (strcmp(decode, "HLT")) { + pr_info("%" PRIu64 " VM %d, vcpu %d: %s event took %" PRIu64 "usec\n", + sample->time, sample->pid, vcpu_record->vcpu_id, + decode, time_diff/1000); + } + } - time_diff = timestamp - time_begin; return update_kvm_event(event, vcpu, time_diff); } @@ -521,7 +575,7 @@ static bool handle_kvm_event(struct perf_kvm_stat *kvm, return handle_begin_event(kvm, vcpu_record, &key, sample->time); if (kvm->events_ops->is_end_event(evsel, sample, &key)) - return handle_end_event(kvm, vcpu_record, &key, sample->time); + return handle_end_event(kvm, vcpu_record, &key, sample); return true; } @@ -550,6 +604,8 @@ static int compare_kvm_event_ ## func(struct kvm_event *one, \ GET_EVENT_KEY(time, time); COMPARE_EVENT_KEY(count, stats.n); COMPARE_EVENT_KEY(mean, stats.mean); +GET_EVENT_KEY(max, stats.max); +GET_EVENT_KEY(min, stats.min); #define DEF_SORT_NAME_KEY(name, compare_key) \ { #name, compare_kvm_event_ ## compare_key } @@ -639,43 +695,81 @@ static struct kvm_event *pop_from_result(struct rb_root *result) return container_of(node, struct kvm_event, rb); } -static void print_vcpu_info(int vcpu) +static void print_vcpu_info(struct perf_kvm_stat *kvm) { + int vcpu = kvm->trace_vcpu; + pr_info("Analyze events for "); + if (kvm->live) { + if (kvm->opts.target.system_wide) + pr_info("all VMs, "); + else if (kvm->opts.target.pid) + pr_info("pid(s) %s, ", kvm->opts.target.pid); + else + pr_info("dazed and confused on what is monitored, "); + } + if (vcpu == -1) pr_info("all VCPUs:\n\n"); else pr_info("VCPU %d:\n\n", vcpu); } +static void show_timeofday(void) +{ + char date[64]; + struct timeval tv; + struct tm ltime; + + gettimeofday(&tv, NULL); + if (localtime_r(&tv.tv_sec, <ime)) { + strftime(date, sizeof(date), "%H:%M:%S", <ime); + pr_info("%s.%06ld", date, tv.tv_usec); + } else + pr_info("00:00:00.000000"); + + return; +} + static void print_result(struct perf_kvm_stat *kvm) { char decode[20]; struct kvm_event *event; int vcpu = kvm->trace_vcpu; + if (kvm->live) { + puts(CONSOLE_CLEAR); + show_timeofday(); + } + pr_info("\n\n"); - print_vcpu_info(vcpu); + print_vcpu_info(kvm); pr_info("%20s ", kvm->events_ops->name); pr_info("%10s ", "Samples"); pr_info("%9s ", "Samples%"); pr_info("%9s ", "Time%"); + pr_info("%10s ", "Min Time"); + pr_info("%10s ", "Max Time"); pr_info("%16s ", "Avg time"); pr_info("\n\n"); while ((event = pop_from_result(&kvm->result))) { - u64 ecount, etime; + u64 ecount, etime, max, min; ecount = get_event_count(event, vcpu); etime = get_event_time(event, vcpu); + max = get_event_max(event, vcpu); + min = get_event_min(event, vcpu); kvm->events_ops->decode_key(kvm, &event->key, decode); pr_info("%20s ", decode); pr_info("%10llu ", (unsigned long long)ecount); pr_info("%8.2f%% ", (double)ecount / kvm->total_count * 100); pr_info("%8.2f%% ", (double)etime / kvm->total_time * 100); + pr_info("%8" PRIu64 "us ", min / 1000); + pr_info("%8" PRIu64 "us ", max / 1000); pr_info("%9.2fus ( +-%7.2f%% )", (double)etime / ecount/1e3, kvm_event_rel_stddev(vcpu, event)); pr_info("\n"); @@ -683,6 +777,29 @@ static void print_result(struct perf_kvm_stat *kvm) pr_info("\nTotal Samples:%" PRIu64 ", Total events handled time:%.2fus.\n\n", kvm->total_count, kvm->total_time / 1e3); + + if (kvm->lost_events) + pr_info("\nLost events: %" PRIu64 "\n\n", kvm->lost_events); +} + +static int process_lost_event(struct perf_tool *tool, + union perf_event *event __maybe_unused, + struct perf_sample *sample __maybe_unused, + struct machine *machine __maybe_unused) +{ + struct perf_kvm_stat *kvm = container_of(tool, struct perf_kvm_stat, tool); + + kvm->lost_events++; + return 0; +} + +static bool skip_sample(struct perf_kvm_stat *kvm, + struct perf_sample *sample) +{ + if (kvm->pid_list && intlist__find(kvm->pid_list, sample->pid) == NULL) + return true; + + return false; } static int process_sample_event(struct perf_tool *tool, @@ -691,10 +808,14 @@ static int process_sample_event(struct perf_tool *tool, struct perf_evsel *evsel, struct machine *machine) { - struct thread *thread = machine__findnew_thread(machine, sample->tid); + struct thread *thread; struct perf_kvm_stat *kvm = container_of(tool, struct perf_kvm_stat, tool); + if (skip_sample(kvm, sample)) + return 0; + + thread = machine__findnew_thread(machine, sample->pid, sample->tid); if (thread == NULL) { pr_debug("problem processing %d event, skipping it.\n", event->header.type); @@ -707,10 +828,20 @@ static int process_sample_event(struct perf_tool *tool, return 0; } -static int get_cpu_isa(struct perf_session *session) +static int cpu_isa_config(struct perf_kvm_stat *kvm) { - char *cpuid = session->header.env.cpuid; - int isa; + char buf[64], *cpuid; + int err, isa; + + if (kvm->live) { + err = get_cpuid(buf, sizeof(buf)); + if (err != 0) { + pr_err("Failed to look up CPU type (Intel or AMD)\n"); + return err; + } + cpuid = buf; + } else + cpuid = kvm->session->header.env.cpuid; if (strstr(cpuid, "Intel")) isa = 1; @@ -718,10 +849,361 @@ static int get_cpu_isa(struct perf_session *session) isa = 0; else { pr_err("CPU %s is not supported.\n", cpuid); - isa = -ENOTSUP; + return -ENOTSUP; + } + + if (isa == 1) { + kvm->exit_reasons = vmx_exit_reasons; + kvm->exit_reasons_size = ARRAY_SIZE(vmx_exit_reasons); + kvm->exit_reasons_isa = "VMX"; + } + + return 0; +} + +static bool verify_vcpu(int vcpu) +{ + if (vcpu != -1 && vcpu < 0) { + pr_err("Invalid vcpu:%d.\n", vcpu); + return false; + } + + return true; +} + +/* keeping the max events to a modest level to keep + * the processing of samples per mmap smooth. + */ +#define PERF_KVM__MAX_EVENTS_PER_MMAP 25 + +static s64 perf_kvm__mmap_read_idx(struct perf_kvm_stat *kvm, int idx, + u64 *mmap_time) +{ + union perf_event *event; + struct perf_sample sample; + s64 n = 0; + int err; + + *mmap_time = ULLONG_MAX; + while ((event = perf_evlist__mmap_read(kvm->evlist, idx)) != NULL) { + err = perf_evlist__parse_sample(kvm->evlist, event, &sample); + if (err) { + pr_err("Failed to parse sample\n"); + return -1; + } + + err = perf_session_queue_event(kvm->session, event, &sample, 0); + if (err) { + pr_err("Failed to enqueue sample: %d\n", err); + return -1; + } + + /* save time stamp of our first sample for this mmap */ + if (n == 0) + *mmap_time = sample.time; + + /* limit events per mmap handled all at once */ + n++; + if (n == PERF_KVM__MAX_EVENTS_PER_MMAP) + break; + } + + return n; +} + +static int perf_kvm__mmap_read(struct perf_kvm_stat *kvm) +{ + int i, err, throttled = 0; + s64 n, ntotal = 0; + u64 flush_time = ULLONG_MAX, mmap_time; + + for (i = 0; i < kvm->evlist->nr_mmaps; i++) { + n = perf_kvm__mmap_read_idx(kvm, i, &mmap_time); + if (n < 0) + return -1; + + /* flush time is going to be the minimum of all the individual + * mmap times. Essentially, we flush all the samples queued up + * from the last pass under our minimal start time -- that leaves + * a very small race for samples to come in with a lower timestamp. + * The ioctl to return the perf_clock timestamp should close the + * race entirely. + */ + if (mmap_time < flush_time) + flush_time = mmap_time; + + ntotal += n; + if (n == PERF_KVM__MAX_EVENTS_PER_MMAP) + throttled = 1; + } + + /* flush queue after each round in which we processed events */ + if (ntotal) { + kvm->session->ordered_samples.next_flush = flush_time; + err = kvm->tool.finished_round(&kvm->tool, NULL, kvm->session); + if (err) { + if (kvm->lost_events) + pr_info("\nLost events: %" PRIu64 "\n\n", + kvm->lost_events); + return err; + } + } + + return throttled; +} + +static volatile int done; + +static void sig_handler(int sig __maybe_unused) +{ + done = 1; +} + +static int perf_kvm__timerfd_create(struct perf_kvm_stat *kvm) +{ + struct itimerspec new_value; + int rc = -1; + + kvm->timerfd = timerfd_create(CLOCK_MONOTONIC, TFD_NONBLOCK); + if (kvm->timerfd < 0) { + pr_err("timerfd_create failed\n"); + goto out; + } + + new_value.it_value.tv_sec = kvm->display_time; + new_value.it_value.tv_nsec = 0; + new_value.it_interval.tv_sec = kvm->display_time; + new_value.it_interval.tv_nsec = 0; + + if (timerfd_settime(kvm->timerfd, 0, &new_value, NULL) != 0) { + pr_err("timerfd_settime failed: %d\n", errno); + close(kvm->timerfd); + goto out; + } + + rc = 0; +out: + return rc; +} + +static int perf_kvm__handle_timerfd(struct perf_kvm_stat *kvm) +{ + uint64_t c; + int rc; + + rc = read(kvm->timerfd, &c, sizeof(uint64_t)); + if (rc < 0) { + if (errno == EAGAIN) + return 0; + + pr_err("Failed to read timer fd: %d\n", errno); + return -1; + } + + if (rc != sizeof(uint64_t)) { + pr_err("Error reading timer fd - invalid size returned\n"); + return -1; + } + + if (c != 1) + pr_debug("Missed timer beats: %" PRIu64 "\n", c-1); + + /* update display */ + sort_result(kvm); + print_result(kvm); + + /* reset counts */ + clear_events_cache_stats(kvm->kvm_events_cache); + kvm->total_count = 0; + kvm->total_time = 0; + kvm->lost_events = 0; + + return 0; +} + +static int fd_set_nonblock(int fd) +{ + long arg = 0; + + arg = fcntl(fd, F_GETFL); + if (arg < 0) { + pr_err("Failed to get current flags for fd %d\n", fd); + return -1; + } + + if (fcntl(fd, F_SETFL, arg | O_NONBLOCK) < 0) { + pr_err("Failed to set non-block option on fd %d\n", fd); + return -1; + } + + return 0; +} + +static +int perf_kvm__handle_stdin(struct termios *tc_now, struct termios *tc_save) +{ + int c; + + tcsetattr(0, TCSANOW, tc_now); + c = getc(stdin); + tcsetattr(0, TCSAFLUSH, tc_save); + + if (c == 'q') + return 1; + + return 0; +} + +static int kvm_events_live_report(struct perf_kvm_stat *kvm) +{ + struct pollfd *pollfds = NULL; + int nr_fds, nr_stdin, ret, err = -EINVAL; + struct termios tc, save; + + /* live flag must be set first */ + kvm->live = true; + + ret = cpu_isa_config(kvm); + if (ret < 0) + return ret; + + if (!verify_vcpu(kvm->trace_vcpu) || + !select_key(kvm) || + !register_kvm_events_ops(kvm)) { + goto out; + } + + init_kvm_event_record(kvm); + + tcgetattr(0, &save); + tc = save; + tc.c_lflag &= ~(ICANON | ECHO); + tc.c_cc[VMIN] = 0; + tc.c_cc[VTIME] = 0; + + signal(SIGINT, sig_handler); + signal(SIGTERM, sig_handler); + + /* copy pollfds -- need to add timerfd and stdin */ + nr_fds = kvm->evlist->nr_fds; + pollfds = zalloc(sizeof(struct pollfd) * (nr_fds + 2)); + if (!pollfds) { + err = -ENOMEM; + goto out; } + memcpy(pollfds, kvm->evlist->pollfd, + sizeof(struct pollfd) * kvm->evlist->nr_fds); + + /* add timer fd */ + if (perf_kvm__timerfd_create(kvm) < 0) { + err = -1; + goto out; + } + + pollfds[nr_fds].fd = kvm->timerfd; + pollfds[nr_fds].events = POLLIN; + nr_fds++; + + pollfds[nr_fds].fd = fileno(stdin); + pollfds[nr_fds].events = POLLIN; + nr_stdin = nr_fds; + nr_fds++; + if (fd_set_nonblock(fileno(stdin)) != 0) + goto out; + + /* everything is good - enable the events and process */ + perf_evlist__enable(kvm->evlist); + + while (!done) { + int rc; + + rc = perf_kvm__mmap_read(kvm); + if (rc < 0) + break; + + err = perf_kvm__handle_timerfd(kvm); + if (err) + goto out; + + if (pollfds[nr_stdin].revents & POLLIN) + done = perf_kvm__handle_stdin(&tc, &save); + + if (!rc && !done) + err = poll(pollfds, nr_fds, 100); + } + + perf_evlist__disable(kvm->evlist); + + if (err == 0) { + sort_result(kvm); + print_result(kvm); + } + +out: + if (kvm->timerfd >= 0) + close(kvm->timerfd); + + if (pollfds) + free(pollfds); - return isa; + return err; +} + +static int kvm_live_open_events(struct perf_kvm_stat *kvm) +{ + int err, rc = -1; + struct perf_evsel *pos; + struct perf_evlist *evlist = kvm->evlist; + + perf_evlist__config(evlist, &kvm->opts); + + /* + * Note: exclude_{guest,host} do not apply here. + * This command processes KVM tracepoints from host only + */ + list_for_each_entry(pos, &evlist->entries, node) { + struct perf_event_attr *attr = &pos->attr; + + /* make sure these *are* set */ + attr->sample_type |= PERF_SAMPLE_TID; + attr->sample_type |= PERF_SAMPLE_TIME; + attr->sample_type |= PERF_SAMPLE_CPU; + attr->sample_type |= PERF_SAMPLE_RAW; + /* make sure these are *not*; want as small a sample as possible */ + attr->sample_type &= ~PERF_SAMPLE_PERIOD; + attr->sample_type &= ~PERF_SAMPLE_IP; + attr->sample_type &= ~PERF_SAMPLE_CALLCHAIN; + attr->sample_type &= ~PERF_SAMPLE_ADDR; + attr->sample_type &= ~PERF_SAMPLE_READ; + attr->mmap = 0; + attr->comm = 0; + attr->task = 0; + + attr->sample_period = 1; + + attr->watermark = 0; + attr->wakeup_events = 1000; + + /* will enable all once we are ready */ + attr->disabled = 1; + } + + err = perf_evlist__open(evlist); + if (err < 0) { + printf("Couldn't create the events: %s\n", strerror(errno)); + goto out; + } + + if (perf_evlist__mmap(evlist, kvm->opts.mmap_pages, false) < 0) { + ui__error("Failed to mmap the events: %s\n", strerror(errno)); + perf_evlist__close(evlist); + goto out; + } + + rc = 0; + +out: + return rc; } static int read_events(struct perf_kvm_stat *kvm) @@ -749,28 +1231,24 @@ static int read_events(struct perf_kvm_stat *kvm) * Do not use 'isa' recorded in kvm_exit tracepoint since it is not * traced in the old kernel. */ - ret = get_cpu_isa(kvm->session); - + ret = cpu_isa_config(kvm); if (ret < 0) return ret; - if (ret == 1) { - kvm->exit_reasons = vmx_exit_reasons; - kvm->exit_reasons_size = ARRAY_SIZE(vmx_exit_reasons); - kvm->exit_reasons_isa = "VMX"; - } - return perf_session__process_events(kvm->session, &kvm->tool); } -static bool verify_vcpu(int vcpu) +static int parse_target_str(struct perf_kvm_stat *kvm) { - if (vcpu != -1 && vcpu < 0) { - pr_err("Invalid vcpu:%d.\n", vcpu); - return false; + if (kvm->pid_str) { + kvm->pid_list = intlist__new(kvm->pid_str); + if (kvm->pid_list == NULL) { + pr_err("Error parsing process id string\n"); + return -EINVAL; + } } - return true; + return 0; } static int kvm_events_report_vcpu(struct perf_kvm_stat *kvm) @@ -778,6 +1256,9 @@ static int kvm_events_report_vcpu(struct perf_kvm_stat *kvm) int ret = -EINVAL; int vcpu = kvm->trace_vcpu; + if (parse_target_str(kvm) != 0) + goto exit; + if (!verify_vcpu(vcpu)) goto exit; @@ -801,16 +1282,11 @@ exit: return ret; } -static const char * const record_args[] = { - "record", - "-R", - "-f", - "-m", "1024", - "-c", "1", - "-e", "kvm:kvm_entry", - "-e", "kvm:kvm_exit", - "-e", "kvm:kvm_mmio", - "-e", "kvm:kvm_pio", +static const char * const kvm_events_tp[] = { + "kvm:kvm_entry", + "kvm:kvm_exit", + "kvm:kvm_mmio", + "kvm:kvm_pio", }; #define STRDUP_FAIL_EXIT(s) \ @@ -826,8 +1302,15 @@ kvm_events_record(struct perf_kvm_stat *kvm, int argc, const char **argv) { unsigned int rec_argc, i, j; const char **rec_argv; + const char * const record_args[] = { + "record", + "-R", + "-m", "1024", + "-c", "1", + }; - rec_argc = ARRAY_SIZE(record_args) + argc + 2; + rec_argc = ARRAY_SIZE(record_args) + argc + 2 + + 2 * ARRAY_SIZE(kvm_events_tp); rec_argv = calloc(rec_argc + 1, sizeof(char *)); if (rec_argv == NULL) @@ -836,6 +1319,11 @@ kvm_events_record(struct perf_kvm_stat *kvm, int argc, const char **argv) for (i = 0; i < ARRAY_SIZE(record_args); i++) rec_argv[i] = STRDUP_FAIL_EXIT(record_args[i]); + for (j = 0; j < ARRAY_SIZE(kvm_events_tp); j++) { + rec_argv[i++] = "-e"; + rec_argv[i++] = STRDUP_FAIL_EXIT(kvm_events_tp[j]); + } + rec_argv[i++] = STRDUP_FAIL_EXIT("-o"); rec_argv[i++] = STRDUP_FAIL_EXIT(kvm->file_name); @@ -856,6 +1344,8 @@ kvm_events_report(struct perf_kvm_stat *kvm, int argc, const char **argv) OPT_STRING('k', "key", &kvm->sort_key, "sort-key", "key for sorting: sample(sort by samples number)" " time (sort by avg time)"), + OPT_STRING('p', "pid", &kvm->pid_str, "pid", + "analyze events only for given process id(s)"), OPT_END() }; @@ -878,6 +1368,190 @@ kvm_events_report(struct perf_kvm_stat *kvm, int argc, const char **argv) return kvm_events_report_vcpu(kvm); } +static struct perf_evlist *kvm_live_event_list(void) +{ + struct perf_evlist *evlist; + char *tp, *name, *sys; + unsigned int j; + int err = -1; + + evlist = perf_evlist__new(); + if (evlist == NULL) + return NULL; + + for (j = 0; j < ARRAY_SIZE(kvm_events_tp); j++) { + + tp = strdup(kvm_events_tp[j]); + if (tp == NULL) + goto out; + + /* split tracepoint into subsystem and name */ + sys = tp; + name = strchr(tp, ':'); + if (name == NULL) { + pr_err("Error parsing %s tracepoint: subsystem delimiter not found\n", + kvm_events_tp[j]); + free(tp); + goto out; + } + *name = '\0'; + name++; + + if (perf_evlist__add_newtp(evlist, sys, name, NULL)) { + pr_err("Failed to add %s tracepoint to the list\n", kvm_events_tp[j]); + free(tp); + goto out; + } + + free(tp); + } + + err = 0; + +out: + if (err) { + perf_evlist__delete(evlist); + evlist = NULL; + } + + return evlist; +} + +static int kvm_events_live(struct perf_kvm_stat *kvm, + int argc, const char **argv) +{ + char errbuf[BUFSIZ]; + int err; + + const struct option live_options[] = { + OPT_STRING('p', "pid", &kvm->opts.target.pid, "pid", + "record events on existing process id"), + OPT_UINTEGER('m', "mmap-pages", &kvm->opts.mmap_pages, + "number of mmap data pages"), + OPT_INCR('v', "verbose", &verbose, + "be more verbose (show counter open errors, etc)"), + OPT_BOOLEAN('a', "all-cpus", &kvm->opts.target.system_wide, + "system-wide collection from all CPUs"), + OPT_UINTEGER('d', "display", &kvm->display_time, + "time in seconds between display updates"), + OPT_STRING(0, "event", &kvm->report_event, "report event", + "event for reporting: vmexit, mmio, ioport"), + OPT_INTEGER(0, "vcpu", &kvm->trace_vcpu, + "vcpu id to report"), + OPT_STRING('k', "key", &kvm->sort_key, "sort-key", + "key for sorting: sample(sort by samples number)" + " time (sort by avg time)"), + OPT_U64(0, "duration", &kvm->duration, + "show events other than HALT that take longer than duration usecs"), + OPT_END() + }; + const char * const live_usage[] = { + "perf kvm stat live [<options>]", + NULL + }; + + + /* event handling */ + kvm->tool.sample = process_sample_event; + kvm->tool.comm = perf_event__process_comm; + kvm->tool.exit = perf_event__process_exit; + kvm->tool.fork = perf_event__process_fork; + kvm->tool.lost = process_lost_event; + kvm->tool.ordered_samples = true; + perf_tool__fill_defaults(&kvm->tool); + + /* set defaults */ + kvm->display_time = 1; + kvm->opts.user_interval = 1; + kvm->opts.mmap_pages = 512; + kvm->opts.target.uses_mmap = false; + kvm->opts.target.uid_str = NULL; + kvm->opts.target.uid = UINT_MAX; + + symbol__init(); + disable_buildid_cache(); + + use_browser = 0; + setup_browser(false); + + if (argc) { + argc = parse_options(argc, argv, live_options, + live_usage, 0); + if (argc) + usage_with_options(live_usage, live_options); + } + + kvm->duration *= NSEC_PER_USEC; /* convert usec to nsec */ + + /* + * target related setups + */ + err = perf_target__validate(&kvm->opts.target); + if (err) { + perf_target__strerror(&kvm->opts.target, err, errbuf, BUFSIZ); + ui__warning("%s", errbuf); + } + + if (perf_target__none(&kvm->opts.target)) + kvm->opts.target.system_wide = true; + + + /* + * generate the event list + */ + kvm->evlist = kvm_live_event_list(); + if (kvm->evlist == NULL) { + err = -1; + goto out; + } + + symbol_conf.nr_events = kvm->evlist->nr_entries; + + if (perf_evlist__create_maps(kvm->evlist, &kvm->opts.target) < 0) + usage_with_options(live_usage, live_options); + + /* + * perf session + */ + kvm->session = perf_session__new(NULL, O_WRONLY, false, false, &kvm->tool); + if (kvm->session == NULL) { + err = -ENOMEM; + goto out; + } + kvm->session->evlist = kvm->evlist; + perf_session__set_id_hdr_size(kvm->session); + + + if (perf_target__has_task(&kvm->opts.target)) + perf_event__synthesize_thread_map(&kvm->tool, + kvm->evlist->threads, + perf_event__process, + &kvm->session->machines.host); + else + perf_event__synthesize_threads(&kvm->tool, perf_event__process, + &kvm->session->machines.host); + + + err = kvm_live_open_events(kvm); + if (err) + goto out; + + err = kvm_events_live_report(kvm); + +out: + exit_browser(0); + + if (kvm->session) + perf_session__delete(kvm->session); + kvm->session = NULL; + if (kvm->evlist) { + perf_evlist__delete_maps(kvm->evlist); + perf_evlist__delete(kvm->evlist); + } + + return err; +} + static void print_kvm_stat_usage(void) { printf("Usage: perf kvm stat <command>\n\n"); @@ -885,6 +1559,7 @@ static void print_kvm_stat_usage(void) printf("# Available commands:\n"); printf("\trecord: record kvm events\n"); printf("\treport: report statistical data of kvm events\n"); + printf("\tlive: live reporting of statistical data of kvm events\n"); printf("\nOtherwise, it is the alias of 'perf stat':\n"); } @@ -914,6 +1589,9 @@ static int kvm_cmd_stat(const char *file_name, int argc, const char **argv) if (!strncmp(argv[1], "rep", 3)) return kvm_events_report(&kvm, argc - 1 , argv + 1); + if (!strncmp(argv[1], "live", 4)) + return kvm_events_live(&kvm, argc - 1 , argv + 1); + perf_stat: return cmd_stat(argc, argv, NULL); } diff --git a/tools/perf/builtin-list.c b/tools/perf/builtin-list.c index 1948eceb517a..e79f423cc302 100644 --- a/tools/perf/builtin-list.c +++ b/tools/perf/builtin-list.c @@ -13,6 +13,7 @@ #include "util/parse-events.h" #include "util/cache.h" +#include "util/pmu.h" int cmd_list(int argc, const char **argv, const char *prefix __maybe_unused) { @@ -37,6 +38,8 @@ int cmd_list(int argc, const char **argv, const char *prefix __maybe_unused) else if (strcmp(argv[i], "cache") == 0 || strcmp(argv[i], "hwcache") == 0) print_hwcache_events(NULL, false); + else if (strcmp(argv[i], "pmu") == 0) + print_pmu_events(NULL, false); else if (strcmp(argv[i], "--raw-dump") == 0) print_events(NULL, true); else { diff --git a/tools/perf/builtin-lock.c b/tools/perf/builtin-lock.c index 76543a4a7a30..ee33ba2f05dd 100644 --- a/tools/perf/builtin-lock.c +++ b/tools/perf/builtin-lock.c @@ -805,7 +805,8 @@ static int process_sample_event(struct perf_tool *tool __maybe_unused, struct perf_evsel *evsel, struct machine *machine) { - struct thread *thread = machine__findnew_thread(machine, sample->tid); + struct thread *thread = machine__findnew_thread(machine, sample->pid, + sample->tid); if (thread == NULL) { pr_debug("problem processing %d event, skipping it.\n", diff --git a/tools/perf/builtin-mem.c b/tools/perf/builtin-mem.c index a8ff6d264e50..791b432df847 100644 --- a/tools/perf/builtin-mem.c +++ b/tools/perf/builtin-mem.c @@ -14,7 +14,6 @@ static const char *mem_operation = MEM_OPERATION_LOAD; struct perf_mem { struct perf_tool tool; char const *input_name; - symbol_filter_t annotate_init; bool hide_unresolved; bool dump_raw; const char *cpu_list; @@ -69,8 +68,7 @@ dump_raw_samples(struct perf_tool *tool, struct addr_location al; const char *fmt; - if (perf_event__preprocess_sample(event, machine, &al, sample, - mem->annotate_init) < 0) { + if (perf_event__preprocess_sample(event, machine, &al, sample) < 0) { fprintf(stderr, "problem processing %d event, skipping it.\n", event->header.type); return -1; @@ -96,7 +94,7 @@ dump_raw_samples(struct perf_tool *tool, symbol_conf.field_sep, sample->tid, symbol_conf.field_sep, - event->ip.ip, + sample->ip, symbol_conf.field_sep, sample->addr, symbol_conf.field_sep, diff --git a/tools/perf/builtin-record.c b/tools/perf/builtin-record.c index ecca62e27b28..a41ac41546c9 100644 --- a/tools/perf/builtin-record.c +++ b/tools/perf/builtin-record.c @@ -474,13 +474,6 @@ static int __cmd_record(struct perf_record *rec, int argc, const char **argv) goto out_delete_session; } - err = perf_event__synthesize_event_types(tool, process_synthesized_event, - machine); - if (err < 0) { - pr_err("Couldn't synthesize event_types.\n"); - goto out_delete_session; - } - if (have_tracepoints(&evsel_list->entries)) { /* * FIXME err <= 0 here actually means that @@ -904,7 +897,6 @@ const struct option record_options[] = { int cmd_record(int argc, const char **argv, const char *prefix __maybe_unused) { int err = -ENOMEM; - struct perf_evsel *pos; struct perf_evlist *evsel_list; struct perf_record *rec = &record; char errbuf[BUFSIZ]; @@ -968,11 +960,6 @@ int cmd_record(int argc, const char **argv, const char *prefix __maybe_unused) if (perf_evlist__create_maps(evsel_list, &rec->opts.target) < 0) usage_with_options(record_usage, record_options); - list_for_each_entry(pos, &evsel_list->entries, node) { - if (perf_header__push_event(pos->attr.config, perf_evsel__name(pos))) - goto out_free_fd; - } - if (rec->opts.user_interval != ULLONG_MAX) rec->opts.default_interval = rec->opts.user_interval; if (rec->opts.user_freq != UINT_MAX) diff --git a/tools/perf/builtin-report.c b/tools/perf/builtin-report.c index 3662047cc6b1..9725aa375414 100644 --- a/tools/perf/builtin-report.c +++ b/tools/perf/builtin-report.c @@ -49,7 +49,6 @@ struct perf_report { bool mem_mode; struct perf_read_values show_threads_values; const char *pretty_printing_style; - symbol_filter_t annotate_init; const char *cpu_list; const char *symbol_filter_str; float min_percent; @@ -89,7 +88,7 @@ static int perf_report__add_mem_hist_entry(struct perf_tool *tool, if ((sort__has_parent || symbol_conf.use_callchain) && sample->callchain) { err = machine__resolve_callchain(machine, evsel, al->thread, - sample, &parent); + sample, &parent, al); if (err) return err; } @@ -180,7 +179,7 @@ static int perf_report__add_branch_hist_entry(struct perf_tool *tool, if ((sort__has_parent || symbol_conf.use_callchain) && sample->callchain) { err = machine__resolve_callchain(machine, evsel, al->thread, - sample, &parent); + sample, &parent, al); if (err) return err; } @@ -254,7 +253,7 @@ static int perf_evsel__add_hist_entry(struct perf_evsel *evsel, if ((sort__has_parent || symbol_conf.use_callchain) && sample->callchain) { err = machine__resolve_callchain(machine, evsel, al->thread, - sample, &parent); + sample, &parent, al); if (err) return err; } @@ -305,8 +304,7 @@ static int process_sample_event(struct perf_tool *tool, struct addr_location al; int ret; - if (perf_event__preprocess_sample(event, machine, &al, sample, - rep->annotate_init) < 0) { + if (perf_event__preprocess_sample(event, machine, &al, sample) < 0) { fprintf(stderr, "problem processing %d event, skipping it.\n", event->header.type); return -1; @@ -367,7 +365,7 @@ static int process_read_event(struct perf_tool *tool, static int perf_report__setup_sample_type(struct perf_report *rep) { struct perf_session *self = rep->session; - u64 sample_type = perf_evlist__sample_type(self->evlist); + u64 sample_type = perf_evlist__combined_sample_type(self->evlist); if (!self->fd_pipe && !(sample_type & PERF_SAMPLE_CALLCHAIN)) { if (sort__has_parent) { @@ -497,7 +495,7 @@ static int __cmd_report(struct perf_report *rep) ret = perf_session__cpu_bitmap(session, rep->cpu_list, rep->cpu_bitmap); if (ret) - goto out_delete; + return ret; } if (use_browser <= 0) @@ -508,11 +506,11 @@ static int __cmd_report(struct perf_report *rep) ret = perf_report__setup_sample_type(rep); if (ret) - goto out_delete; + return ret; ret = perf_session__process_events(session, &rep->tool); if (ret) - goto out_delete; + return ret; kernel_map = session->machines.host.vmlinux_maps[MAP__FUNCTION]; kernel_kmap = map__kmap(kernel_map); @@ -547,7 +545,7 @@ static int __cmd_report(struct perf_report *rep) if (dump_trace) { perf_session__fprintf_nr_events(session, stdout); - goto out_delete; + return 0; } nr_samples = 0; @@ -572,7 +570,7 @@ static int __cmd_report(struct perf_report *rep) if (nr_samples == 0) { ui__error("The %s file has no samples!\n", session->filename); - goto out_delete; + return 0; } list_for_each_entry(pos, &session->evlist->entries, node) @@ -598,19 +596,6 @@ static int __cmd_report(struct perf_report *rep) } else perf_evlist__tty_browse_hists(session->evlist, rep, help); -out_delete: - /* - * Speed up the exit process, for large files this can - * take quite a while. - * - * XXX Enable this when using valgrind or if we ever - * librarize this command. - * - * Also experiment with obstacks to see how much speed - * up we'll get here. - * - * perf_session__delete(session); - */ return ret; } @@ -680,12 +665,23 @@ parse_callchain_opt(const struct option *opt, const char *arg, int unset) } /* get the call chain order */ - if (!strcmp(tok2, "caller")) + if (!strncmp(tok2, "caller", strlen("caller"))) callchain_param.order = ORDER_CALLER; - else if (!strcmp(tok2, "callee")) + else if (!strncmp(tok2, "callee", strlen("callee"))) callchain_param.order = ORDER_CALLEE; else return -1; + + /* Get the sort key */ + tok2 = strtok(NULL, ","); + if (!tok2) + goto setup; + if (!strncmp(tok2, "function", strlen("function"))) + callchain_param.key = CCKEY_FUNCTION; + else if (!strncmp(tok2, "address", strlen("address"))) + callchain_param.key = CCKEY_ADDRESS; + else + return -1; setup: if (callchain_register_param(&callchain_param) < 0) { fprintf(stderr, "Can't register callchain params\n"); @@ -694,6 +690,24 @@ setup: return 0; } +int +report_parse_ignore_callees_opt(const struct option *opt __maybe_unused, + const char *arg, int unset __maybe_unused) +{ + if (arg) { + int err = regcomp(&ignore_callees_regex, arg, REG_EXTENDED); + if (err) { + char buf[BUFSIZ]; + regerror(err, &ignore_callees_regex, buf, sizeof(buf)); + pr_err("Invalid --ignore-callees regex: %s\n%s", arg, buf); + return -1; + } + have_ignore_callees = 1; + } + + return 0; +} + static int parse_branch_mode(const struct option *opt __maybe_unused, const char *str __maybe_unused, int unset) @@ -736,7 +750,6 @@ int cmd_report(int argc, const char **argv, const char *prefix __maybe_unused) .lost = perf_event__process_lost, .read = process_read_event, .attr = perf_event__process_attr, - .event_type = perf_event__process_event_type, .tracing_data = perf_event__process_tracing_data, .build_id = perf_event__process_build_id, .ordered_samples = true, @@ -780,10 +793,13 @@ int cmd_report(int argc, const char **argv, const char *prefix __maybe_unused) OPT_BOOLEAN('x', "exclude-other", &symbol_conf.exclude_other, "Only display entries with parent-match"), OPT_CALLBACK_DEFAULT('g', "call-graph", &report, "output_type,min_percent[,print_limit],call_order", - "Display callchains using output_type (graph, flat, fractal, or none) , min percent threshold, optional print limit and callchain order. " - "Default: fractal,0.5,callee", &parse_callchain_opt, callchain_default_opt), + "Display callchains using output_type (graph, flat, fractal, or none) , min percent threshold, optional print limit, callchain order, key (function or address). " + "Default: fractal,0.5,callee,function", &parse_callchain_opt, callchain_default_opt), OPT_BOOLEAN('G', "inverted", &report.inverted_callchain, "alias for inverted call graph"), + OPT_CALLBACK(0, "ignore-callees", NULL, "regex", + "ignore callees of these functions in call graphs", + report_parse_ignore_callees_opt), OPT_STRING('d', "dsos", &symbol_conf.dso_list_str, "dso[,dso...]", "only consider symbols in these dsos"), OPT_STRING('c', "comms", &symbol_conf.comm_list_str, "comm[,comm...]", @@ -853,7 +869,6 @@ int cmd_report(int argc, const char **argv, const char *prefix __maybe_unused) setup_browser(true); else { use_browser = 0; - perf_hpp__column_enable(PERF_HPP__OVERHEAD); perf_hpp__init(); } @@ -907,7 +922,8 @@ repeat: */ if (use_browser == 1 && sort__has_sym) { symbol_conf.priv_size = sizeof(struct annotation); - report.annotate_init = symbol__annotate_init; + machines__set_symbol_filter(&session->machines, + symbol__annotate_init); /* * For searching by name on the "Browse map details". * providing it only in verbose mode not to bloat too @@ -931,14 +947,6 @@ repeat: if (parent_pattern != default_parent_pattern) { if (sort_dimension__add("parent") < 0) goto error; - - /* - * Only show the parent fields if we explicitly - * sort that way. If we only use parent machinery - * for filtering, we don't want it. - */ - if (!strstr(sort_order, "parent")) - sort_parent.elide = 1; } if (argc) { diff --git a/tools/perf/builtin-sched.c b/tools/perf/builtin-sched.c index fed9ae432c16..d8c51b2f263f 100644 --- a/tools/perf/builtin-sched.c +++ b/tools/perf/builtin-sched.c @@ -109,8 +109,9 @@ struct trace_sched_handler { int (*wakeup_event)(struct perf_sched *sched, struct perf_evsel *evsel, struct perf_sample *sample, struct machine *machine); - int (*fork_event)(struct perf_sched *sched, struct perf_evsel *evsel, - struct perf_sample *sample); + /* PERF_RECORD_FORK event, not sched_process_fork tracepoint */ + int (*fork_event)(struct perf_sched *sched, union perf_event *event, + struct machine *machine); int (*migrate_task_event)(struct perf_sched *sched, struct perf_evsel *evsel, @@ -717,22 +718,31 @@ static int replay_switch_event(struct perf_sched *sched, return 0; } -static int replay_fork_event(struct perf_sched *sched, struct perf_evsel *evsel, - struct perf_sample *sample) +static int replay_fork_event(struct perf_sched *sched, + union perf_event *event, + struct machine *machine) { - const char *parent_comm = perf_evsel__strval(evsel, sample, "parent_comm"), - *child_comm = perf_evsel__strval(evsel, sample, "child_comm"); - const u32 parent_pid = perf_evsel__intval(evsel, sample, "parent_pid"), - child_pid = perf_evsel__intval(evsel, sample, "child_pid"); + struct thread *child, *parent; + + child = machine__findnew_thread(machine, event->fork.pid, + event->fork.tid); + parent = machine__findnew_thread(machine, event->fork.ppid, + event->fork.ptid); + + if (child == NULL || parent == NULL) { + pr_debug("thread does not exist on fork event: child %p, parent %p\n", + child, parent); + return 0; + } if (verbose) { - printf("sched_fork event %p\n", evsel); - printf("... parent: %s/%d\n", parent_comm, parent_pid); - printf("... child: %s/%d\n", child_comm, child_pid); + printf("fork event\n"); + printf("... parent: %s/%d\n", parent->comm, parent->tid); + printf("... child: %s/%d\n", child->comm, child->tid); } - register_pid(sched, parent_pid, parent_comm); - register_pid(sched, child_pid, child_comm); + register_pid(sched, parent->tid, parent->comm); + register_pid(sched, child->tid, child->comm); return 0; } @@ -824,14 +834,6 @@ static int thread_atoms_insert(struct perf_sched *sched, struct thread *thread) return 0; } -static int latency_fork_event(struct perf_sched *sched __maybe_unused, - struct perf_evsel *evsel __maybe_unused, - struct perf_sample *sample __maybe_unused) -{ - /* should insert the newcomer */ - return 0; -} - static char sched_out_state(u64 prev_state) { const char *str = TASK_STATE_TO_CHAR_STR; @@ -934,8 +936,8 @@ static int latency_switch_event(struct perf_sched *sched, return -1; } - sched_out = machine__findnew_thread(machine, prev_pid); - sched_in = machine__findnew_thread(machine, next_pid); + sched_out = machine__findnew_thread(machine, 0, prev_pid); + sched_in = machine__findnew_thread(machine, 0, next_pid); out_events = thread_atoms_search(&sched->atom_root, sched_out, &sched->cmp_pid); if (!out_events) { @@ -978,7 +980,7 @@ static int latency_runtime_event(struct perf_sched *sched, { const u32 pid = perf_evsel__intval(evsel, sample, "pid"); const u64 runtime = perf_evsel__intval(evsel, sample, "runtime"); - struct thread *thread = machine__findnew_thread(machine, pid); + struct thread *thread = machine__findnew_thread(machine, 0, pid); struct work_atoms *atoms = thread_atoms_search(&sched->atom_root, thread, &sched->cmp_pid); u64 timestamp = sample->time; int cpu = sample->cpu; @@ -1016,7 +1018,7 @@ static int latency_wakeup_event(struct perf_sched *sched, if (!success) return 0; - wakee = machine__findnew_thread(machine, pid); + wakee = machine__findnew_thread(machine, 0, pid); atoms = thread_atoms_search(&sched->atom_root, wakee, &sched->cmp_pid); if (!atoms) { if (thread_atoms_insert(sched, wakee)) @@ -1070,12 +1072,12 @@ static int latency_migrate_task_event(struct perf_sched *sched, if (sched->profile_cpu == -1) return 0; - migrant = machine__findnew_thread(machine, pid); + migrant = machine__findnew_thread(machine, 0, pid); atoms = thread_atoms_search(&sched->atom_root, migrant, &sched->cmp_pid); if (!atoms) { if (thread_atoms_insert(sched, migrant)) return -1; - register_pid(sched, migrant->pid, migrant->comm); + register_pid(sched, migrant->tid, migrant->comm); atoms = thread_atoms_search(&sched->atom_root, migrant, &sched->cmp_pid); if (!atoms) { pr_err("migration-event: Internal tree error"); @@ -1115,7 +1117,7 @@ static void output_lat_thread(struct perf_sched *sched, struct work_atoms *work_ sched->all_runtime += work_list->total_runtime; sched->all_count += work_list->nb_atoms; - ret = printf(" %s:%d ", work_list->thread->comm, work_list->thread->pid); + ret = printf(" %s:%d ", work_list->thread->comm, work_list->thread->tid); for (i = 0; i < 24 - ret; i++) printf(" "); @@ -1131,9 +1133,9 @@ static void output_lat_thread(struct perf_sched *sched, struct work_atoms *work_ static int pid_cmp(struct work_atoms *l, struct work_atoms *r) { - if (l->thread->pid < r->thread->pid) + if (l->thread->tid < r->thread->tid) return -1; - if (l->thread->pid > r->thread->pid) + if (l->thread->tid > r->thread->tid) return 1; return 0; @@ -1289,8 +1291,8 @@ static int map_switch_event(struct perf_sched *sched, struct perf_evsel *evsel, return -1; } - sched_out = machine__findnew_thread(machine, prev_pid); - sched_in = machine__findnew_thread(machine, next_pid); + sched_out = machine__findnew_thread(machine, 0, prev_pid); + sched_in = machine__findnew_thread(machine, 0, next_pid); sched->curr_thread[this_cpu] = sched_in; @@ -1321,7 +1323,7 @@ static int map_switch_event(struct perf_sched *sched, struct perf_evsel *evsel, printf("*"); if (sched->curr_thread[cpu]) { - if (sched->curr_thread[cpu]->pid) + if (sched->curr_thread[cpu]->tid) printf("%2s ", sched->curr_thread[cpu]->shortname); else printf(". "); @@ -1332,7 +1334,7 @@ static int map_switch_event(struct perf_sched *sched, struct perf_evsel *evsel, printf(" %12.6f secs ", (double)timestamp/1e9); if (new_shortname) { printf("%s => %s:%d\n", - sched_in->shortname, sched_in->comm, sched_in->pid); + sched_in->shortname, sched_in->comm, sched_in->tid); } else { printf("\n"); } @@ -1379,25 +1381,20 @@ static int process_sched_runtime_event(struct perf_tool *tool, return 0; } -static int process_sched_fork_event(struct perf_tool *tool, - struct perf_evsel *evsel, - struct perf_sample *sample, - struct machine *machine __maybe_unused) +static int perf_sched__process_fork_event(struct perf_tool *tool, + union perf_event *event, + struct perf_sample *sample, + struct machine *machine) { struct perf_sched *sched = container_of(tool, struct perf_sched, tool); - if (sched->tp_handler->fork_event) - return sched->tp_handler->fork_event(sched, evsel, sample); + /* run the fork event through the perf machineruy */ + perf_event__process_fork(tool, event, sample, machine); - return 0; -} + /* and then run additional processing needed for this command */ + if (sched->tp_handler->fork_event) + return sched->tp_handler->fork_event(sched, event, machine); -static int process_sched_exit_event(struct perf_tool *tool __maybe_unused, - struct perf_evsel *evsel, - struct perf_sample *sample __maybe_unused, - struct machine *machine __maybe_unused) -{ - pr_debug("sched_exit event %p\n", evsel); return 0; } @@ -1425,15 +1422,8 @@ static int perf_sched__process_tracepoint_sample(struct perf_tool *tool __maybe_ struct perf_evsel *evsel, struct machine *machine) { - struct thread *thread = machine__findnew_thread(machine, sample->tid); int err = 0; - if (thread == NULL) { - pr_debug("problem processing %s event, skipping it.\n", - perf_evsel__name(evsel)); - return -1; - } - evsel->hists.stats.total_period += sample->period; hists__inc_nr_events(&evsel->hists, PERF_RECORD_SAMPLE); @@ -1445,7 +1435,7 @@ static int perf_sched__process_tracepoint_sample(struct perf_tool *tool __maybe_ return err; } -static int perf_sched__read_events(struct perf_sched *sched, bool destroy, +static int perf_sched__read_events(struct perf_sched *sched, struct perf_session **psession) { const struct perf_evsel_str_handler handlers[] = { @@ -1453,8 +1443,6 @@ static int perf_sched__read_events(struct perf_sched *sched, bool destroy, { "sched:sched_stat_runtime", process_sched_runtime_event, }, { "sched:sched_wakeup", process_sched_wakeup_event, }, { "sched:sched_wakeup_new", process_sched_wakeup_event, }, - { "sched:sched_process_fork", process_sched_fork_event, }, - { "sched:sched_process_exit", process_sched_exit_event, }, { "sched:sched_migrate_task", process_sched_migrate_task_event, }, }; struct perf_session *session; @@ -1480,11 +1468,10 @@ static int perf_sched__read_events(struct perf_sched *sched, bool destroy, sched->nr_lost_chunks = session->stats.nr_events[PERF_RECORD_LOST]; } - if (destroy) - perf_session__delete(session); - if (psession) *psession = session; + else + perf_session__delete(session); return 0; @@ -1529,8 +1516,11 @@ static int perf_sched__lat(struct perf_sched *sched) struct perf_session *session; setup_pager(); - if (perf_sched__read_events(sched, false, &session)) + + /* save session -- references to threads are held in work_list */ + if (perf_sched__read_events(sched, &session)) return -1; + perf_sched__sort_lat(sched); printf("\n ---------------------------------------------------------------------------------------------------------------\n"); @@ -1565,7 +1555,7 @@ static int perf_sched__map(struct perf_sched *sched) sched->max_cpu = sysconf(_SC_NPROCESSORS_CONF); setup_pager(); - if (perf_sched__read_events(sched, true, NULL)) + if (perf_sched__read_events(sched, NULL)) return -1; print_bad_events(sched); return 0; @@ -1580,7 +1570,7 @@ static int perf_sched__replay(struct perf_sched *sched) test_calibrations(sched); - if (perf_sched__read_events(sched, true, NULL)) + if (perf_sched__read_events(sched, NULL)) return -1; printf("nr_run_events: %ld\n", sched->nr_run_events); @@ -1639,7 +1629,6 @@ static int __cmd_record(int argc, const char **argv) "-e", "sched:sched_stat_sleep", "-e", "sched:sched_stat_iowait", "-e", "sched:sched_stat_runtime", - "-e", "sched:sched_process_exit", "-e", "sched:sched_process_fork", "-e", "sched:sched_wakeup", "-e", "sched:sched_migrate_task", @@ -1662,28 +1651,29 @@ static int __cmd_record(int argc, const char **argv) return cmd_record(i, rec_argv, NULL); } +static const char default_sort_order[] = "avg, max, switch, runtime"; +static struct perf_sched sched = { + .tool = { + .sample = perf_sched__process_tracepoint_sample, + .comm = perf_event__process_comm, + .lost = perf_event__process_lost, + .fork = perf_sched__process_fork_event, + .ordered_samples = true, + }, + .cmp_pid = LIST_HEAD_INIT(sched.cmp_pid), + .sort_list = LIST_HEAD_INIT(sched.sort_list), + .start_work_mutex = PTHREAD_MUTEX_INITIALIZER, + .work_done_wait_mutex = PTHREAD_MUTEX_INITIALIZER, + .curr_pid = { [0 ... MAX_CPUS - 1] = -1 }, + .sort_order = default_sort_order, + .replay_repeat = 10, + .profile_cpu = -1, + .next_shortname1 = 'A', + .next_shortname2 = '0', +}; + int cmd_sched(int argc, const char **argv, const char *prefix __maybe_unused) { - const char default_sort_order[] = "avg, max, switch, runtime"; - struct perf_sched sched = { - .tool = { - .sample = perf_sched__process_tracepoint_sample, - .comm = perf_event__process_comm, - .lost = perf_event__process_lost, - .fork = perf_event__process_fork, - .ordered_samples = true, - }, - .cmp_pid = LIST_HEAD_INIT(sched.cmp_pid), - .sort_list = LIST_HEAD_INIT(sched.sort_list), - .start_work_mutex = PTHREAD_MUTEX_INITIALIZER, - .work_done_wait_mutex = PTHREAD_MUTEX_INITIALIZER, - .curr_pid = { [0 ... MAX_CPUS - 1] = -1 }, - .sort_order = default_sort_order, - .replay_repeat = 10, - .profile_cpu = -1, - .next_shortname1 = 'A', - .next_shortname2 = '0', - }; const struct option latency_options[] = { OPT_STRING('s', "sort", &sched.sort_order, "key[,key2...]", "sort by key(s): runtime, switch, avg, max"), @@ -1729,7 +1719,6 @@ int cmd_sched(int argc, const char **argv, const char *prefix __maybe_unused) .wakeup_event = latency_wakeup_event, .switch_event = latency_switch_event, .runtime_event = latency_runtime_event, - .fork_event = latency_fork_event, .migrate_task_event = latency_migrate_task_event, }; struct trace_sched_handler map_ops = { diff --git a/tools/perf/builtin-script.c b/tools/perf/builtin-script.c index 92d4658f56fb..93a34cef9676 100644 --- a/tools/perf/builtin-script.c +++ b/tools/perf/builtin-script.c @@ -24,6 +24,7 @@ static u64 last_timestamp; static u64 nr_unordered; extern const struct option record_options[]; static bool no_callchain; +static bool latency_format; static bool system_wide; static const char *cpu_list; static DECLARE_BITMAP(cpu_bitmap, MAX_NR_CPUS); @@ -65,6 +66,7 @@ struct output_option { static struct { bool user_set; bool wildcard_set; + unsigned int print_ip_opts; u64 fields; u64 invalid_fields; } output[PERF_TYPE_MAX] = { @@ -234,6 +236,7 @@ static int perf_session__check_output_opt(struct perf_session *session) { int j; struct perf_evsel *evsel; + struct perf_event_attr *attr; for (j = 0; j < PERF_TYPE_MAX; ++j) { evsel = perf_session__find_first_evtype(session, j); @@ -252,6 +255,24 @@ static int perf_session__check_output_opt(struct perf_session *session) if (evsel && output[j].fields && perf_evsel__check_attr(evsel, session)) return -1; + + if (evsel == NULL) + continue; + + attr = &evsel->attr; + + output[j].print_ip_opts = 0; + if (PRINT_FIELD(IP)) + output[j].print_ip_opts |= PRINT_IP_OPT_IP; + + if (PRINT_FIELD(SYM)) + output[j].print_ip_opts |= PRINT_IP_OPT_SYM; + + if (PRINT_FIELD(DSO)) + output[j].print_ip_opts |= PRINT_IP_OPT_DSO; + + if (PRINT_FIELD(SYMOFFSET)) + output[j].print_ip_opts |= PRINT_IP_OPT_SYMOFFSET; } return 0; @@ -381,8 +402,8 @@ static void print_sample_bts(union perf_event *event, else printf("\n"); perf_evsel__print_ip(evsel, event, sample, machine, - PRINT_FIELD(SYM), PRINT_FIELD(DSO), - PRINT_FIELD(SYMOFFSET)); + output[attr->type].print_ip_opts, + PERF_MAX_STACK_DEPTH); } printf(" => "); @@ -396,10 +417,10 @@ static void print_sample_bts(union perf_event *event, static void process_event(union perf_event *event, struct perf_sample *sample, struct perf_evsel *evsel, struct machine *machine, - struct addr_location *al) + struct thread *thread, + struct addr_location *al __maybe_unused) { struct perf_event_attr *attr = &evsel->attr; - struct thread *thread = al->thread; if (output[attr->type].fields == 0) return; @@ -422,9 +443,10 @@ static void process_event(union perf_event *event, struct perf_sample *sample, printf(" "); else printf("\n"); + perf_evsel__print_ip(evsel, event, sample, machine, - PRINT_FIELD(SYM), PRINT_FIELD(DSO), - PRINT_FIELD(SYMOFFSET)); + output[attr->type].print_ip_opts, + PERF_MAX_STACK_DEPTH); } printf("\n"); @@ -479,7 +501,8 @@ static int process_sample_event(struct perf_tool *tool __maybe_unused, struct machine *machine) { struct addr_location al; - struct thread *thread = machine__findnew_thread(machine, event->ip.tid); + struct thread *thread = machine__findnew_thread(machine, sample->pid, + sample->tid); if (thread == NULL) { pr_debug("problem processing %d event, skipping it.\n", @@ -498,7 +521,7 @@ static int process_sample_event(struct perf_tool *tool __maybe_unused, return 0; } - if (perf_event__preprocess_sample(event, machine, &al, sample, 0) < 0) { + if (perf_event__preprocess_sample(event, machine, &al, sample) < 0) { pr_err("problem processing %d event, skipping it.\n", event->header.type); return -1; @@ -510,7 +533,7 @@ static int process_sample_event(struct perf_tool *tool __maybe_unused, if (cpu_list && !test_bit(sample->cpu, cpu_bitmap)) return 0; - scripting_ops->process_event(event, sample, evsel, machine, &al); + scripting_ops->process_event(event, sample, evsel, machine, thread, &al); evsel->hists.stats.total_period += sample->period; return 0; @@ -523,7 +546,6 @@ static struct perf_tool perf_script = { .exit = perf_event__process_exit, .fork = perf_event__process_fork, .attr = perf_event__process_attr, - .event_type = perf_event__process_event_type, .tracing_data = perf_event__process_tracing_data, .build_id = perf_event__process_build_id, .ordered_samples = true, diff --git a/tools/perf/builtin-stat.c b/tools/perf/builtin-stat.c index 352fbd7ff4a1..f686d5ff594e 100644 --- a/tools/perf/builtin-stat.c +++ b/tools/perf/builtin-stat.c @@ -100,6 +100,7 @@ static const char *pre_cmd = NULL; static const char *post_cmd = NULL; static bool sync_run = false; static unsigned int interval = 0; +static unsigned int initial_delay = 0; static bool forever = false; static struct timespec ref_time; static struct cpu_map *aggr_map; @@ -254,7 +255,8 @@ static int create_perf_stat_counter(struct perf_evsel *evsel) if (!perf_target__has_task(&target) && perf_evsel__is_group_leader(evsel)) { attr->disabled = 1; - attr->enable_on_exec = 1; + if (!initial_delay) + attr->enable_on_exec = 1; } return perf_evsel__open_per_thread(evsel, evsel_list->threads); @@ -414,6 +416,22 @@ static void print_interval(void) list_for_each_entry(counter, &evsel_list->entries, node) print_counter_aggr(counter, prefix); } + + fflush(output); +} + +static void handle_initial_delay(void) +{ + struct perf_evsel *counter; + + if (initial_delay) { + const int ncpus = cpu_map__nr(evsel_list->cpus), + nthreads = thread_map__nr(evsel_list->threads); + + usleep(initial_delay * 1000); + list_for_each_entry(counter, &evsel_list->entries, node) + perf_evsel__enable(counter, ncpus, nthreads); + } } static int __run_perf_stat(int argc, const char **argv) @@ -486,6 +504,7 @@ static int __run_perf_stat(int argc, const char **argv) if (forks) { perf_evlist__start_workload(evsel_list); + handle_initial_delay(); if (interval) { while (!waitpid(child_pid, &status, WNOHANG)) { @@ -497,6 +516,7 @@ static int __run_perf_stat(int argc, const char **argv) if (WIFSIGNALED(status)) psignal(WTERMSIG(status), argv[0]); } else { + handle_initial_delay(); while (!done) { nanosleep(&ts, NULL); if (interval) @@ -1419,6 +1439,8 @@ int cmd_stat(int argc, const char **argv, const char *prefix __maybe_unused) "aggregate counts per processor socket", AGGR_SOCKET), OPT_SET_UINT(0, "per-core", &aggr_mode, "aggregate counts per physical processor core", AGGR_CORE), + OPT_UINTEGER('D', "delay", &initial_delay, + "ms to wait before starting measurement after program start"), OPT_END() }; const char * const stat_usage[] = { diff --git a/tools/perf/builtin-timechart.c b/tools/perf/builtin-timechart.c index 4536a92b18f3..c2e02319347a 100644 --- a/tools/perf/builtin-timechart.c +++ b/tools/perf/builtin-timechart.c @@ -12,6 +12,8 @@ * of the License. */ +#include <traceevent/event-parse.h> + #include "builtin.h" #include "util/util.h" @@ -19,6 +21,7 @@ #include "util/color.h" #include <linux/list.h> #include "util/cache.h" +#include "util/evlist.h" #include "util/evsel.h" #include <linux/rbtree.h> #include "util/symbol.h" @@ -328,25 +331,6 @@ struct wakeup_entry { int success; }; -/* - * trace_flag_type is an enumeration that holds different - * states when a trace occurs. These are: - * IRQS_OFF - interrupts were disabled - * IRQS_NOSUPPORT - arch does not support irqs_disabled_flags - * NEED_RESCED - reschedule is requested - * HARDIRQ - inside an interrupt handler - * SOFTIRQ - inside a softirq handler - */ -enum trace_flag_type { - TRACE_FLAG_IRQS_OFF = 0x01, - TRACE_FLAG_IRQS_NOSUPPORT = 0x02, - TRACE_FLAG_NEED_RESCHED = 0x04, - TRACE_FLAG_HARDIRQ = 0x08, - TRACE_FLAG_SOFTIRQ = 0x10, -}; - - - struct sched_switch { struct trace_entry te; char prev_comm[TASK_COMM_LEN]; @@ -479,6 +463,8 @@ static void sched_switch(int cpu, u64 timestamp, struct trace_entry *te) } } +typedef int (*tracepoint_handler)(struct perf_evsel *evsel, + struct perf_sample *sample); static int process_sample_event(struct perf_tool *tool __maybe_unused, union perf_event *event __maybe_unused, @@ -486,8 +472,6 @@ static int process_sample_event(struct perf_tool *tool __maybe_unused, struct perf_evsel *evsel, struct machine *machine __maybe_unused) { - struct trace_entry *te; - if (evsel->attr.sample_type & PERF_SAMPLE_TIME) { if (!first_time || first_time > sample->time) first_time = sample->time; @@ -495,69 +479,90 @@ static int process_sample_event(struct perf_tool *tool __maybe_unused, last_time = sample->time; } - te = (void *)sample->raw_data; - if ((evsel->attr.sample_type & PERF_SAMPLE_RAW) && sample->raw_size > 0) { - char *event_str; -#ifdef SUPPORT_OLD_POWER_EVENTS - struct power_entry_old *peo; - peo = (void *)te; -#endif - /* - * FIXME: use evsel, its already mapped from id to perf_evsel, - * remove perf_header__find_event infrastructure bits. - * Mapping all these "power:cpu_idle" strings to the tracepoint - * ID and then just comparing against evsel->attr.config. - * - * e.g.: - * - * if (evsel->attr.config == power_cpu_idle_id) - */ - event_str = perf_header__find_event(te->type); - - if (!event_str) - return 0; - - if (sample->cpu > numcpus) - numcpus = sample->cpu; - - if (strcmp(event_str, "power:cpu_idle") == 0) { - struct power_processor_entry *ppe = (void *)te; - if (ppe->state == (u32)PWR_EVENT_EXIT) - c_state_end(ppe->cpu_id, sample->time); - else - c_state_start(ppe->cpu_id, sample->time, - ppe->state); - } - else if (strcmp(event_str, "power:cpu_frequency") == 0) { - struct power_processor_entry *ppe = (void *)te; - p_state_change(ppe->cpu_id, sample->time, ppe->state); - } + if (sample->cpu > numcpus) + numcpus = sample->cpu; + + if (evsel->handler.func != NULL) { + tracepoint_handler f = evsel->handler.func; + return f(evsel, sample); + } + + return 0; +} + +static int +process_sample_cpu_idle(struct perf_evsel *evsel __maybe_unused, + struct perf_sample *sample) +{ + struct power_processor_entry *ppe = sample->raw_data; + + if (ppe->state == (u32) PWR_EVENT_EXIT) + c_state_end(ppe->cpu_id, sample->time); + else + c_state_start(ppe->cpu_id, sample->time, ppe->state); + return 0; +} + +static int +process_sample_cpu_frequency(struct perf_evsel *evsel __maybe_unused, + struct perf_sample *sample) +{ + struct power_processor_entry *ppe = sample->raw_data; + + p_state_change(ppe->cpu_id, sample->time, ppe->state); + return 0; +} + +static int +process_sample_sched_wakeup(struct perf_evsel *evsel __maybe_unused, + struct perf_sample *sample) +{ + struct trace_entry *te = sample->raw_data; + + sched_wakeup(sample->cpu, sample->time, sample->pid, te); + return 0; +} - else if (strcmp(event_str, "sched:sched_wakeup") == 0) - sched_wakeup(sample->cpu, sample->time, sample->pid, te); +static int +process_sample_sched_switch(struct perf_evsel *evsel __maybe_unused, + struct perf_sample *sample) +{ + struct trace_entry *te = sample->raw_data; - else if (strcmp(event_str, "sched:sched_switch") == 0) - sched_switch(sample->cpu, sample->time, te); + sched_switch(sample->cpu, sample->time, te); + return 0; +} #ifdef SUPPORT_OLD_POWER_EVENTS - if (use_old_power_events) { - if (strcmp(event_str, "power:power_start") == 0) - c_state_start(peo->cpu_id, sample->time, - peo->value); - - else if (strcmp(event_str, "power:power_end") == 0) - c_state_end(sample->cpu, sample->time); - - else if (strcmp(event_str, - "power:power_frequency") == 0) - p_state_change(peo->cpu_id, sample->time, - peo->value); - } -#endif - } +static int +process_sample_power_start(struct perf_evsel *evsel __maybe_unused, + struct perf_sample *sample) +{ + struct power_entry_old *peo = sample->raw_data; + + c_state_start(peo->cpu_id, sample->time, peo->value); return 0; } +static int +process_sample_power_end(struct perf_evsel *evsel __maybe_unused, + struct perf_sample *sample) +{ + c_state_end(sample->cpu, sample->time); + return 0; +} + +static int +process_sample_power_frequency(struct perf_evsel *evsel __maybe_unused, + struct perf_sample *sample) +{ + struct power_entry_old *peo = sample->raw_data; + + p_state_change(peo->cpu_id, sample->time, peo->value); + return 0; +} +#endif /* SUPPORT_OLD_POWER_EVENTS */ + /* * After the last sample we need to wrap up the current C/P state * and close out each CPU for these. @@ -974,6 +979,17 @@ static int __cmd_timechart(const char *output_name) .sample = process_sample_event, .ordered_samples = true, }; + const struct perf_evsel_str_handler power_tracepoints[] = { + { "power:cpu_idle", process_sample_cpu_idle }, + { "power:cpu_frequency", process_sample_cpu_frequency }, + { "sched:sched_wakeup", process_sample_sched_wakeup }, + { "sched:sched_switch", process_sample_sched_switch }, +#ifdef SUPPORT_OLD_POWER_EVENTS + { "power:power_start", process_sample_power_start }, + { "power:power_end", process_sample_power_end }, + { "power:power_frequency", process_sample_power_frequency }, +#endif + }; struct perf_session *session = perf_session__new(input_name, O_RDONLY, 0, false, &perf_timechart); int ret = -EINVAL; @@ -984,6 +1000,12 @@ static int __cmd_timechart(const char *output_name) if (!perf_session__has_traces(session, "timechart record")) goto out_delete; + if (perf_session__set_tracepoints_handlers(session, + power_tracepoints)) { + pr_err("Initializing session tracepoint handlers failed\n"); + goto out_delete; + } + ret = perf_session__process_events(session, &perf_timechart); if (ret) goto out_delete; diff --git a/tools/perf/builtin-top.c b/tools/perf/builtin-top.c index e06c4f869330..212214162bb2 100644 --- a/tools/perf/builtin-top.c +++ b/tools/perf/builtin-top.c @@ -40,6 +40,7 @@ #include "util/xyarray.h" #include "util/sort.h" #include "util/intlist.h" +#include "arch/common.h" #include "util/debug.h" @@ -102,7 +103,8 @@ static int perf_top__parse_source(struct perf_top *top, struct hist_entry *he) /* * We can't annotate with just /proc/kallsyms */ - if (map->dso->symtab_type == DSO_BINARY_TYPE__KALLSYMS) { + if (map->dso->symtab_type == DSO_BINARY_TYPE__KALLSYMS && + !dso__is_kcore(map->dso)) { pr_err("Can't annotate %s: No vmlinux file was found in the " "path\n", sym->name); sleep(1); @@ -237,8 +239,6 @@ out_unlock: pthread_mutex_unlock(¬es->lock); } -static const char CONSOLE_CLEAR[] = "[H[2J"; - static struct hist_entry *perf_evsel__add_hist_entry(struct perf_evsel *evsel, struct addr_location *al, struct perf_sample *sample) @@ -689,7 +689,7 @@ static void perf_event__process_sample(struct perf_tool *tool, { struct perf_top *top = container_of(tool, struct perf_top, tool); struct symbol *parent = NULL; - u64 ip = event->ip.ip; + u64 ip = sample->ip; struct addr_location al; int err; @@ -699,10 +699,10 @@ static void perf_event__process_sample(struct perf_tool *tool, if (!seen) seen = intlist__new(NULL); - if (!intlist__has_entry(seen, event->ip.pid)) { + if (!intlist__has_entry(seen, sample->pid)) { pr_err("Can't find guest [%d]'s kernel information\n", - event->ip.pid); - intlist__add(seen, event->ip.pid); + sample->pid); + intlist__add(seen, sample->pid); } return; } @@ -716,8 +716,7 @@ static void perf_event__process_sample(struct perf_tool *tool, if (event->header.misc & PERF_RECORD_MISC_EXACT_IP) top->exact_samples++; - if (perf_event__preprocess_sample(event, machine, &al, sample, - symbol_filter) < 0 || + if (perf_event__preprocess_sample(event, machine, &al, sample) < 0 || al.filtered) return; @@ -772,8 +771,7 @@ static void perf_event__process_sample(struct perf_tool *tool, sample->callchain) { err = machine__resolve_callchain(machine, evsel, al.thread, sample, - &parent); - + &parent, &al); if (err) return; } @@ -838,7 +836,8 @@ static void perf_top__mmap_read_idx(struct perf_top *top, int idx) break; case PERF_RECORD_MISC_GUEST_KERNEL: ++top->guest_kernel_samples; - machine = perf_session__find_machine(session, event->ip.pid); + machine = perf_session__find_machine(session, + sample.pid); break; case PERF_RECORD_MISC_GUEST_USER: ++top->guest_us_samples; @@ -939,6 +938,14 @@ static int __cmd_top(struct perf_top *top) if (top->session == NULL) return -ENOMEM; + machines__set_symbol_filter(&top->session->machines, symbol_filter); + + if (!objdump_path) { + ret = perf_session_env__lookup_objdump(&top->session->header.env); + if (ret) + goto out_delete; + } + ret = perf_top__setup_sample_type(top); if (ret) goto out_delete; @@ -1102,6 +1109,9 @@ int cmd_top(int argc, const char **argv, const char *prefix __maybe_unused) OPT_CALLBACK_DEFAULT('G', "call-graph", &top.record_opts, "mode[,dump_size]", record_callchain_help, &parse_callchain_opt, "fp"), + OPT_CALLBACK(0, "ignore-callees", NULL, "regex", + "ignore callees of these functions in call graphs", + report_parse_ignore_callees_opt), OPT_BOOLEAN(0, "show-total-period", &symbol_conf.show_total_period, "Show a column with the sum of periods"), OPT_STRING(0, "dsos", &symbol_conf.dso_list_str, "dso[,dso...]", @@ -1114,6 +1124,8 @@ int cmd_top(int argc, const char **argv, const char *prefix __maybe_unused) "Interleave source code with assembly code (default)"), OPT_BOOLEAN(0, "asm-raw", &symbol_conf.annotate_asm_raw, "Display raw encoding of assembly instructions (default)"), + OPT_STRING(0, "objdump", &objdump_path, "path", + "objdump binary to use for disassembly and annotations"), OPT_STRING('M', "disassembler-style", &disassembler_style, "disassembler style", "Specify disassembler style (e.g. -M intel for intel syntax)"), OPT_STRING('u', "uid", &target->uid_str, "user", "user to profile"), diff --git a/tools/perf/builtin-trace.c b/tools/perf/builtin-trace.c index ab3ed4af1466..b6f0725068bd 100644 --- a/tools/perf/builtin-trace.c +++ b/tools/perf/builtin-trace.c @@ -1,34 +1,331 @@ +#include <traceevent/event-parse.h> #include "builtin.h" #include "util/color.h" +#include "util/debug.h" #include "util/evlist.h" #include "util/machine.h" +#include "util/session.h" #include "util/thread.h" #include "util/parse-options.h" +#include "util/strlist.h" +#include "util/intlist.h" #include "util/thread_map.h" -#include "event-parse.h" #include <libaudit.h> #include <stdlib.h> +#include <sys/mman.h> +#include <linux/futex.h> + +static size_t syscall_arg__scnprintf_hex(char *bf, size_t size, + unsigned long arg, + u8 arg_idx __maybe_unused, + u8 *arg_mask __maybe_unused) +{ + return scnprintf(bf, size, "%#lx", arg); +} + +#define SCA_HEX syscall_arg__scnprintf_hex + +static size_t syscall_arg__scnprintf_whence(char *bf, size_t size, + unsigned long arg, + u8 arg_idx __maybe_unused, + u8 *arg_mask __maybe_unused) +{ + int whence = arg; + + switch (whence) { +#define P_WHENCE(n) case SEEK_##n: return scnprintf(bf, size, #n) + P_WHENCE(SET); + P_WHENCE(CUR); + P_WHENCE(END); +#ifdef SEEK_DATA + P_WHENCE(DATA); +#endif +#ifdef SEEK_HOLE + P_WHENCE(HOLE); +#endif +#undef P_WHENCE + default: break; + } + + return scnprintf(bf, size, "%#x", whence); +} + +#define SCA_WHENCE syscall_arg__scnprintf_whence + +static size_t syscall_arg__scnprintf_mmap_prot(char *bf, size_t size, + unsigned long arg, + u8 arg_idx __maybe_unused, + u8 *arg_mask __maybe_unused) +{ + int printed = 0, prot = arg; + + if (prot == PROT_NONE) + return scnprintf(bf, size, "NONE"); +#define P_MMAP_PROT(n) \ + if (prot & PROT_##n) { \ + printed += scnprintf(bf + printed, size - printed, "%s%s", printed ? "|" : "", #n); \ + prot &= ~PROT_##n; \ + } + + P_MMAP_PROT(EXEC); + P_MMAP_PROT(READ); + P_MMAP_PROT(WRITE); +#ifdef PROT_SEM + P_MMAP_PROT(SEM); +#endif + P_MMAP_PROT(GROWSDOWN); + P_MMAP_PROT(GROWSUP); +#undef P_MMAP_PROT + + if (prot) + printed += scnprintf(bf + printed, size - printed, "%s%#x", printed ? "|" : "", prot); + + return printed; +} + +#define SCA_MMAP_PROT syscall_arg__scnprintf_mmap_prot + +static size_t syscall_arg__scnprintf_mmap_flags(char *bf, size_t size, + unsigned long arg, u8 arg_idx __maybe_unused, + u8 *arg_mask __maybe_unused) +{ + int printed = 0, flags = arg; + +#define P_MMAP_FLAG(n) \ + if (flags & MAP_##n) { \ + printed += scnprintf(bf + printed, size - printed, "%s%s", printed ? "|" : "", #n); \ + flags &= ~MAP_##n; \ + } + + P_MMAP_FLAG(SHARED); + P_MMAP_FLAG(PRIVATE); + P_MMAP_FLAG(32BIT); + P_MMAP_FLAG(ANONYMOUS); + P_MMAP_FLAG(DENYWRITE); + P_MMAP_FLAG(EXECUTABLE); + P_MMAP_FLAG(FILE); + P_MMAP_FLAG(FIXED); + P_MMAP_FLAG(GROWSDOWN); +#ifdef MAP_HUGETLB + P_MMAP_FLAG(HUGETLB); +#endif + P_MMAP_FLAG(LOCKED); + P_MMAP_FLAG(NONBLOCK); + P_MMAP_FLAG(NORESERVE); + P_MMAP_FLAG(POPULATE); + P_MMAP_FLAG(STACK); +#ifdef MAP_UNINITIALIZED + P_MMAP_FLAG(UNINITIALIZED); +#endif +#undef P_MMAP_FLAG + + if (flags) + printed += scnprintf(bf + printed, size - printed, "%s%#x", printed ? "|" : "", flags); + + return printed; +} + +#define SCA_MMAP_FLAGS syscall_arg__scnprintf_mmap_flags + +static size_t syscall_arg__scnprintf_madvise_behavior(char *bf, size_t size, + unsigned long arg, u8 arg_idx __maybe_unused, + u8 *arg_mask __maybe_unused) +{ + int behavior = arg; + + switch (behavior) { +#define P_MADV_BHV(n) case MADV_##n: return scnprintf(bf, size, #n) + P_MADV_BHV(NORMAL); + P_MADV_BHV(RANDOM); + P_MADV_BHV(SEQUENTIAL); + P_MADV_BHV(WILLNEED); + P_MADV_BHV(DONTNEED); + P_MADV_BHV(REMOVE); + P_MADV_BHV(DONTFORK); + P_MADV_BHV(DOFORK); + P_MADV_BHV(HWPOISON); +#ifdef MADV_SOFT_OFFLINE + P_MADV_BHV(SOFT_OFFLINE); +#endif + P_MADV_BHV(MERGEABLE); + P_MADV_BHV(UNMERGEABLE); +#ifdef MADV_HUGEPAGE + P_MADV_BHV(HUGEPAGE); +#endif +#ifdef MADV_NOHUGEPAGE + P_MADV_BHV(NOHUGEPAGE); +#endif +#ifdef MADV_DONTDUMP + P_MADV_BHV(DONTDUMP); +#endif +#ifdef MADV_DODUMP + P_MADV_BHV(DODUMP); +#endif +#undef P_MADV_PHV + default: break; + } + + return scnprintf(bf, size, "%#x", behavior); +} + +#define SCA_MADV_BHV syscall_arg__scnprintf_madvise_behavior + +static size_t syscall_arg__scnprintf_futex_op(char *bf, size_t size, unsigned long arg, + u8 arg_idx __maybe_unused, u8 *arg_mask) +{ + enum syscall_futex_args { + SCF_UADDR = (1 << 0), + SCF_OP = (1 << 1), + SCF_VAL = (1 << 2), + SCF_TIMEOUT = (1 << 3), + SCF_UADDR2 = (1 << 4), + SCF_VAL3 = (1 << 5), + }; + int op = arg; + int cmd = op & FUTEX_CMD_MASK; + size_t printed = 0; + + switch (cmd) { +#define P_FUTEX_OP(n) case FUTEX_##n: printed = scnprintf(bf, size, #n); + P_FUTEX_OP(WAIT); *arg_mask |= SCF_VAL3|SCF_UADDR2; break; + P_FUTEX_OP(WAKE); *arg_mask |= SCF_VAL3|SCF_UADDR2|SCF_TIMEOUT; break; + P_FUTEX_OP(FD); *arg_mask |= SCF_VAL3|SCF_UADDR2|SCF_TIMEOUT; break; + P_FUTEX_OP(REQUEUE); *arg_mask |= SCF_VAL3|SCF_TIMEOUT; break; + P_FUTEX_OP(CMP_REQUEUE); *arg_mask |= SCF_TIMEOUT; break; + P_FUTEX_OP(CMP_REQUEUE_PI); *arg_mask |= SCF_TIMEOUT; break; + P_FUTEX_OP(WAKE_OP); break; + P_FUTEX_OP(LOCK_PI); *arg_mask |= SCF_VAL3|SCF_UADDR2|SCF_TIMEOUT; break; + P_FUTEX_OP(UNLOCK_PI); *arg_mask |= SCF_VAL3|SCF_UADDR2|SCF_TIMEOUT; break; + P_FUTEX_OP(TRYLOCK_PI); *arg_mask |= SCF_VAL3|SCF_UADDR2; break; + P_FUTEX_OP(WAIT_BITSET); *arg_mask |= SCF_UADDR2; break; + P_FUTEX_OP(WAKE_BITSET); *arg_mask |= SCF_UADDR2; break; + P_FUTEX_OP(WAIT_REQUEUE_PI); break; + default: printed = scnprintf(bf, size, "%#x", cmd); break; + } + + if (op & FUTEX_PRIVATE_FLAG) + printed += scnprintf(bf + printed, size - printed, "|PRIV"); + + if (op & FUTEX_CLOCK_REALTIME) + printed += scnprintf(bf + printed, size - printed, "|CLKRT"); + + return printed; +} + +#define SCA_FUTEX_OP syscall_arg__scnprintf_futex_op + +static size_t syscall_arg__scnprintf_open_flags(char *bf, size_t size, + unsigned long arg, + u8 arg_idx, u8 *arg_mask) +{ + int printed = 0, flags = arg; + + if (!(flags & O_CREAT)) + *arg_mask |= 1 << (arg_idx + 1); /* Mask the mode parm */ + + if (flags == 0) + return scnprintf(bf, size, "RDONLY"); +#define P_FLAG(n) \ + if (flags & O_##n) { \ + printed += scnprintf(bf + printed, size - printed, "%s%s", printed ? "|" : "", #n); \ + flags &= ~O_##n; \ + } + + P_FLAG(APPEND); + P_FLAG(ASYNC); + P_FLAG(CLOEXEC); + P_FLAG(CREAT); + P_FLAG(DIRECT); + P_FLAG(DIRECTORY); + P_FLAG(EXCL); + P_FLAG(LARGEFILE); + P_FLAG(NOATIME); + P_FLAG(NOCTTY); +#ifdef O_NONBLOCK + P_FLAG(NONBLOCK); +#elif O_NDELAY + P_FLAG(NDELAY); +#endif +#ifdef O_PATH + P_FLAG(PATH); +#endif + P_FLAG(RDWR); +#ifdef O_DSYNC + if ((flags & O_SYNC) == O_SYNC) + printed += scnprintf(bf + printed, size - printed, "%s%s", printed ? "|" : "", "SYNC"); + else { + P_FLAG(DSYNC); + } +#else + P_FLAG(SYNC); +#endif + P_FLAG(TRUNC); + P_FLAG(WRONLY); +#undef P_FLAG + + if (flags) + printed += scnprintf(bf + printed, size - printed, "%s%#x", printed ? "|" : "", flags); + + return printed; +} + +#define SCA_OPEN_FLAGS syscall_arg__scnprintf_open_flags static struct syscall_fmt { const char *name; const char *alias; + size_t (*arg_scnprintf[6])(char *bf, size_t size, unsigned long arg, u8 arg_idx, u8 *arg_mask); bool errmsg; bool timeout; + bool hexret; } syscall_fmts[] = { { .name = "access", .errmsg = true, }, { .name = "arch_prctl", .errmsg = true, .alias = "prctl", }, + { .name = "brk", .hexret = true, + .arg_scnprintf = { [0] = SCA_HEX, /* brk */ }, }, + { .name = "mmap", .hexret = true, }, + { .name = "connect", .errmsg = true, }, { .name = "fstat", .errmsg = true, .alias = "newfstat", }, { .name = "fstatat", .errmsg = true, .alias = "newfstatat", }, - { .name = "futex", .errmsg = true, }, - { .name = "open", .errmsg = true, }, + { .name = "futex", .errmsg = true, + .arg_scnprintf = { [1] = SCA_FUTEX_OP, /* op */ }, }, + { .name = "ioctl", .errmsg = true, + .arg_scnprintf = { [2] = SCA_HEX, /* arg */ }, }, + { .name = "lseek", .errmsg = true, + .arg_scnprintf = { [2] = SCA_WHENCE, /* whence */ }, }, + { .name = "lstat", .errmsg = true, .alias = "newlstat", }, + { .name = "madvise", .errmsg = true, + .arg_scnprintf = { [0] = SCA_HEX, /* start */ + [2] = SCA_MADV_BHV, /* behavior */ }, }, + { .name = "mmap", .hexret = true, + .arg_scnprintf = { [0] = SCA_HEX, /* addr */ + [2] = SCA_MMAP_PROT, /* prot */ + [3] = SCA_MMAP_FLAGS, /* flags */ }, }, + { .name = "mprotect", .errmsg = true, + .arg_scnprintf = { [0] = SCA_HEX, /* start */ + [2] = SCA_MMAP_PROT, /* prot */ }, }, + { .name = "mremap", .hexret = true, + .arg_scnprintf = { [0] = SCA_HEX, /* addr */ + [4] = SCA_HEX, /* new_addr */ }, }, + { .name = "munmap", .errmsg = true, + .arg_scnprintf = { [0] = SCA_HEX, /* addr */ }, }, + { .name = "open", .errmsg = true, + .arg_scnprintf = { [1] = SCA_OPEN_FLAGS, /* flags */ }, }, + { .name = "open_by_handle_at", .errmsg = true, + .arg_scnprintf = { [2] = SCA_OPEN_FLAGS, /* flags */ }, }, + { .name = "openat", .errmsg = true, + .arg_scnprintf = { [2] = SCA_OPEN_FLAGS, /* flags */ }, }, { .name = "poll", .errmsg = true, .timeout = true, }, { .name = "ppoll", .errmsg = true, .timeout = true, }, + { .name = "pread", .errmsg = true, .alias = "pread64", }, + { .name = "pwrite", .errmsg = true, .alias = "pwrite64", }, { .name = "read", .errmsg = true, }, { .name = "recvfrom", .errmsg = true, }, { .name = "select", .errmsg = true, .timeout = true, }, { .name = "socket", .errmsg = true, }, { .name = "stat", .errmsg = true, .alias = "newstat", }, + { .name = "uname", .errmsg = true, .alias = "newuname", }, }; static int syscall_fmt__cmp(const void *name, const void *fmtp) @@ -46,7 +343,10 @@ static struct syscall_fmt *syscall_fmt__find(const char *name) struct syscall { struct event_format *tp_format; const char *name; + bool filtered; struct syscall_fmt *fmt; + size_t (**arg_scnprintf)(char *bf, size_t size, + unsigned long arg, u8 arg_idx, u8 *args_mask); }; static size_t fprintf_duration(unsigned long t, FILE *fp) @@ -60,7 +360,7 @@ static size_t fprintf_duration(unsigned long t, FILE *fp) printed += color_fprintf(fp, PERF_COLOR_YELLOW, "%6.3f ms", duration); else printed += color_fprintf(fp, PERF_COLOR_NORMAL, "%6.3f ms", duration); - return printed + fprintf(stdout, "): "); + return printed + fprintf(fp, "): "); } struct thread_trace { @@ -77,7 +377,7 @@ static struct thread_trace *thread_trace__new(void) return zalloc(sizeof(struct thread_trace)); } -static struct thread_trace *thread__trace(struct thread *thread) +static struct thread_trace *thread__trace(struct thread *thread, FILE *fp) { struct thread_trace *ttrace; @@ -95,12 +395,13 @@ static struct thread_trace *thread__trace(struct thread *thread) return ttrace; fail: - color_fprintf(stdout, PERF_COLOR_RED, + color_fprintf(fp, PERF_COLOR_RED, "WARNING: not enough memory, dropping samples!\n"); return NULL; } struct trace { + struct perf_tool tool; int audit_machine; struct { int max; @@ -109,7 +410,12 @@ struct trace { struct perf_record_opts opts; struct machine host; u64 base_time; + FILE *output; unsigned long nr_events; + struct strlist *ev_qualifier; + bool not_ev_qualifier; + struct intlist *tid_list; + struct intlist *pid_list; bool sched; bool multiple_threads; double duration_filter; @@ -142,18 +448,19 @@ static size_t trace__fprintf_entry_head(struct trace *trace, struct thread *thre printed += fprintf_duration(duration, fp); if (trace->multiple_threads) - printed += fprintf(fp, "%d ", thread->pid); + printed += fprintf(fp, "%d ", thread->tid); return printed; } -static int trace__process_event(struct machine *machine, union perf_event *event) +static int trace__process_event(struct trace *trace, struct machine *machine, + union perf_event *event) { int ret = 0; switch (event->header.type) { case PERF_RECORD_LOST: - color_fprintf(stdout, PERF_COLOR_RED, + color_fprintf(trace->output, PERF_COLOR_RED, "LOST %" PRIu64 " events!\n", event->lost.lost); ret = machine__process_lost_event(machine, event); default: @@ -164,12 +471,13 @@ static int trace__process_event(struct machine *machine, union perf_event *event return ret; } -static int trace__tool_process(struct perf_tool *tool __maybe_unused, +static int trace__tool_process(struct perf_tool *tool, union perf_event *event, struct perf_sample *sample __maybe_unused, struct machine *machine) { - return trace__process_event(machine, event); + struct trace *trace = container_of(tool, struct trace, tool); + return trace__process_event(trace, machine, event); } static int trace__symbols_init(struct trace *trace, struct perf_evlist *evlist) @@ -183,11 +491,11 @@ static int trace__symbols_init(struct trace *trace, struct perf_evlist *evlist) machine__create_kernel_maps(&trace->host); if (perf_target__has_task(&trace->opts.target)) { - err = perf_event__synthesize_thread_map(NULL, evlist->threads, + err = perf_event__synthesize_thread_map(&trace->tool, evlist->threads, trace__tool_process, &trace->host); } else { - err = perf_event__synthesize_threads(NULL, trace__tool_process, + err = perf_event__synthesize_threads(&trace->tool, trace__tool_process, &trace->host); } @@ -197,6 +505,26 @@ static int trace__symbols_init(struct trace *trace, struct perf_evlist *evlist) return err; } +static int syscall__set_arg_fmts(struct syscall *sc) +{ + struct format_field *field; + int idx = 0; + + sc->arg_scnprintf = calloc(sc->tp_format->format.nr_fields - 1, sizeof(void *)); + if (sc->arg_scnprintf == NULL) + return -1; + + for (field = sc->tp_format->format.fields->next; field; field = field->next) { + if (sc->fmt && sc->fmt->arg_scnprintf[idx]) + sc->arg_scnprintf[idx] = sc->fmt->arg_scnprintf[idx]; + else if (field->flags & FIELD_IS_POINTER) + sc->arg_scnprintf[idx] = syscall_arg__scnprintf_hex; + ++idx; + } + + return 0; +} + static int trace__read_syscall_info(struct trace *trace, int id) { char tp_name[128]; @@ -225,6 +553,20 @@ static int trace__read_syscall_info(struct trace *trace, int id) sc = trace->syscalls.table + id; sc->name = name; + + if (trace->ev_qualifier) { + bool in = strlist__find(trace->ev_qualifier, name) != NULL; + + if (!(in ^ trace->not_ev_qualifier)) { + sc->filtered = true; + /* + * No need to do read tracepoint information since this will be + * filtered out. + */ + return 0; + } + } + sc->fmt = syscall_fmt__find(sc->name); snprintf(tp_name, sizeof(tp_name), "sys_enter_%s", sc->name); @@ -235,7 +577,10 @@ static int trace__read_syscall_info(struct trace *trace, int id) sc->tp_format = event_format__new("syscalls", tp_name); } - return sc->tp_format != NULL ? 0 : -1; + if (sc->tp_format == NULL) + return -1; + + return syscall__set_arg_fmts(sc); } static size_t syscall__scnprintf_args(struct syscall *sc, char *bf, size_t size, @@ -246,11 +591,23 @@ static size_t syscall__scnprintf_args(struct syscall *sc, char *bf, size_t size, if (sc->tp_format != NULL) { struct format_field *field; + u8 mask = 0, bit = 1; + + for (field = sc->tp_format->format.fields->next; field; + field = field->next, ++i, bit <<= 1) { + if (mask & bit) + continue; - for (field = sc->tp_format->format.fields->next; field; field = field->next) { printed += scnprintf(bf + printed, size - printed, - "%s%s: %ld", printed ? ", " : "", - field->name, args[i++]); + "%s%s: ", printed ? ", " : "", field->name); + + if (sc->arg_scnprintf && sc->arg_scnprintf[i]) { + printed += sc->arg_scnprintf[i](bf + printed, size - printed, + args[i], i, &mask); + } else { + printed += scnprintf(bf + printed, size - printed, + "%ld", args[i]); + } } } else { while (i < 6) { @@ -274,7 +631,22 @@ static struct syscall *trace__syscall_info(struct trace *trace, int id = perf_evsel__intval(evsel, sample, "id"); if (id < 0) { - printf("Invalid syscall %d id, skipping...\n", id); + + /* + * XXX: Noticed on x86_64, reproduced as far back as 3.0.36, haven't tried + * before that, leaving at a higher verbosity level till that is + * explained. Reproduced with plain ftrace with: + * + * echo 1 > /t/events/raw_syscalls/sys_exit/enable + * grep "NR -1 " /t/trace_pipe + * + * After generating some load on the machine. + */ + if (verbose > 1) { + static u64 n; + fprintf(trace->output, "Invalid syscall %d id, skipping (%s, %" PRIu64 ") ...\n", + id, perf_evsel__name(evsel), ++n); + } return NULL; } @@ -288,10 +660,12 @@ static struct syscall *trace__syscall_info(struct trace *trace, return &trace->syscalls.table[id]; out_cant_read: - printf("Problems reading syscall %d", id); - if (id <= trace->syscalls.max && trace->syscalls.table[id].name != NULL) - printf("(%s)", trace->syscalls.table[id].name); - puts(" information"); + if (verbose) { + fprintf(trace->output, "Problems reading syscall %d", id); + if (id <= trace->syscalls.max && trace->syscalls.table[id].name != NULL) + fprintf(trace->output, "(%s)", trace->syscalls.table[id].name); + fputs(" information\n", trace->output); + } return NULL; } @@ -301,16 +675,25 @@ static int trace__sys_enter(struct trace *trace, struct perf_evsel *evsel, char *msg; void *args; size_t printed = 0; - struct thread *thread = machine__findnew_thread(&trace->host, sample->tid); + struct thread *thread; struct syscall *sc = trace__syscall_info(trace, evsel, sample); - struct thread_trace *ttrace = thread__trace(thread); + struct thread_trace *ttrace; + + if (sc == NULL) + return -1; - if (ttrace == NULL || sc == NULL) + if (sc->filtered) + return 0; + + thread = machine__findnew_thread(&trace->host, sample->pid, + sample->tid); + ttrace = thread__trace(thread, trace->output); + if (ttrace == NULL) return -1; args = perf_evsel__rawptr(evsel, sample, "args"); if (args == NULL) { - printf("Problems reading syscall arguments\n"); + fprintf(trace->output, "Problems reading syscall arguments\n"); return -1; } @@ -330,8 +713,8 @@ static int trace__sys_enter(struct trace *trace, struct perf_evsel *evsel, if (!strcmp(sc->name, "exit_group") || !strcmp(sc->name, "exit")) { if (!trace->duration_filter) { - trace__fprintf_entry_head(trace, thread, 1, sample->time, stdout); - printf("%-70s\n", ttrace->entry_str); + trace__fprintf_entry_head(trace, thread, 1, sample->time, trace->output); + fprintf(trace->output, "%-70s\n", ttrace->entry_str); } } else ttrace->entry_pending = true; @@ -344,11 +727,20 @@ static int trace__sys_exit(struct trace *trace, struct perf_evsel *evsel, { int ret; u64 duration = 0; - struct thread *thread = machine__findnew_thread(&trace->host, sample->tid); - struct thread_trace *ttrace = thread__trace(thread); + struct thread *thread; struct syscall *sc = trace__syscall_info(trace, evsel, sample); + struct thread_trace *ttrace; + + if (sc == NULL) + return -1; - if (ttrace == NULL || sc == NULL) + if (sc->filtered) + return 0; + + thread = machine__findnew_thread(&trace->host, sample->pid, + sample->tid); + ttrace = thread__trace(thread, trace->output); + if (ttrace == NULL) return -1; ret = perf_evsel__intval(evsel, sample, "ret"); @@ -364,28 +756,33 @@ static int trace__sys_exit(struct trace *trace, struct perf_evsel *evsel, } else if (trace->duration_filter) goto out; - trace__fprintf_entry_head(trace, thread, duration, sample->time, stdout); + trace__fprintf_entry_head(trace, thread, duration, sample->time, trace->output); if (ttrace->entry_pending) { - printf("%-70s", ttrace->entry_str); + fprintf(trace->output, "%-70s", ttrace->entry_str); } else { - printf(" ... ["); - color_fprintf(stdout, PERF_COLOR_YELLOW, "continued"); - printf("]: %s()", sc->name); + fprintf(trace->output, " ... ["); + color_fprintf(trace->output, PERF_COLOR_YELLOW, "continued"); + fprintf(trace->output, "]: %s()", sc->name); } - if (ret < 0 && sc->fmt && sc->fmt->errmsg) { + if (sc->fmt == NULL) { +signed_print: + fprintf(trace->output, ") = %d", ret); + } else if (ret < 0 && sc->fmt->errmsg) { char bf[256]; const char *emsg = strerror_r(-ret, bf, sizeof(bf)), *e = audit_errno_to_name(-ret); - printf(") = -1 %s %s", e, emsg); - } else if (ret == 0 && sc->fmt && sc->fmt->timeout) - printf(") = 0 Timeout"); + fprintf(trace->output, ") = -1 %s %s", e, emsg); + } else if (ret == 0 && sc->fmt->timeout) + fprintf(trace->output, ") = 0 Timeout"); + else if (sc->fmt->hexret) + fprintf(trace->output, ") = %#x", ret); else - printf(") = %d", ret); + goto signed_print; - putchar('\n'); + fputc('\n', trace->output); out: ttrace->entry_pending = false; @@ -397,8 +794,10 @@ static int trace__sched_stat_runtime(struct trace *trace, struct perf_evsel *evs { u64 runtime = perf_evsel__intval(evsel, sample, "runtime"); double runtime_ms = (double)runtime / NSEC_PER_MSEC; - struct thread *thread = machine__findnew_thread(&trace->host, sample->tid); - struct thread_trace *ttrace = thread__trace(thread); + struct thread *thread = machine__findnew_thread(&trace->host, + sample->pid, + sample->tid); + struct thread_trace *ttrace = thread__trace(thread, trace->output); if (ttrace == NULL) goto out_dump; @@ -408,7 +807,7 @@ static int trace__sched_stat_runtime(struct trace *trace, struct perf_evsel *evs return 0; out_dump: - printf("%s: comm=%s,pid=%u,runtime=%" PRIu64 ",vruntime=%" PRIu64 ")\n", + fprintf(trace->output, "%s: comm=%s,pid=%u,runtime=%" PRIu64 ",vruntime=%" PRIu64 ")\n", evsel->name, perf_evsel__strval(evsel, sample, "comm"), (pid_t)perf_evsel__intval(evsel, sample, "pid"), @@ -417,6 +816,72 @@ out_dump: return 0; } +static bool skip_sample(struct trace *trace, struct perf_sample *sample) +{ + if ((trace->pid_list && intlist__find(trace->pid_list, sample->pid)) || + (trace->tid_list && intlist__find(trace->tid_list, sample->tid))) + return false; + + if (trace->pid_list || trace->tid_list) + return true; + + return false; +} + +static int trace__process_sample(struct perf_tool *tool, + union perf_event *event __maybe_unused, + struct perf_sample *sample, + struct perf_evsel *evsel, + struct machine *machine __maybe_unused) +{ + struct trace *trace = container_of(tool, struct trace, tool); + int err = 0; + + tracepoint_handler handler = evsel->handler.func; + + if (skip_sample(trace, sample)) + return 0; + + if (trace->base_time == 0) + trace->base_time = sample->time; + + if (handler) + handler(trace, evsel, sample); + + return err; +} + +static bool +perf_session__has_tp(struct perf_session *session, const char *name) +{ + struct perf_evsel *evsel; + + evsel = perf_evlist__find_tracepoint_by_name(session->evlist, name); + + return evsel != NULL; +} + +static int parse_target_str(struct trace *trace) +{ + if (trace->opts.target.pid) { + trace->pid_list = intlist__new(trace->opts.target.pid); + if (trace->pid_list == NULL) { + pr_err("Error parsing process id string\n"); + return -EINVAL; + } + } + + if (trace->opts.target.tid) { + trace->tid_list = intlist__new(trace->opts.target.tid); + if (trace->tid_list == NULL) { + pr_err("Error parsing thread id string\n"); + return -EINVAL; + } + } + + return 0; +} + static int trace__run(struct trace *trace, int argc, const char **argv) { struct perf_evlist *evlist = perf_evlist__new(); @@ -426,32 +891,32 @@ static int trace__run(struct trace *trace, int argc, const char **argv) const bool forks = argc > 0; if (evlist == NULL) { - printf("Not enough memory to run!\n"); + fprintf(trace->output, "Not enough memory to run!\n"); goto out; } if (perf_evlist__add_newtp(evlist, "raw_syscalls", "sys_enter", trace__sys_enter) || perf_evlist__add_newtp(evlist, "raw_syscalls", "sys_exit", trace__sys_exit)) { - printf("Couldn't read the raw_syscalls tracepoints information!\n"); + fprintf(trace->output, "Couldn't read the raw_syscalls tracepoints information!\n"); goto out_delete_evlist; } if (trace->sched && perf_evlist__add_newtp(evlist, "sched", "sched_stat_runtime", trace__sched_stat_runtime)) { - printf("Couldn't read the sched_stat_runtime tracepoint information!\n"); + fprintf(trace->output, "Couldn't read the sched_stat_runtime tracepoint information!\n"); goto out_delete_evlist; } err = perf_evlist__create_maps(evlist, &trace->opts.target); if (err < 0) { - printf("Problems parsing the target to trace, check your options!\n"); + fprintf(trace->output, "Problems parsing the target to trace, check your options!\n"); goto out_delete_evlist; } err = trace__symbols_init(trace, evlist); if (err < 0) { - printf("Problems initializing symbol libraries!\n"); + fprintf(trace->output, "Problems initializing symbol libraries!\n"); goto out_delete_maps; } @@ -464,20 +929,20 @@ static int trace__run(struct trace *trace, int argc, const char **argv) err = perf_evlist__prepare_workload(evlist, &trace->opts.target, argv, false, false); if (err < 0) { - printf("Couldn't run the workload!\n"); + fprintf(trace->output, "Couldn't run the workload!\n"); goto out_delete_maps; } } err = perf_evlist__open(evlist); if (err < 0) { - printf("Couldn't create the events: %s\n", strerror(errno)); + fprintf(trace->output, "Couldn't create the events: %s\n", strerror(errno)); goto out_delete_maps; } err = perf_evlist__mmap(evlist, UINT_MAX, false); if (err < 0) { - printf("Couldn't mmap the events: %s\n", strerror(errno)); + fprintf(trace->output, "Couldn't mmap the events: %s\n", strerror(errno)); goto out_close_evlist; } @@ -502,7 +967,7 @@ again: err = perf_evlist__parse_sample(evlist, event, &sample); if (err) { - printf("Can't parse sample, err = %d, skipping...\n", err); + fprintf(trace->output, "Can't parse sample, err = %d, skipping...\n", err); continue; } @@ -510,18 +975,18 @@ again: trace->base_time = sample.time; if (type != PERF_RECORD_SAMPLE) { - trace__process_event(&trace->host, event); + trace__process_event(trace, &trace->host, event); continue; } evsel = perf_evlist__id2evsel(evlist, sample.id); if (evsel == NULL) { - printf("Unknown tp ID %" PRIu64 ", skipping...\n", sample.id); + fprintf(trace->output, "Unknown tp ID %" PRIu64 ", skipping...\n", sample.id); continue; } if (sample.raw_data == NULL) { - printf("%s sample with no payload for tid: %d, cpu %d, raw_size=%d, skipping...\n", + fprintf(trace->output, "%s sample with no payload for tid: %d, cpu %d, raw_size=%d, skipping...\n", perf_evsel__name(evsel), sample.tid, sample.cpu, sample.raw_size); continue; @@ -556,6 +1021,69 @@ out: return err; } +static int trace__replay(struct trace *trace) +{ + const struct perf_evsel_str_handler handlers[] = { + { "raw_syscalls:sys_enter", trace__sys_enter, }, + { "raw_syscalls:sys_exit", trace__sys_exit, }, + }; + + struct perf_session *session; + int err = -1; + + trace->tool.sample = trace__process_sample; + trace->tool.mmap = perf_event__process_mmap; + trace->tool.comm = perf_event__process_comm; + trace->tool.exit = perf_event__process_exit; + trace->tool.fork = perf_event__process_fork; + trace->tool.attr = perf_event__process_attr; + trace->tool.tracing_data = perf_event__process_tracing_data; + trace->tool.build_id = perf_event__process_build_id; + + trace->tool.ordered_samples = true; + trace->tool.ordering_requires_timestamps = true; + + /* add tid to output */ + trace->multiple_threads = true; + + if (symbol__init() < 0) + return -1; + + session = perf_session__new(input_name, O_RDONLY, 0, false, + &trace->tool); + if (session == NULL) + return -ENOMEM; + + err = perf_session__set_tracepoints_handlers(session, handlers); + if (err) + goto out; + + if (!perf_session__has_tp(session, "raw_syscalls:sys_enter")) { + pr_err("Data file does not have raw_syscalls:sys_enter events\n"); + goto out; + } + + if (!perf_session__has_tp(session, "raw_syscalls:sys_exit")) { + pr_err("Data file does not have raw_syscalls:sys_exit events\n"); + goto out; + } + + err = parse_target_str(trace); + if (err != 0) + goto out; + + setup_pager(); + + err = perf_session__process_events(session, &trace->tool); + if (err) + pr_err("Failed to process events, error %d", err); + +out: + perf_session__delete(session); + + return err; +} + static size_t trace__fprintf_threads_header(FILE *fp) { size_t printed; @@ -593,7 +1121,7 @@ static size_t trace__fprintf_thread_summary(struct trace *trace, FILE *fp) color = PERF_COLOR_YELLOW; printed += color_fprintf(fp, color, "%20s", thread->comm); - printed += fprintf(fp, " - %-5d :%11lu [", thread->pid, ttrace->nr_events); + printed += fprintf(fp, " - %-5d :%11lu [", thread->tid, ttrace->nr_events); printed += color_fprintf(fp, color, "%5.1f%%", ratio); printed += fprintf(fp, " ] %10.3f ms\n", ttrace->runtime_ms); } @@ -610,6 +1138,23 @@ static int trace__set_duration(const struct option *opt, const char *str, return 0; } +static int trace__open_output(struct trace *trace, const char *filename) +{ + struct stat st; + + if (!stat(filename, &st) && st.st_size) { + char oldname[PATH_MAX]; + + scnprintf(oldname, sizeof(oldname), "%s.old", filename); + unlink(oldname); + rename(filename, oldname); + } + + trace->output = fopen(filename, "w"); + + return trace->output == NULL ? -errno : 0; +} + int cmd_trace(int argc, const char **argv, const char *prefix __maybe_unused) { const char * const trace_usage[] = { @@ -632,26 +1177,34 @@ int cmd_trace(int argc, const char **argv, const char *prefix __maybe_unused) .no_delay = true, .mmap_pages = 1024, }, + .output = stdout, }; + const char *output_name = NULL; + const char *ev_qualifier_str = NULL; const struct option trace_options[] = { + OPT_STRING('e', "expr", &ev_qualifier_str, "expr", + "list of events to trace"), + OPT_STRING('o', "output", &output_name, "file", "output file name"), + OPT_STRING('i', "input", &input_name, "file", "Analyze events in file"), OPT_STRING('p', "pid", &trace.opts.target.pid, "pid", "trace events on existing process id"), - OPT_STRING(0, "tid", &trace.opts.target.tid, "tid", + OPT_STRING('t', "tid", &trace.opts.target.tid, "tid", "trace events on existing thread id"), - OPT_BOOLEAN(0, "all-cpus", &trace.opts.target.system_wide, + OPT_BOOLEAN('a', "all-cpus", &trace.opts.target.system_wide, "system-wide collection from all CPUs"), - OPT_STRING(0, "cpu", &trace.opts.target.cpu_list, "cpu", + OPT_STRING('C', "cpu", &trace.opts.target.cpu_list, "cpu", "list of cpus to monitor"), OPT_BOOLEAN(0, "no-inherit", &trace.opts.no_inherit, "child tasks do not inherit counters"), - OPT_UINTEGER(0, "mmap-pages", &trace.opts.mmap_pages, + OPT_UINTEGER('m', "mmap-pages", &trace.opts.mmap_pages, "number of mmap data pages"), - OPT_STRING(0, "uid", &trace.opts.target.uid_str, "user", + OPT_STRING('u', "uid", &trace.opts.target.uid_str, "user", "user to profile"), OPT_CALLBACK(0, "duration", &trace, "float", "show only events with duration > N.M ms", trace__set_duration), OPT_BOOLEAN(0, "sched", &trace.sched, "show blocking scheduler events"), + OPT_INCR('v', "verbose", &verbose, "be more verbose"), OPT_END() }; int err; @@ -659,27 +1212,57 @@ int cmd_trace(int argc, const char **argv, const char *prefix __maybe_unused) argc = parse_options(argc, argv, trace_options, trace_usage, 0); + if (output_name != NULL) { + err = trace__open_output(&trace, output_name); + if (err < 0) { + perror("failed to create output file"); + goto out; + } + } + + if (ev_qualifier_str != NULL) { + const char *s = ev_qualifier_str; + + trace.not_ev_qualifier = *s == '!'; + if (trace.not_ev_qualifier) + ++s; + trace.ev_qualifier = strlist__new(true, s); + if (trace.ev_qualifier == NULL) { + fputs("Not enough memory to parse event qualifier", + trace.output); + err = -ENOMEM; + goto out_close; + } + } + err = perf_target__validate(&trace.opts.target); if (err) { perf_target__strerror(&trace.opts.target, err, bf, sizeof(bf)); - printf("%s", bf); - return err; + fprintf(trace.output, "%s", bf); + goto out_close; } err = perf_target__parse_uid(&trace.opts.target); if (err) { perf_target__strerror(&trace.opts.target, err, bf, sizeof(bf)); - printf("%s", bf); - return err; + fprintf(trace.output, "%s", bf); + goto out_close; } if (!argc && perf_target__none(&trace.opts.target)) trace.opts.target.system_wide = true; - err = trace__run(&trace, argc, argv); + if (input_name) + err = trace__replay(&trace); + else + err = trace__run(&trace, argc, argv); if (trace.sched && !err) - trace__fprintf_thread_summary(&trace, stdout); + trace__fprintf_thread_summary(&trace, trace.output); +out_close: + if (output_name != NULL) + fclose(trace.output); +out: return err; } diff --git a/tools/perf/config/Makefile b/tools/perf/config/Makefile index b5d9238cb181..214e17e97e5c 100644 --- a/tools/perf/config/Makefile +++ b/tools/perf/config/Makefile @@ -46,6 +46,8 @@ ifneq ($(obj-perf),) obj-perf := $(abspath $(obj-perf))/ endif +LIB_INCLUDE := $(srctree)/tools/lib/ + # include ARCH specific config -include $(src-perf)/arch/$(ARCH)/Makefile @@ -121,8 +123,7 @@ endif CFLAGS += -I$(src-perf)/util CFLAGS += -I$(src-perf) -CFLAGS += -I$(TRACE_EVENT_DIR) -CFLAGS += -I$(srctree)/tools/lib/ +CFLAGS += -I$(LIB_INCLUDE) CFLAGS += -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 -D_GNU_SOURCE diff --git a/tools/perf/perf.h b/tools/perf/perf.h index 32bd102c32b6..cf20187eee0a 100644 --- a/tools/perf/perf.h +++ b/tools/perf/perf.h @@ -125,6 +125,9 @@ #ifndef NSEC_PER_SEC # define NSEC_PER_SEC 1000000000ULL #endif +#ifndef NSEC_PER_USEC +# define NSEC_PER_USEC 1000ULL +#endif static inline unsigned long long rdclock(void) { diff --git a/tools/perf/python/twatch.py b/tools/perf/python/twatch.py index b11cca584238..2225162ee1fc 100755 --- a/tools/perf/python/twatch.py +++ b/tools/perf/python/twatch.py @@ -21,7 +21,7 @@ def main(): evsel = perf.evsel(task = 1, comm = 1, mmap = 0, wakeup_events = 1, watermark = 1, sample_id_all = 1, - sample_type = perf.SAMPLE_PERIOD | perf.SAMPLE_TID | perf.SAMPLE_CPU | perf.SAMPLE_TID) + sample_type = perf.SAMPLE_PERIOD | perf.SAMPLE_TID | perf.SAMPLE_CPU) evsel.open(cpus = cpus, threads = threads); evlist = perf.evlist(cpus, threads) evlist.add(evsel) diff --git a/tools/perf/tests/attr/test-record-group-sampling b/tools/perf/tests/attr/test-record-group-sampling new file mode 100644 index 000000000000..658f5d60c873 --- /dev/null +++ b/tools/perf/tests/attr/test-record-group-sampling @@ -0,0 +1,36 @@ +[config] +command = record +args = -e '{cycles,cache-misses}:S' kill >/dev/null 2>&1 + +[event-1:base-record] +fd=1 +group_fd=-1 +sample_type=343 +read_format=12 +inherit=0 + +[event-2:base-record] +fd=2 +group_fd=1 + +# cache-misses +type=0 +config=3 + +# default | PERF_SAMPLE_READ +sample_type=343 + +# PERF_FORMAT_ID | PERF_FORMAT_GROUP +read_format=12 + +mmap=0 +comm=0 +enable_on_exec=0 +disabled=0 + +# inherit is disabled for group sampling +inherit=0 + +# sampling disabled +sample_freq=0 +sample_period=0 diff --git a/tools/perf/tests/builtin-test.c b/tools/perf/tests/builtin-test.c index 35b45f1466b5..8bbeba322df9 100644 --- a/tools/perf/tests/builtin-test.c +++ b/tools/perf/tests/builtin-test.c @@ -93,6 +93,24 @@ static struct test { .desc = "Test software clock events have valid period values", .func = test__sw_clock_freq, }, +#if defined(__x86_64__) || defined(__i386__) + { + .desc = "Test converting perf time to TSC", + .func = test__perf_time_to_tsc, + }, +#endif + { + .desc = "Test object code reading", + .func = test__code_reading, + }, + { + .desc = "Test sample parsing", + .func = test__sample_parsing, + }, + { + .desc = "Test using a dummy software event to keep tracking", + .func = test__keep_tracking, + }, { .func = NULL, }, diff --git a/tools/perf/tests/code-reading.c b/tools/perf/tests/code-reading.c new file mode 100644 index 000000000000..6fb781d5586c --- /dev/null +++ b/tools/perf/tests/code-reading.c @@ -0,0 +1,572 @@ +#include <sys/types.h> +#include <stdlib.h> +#include <unistd.h> +#include <stdio.h> +#include <inttypes.h> +#include <ctype.h> +#include <string.h> + +#include "parse-events.h" +#include "evlist.h" +#include "evsel.h" +#include "thread_map.h" +#include "cpumap.h" +#include "machine.h" +#include "event.h" +#include "thread.h" + +#include "tests.h" + +#define BUFSZ 1024 +#define READLEN 128 + +struct state { + u64 done[1024]; + size_t done_cnt; +}; + +static unsigned int hex(char c) +{ + if (c >= '0' && c <= '9') + return c - '0'; + if (c >= 'a' && c <= 'f') + return c - 'a' + 10; + return c - 'A' + 10; +} + +static void read_objdump_line(const char *line, size_t line_len, void **buf, + size_t *len) +{ + const char *p; + size_t i; + + /* Skip to a colon */ + p = strchr(line, ':'); + if (!p) + return; + i = p + 1 - line; + + /* Read bytes */ + while (*len) { + char c1, c2; + + /* Skip spaces */ + for (; i < line_len; i++) { + if (!isspace(line[i])) + break; + } + /* Get 2 hex digits */ + if (i >= line_len || !isxdigit(line[i])) + break; + c1 = line[i++]; + if (i >= line_len || !isxdigit(line[i])) + break; + c2 = line[i++]; + /* Followed by a space */ + if (i < line_len && line[i] && !isspace(line[i])) + break; + /* Store byte */ + *(unsigned char *)*buf = (hex(c1) << 4) | hex(c2); + *buf += 1; + *len -= 1; + } +} + +static int read_objdump_output(FILE *f, void **buf, size_t *len) +{ + char *line = NULL; + size_t line_len; + ssize_t ret; + int err = 0; + + while (1) { + ret = getline(&line, &line_len, f); + if (feof(f)) + break; + if (ret < 0) { + pr_debug("getline failed\n"); + err = -1; + break; + } + read_objdump_line(line, ret, buf, len); + } + + free(line); + + return err; +} + +static int read_via_objdump(const char *filename, u64 addr, void *buf, + size_t len) +{ + char cmd[PATH_MAX * 2]; + const char *fmt; + FILE *f; + int ret; + + fmt = "%s -d --start-address=0x%"PRIx64" --stop-address=0x%"PRIx64" %s"; + ret = snprintf(cmd, sizeof(cmd), fmt, "objdump", addr, addr + len, + filename); + if (ret <= 0 || (size_t)ret >= sizeof(cmd)) + return -1; + + pr_debug("Objdump command is: %s\n", cmd); + + /* Ignore objdump errors */ + strcat(cmd, " 2>/dev/null"); + + f = popen(cmd, "r"); + if (!f) { + pr_debug("popen failed\n"); + return -1; + } + + ret = read_objdump_output(f, &buf, &len); + if (len) { + pr_debug("objdump read too few bytes\n"); + if (!ret) + ret = len; + } + + pclose(f); + + return ret; +} + +static int read_object_code(u64 addr, size_t len, u8 cpumode, + struct thread *thread, struct machine *machine, + struct state *state) +{ + struct addr_location al; + unsigned char buf1[BUFSZ]; + unsigned char buf2[BUFSZ]; + size_t ret_len; + u64 objdump_addr; + int ret; + + pr_debug("Reading object code for memory address: %#"PRIx64"\n", addr); + + thread__find_addr_map(thread, machine, cpumode, MAP__FUNCTION, addr, + &al); + if (!al.map || !al.map->dso) { + pr_debug("thread__find_addr_map failed\n"); + return -1; + } + + pr_debug("File is: %s\n", al.map->dso->long_name); + + if (al.map->dso->symtab_type == DSO_BINARY_TYPE__KALLSYMS && + !dso__is_kcore(al.map->dso)) { + pr_debug("Unexpected kernel address - skipping\n"); + return 0; + } + + pr_debug("On file address is: %#"PRIx64"\n", al.addr); + + if (len > BUFSZ) + len = BUFSZ; + + /* Do not go off the map */ + if (addr + len > al.map->end) + len = al.map->end - addr; + + /* Read the object code using perf */ + ret_len = dso__data_read_offset(al.map->dso, machine, al.addr, buf1, + len); + if (ret_len != len) { + pr_debug("dso__data_read_offset failed\n"); + return -1; + } + + /* + * Converting addresses for use by objdump requires more information. + * map__load() does that. See map__rip_2objdump() for details. + */ + if (map__load(al.map, NULL)) + return -1; + + /* objdump struggles with kcore - try each map only once */ + if (dso__is_kcore(al.map->dso)) { + size_t d; + + for (d = 0; d < state->done_cnt; d++) { + if (state->done[d] == al.map->start) { + pr_debug("kcore map tested already"); + pr_debug(" - skipping\n"); + return 0; + } + } + if (state->done_cnt >= ARRAY_SIZE(state->done)) { + pr_debug("Too many kcore maps - skipping\n"); + return 0; + } + state->done[state->done_cnt++] = al.map->start; + } + + /* Read the object code using objdump */ + objdump_addr = map__rip_2objdump(al.map, al.addr); + ret = read_via_objdump(al.map->dso->long_name, objdump_addr, buf2, len); + if (ret > 0) { + /* + * The kernel maps are inaccurate - assume objdump is right in + * that case. + */ + if (cpumode == PERF_RECORD_MISC_KERNEL || + cpumode == PERF_RECORD_MISC_GUEST_KERNEL) { + len -= ret; + if (len) { + pr_debug("Reducing len to %zu\n", len); + } else if (dso__is_kcore(al.map->dso)) { + /* + * objdump cannot handle very large segments + * that may be found in kcore. + */ + pr_debug("objdump failed for kcore"); + pr_debug(" - skipping\n"); + return 0; + } else { + return -1; + } + } + } + if (ret < 0) { + pr_debug("read_via_objdump failed\n"); + return -1; + } + + /* The results should be identical */ + if (memcmp(buf1, buf2, len)) { + pr_debug("Bytes read differ from those read by objdump\n"); + return -1; + } + pr_debug("Bytes read match those read by objdump\n"); + + return 0; +} + +static int process_sample_event(struct machine *machine, + struct perf_evlist *evlist, + union perf_event *event, struct state *state) +{ + struct perf_sample sample; + struct thread *thread; + u8 cpumode; + + if (perf_evlist__parse_sample(evlist, event, &sample)) { + pr_debug("perf_evlist__parse_sample failed\n"); + return -1; + } + + thread = machine__findnew_thread(machine, sample.pid, sample.pid); + if (!thread) { + pr_debug("machine__findnew_thread failed\n"); + return -1; + } + + cpumode = event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK; + + return read_object_code(sample.ip, READLEN, cpumode, thread, machine, + state); +} + +static int process_event(struct machine *machine, struct perf_evlist *evlist, + union perf_event *event, struct state *state) +{ + if (event->header.type == PERF_RECORD_SAMPLE) + return process_sample_event(machine, evlist, event, state); + + if (event->header.type < PERF_RECORD_MAX) + return machine__process_event(machine, event); + + return 0; +} + +static int process_events(struct machine *machine, struct perf_evlist *evlist, + struct state *state) +{ + union perf_event *event; + int i, ret; + + for (i = 0; i < evlist->nr_mmaps; i++) { + while ((event = perf_evlist__mmap_read(evlist, i)) != NULL) { + ret = process_event(machine, evlist, event, state); + if (ret < 0) + return ret; + } + } + return 0; +} + +static int comp(const void *a, const void *b) +{ + return *(int *)a - *(int *)b; +} + +static void do_sort_something(void) +{ + int buf[40960], i; + + for (i = 0; i < (int)ARRAY_SIZE(buf); i++) + buf[i] = ARRAY_SIZE(buf) - i - 1; + + qsort(buf, ARRAY_SIZE(buf), sizeof(int), comp); + + for (i = 0; i < (int)ARRAY_SIZE(buf); i++) { + if (buf[i] != i) { + pr_debug("qsort failed\n"); + break; + } + } +} + +static void sort_something(void) +{ + int i; + + for (i = 0; i < 10; i++) + do_sort_something(); +} + +static void syscall_something(void) +{ + int pipefd[2]; + int i; + + for (i = 0; i < 1000; i++) { + if (pipe(pipefd) < 0) { + pr_debug("pipe failed\n"); + break; + } + close(pipefd[1]); + close(pipefd[0]); + } +} + +static void fs_something(void) +{ + const char *test_file_name = "temp-perf-code-reading-test-file--"; + FILE *f; + int i; + + for (i = 0; i < 1000; i++) { + f = fopen(test_file_name, "w+"); + if (f) { + fclose(f); + unlink(test_file_name); + } + } +} + +static void do_something(void) +{ + fs_something(); + + sort_something(); + + syscall_something(); +} + +enum { + TEST_CODE_READING_OK, + TEST_CODE_READING_NO_VMLINUX, + TEST_CODE_READING_NO_KCORE, + TEST_CODE_READING_NO_ACCESS, + TEST_CODE_READING_NO_KERNEL_OBJ, +}; + +static int do_test_code_reading(bool try_kcore) +{ + struct machines machines; + struct machine *machine; + struct thread *thread; + struct perf_record_opts opts = { + .mmap_pages = UINT_MAX, + .user_freq = UINT_MAX, + .user_interval = ULLONG_MAX, + .freq = 4000, + .target = { + .uses_mmap = true, + }, + }; + struct state state = { + .done_cnt = 0, + }; + struct thread_map *threads = NULL; + struct cpu_map *cpus = NULL; + struct perf_evlist *evlist = NULL; + struct perf_evsel *evsel = NULL; + int err = -1, ret; + pid_t pid; + struct map *map; + bool have_vmlinux, have_kcore, excl_kernel = false; + + pid = getpid(); + + machines__init(&machines); + machine = &machines.host; + + ret = machine__create_kernel_maps(machine); + if (ret < 0) { + pr_debug("machine__create_kernel_maps failed\n"); + goto out_err; + } + + /* Force the use of kallsyms instead of vmlinux to try kcore */ + if (try_kcore) + symbol_conf.kallsyms_name = "/proc/kallsyms"; + + /* Load kernel map */ + map = machine->vmlinux_maps[MAP__FUNCTION]; + ret = map__load(map, NULL); + if (ret < 0) { + pr_debug("map__load failed\n"); + goto out_err; + } + have_vmlinux = dso__is_vmlinux(map->dso); + have_kcore = dso__is_kcore(map->dso); + + /* 2nd time through we just try kcore */ + if (try_kcore && !have_kcore) + return TEST_CODE_READING_NO_KCORE; + + /* No point getting kernel events if there is no kernel object */ + if (!have_vmlinux && !have_kcore) + excl_kernel = true; + + threads = thread_map__new_by_tid(pid); + if (!threads) { + pr_debug("thread_map__new_by_tid failed\n"); + goto out_err; + } + + ret = perf_event__synthesize_thread_map(NULL, threads, + perf_event__process, machine); + if (ret < 0) { + pr_debug("perf_event__synthesize_thread_map failed\n"); + goto out_err; + } + + thread = machine__findnew_thread(machine, pid, pid); + if (!thread) { + pr_debug("machine__findnew_thread failed\n"); + goto out_err; + } + + cpus = cpu_map__new(NULL); + if (!cpus) { + pr_debug("cpu_map__new failed\n"); + goto out_err; + } + + while (1) { + const char *str; + + evlist = perf_evlist__new(); + if (!evlist) { + pr_debug("perf_evlist__new failed\n"); + goto out_err; + } + + perf_evlist__set_maps(evlist, cpus, threads); + + if (excl_kernel) + str = "cycles:u"; + else + str = "cycles"; + pr_debug("Parsing event '%s'\n", str); + ret = parse_events(evlist, str); + if (ret < 0) { + pr_debug("parse_events failed\n"); + goto out_err; + } + + perf_evlist__config(evlist, &opts); + + evsel = perf_evlist__first(evlist); + + evsel->attr.comm = 1; + evsel->attr.disabled = 1; + evsel->attr.enable_on_exec = 0; + + ret = perf_evlist__open(evlist); + if (ret < 0) { + if (!excl_kernel) { + excl_kernel = true; + perf_evlist__delete(evlist); + evlist = NULL; + continue; + } + pr_debug("perf_evlist__open failed\n"); + goto out_err; + } + break; + } + + ret = perf_evlist__mmap(evlist, UINT_MAX, false); + if (ret < 0) { + pr_debug("perf_evlist__mmap failed\n"); + goto out_err; + } + + perf_evlist__enable(evlist); + + do_something(); + + perf_evlist__disable(evlist); + + ret = process_events(machine, evlist, &state); + if (ret < 0) + goto out_err; + + if (!have_vmlinux && !have_kcore && !try_kcore) + err = TEST_CODE_READING_NO_KERNEL_OBJ; + else if (!have_vmlinux && !try_kcore) + err = TEST_CODE_READING_NO_VMLINUX; + else if (excl_kernel) + err = TEST_CODE_READING_NO_ACCESS; + else + err = TEST_CODE_READING_OK; +out_err: + if (evlist) { + perf_evlist__munmap(evlist); + perf_evlist__close(evlist); + perf_evlist__delete(evlist); + } + if (cpus) + cpu_map__delete(cpus); + if (threads) + thread_map__delete(threads); + machines__destroy_kernel_maps(&machines); + machine__delete_threads(machine); + machines__exit(&machines); + + return err; +} + +int test__code_reading(void) +{ + int ret; + + ret = do_test_code_reading(false); + if (!ret) + ret = do_test_code_reading(true); + + switch (ret) { + case TEST_CODE_READING_OK: + return 0; + case TEST_CODE_READING_NO_VMLINUX: + fprintf(stderr, " (no vmlinux)"); + return 0; + case TEST_CODE_READING_NO_KCORE: + fprintf(stderr, " (no kcore)"); + return 0; + case TEST_CODE_READING_NO_ACCESS: + fprintf(stderr, " (no access)"); + return 0; + case TEST_CODE_READING_NO_KERNEL_OBJ: + fprintf(stderr, " (no kernel obj)"); + return 0; + default: + return -1; + }; +} diff --git a/tools/perf/tests/dso-data.c b/tools/perf/tests/dso-data.c index 5eaffa2de9c5..dffe0551acaa 100644 --- a/tools/perf/tests/dso-data.c +++ b/tools/perf/tests/dso-data.c @@ -10,14 +10,6 @@ #include "symbol.h" #include "tests.h" -#define TEST_ASSERT_VAL(text, cond) \ -do { \ - if (!(cond)) { \ - pr_debug("FAILED %s:%d %s\n", __FILE__, __LINE__, text); \ - return -1; \ - } \ -} while (0) - static char *test_file(int size) { static char buf_templ[] = "/tmp/test-XXXXXX"; diff --git a/tools/perf/tests/evsel-tp-sched.c b/tools/perf/tests/evsel-tp-sched.c index a5d2fcc5ae35..9b98c1554833 100644 --- a/tools/perf/tests/evsel-tp-sched.c +++ b/tools/perf/tests/evsel-tp-sched.c @@ -1,6 +1,6 @@ +#include <traceevent/event-parse.h> #include "evsel.h" #include "tests.h" -#include "event-parse.h" static int perf_evsel__test_field(struct perf_evsel *evsel, const char *name, int size, bool should_be_signed) @@ -49,7 +49,7 @@ int test__perf_evsel__tp_sched_test(void) if (perf_evsel__test_field(evsel, "prev_prio", 4, true)) ret = -1; - if (perf_evsel__test_field(evsel, "prev_state", 8, true)) + if (perf_evsel__test_field(evsel, "prev_state", sizeof(long), true)) ret = -1; if (perf_evsel__test_field(evsel, "next_comm", 16, true)) diff --git a/tools/perf/tests/hists_link.c b/tools/perf/tests/hists_link.c index 89085a9615e2..4228ffc0d968 100644 --- a/tools/perf/tests/hists_link.c +++ b/tools/perf/tests/hists_link.c @@ -88,7 +88,8 @@ static struct machine *setup_fake_machine(struct machines *machines) for (i = 0; i < ARRAY_SIZE(fake_threads); i++) { struct thread *thread; - thread = machine__findnew_thread(machine, fake_threads[i].pid); + thread = machine__findnew_thread(machine, fake_threads[i].pid, + fake_threads[i].pid); if (thread == NULL) goto out; @@ -210,17 +211,15 @@ static int add_hist_entries(struct perf_evlist *evlist, struct machine *machine) list_for_each_entry(evsel, &evlist->entries, node) { for (k = 0; k < ARRAY_SIZE(fake_common_samples); k++) { const union perf_event event = { - .ip = { - .header = { - .misc = PERF_RECORD_MISC_USER, - }, - .pid = fake_common_samples[k].pid, - .ip = fake_common_samples[k].ip, + .header = { + .misc = PERF_RECORD_MISC_USER, }, }; + sample.pid = fake_common_samples[k].pid; + sample.ip = fake_common_samples[k].ip; if (perf_event__preprocess_sample(&event, machine, &al, - &sample, 0) < 0) + &sample) < 0) goto out; he = __hists__add_entry(&evsel->hists, &al, NULL, 1, 1); @@ -234,17 +233,15 @@ static int add_hist_entries(struct perf_evlist *evlist, struct machine *machine) for (k = 0; k < ARRAY_SIZE(fake_samples[i]); k++) { const union perf_event event = { - .ip = { - .header = { - .misc = PERF_RECORD_MISC_USER, - }, - .pid = fake_samples[i][k].pid, - .ip = fake_samples[i][k].ip, + .header = { + .misc = PERF_RECORD_MISC_USER, }, }; + sample.pid = fake_samples[i][k].pid; + sample.ip = fake_samples[i][k].ip; if (perf_event__preprocess_sample(&event, machine, &al, - &sample, 0) < 0) + &sample) < 0) goto out; he = __hists__add_entry(&evsel->hists, &al, NULL, 1, 1); diff --git a/tools/perf/tests/keep-tracking.c b/tools/perf/tests/keep-tracking.c new file mode 100644 index 000000000000..d444ea2c47d9 --- /dev/null +++ b/tools/perf/tests/keep-tracking.c @@ -0,0 +1,154 @@ +#include <sys/types.h> +#include <unistd.h> +#include <sys/prctl.h> + +#include "parse-events.h" +#include "evlist.h" +#include "evsel.h" +#include "thread_map.h" +#include "cpumap.h" +#include "tests.h" + +#define CHECK__(x) { \ + while ((x) < 0) { \ + pr_debug(#x " failed!\n"); \ + goto out_err; \ + } \ +} + +#define CHECK_NOT_NULL__(x) { \ + while ((x) == NULL) { \ + pr_debug(#x " failed!\n"); \ + goto out_err; \ + } \ +} + +static int find_comm(struct perf_evlist *evlist, const char *comm) +{ + union perf_event *event; + int i, found; + + found = 0; + for (i = 0; i < evlist->nr_mmaps; i++) { + while ((event = perf_evlist__mmap_read(evlist, i)) != NULL) { + if (event->header.type == PERF_RECORD_COMM && + (pid_t)event->comm.pid == getpid() && + (pid_t)event->comm.tid == getpid() && + strcmp(event->comm.comm, comm) == 0) + found += 1; + } + } + return found; +} + +/** + * test__keep_tracking - test using a dummy software event to keep tracking. + * + * This function implements a test that checks that tracking events continue + * when an event is disabled but a dummy software event is not disabled. If the + * test passes %0 is returned, otherwise %-1 is returned. + */ +int test__keep_tracking(void) +{ + struct perf_record_opts opts = { + .mmap_pages = UINT_MAX, + .user_freq = UINT_MAX, + .user_interval = ULLONG_MAX, + .freq = 4000, + .target = { + .uses_mmap = true, + }, + }; + struct thread_map *threads = NULL; + struct cpu_map *cpus = NULL; + struct perf_evlist *evlist = NULL; + struct perf_evsel *evsel = NULL; + int found, err = -1; + const char *comm; + + threads = thread_map__new(-1, getpid(), UINT_MAX); + CHECK_NOT_NULL__(threads); + + cpus = cpu_map__new(NULL); + CHECK_NOT_NULL__(cpus); + + evlist = perf_evlist__new(); + CHECK_NOT_NULL__(evlist); + + perf_evlist__set_maps(evlist, cpus, threads); + + CHECK__(parse_events(evlist, "dummy:u")); + CHECK__(parse_events(evlist, "cycles:u")); + + perf_evlist__config(evlist, &opts); + + evsel = perf_evlist__first(evlist); + + evsel->attr.comm = 1; + evsel->attr.disabled = 1; + evsel->attr.enable_on_exec = 0; + + if (perf_evlist__open(evlist) < 0) { + fprintf(stderr, " (not supported)"); + err = 0; + goto out_err; + } + + CHECK__(perf_evlist__mmap(evlist, UINT_MAX, false)); + + /* + * First, test that a 'comm' event can be found when the event is + * enabled. + */ + + perf_evlist__enable(evlist); + + comm = "Test COMM 1"; + CHECK__(prctl(PR_SET_NAME, (unsigned long)comm, 0, 0, 0)); + + perf_evlist__disable(evlist); + + found = find_comm(evlist, comm); + if (found != 1) { + pr_debug("First time, failed to find tracking event.\n"); + goto out_err; + } + + /* + * Secondly, test that a 'comm' event can be found when the event is + * disabled with the dummy event still enabled. + */ + + perf_evlist__enable(evlist); + + evsel = perf_evlist__last(evlist); + + CHECK__(perf_evlist__disable_event(evlist, evsel)); + + comm = "Test COMM 2"; + CHECK__(prctl(PR_SET_NAME, (unsigned long)comm, 0, 0, 0)); + + perf_evlist__disable(evlist); + + found = find_comm(evlist, comm); + if (found != 1) { + pr_debug("Seconf time, failed to find tracking event.\n"); + goto out_err; + } + + err = 0; + +out_err: + if (evlist) { + perf_evlist__disable(evlist); + perf_evlist__munmap(evlist); + perf_evlist__close(evlist); + perf_evlist__delete(evlist); + } + if (cpus) + cpu_map__delete(cpus); + if (threads) + thread_map__delete(threads); + + return err; +} diff --git a/tools/perf/tests/make b/tools/perf/tests/make index c441a2875128..2ca0abf1b2b6 100644 --- a/tools/perf/tests/make +++ b/tools/perf/tests/make @@ -1,6 +1,8 @@ PERF := . MK := Makefile +has = $(shell which $1 2>/dev/null) + # standard single make variable specified make_clean_all := clean all make_python_perf_so := python/perf.so @@ -25,6 +27,13 @@ make_help := help make_doc := doc make_perf_o := perf.o make_util_map_o := util/map.o +make_install := install +make_install_bin := install-bin +make_install_doc := install-doc +make_install_man := install-man +make_install_html := install-html +make_install_info := install-info +make_install_pdf := install-pdf # all the NO_* variable combined make_minimal := NO_LIBPERL=1 NO_LIBPYTHON=1 NO_NEWT=1 NO_GTK2=1 @@ -50,14 +59,27 @@ run += make_no_backtrace run += make_no_libnuma run += make_no_libaudit run += make_no_libbionic -run += make_tags -run += make_cscope run += make_help run += make_doc run += make_perf_o run += make_util_map_o +run += make_install +run += make_install_bin +# FIXME 'install-*' commented out till they're fixed +# run += make_install_doc +# run += make_install_man +# run += make_install_html +# run += make_install_info +# run += make_install_pdf run += make_minimal +ifneq ($(call has,ctags),) +run += make_tags +endif +ifneq ($(call has,cscope),) +run += make_cscope +endif + # $(run_O) contains same portion of $(run) tests with '_O' attached # to distinguish O=... tests run_O := $(addsuffix _O,$(run)) @@ -84,6 +106,31 @@ test_make_python_perf_so := test -f $(PERF)/python/perf.so test_make_perf_o := test -f $(PERF)/perf.o test_make_util_map_o := test -f $(PERF)/util/map.o +test_make_install := test -x $$TMP_DEST/bin/perf +test_make_install_O := $(test_make_install) +test_make_install_bin := $(test_make_install) +test_make_install_bin_O := $(test_make_install) + +# FIXME nothing gets installed +test_make_install_man := test -f $$TMP_DEST/share/man/man1/perf.1 +test_make_install_man_O := $(test_make_install_man) + +# FIXME nothing gets installed +test_make_install_doc := $(test_ok) +test_make_install_doc_O := $(test_ok) + +# FIXME nothing gets installed +test_make_install_html := $(test_ok) +test_make_install_html_O := $(test_ok) + +# FIXME nothing gets installed +test_make_install_info := $(test_ok) +test_make_install_info_O := $(test_ok) + +# FIXME nothing gets installed +test_make_install_pdf := $(test_ok) +test_make_install_pdf_O := $(test_ok) + # Kbuild tests only #test_make_python_perf_so_O := test -f $$TMP/tools/perf/python/perf.so #test_make_perf_o_O := test -f $$TMP/tools/perf/perf.o @@ -95,7 +142,7 @@ test_make_util_map_o_O := true test_default = test -x $(PERF)/perf test = $(if $(test_$1),$(test_$1),$(test_default)) -test_default_O = test -x $$TMP/perf +test_default_O = test -x $$TMP_O/perf test_O = $(if $(test_$1),$(test_$1),$(test_default_O)) all: @@ -111,23 +158,27 @@ clean := @(cd $(PERF); make -s -f $(MK) clean >/dev/null) $(run): $(call clean) - @cmd="cd $(PERF) && make -f $(MK) $($@)"; \ + @TMP_DEST=$$(mktemp -d); \ + cmd="cd $(PERF) && make -f $(MK) DESTDIR=$$TMP_DEST $($@)"; \ echo "- $@: $$cmd" && echo $$cmd > $@ && \ ( eval $$cmd ) >> $@ 2>&1; \ echo " test: $(call test,$@)"; \ $(call test,$@) && \ - rm -f $@ + rm -f $@ \ + rm -rf $$TMP_DEST $(run_O): $(call clean) - @TMP=$$(mktemp -d); \ - cmd="cd $(PERF) && make -f $(MK) $($(patsubst %_O,%,$@)) O=$$TMP"; \ + @TMP_O=$$(mktemp -d); \ + TMP_DEST=$$(mktemp -d); \ + cmd="cd $(PERF) && make -f $(MK) O=$$TMP_O DESTDIR=$$TMP_DEST $($(patsubst %_O,%,$@))"; \ echo "- $@: $$cmd" && echo $$cmd > $@ && \ ( eval $$cmd ) >> $@ 2>&1 && \ echo " test: $(call test_O,$@)"; \ $(call test_O,$@) && \ rm -f $@ && \ - rm -rf $$TMP + rm -rf $$TMP_O \ + rm -rf $$TMP_DEST all: $(run) $(run_O) @echo OK diff --git a/tools/perf/tests/mmap-basic.c b/tools/perf/tests/mmap-basic.c index 5b1b5aba722b..c4185b9aeb80 100644 --- a/tools/perf/tests/mmap-basic.c +++ b/tools/perf/tests/mmap-basic.c @@ -72,7 +72,7 @@ int test__basic_mmap(void) } evsels[i]->attr.wakeup_events = 1; - perf_evsel__set_sample_id(evsels[i]); + perf_evsel__set_sample_id(evsels[i], false); perf_evlist__add(evlist, evsels[i]); diff --git a/tools/perf/tests/parse-events.c b/tools/perf/tests/parse-events.c index 0275bab4ea9e..48114d164e9f 100644 --- a/tools/perf/tests/parse-events.c +++ b/tools/perf/tests/parse-events.c @@ -7,14 +7,6 @@ #include "tests.h" #include <linux/hw_breakpoint.h> -#define TEST_ASSERT_VAL(text, cond) \ -do { \ - if (!(cond)) { \ - pr_debug("FAILED %s:%d %s\n", __FILE__, __LINE__, text); \ - return -1; \ - } \ -} while (0) - #define PERF_TP_SAMPLE_TYPE (PERF_SAMPLE_RAW | PERF_SAMPLE_TIME | \ PERF_SAMPLE_CPU | PERF_SAMPLE_PERIOD) @@ -460,6 +452,7 @@ static int test__checkevent_pmu_events(struct perf_evlist *evlist) evsel->attr.exclude_kernel); TEST_ASSERT_VAL("wrong exclude_hv", evsel->attr.exclude_hv); TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); + TEST_ASSERT_VAL("wrong pinned", !evsel->attr.pinned); return 0; } @@ -528,6 +521,7 @@ static int test__group1(struct perf_evlist *evlist) TEST_ASSERT_VAL("wrong leader", perf_evsel__is_group_leader(evsel)); TEST_ASSERT_VAL("wrong nr_members", evsel->nr_members == 2); TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 0); + TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); /* cycles:upp */ evsel = perf_evsel__next(evsel); @@ -543,6 +537,7 @@ static int test__group1(struct perf_evlist *evlist) TEST_ASSERT_VAL("wrong precise_ip", evsel->attr.precise_ip == 2); TEST_ASSERT_VAL("wrong leader", evsel->leader == leader); TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 1); + TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); return 0; } @@ -568,6 +563,7 @@ static int test__group2(struct perf_evlist *evlist) TEST_ASSERT_VAL("wrong leader", perf_evsel__is_group_leader(evsel)); TEST_ASSERT_VAL("wrong nr_members", evsel->nr_members == 2); TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 0); + TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); /* cache-references + :u modifier */ evsel = perf_evsel__next(evsel); @@ -582,6 +578,7 @@ static int test__group2(struct perf_evlist *evlist) TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); TEST_ASSERT_VAL("wrong leader", evsel->leader == leader); TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 1); + TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); /* cycles:k */ evsel = perf_evsel__next(evsel); @@ -595,6 +592,7 @@ static int test__group2(struct perf_evlist *evlist) TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host); TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); TEST_ASSERT_VAL("wrong leader", perf_evsel__is_group_leader(evsel)); + TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); return 0; } @@ -623,6 +621,7 @@ static int test__group3(struct perf_evlist *evlist __maybe_unused) !strcmp(leader->group_name, "group1")); TEST_ASSERT_VAL("wrong nr_members", evsel->nr_members == 2); TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 0); + TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); /* group1 cycles:kppp */ evsel = perf_evsel__next(evsel); @@ -639,6 +638,7 @@ static int test__group3(struct perf_evlist *evlist __maybe_unused) TEST_ASSERT_VAL("wrong leader", evsel->leader == leader); TEST_ASSERT_VAL("wrong group name", !evsel->group_name); TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 1); + TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); /* group2 cycles + G modifier */ evsel = leader = perf_evsel__next(evsel); @@ -656,6 +656,7 @@ static int test__group3(struct perf_evlist *evlist __maybe_unused) !strcmp(leader->group_name, "group2")); TEST_ASSERT_VAL("wrong nr_members", evsel->nr_members == 2); TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 0); + TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); /* group2 1:3 + G modifier */ evsel = perf_evsel__next(evsel); @@ -669,6 +670,7 @@ static int test__group3(struct perf_evlist *evlist __maybe_unused) TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); TEST_ASSERT_VAL("wrong leader", evsel->leader == leader); TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 1); + TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); /* instructions:u */ evsel = perf_evsel__next(evsel); @@ -682,6 +684,7 @@ static int test__group3(struct perf_evlist *evlist __maybe_unused) TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host); TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); TEST_ASSERT_VAL("wrong leader", perf_evsel__is_group_leader(evsel)); + TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); return 0; } @@ -709,6 +712,7 @@ static int test__group4(struct perf_evlist *evlist __maybe_unused) TEST_ASSERT_VAL("wrong leader", perf_evsel__is_group_leader(evsel)); TEST_ASSERT_VAL("wrong nr_members", evsel->nr_members == 2); TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 0); + TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); /* instructions:kp + p */ evsel = perf_evsel__next(evsel); @@ -724,6 +728,7 @@ static int test__group4(struct perf_evlist *evlist __maybe_unused) TEST_ASSERT_VAL("wrong precise_ip", evsel->attr.precise_ip == 2); TEST_ASSERT_VAL("wrong leader", evsel->leader == leader); TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 1); + TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); return 0; } @@ -750,6 +755,7 @@ static int test__group5(struct perf_evlist *evlist __maybe_unused) TEST_ASSERT_VAL("wrong leader", perf_evsel__is_group_leader(evsel)); TEST_ASSERT_VAL("wrong nr_members", evsel->nr_members == 2); TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 0); + TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); /* instructions + G */ evsel = perf_evsel__next(evsel); @@ -764,6 +770,7 @@ static int test__group5(struct perf_evlist *evlist __maybe_unused) TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); TEST_ASSERT_VAL("wrong leader", evsel->leader == leader); TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 1); + TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); /* cycles:G */ evsel = leader = perf_evsel__next(evsel); @@ -780,6 +787,7 @@ static int test__group5(struct perf_evlist *evlist __maybe_unused) TEST_ASSERT_VAL("wrong leader", perf_evsel__is_group_leader(evsel)); TEST_ASSERT_VAL("wrong nr_members", evsel->nr_members == 2); TEST_ASSERT_VAL("wrong group_idx", perf_evsel__group_idx(evsel) == 0); + TEST_ASSERT_VAL("wrong sample_read", !evsel->sample_read); /* instructions:G */ evsel = perf_evsel__next(evsel); @@ -971,6 +979,142 @@ static int test__group_gh4(struct perf_evlist *evlist) return 0; } +static int test__leader_sample1(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel, *leader; + + TEST_ASSERT_VAL("wrong number of entries", 3 == evlist->nr_entries); + + /* cycles - sampling group leader */ + evsel = leader = perf_evlist__first(evlist); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", + PERF_COUNT_HW_CPU_CYCLES == evsel->attr.config); + TEST_ASSERT_VAL("wrong exclude_user", !evsel->attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", !evsel->attr.exclude_hv); + TEST_ASSERT_VAL("wrong exclude guest", evsel->attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); + TEST_ASSERT_VAL("wrong group name", !evsel->group_name); + TEST_ASSERT_VAL("wrong leader", evsel->leader == leader); + TEST_ASSERT_VAL("wrong sample_read", evsel->sample_read); + + /* cache-misses - not sampling */ + evsel = perf_evsel__next(evsel); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", + PERF_COUNT_HW_CACHE_MISSES == evsel->attr.config); + TEST_ASSERT_VAL("wrong exclude_user", !evsel->attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", !evsel->attr.exclude_hv); + TEST_ASSERT_VAL("wrong exclude guest", evsel->attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); + TEST_ASSERT_VAL("wrong leader", evsel->leader == leader); + TEST_ASSERT_VAL("wrong sample_read", evsel->sample_read); + + /* branch-misses - not sampling */ + evsel = perf_evsel__next(evsel); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", + PERF_COUNT_HW_BRANCH_MISSES == evsel->attr.config); + TEST_ASSERT_VAL("wrong exclude_user", !evsel->attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", !evsel->attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", !evsel->attr.exclude_hv); + TEST_ASSERT_VAL("wrong exclude guest", evsel->attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); + TEST_ASSERT_VAL("wrong group name", !evsel->group_name); + TEST_ASSERT_VAL("wrong leader", evsel->leader == leader); + TEST_ASSERT_VAL("wrong sample_read", evsel->sample_read); + + return 0; +} + +static int test__leader_sample2(struct perf_evlist *evlist __maybe_unused) +{ + struct perf_evsel *evsel, *leader; + + TEST_ASSERT_VAL("wrong number of entries", 2 == evlist->nr_entries); + + /* instructions - sampling group leader */ + evsel = leader = perf_evlist__first(evlist); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", + PERF_COUNT_HW_INSTRUCTIONS == evsel->attr.config); + TEST_ASSERT_VAL("wrong exclude_user", !evsel->attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", evsel->attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", evsel->attr.exclude_hv); + TEST_ASSERT_VAL("wrong exclude guest", evsel->attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); + TEST_ASSERT_VAL("wrong group name", !evsel->group_name); + TEST_ASSERT_VAL("wrong leader", evsel->leader == leader); + TEST_ASSERT_VAL("wrong sample_read", evsel->sample_read); + + /* branch-misses - not sampling */ + evsel = perf_evsel__next(evsel); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", + PERF_COUNT_HW_BRANCH_MISSES == evsel->attr.config); + TEST_ASSERT_VAL("wrong exclude_user", !evsel->attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", evsel->attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", evsel->attr.exclude_hv); + TEST_ASSERT_VAL("wrong exclude guest", evsel->attr.exclude_guest); + TEST_ASSERT_VAL("wrong exclude host", !evsel->attr.exclude_host); + TEST_ASSERT_VAL("wrong precise_ip", !evsel->attr.precise_ip); + TEST_ASSERT_VAL("wrong group name", !evsel->group_name); + TEST_ASSERT_VAL("wrong leader", evsel->leader == leader); + TEST_ASSERT_VAL("wrong sample_read", evsel->sample_read); + + return 0; +} + +static int test__checkevent_pinned_modifier(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel = perf_evlist__first(evlist); + + TEST_ASSERT_VAL("wrong exclude_user", !evsel->attr.exclude_user); + TEST_ASSERT_VAL("wrong exclude_kernel", evsel->attr.exclude_kernel); + TEST_ASSERT_VAL("wrong exclude_hv", evsel->attr.exclude_hv); + TEST_ASSERT_VAL("wrong precise_ip", evsel->attr.precise_ip); + TEST_ASSERT_VAL("wrong pinned", evsel->attr.pinned); + + return test__checkevent_symbolic_name(evlist); +} + +static int test__pinned_group(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel, *leader; + + TEST_ASSERT_VAL("wrong number of entries", 3 == evlist->nr_entries); + + /* cycles - group leader */ + evsel = leader = perf_evlist__first(evlist); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", + PERF_COUNT_HW_CPU_CYCLES == evsel->attr.config); + TEST_ASSERT_VAL("wrong group name", !evsel->group_name); + TEST_ASSERT_VAL("wrong leader", evsel->leader == leader); + TEST_ASSERT_VAL("wrong pinned", evsel->attr.pinned); + + /* cache-misses - can not be pinned, but will go on with the leader */ + evsel = perf_evsel__next(evsel); + TEST_ASSERT_VAL("wrong type", PERF_TYPE_HARDWARE == evsel->attr.type); + TEST_ASSERT_VAL("wrong config", + PERF_COUNT_HW_CACHE_MISSES == evsel->attr.config); + TEST_ASSERT_VAL("wrong pinned", !evsel->attr.pinned); + + /* branch-misses - ditto */ + evsel = perf_evsel__next(evsel); + TEST_ASSERT_VAL("wrong config", + PERF_COUNT_HW_BRANCH_MISSES == evsel->attr.config); + TEST_ASSERT_VAL("wrong pinned", !evsel->attr.pinned); + + return 0; +} + static int count_tracepoints(void) { char events_path[PATH_MAX]; @@ -1187,6 +1331,22 @@ static struct evlist_test test__events[] = { .name = "{cycles:G,cache-misses:H}:uG", .check = test__group_gh4, }, + [38] = { + .name = "{cycles,cache-misses,branch-misses}:S", + .check = test__leader_sample1, + }, + [39] = { + .name = "{instructions,branch-misses}:Su", + .check = test__leader_sample2, + }, + [40] = { + .name = "instructions:uDp", + .check = test__checkevent_pinned_modifier, + }, + [41] = { + .name = "{cycles,cache-misses,branch-misses}:D", + .check = test__pinned_group, + }, }; static struct evlist_test test__events_pmu[] = { @@ -1254,24 +1414,20 @@ static int test_events(struct evlist_test *events, unsigned cnt) static int test_term(struct terms_test *t) { - struct list_head *terms; + struct list_head terms; int ret; - terms = malloc(sizeof(*terms)); - if (!terms) - return -ENOMEM; - - INIT_LIST_HEAD(terms); + INIT_LIST_HEAD(&terms); - ret = parse_events_terms(terms, t->str); + ret = parse_events_terms(&terms, t->str); if (ret) { pr_debug("failed to parse terms '%s', err %d\n", t->str , ret); return ret; } - ret = t->check(terms); - parse_events__free_terms(terms); + ret = t->check(&terms); + parse_events__free_terms(&terms); return ret; } diff --git a/tools/perf/tests/perf-time-to-tsc.c b/tools/perf/tests/perf-time-to-tsc.c new file mode 100644 index 000000000000..0ab61b1f408e --- /dev/null +++ b/tools/perf/tests/perf-time-to-tsc.c @@ -0,0 +1,177 @@ +#include <stdio.h> +#include <sys/types.h> +#include <unistd.h> +#include <inttypes.h> +#include <sys/prctl.h> + +#include "parse-events.h" +#include "evlist.h" +#include "evsel.h" +#include "thread_map.h" +#include "cpumap.h" +#include "tests.h" + +#include "../arch/x86/util/tsc.h" + +#define CHECK__(x) { \ + while ((x) < 0) { \ + pr_debug(#x " failed!\n"); \ + goto out_err; \ + } \ +} + +#define CHECK_NOT_NULL__(x) { \ + while ((x) == NULL) { \ + pr_debug(#x " failed!\n"); \ + goto out_err; \ + } \ +} + +static u64 rdtsc(void) +{ + unsigned int low, high; + + asm volatile("rdtsc" : "=a" (low), "=d" (high)); + + return low | ((u64)high) << 32; +} + +/** + * test__perf_time_to_tsc - test converting perf time to TSC. + * + * This function implements a test that checks that the conversion of perf time + * to and from TSC is consistent with the order of events. If the test passes + * %0 is returned, otherwise %-1 is returned. If TSC conversion is not + * supported then then the test passes but " (not supported)" is printed. + */ +int test__perf_time_to_tsc(void) +{ + struct perf_record_opts opts = { + .mmap_pages = UINT_MAX, + .user_freq = UINT_MAX, + .user_interval = ULLONG_MAX, + .freq = 4000, + .target = { + .uses_mmap = true, + }, + .sample_time = true, + }; + struct thread_map *threads = NULL; + struct cpu_map *cpus = NULL; + struct perf_evlist *evlist = NULL; + struct perf_evsel *evsel = NULL; + int err = -1, ret, i; + const char *comm1, *comm2; + struct perf_tsc_conversion tc; + struct perf_event_mmap_page *pc; + union perf_event *event; + u64 test_tsc, comm1_tsc, comm2_tsc; + u64 test_time, comm1_time = 0, comm2_time = 0; + + threads = thread_map__new(-1, getpid(), UINT_MAX); + CHECK_NOT_NULL__(threads); + + cpus = cpu_map__new(NULL); + CHECK_NOT_NULL__(cpus); + + evlist = perf_evlist__new(); + CHECK_NOT_NULL__(evlist); + + perf_evlist__set_maps(evlist, cpus, threads); + + CHECK__(parse_events(evlist, "cycles:u")); + + perf_evlist__config(evlist, &opts); + + evsel = perf_evlist__first(evlist); + + evsel->attr.comm = 1; + evsel->attr.disabled = 1; + evsel->attr.enable_on_exec = 0; + + CHECK__(perf_evlist__open(evlist)); + + CHECK__(perf_evlist__mmap(evlist, UINT_MAX, false)); + + pc = evlist->mmap[0].base; + ret = perf_read_tsc_conversion(pc, &tc); + if (ret) { + if (ret == -EOPNOTSUPP) { + fprintf(stderr, " (not supported)"); + return 0; + } + goto out_err; + } + + perf_evlist__enable(evlist); + + comm1 = "Test COMM 1"; + CHECK__(prctl(PR_SET_NAME, (unsigned long)comm1, 0, 0, 0)); + + test_tsc = rdtsc(); + + comm2 = "Test COMM 2"; + CHECK__(prctl(PR_SET_NAME, (unsigned long)comm2, 0, 0, 0)); + + perf_evlist__disable(evlist); + + for (i = 0; i < evlist->nr_mmaps; i++) { + while ((event = perf_evlist__mmap_read(evlist, i)) != NULL) { + struct perf_sample sample; + + if (event->header.type != PERF_RECORD_COMM || + (pid_t)event->comm.pid != getpid() || + (pid_t)event->comm.tid != getpid()) + continue; + + if (strcmp(event->comm.comm, comm1) == 0) { + CHECK__(perf_evsel__parse_sample(evsel, event, + &sample)); + comm1_time = sample.time; + } + if (strcmp(event->comm.comm, comm2) == 0) { + CHECK__(perf_evsel__parse_sample(evsel, event, + &sample)); + comm2_time = sample.time; + } + } + } + + if (!comm1_time || !comm2_time) + goto out_err; + + test_time = tsc_to_perf_time(test_tsc, &tc); + comm1_tsc = perf_time_to_tsc(comm1_time, &tc); + comm2_tsc = perf_time_to_tsc(comm2_time, &tc); + + pr_debug("1st event perf time %"PRIu64" tsc %"PRIu64"\n", + comm1_time, comm1_tsc); + pr_debug("rdtsc time %"PRIu64" tsc %"PRIu64"\n", + test_time, test_tsc); + pr_debug("2nd event perf time %"PRIu64" tsc %"PRIu64"\n", + comm2_time, comm2_tsc); + + if (test_time <= comm1_time || + test_time >= comm2_time) + goto out_err; + + if (test_tsc <= comm1_tsc || + test_tsc >= comm2_tsc) + goto out_err; + + err = 0; + +out_err: + if (evlist) { + perf_evlist__disable(evlist); + perf_evlist__munmap(evlist); + perf_evlist__close(evlist); + perf_evlist__delete(evlist); + } + if (cpus) + cpu_map__delete(cpus); + if (threads) + thread_map__delete(threads); + + return err; +} diff --git a/tools/perf/tests/sample-parsing.c b/tools/perf/tests/sample-parsing.c new file mode 100644 index 000000000000..77f598dbd97a --- /dev/null +++ b/tools/perf/tests/sample-parsing.c @@ -0,0 +1,316 @@ +#include <stdbool.h> +#include <inttypes.h> + +#include "util.h" +#include "event.h" +#include "evsel.h" + +#include "tests.h" + +#define COMP(m) do { \ + if (s1->m != s2->m) { \ + pr_debug("Samples differ at '"#m"'\n"); \ + return false; \ + } \ +} while (0) + +#define MCOMP(m) do { \ + if (memcmp(&s1->m, &s2->m, sizeof(s1->m))) { \ + pr_debug("Samples differ at '"#m"'\n"); \ + return false; \ + } \ +} while (0) + +static bool samples_same(const struct perf_sample *s1, + const struct perf_sample *s2, u64 type, u64 regs_user, + u64 read_format) +{ + size_t i; + + if (type & PERF_SAMPLE_IDENTIFIER) + COMP(id); + + if (type & PERF_SAMPLE_IP) + COMP(ip); + + if (type & PERF_SAMPLE_TID) { + COMP(pid); + COMP(tid); + } + + if (type & PERF_SAMPLE_TIME) + COMP(time); + + if (type & PERF_SAMPLE_ADDR) + COMP(addr); + + if (type & PERF_SAMPLE_ID) + COMP(id); + + if (type & PERF_SAMPLE_STREAM_ID) + COMP(stream_id); + + if (type & PERF_SAMPLE_CPU) + COMP(cpu); + + if (type & PERF_SAMPLE_PERIOD) + COMP(period); + + if (type & PERF_SAMPLE_READ) { + if (read_format & PERF_FORMAT_GROUP) + COMP(read.group.nr); + else + COMP(read.one.value); + if (read_format & PERF_FORMAT_TOTAL_TIME_ENABLED) + COMP(read.time_enabled); + if (read_format & PERF_FORMAT_TOTAL_TIME_RUNNING) + COMP(read.time_running); + /* PERF_FORMAT_ID is forced for PERF_SAMPLE_READ */ + if (read_format & PERF_FORMAT_GROUP) { + for (i = 0; i < s1->read.group.nr; i++) + MCOMP(read.group.values[i]); + } else { + COMP(read.one.id); + } + } + + if (type & PERF_SAMPLE_CALLCHAIN) { + COMP(callchain->nr); + for (i = 0; i < s1->callchain->nr; i++) + COMP(callchain->ips[i]); + } + + if (type & PERF_SAMPLE_RAW) { + COMP(raw_size); + if (memcmp(s1->raw_data, s2->raw_data, s1->raw_size)) { + pr_debug("Samples differ at 'raw_data'\n"); + return false; + } + } + + if (type & PERF_SAMPLE_BRANCH_STACK) { + COMP(branch_stack->nr); + for (i = 0; i < s1->branch_stack->nr; i++) + MCOMP(branch_stack->entries[i]); + } + + if (type & PERF_SAMPLE_REGS_USER) { + size_t sz = hweight_long(regs_user) * sizeof(u64); + + COMP(user_regs.abi); + if (s1->user_regs.abi && + (!s1->user_regs.regs || !s2->user_regs.regs || + memcmp(s1->user_regs.regs, s2->user_regs.regs, sz))) { + pr_debug("Samples differ at 'user_regs'\n"); + return false; + } + } + + if (type & PERF_SAMPLE_STACK_USER) { + COMP(user_stack.size); + if (memcmp(s1->user_stack.data, s1->user_stack.data, + s1->user_stack.size)) { + pr_debug("Samples differ at 'user_stack'\n"); + return false; + } + } + + if (type & PERF_SAMPLE_WEIGHT) + COMP(weight); + + if (type & PERF_SAMPLE_DATA_SRC) + COMP(data_src); + + return true; +} + +static int do_test(u64 sample_type, u64 sample_regs_user, u64 read_format) +{ + struct perf_evsel evsel = { + .needs_swap = false, + .attr = { + .sample_type = sample_type, + .sample_regs_user = sample_regs_user, + .read_format = read_format, + }, + }; + union perf_event *event; + union { + struct ip_callchain callchain; + u64 data[64]; + } callchain = { + /* 3 ips */ + .data = {3, 201, 202, 203}, + }; + union { + struct branch_stack branch_stack; + u64 data[64]; + } branch_stack = { + /* 1 branch_entry */ + .data = {1, 211, 212, 213}, + }; + u64 user_regs[64]; + const u64 raw_data[] = {0x123456780a0b0c0dULL, 0x1102030405060708ULL}; + const u64 data[] = {0x2211443366558877ULL, 0, 0xaabbccddeeff4321ULL}; + struct perf_sample sample = { + .ip = 101, + .pid = 102, + .tid = 103, + .time = 104, + .addr = 105, + .id = 106, + .stream_id = 107, + .period = 108, + .weight = 109, + .cpu = 110, + .raw_size = sizeof(raw_data), + .data_src = 111, + .raw_data = (void *)raw_data, + .callchain = &callchain.callchain, + .branch_stack = &branch_stack.branch_stack, + .user_regs = { + .abi = PERF_SAMPLE_REGS_ABI_64, + .regs = user_regs, + }, + .user_stack = { + .size = sizeof(data), + .data = (void *)data, + }, + .read = { + .time_enabled = 0x030a59d664fca7deULL, + .time_running = 0x011b6ae553eb98edULL, + }, + }; + struct sample_read_value values[] = {{1, 5}, {9, 3}, {2, 7}, {6, 4},}; + struct perf_sample sample_out; + size_t i, sz, bufsz; + int err, ret = -1; + + for (i = 0; i < sizeof(user_regs); i++) + *(i + (u8 *)user_regs) = i & 0xfe; + + if (read_format & PERF_FORMAT_GROUP) { + sample.read.group.nr = 4; + sample.read.group.values = values; + } else { + sample.read.one.value = 0x08789faeb786aa87ULL; + sample.read.one.id = 99; + } + + sz = perf_event__sample_event_size(&sample, sample_type, + sample_regs_user, read_format); + bufsz = sz + 4096; /* Add a bit for overrun checking */ + event = malloc(bufsz); + if (!event) { + pr_debug("malloc failed\n"); + return -1; + } + + memset(event, 0xff, bufsz); + event->header.type = PERF_RECORD_SAMPLE; + event->header.misc = 0; + event->header.size = sz; + + err = perf_event__synthesize_sample(event, sample_type, + sample_regs_user, read_format, + &sample, false); + if (err) { + pr_debug("%s failed for sample_type %#"PRIx64", error %d\n", + "perf_event__synthesize_sample", sample_type, err); + goto out_free; + } + + /* The data does not contain 0xff so we use that to check the size */ + for (i = bufsz; i > 0; i--) { + if (*(i - 1 + (u8 *)event) != 0xff) + break; + } + if (i != sz) { + pr_debug("Event size mismatch: actual %zu vs expected %zu\n", + i, sz); + goto out_free; + } + + evsel.sample_size = __perf_evsel__sample_size(sample_type); + + err = perf_evsel__parse_sample(&evsel, event, &sample_out); + if (err) { + pr_debug("%s failed for sample_type %#"PRIx64", error %d\n", + "perf_evsel__parse_sample", sample_type, err); + goto out_free; + } + + if (!samples_same(&sample, &sample_out, sample_type, + sample_regs_user, read_format)) { + pr_debug("parsing failed for sample_type %#"PRIx64"\n", + sample_type); + goto out_free; + } + + ret = 0; +out_free: + free(event); + if (ret && read_format) + pr_debug("read_format %#"PRIx64"\n", read_format); + return ret; +} + +/** + * test__sample_parsing - test sample parsing. + * + * This function implements a test that synthesizes a sample event, parses it + * and then checks that the parsed sample matches the original sample. The test + * checks sample format bits separately and together. If the test passes %0 is + * returned, otherwise %-1 is returned. + */ +int test__sample_parsing(void) +{ + const u64 rf[] = {4, 5, 6, 7, 12, 13, 14, 15}; + u64 sample_type; + u64 sample_regs_user; + size_t i; + int err; + + /* + * Fail the test if it has not been updated when new sample format bits + * were added. + */ + if (PERF_SAMPLE_MAX > PERF_SAMPLE_IDENTIFIER << 1) { + pr_debug("sample format has changed - test needs updating\n"); + return -1; + } + + /* Test each sample format bit separately */ + for (sample_type = 1; sample_type != PERF_SAMPLE_MAX; + sample_type <<= 1) { + /* Test read_format variations */ + if (sample_type == PERF_SAMPLE_READ) { + for (i = 0; i < ARRAY_SIZE(rf); i++) { + err = do_test(sample_type, 0, rf[i]); + if (err) + return err; + } + continue; + } + + if (sample_type == PERF_SAMPLE_REGS_USER) + sample_regs_user = 0x3fff; + else + sample_regs_user = 0; + + err = do_test(sample_type, sample_regs_user, 0); + if (err) + return err; + } + + /* Test all sample format bits together */ + sample_type = PERF_SAMPLE_MAX - 1; + sample_regs_user = 0x3fff; + for (i = 0; i < ARRAY_SIZE(rf); i++) { + err = do_test(sample_type, sample_regs_user, rf[i]); + if (err) + return err; + } + + return 0; +} diff --git a/tools/perf/tests/tests.h b/tools/perf/tests/tests.h index dd7feae2d37b..c048b589998a 100644 --- a/tools/perf/tests/tests.h +++ b/tools/perf/tests/tests.h @@ -1,6 +1,14 @@ #ifndef TESTS_H #define TESTS_H +#define TEST_ASSERT_VAL(text, cond) \ +do { \ + if (!(cond)) { \ + pr_debug("FAILED %s:%d %s\n", __FILE__, __LINE__, text); \ + return -1; \ + } \ +} while (0) + enum { TEST_OK = 0, TEST_FAIL = -1, @@ -27,5 +35,9 @@ int test__bp_signal(void); int test__bp_signal_overflow(void); int test__task_exit(void); int test__sw_clock_freq(void); +int test__perf_time_to_tsc(void); +int test__code_reading(void); +int test__sample_parsing(void); +int test__keep_tracking(void); #endif /* TESTS_H */ diff --git a/tools/perf/tests/vmlinux-kallsyms.c b/tools/perf/tests/vmlinux-kallsyms.c index 7b4c4d26d1ba..2bd13edcbc17 100644 --- a/tools/perf/tests/vmlinux-kallsyms.c +++ b/tools/perf/tests/vmlinux-kallsyms.c @@ -16,6 +16,8 @@ static int vmlinux_matches_kallsyms_filter(struct map *map __maybe_unused, return 0; } +#define UM(x) kallsyms_map->unmap_ip(kallsyms_map, (x)) + int test__vmlinux_matches_kallsyms(void) { int err = -1; @@ -25,6 +27,7 @@ int test__vmlinux_matches_kallsyms(void) struct machine kallsyms, vmlinux; enum map_type type = MAP__FUNCTION; struct ref_reloc_sym ref_reloc_sym = { .name = "_stext", }; + u64 mem_start, mem_end; /* * Step 1: @@ -73,7 +76,7 @@ int test__vmlinux_matches_kallsyms(void) goto out; } - ref_reloc_sym.addr = sym->start; + ref_reloc_sym.addr = UM(sym->start); /* * Step 5: @@ -123,10 +126,14 @@ int test__vmlinux_matches_kallsyms(void) if (sym->start == sym->end) continue; - first_pair = machine__find_kernel_symbol(&kallsyms, type, sym->start, NULL, NULL); + mem_start = vmlinux_map->unmap_ip(vmlinux_map, sym->start); + mem_end = vmlinux_map->unmap_ip(vmlinux_map, sym->end); + + first_pair = machine__find_kernel_symbol(&kallsyms, type, + mem_start, NULL, NULL); pair = first_pair; - if (pair && pair->start == sym->start) { + if (pair && UM(pair->start) == mem_start) { next_pair: if (strcmp(sym->name, pair->name) == 0) { /* @@ -138,12 +145,20 @@ next_pair: * off the real size. More than that and we * _really_ have a problem. */ - s64 skew = sym->end - pair->end; - if (llabs(skew) < page_size) - continue; + s64 skew = mem_end - UM(pair->end); + if (llabs(skew) >= page_size) + pr_debug("%#" PRIx64 ": diff end addr for %s v: %#" PRIx64 " k: %#" PRIx64 "\n", + mem_start, sym->name, mem_end, + UM(pair->end)); + + /* + * Do not count this as a failure, because we + * could really find a case where it's not + * possible to get proper function end from + * kallsyms. + */ + continue; - pr_debug("%#" PRIx64 ": diff end addr for %s v: %#" PRIx64 " k: %#" PRIx64 "\n", - sym->start, sym->name, sym->end, pair->end); } else { struct rb_node *nnd; detour: @@ -152,7 +167,7 @@ detour: if (nnd) { struct symbol *next = rb_entry(nnd, struct symbol, rb_node); - if (next->start == sym->start) { + if (UM(next->start) == mem_start) { pair = next; goto next_pair; } @@ -165,10 +180,11 @@ detour: } pr_debug("%#" PRIx64 ": diff name v: %s k: %s\n", - sym->start, sym->name, pair->name); + mem_start, sym->name, pair->name); } } else - pr_debug("%#" PRIx64 ": %s not on kallsyms\n", sym->start, sym->name); + pr_debug("%#" PRIx64 ": %s not on kallsyms\n", + mem_start, sym->name); err = -1; } @@ -201,16 +217,19 @@ detour: for (nd = rb_first(&vmlinux.kmaps.maps[type]); nd; nd = rb_next(nd)) { struct map *pos = rb_entry(nd, struct map, rb_node), *pair; - pair = map_groups__find(&kallsyms.kmaps, type, pos->start); + mem_start = vmlinux_map->unmap_ip(vmlinux_map, pos->start); + mem_end = vmlinux_map->unmap_ip(vmlinux_map, pos->end); + + pair = map_groups__find(&kallsyms.kmaps, type, mem_start); if (pair == NULL || pair->priv) continue; - if (pair->start == pos->start) { + if (pair->start == mem_start) { pair->priv = 1; pr_info(" %" PRIx64 "-%" PRIx64 " %" PRIx64 " %s in kallsyms as", pos->start, pos->end, pos->pgoff, pos->dso->name); - if (pos->pgoff != pair->pgoff || pos->end != pair->end) - pr_info(": \n*%" PRIx64 "-%" PRIx64 " %" PRIx64 "", + if (mem_end != pair->end) + pr_info(":\n*%" PRIx64 "-%" PRIx64 " %" PRIx64, pair->start, pair->end, pair->pgoff); pr_info(" %s\n", pair->dso->name); pair->priv = 1; diff --git a/tools/perf/ui/browsers/annotate.c b/tools/perf/ui/browsers/annotate.c index cc64d3f7fc36..08545ae46992 100644 --- a/tools/perf/ui/browsers/annotate.c +++ b/tools/perf/ui/browsers/annotate.c @@ -428,6 +428,14 @@ static void annotate_browser__init_asm_mode(struct annotate_browser *browser) browser->b.nr_entries = browser->nr_asm_entries; } +#define SYM_TITLE_MAX_SIZE (PATH_MAX + 64) + +static int sym_title(struct symbol *sym, struct map *map, char *title, + size_t sz) +{ + return snprintf(title, sz, "%s %s", sym->name, map->dso->long_name); +} + static bool annotate_browser__callq(struct annotate_browser *browser, struct perf_evsel *evsel, struct hist_browser_timer *hbt) @@ -438,6 +446,7 @@ static bool annotate_browser__callq(struct annotate_browser *browser, struct annotation *notes; struct symbol *target; u64 ip; + char title[SYM_TITLE_MAX_SIZE]; if (!ins__is_call(dl->ins)) return false; @@ -461,7 +470,8 @@ static bool annotate_browser__callq(struct annotate_browser *browser, pthread_mutex_unlock(¬es->lock); symbol__tui_annotate(target, ms->map, evsel, hbt); - ui_browser__show_title(&browser->b, sym->name); + sym_title(sym, ms->map, title, sizeof(title)); + ui_browser__show_title(&browser->b, title); return true; } @@ -495,7 +505,7 @@ static bool annotate_browser__jump(struct annotate_browser *browser) dl = annotate_browser__find_offset(browser, dl->ops.target.offset, &idx); if (dl == NULL) { - ui_helpline__puts("Invallid jump offset"); + ui_helpline__puts("Invalid jump offset"); return true; } @@ -653,8 +663,10 @@ static int annotate_browser__run(struct annotate_browser *browser, const char *help = "Press 'h' for help on key bindings"; int delay_secs = hbt ? hbt->refresh : 0; int key; + char title[SYM_TITLE_MAX_SIZE]; - if (ui_browser__show(&browser->b, sym->name, help) < 0) + sym_title(sym, ms->map, title, sizeof(title)); + if (ui_browser__show(&browser->b, title, help) < 0) return -1; annotate_browser__calc_percent(browser, evsel); @@ -720,7 +732,7 @@ static int annotate_browser__run(struct annotate_browser *browser, "s Toggle source code view\n" "/ Search string\n" "r Run available scripts\n" - "? Search previous string\n"); + "? Search string backwards\n"); continue; case 'r': { diff --git a/tools/perf/ui/browsers/hists.c b/tools/perf/ui/browsers/hists.c index fc0bd3843d34..7ef36c360471 100644 --- a/tools/perf/ui/browsers/hists.c +++ b/tools/perf/ui/browsers/hists.c @@ -685,8 +685,10 @@ static u64 __hpp_get_##_field(struct hist_entry *he) \ return he->stat._field; \ } \ \ -static int hist_browser__hpp_color_##_type(struct perf_hpp *hpp, \ - struct hist_entry *he) \ +static int \ +hist_browser__hpp_color_##_type(struct perf_hpp_fmt *fmt __maybe_unused,\ + struct perf_hpp *hpp, \ + struct hist_entry *he) \ { \ return __hpp__color_fmt(hpp, he, __hpp_get_##_field, _cb); \ } @@ -701,8 +703,6 @@ __HPP_COLOR_PERCENT_FN(overhead_guest_us, period_guest_us, NULL) void hist_browser__init_hpp(void) { - perf_hpp__column_enable(PERF_HPP__OVERHEAD); - perf_hpp__init(); perf_hpp__format[PERF_HPP__OVERHEAD].color = @@ -762,9 +762,9 @@ static int hist_browser__show_entry(struct hist_browser *browser, first = false; if (fmt->color) { - width -= fmt->color(&hpp, entry); + width -= fmt->color(fmt, &hpp, entry); } else { - width -= fmt->entry(&hpp, entry); + width -= fmt->entry(fmt, &hpp, entry); slsmg_printf("%s", s); } } @@ -1256,7 +1256,7 @@ static int hists__browser_title(struct hists *hists, char *bf, size_t size, printed += scnprintf(bf + printed, size - printed, ", Thread: %s(%d)", (thread->comm_set ? thread->comm : ""), - thread->pid); + thread->tid); if (dso) printed += scnprintf(bf + printed, size - printed, ", DSO: %s", dso->short_name); @@ -1579,7 +1579,7 @@ static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events, asprintf(&options[nr_options], "Zoom %s %s(%d) thread", (browser->hists->thread_filter ? "out of" : "into"), (thread->comm_set ? thread->comm : ""), - thread->pid) > 0) + thread->tid) > 0) zoom_thread = nr_options++; if (dso != NULL && @@ -1702,7 +1702,7 @@ zoom_out_thread: } else { ui_helpline__fpush("To zoom out press <- or -> + \"Zoom out of %s(%d) thread\"", thread->comm_set ? thread->comm : "", - thread->pid); + thread->tid); browser->hists->thread_filter = thread; sort_thread.elide = true; pstack__push(fstack, &browser->hists->thread_filter); diff --git a/tools/perf/ui/gtk/hists.c b/tools/perf/ui/gtk/hists.c index 9708dd5fb8f3..2ca66cc1160f 100644 --- a/tools/perf/ui/gtk/hists.c +++ b/tools/perf/ui/gtk/hists.c @@ -91,7 +91,8 @@ static u64 he_get_##_field(struct hist_entry *he) \ return he->stat._field; \ } \ \ -static int perf_gtk__hpp_color_##_type(struct perf_hpp *hpp, \ +static int perf_gtk__hpp_color_##_type(struct perf_hpp_fmt *fmt __maybe_unused, \ + struct perf_hpp *hpp, \ struct hist_entry *he) \ { \ return __hpp__color_fmt(hpp, he, he_get_##_field); \ @@ -108,8 +109,6 @@ __HPP_COLOR_PERCENT_FN(overhead_guest_us, period_guest_us) void perf_gtk__init_hpp(void) { - perf_hpp__column_enable(PERF_HPP__OVERHEAD); - perf_hpp__init(); perf_hpp__format[PERF_HPP__OVERHEAD].color = @@ -124,6 +123,81 @@ void perf_gtk__init_hpp(void) perf_gtk__hpp_color_overhead_guest_us; } +static void callchain_list__sym_name(struct callchain_list *cl, + char *bf, size_t bfsize) +{ + if (cl->ms.sym) + scnprintf(bf, bfsize, "%s", cl->ms.sym->name); + else + scnprintf(bf, bfsize, "%#" PRIx64, cl->ip); +} + +static void perf_gtk__add_callchain(struct rb_root *root, GtkTreeStore *store, + GtkTreeIter *parent, int col, u64 total) +{ + struct rb_node *nd; + bool has_single_node = (rb_first(root) == rb_last(root)); + + for (nd = rb_first(root); nd; nd = rb_next(nd)) { + struct callchain_node *node; + struct callchain_list *chain; + GtkTreeIter iter, new_parent; + bool need_new_parent; + double percent; + u64 hits, child_total; + + node = rb_entry(nd, struct callchain_node, rb_node); + + hits = callchain_cumul_hits(node); + percent = 100.0 * hits / total; + + new_parent = *parent; + need_new_parent = !has_single_node && (node->val_nr > 1); + + list_for_each_entry(chain, &node->val, list) { + char buf[128]; + + gtk_tree_store_append(store, &iter, &new_parent); + + scnprintf(buf, sizeof(buf), "%5.2f%%", percent); + gtk_tree_store_set(store, &iter, 0, buf, -1); + + callchain_list__sym_name(chain, buf, sizeof(buf)); + gtk_tree_store_set(store, &iter, col, buf, -1); + + if (need_new_parent) { + /* + * Only show the top-most symbol in a callchain + * if it's not the only callchain. + */ + new_parent = iter; + need_new_parent = false; + } + } + + if (callchain_param.mode == CHAIN_GRAPH_REL) + child_total = node->children_hit; + else + child_total = total; + + /* Now 'iter' contains info of the last callchain_list */ + perf_gtk__add_callchain(&node->rb_root, store, &iter, col, + child_total); + } +} + +static void on_row_activated(GtkTreeView *view, GtkTreePath *path, + GtkTreeViewColumn *col __maybe_unused, + gpointer user_data __maybe_unused) +{ + bool expanded = gtk_tree_view_row_expanded(view, path); + + if (expanded) + gtk_tree_view_collapse_row(view, path); + else + gtk_tree_view_expand_row(view, path, FALSE); +} + static void perf_gtk__show_hists(GtkWidget *window, struct hists *hists, float min_pcnt) { @@ -131,10 +205,11 @@ static void perf_gtk__show_hists(GtkWidget *window, struct hists *hists, GType col_types[MAX_COLUMNS]; GtkCellRenderer *renderer; struct sort_entry *se; - GtkListStore *store; + GtkTreeStore *store; struct rb_node *nd; GtkWidget *view; int col_idx; + int sym_col = -1; int nr_cols; char s[512]; @@ -153,10 +228,13 @@ static void perf_gtk__show_hists(GtkWidget *window, struct hists *hists, if (se->elide) continue; + if (se == &sort_sym) + sym_col = nr_cols; + col_types[nr_cols++] = G_TYPE_STRING; } - store = gtk_list_store_newv(nr_cols, col_types); + store = gtk_tree_store_newv(nr_cols, col_types); view = gtk_tree_view_new(); @@ -165,7 +243,7 @@ static void perf_gtk__show_hists(GtkWidget *window, struct hists *hists, col_idx = 0; perf_hpp__for_each_format(fmt) { - fmt->header(&hpp); + fmt->header(fmt, &hpp); gtk_tree_view_insert_column_with_attributes(GTK_TREE_VIEW(view), -1, ltrim(s), @@ -183,6 +261,18 @@ static void perf_gtk__show_hists(GtkWidget *window, struct hists *hists, col_idx++, NULL); } + for (col_idx = 0; col_idx < nr_cols; col_idx++) { + GtkTreeViewColumn *column; + + column = gtk_tree_view_get_column(GTK_TREE_VIEW(view), col_idx); + gtk_tree_view_column_set_resizable(column, TRUE); + + if (col_idx == sym_col) { + gtk_tree_view_set_expander_column(GTK_TREE_VIEW(view), + column); + } + } + gtk_tree_view_set_model(GTK_TREE_VIEW(view), GTK_TREE_MODEL(store)); g_object_unref(GTK_TREE_MODEL(store)); @@ -199,17 +289,17 @@ static void perf_gtk__show_hists(GtkWidget *window, struct hists *hists, if (percent < min_pcnt) continue; - gtk_list_store_append(store, &iter); + gtk_tree_store_append(store, &iter, NULL); col_idx = 0; perf_hpp__for_each_format(fmt) { if (fmt->color) - fmt->color(&hpp, h); + fmt->color(fmt, &hpp, h); else - fmt->entry(&hpp, h); + fmt->entry(fmt, &hpp, h); - gtk_list_store_set(store, &iter, col_idx++, s, -1); + gtk_tree_store_set(store, &iter, col_idx++, s, -1); } list_for_each_entry(se, &hist_entry__sort_list, list) { @@ -219,10 +309,26 @@ static void perf_gtk__show_hists(GtkWidget *window, struct hists *hists, se->se_snprintf(h, s, ARRAY_SIZE(s), hists__col_len(hists, se->se_width_idx)); - gtk_list_store_set(store, &iter, col_idx++, s, -1); + gtk_tree_store_set(store, &iter, col_idx++, s, -1); + } + + if (symbol_conf.use_callchain && sort__has_sym) { + u64 total; + + if (callchain_param.mode == CHAIN_GRAPH_REL) + total = h->stat.period; + else + total = hists->stats.total_period; + + perf_gtk__add_callchain(&h->sorted_chain, store, &iter, + sym_col, total); } } + gtk_tree_view_set_rules_hint(GTK_TREE_VIEW(view), TRUE); + + g_signal_connect(view, "row-activated", + G_CALLBACK(on_row_activated), NULL); gtk_container_add(GTK_CONTAINER(window), view); } diff --git a/tools/perf/ui/hist.c b/tools/perf/ui/hist.c index 4bf91b09d62d..0a193281eba8 100644 --- a/tools/perf/ui/hist.c +++ b/tools/perf/ui/hist.c @@ -1,4 +1,5 @@ #include <math.h> +#include <linux/compiler.h> #include "../util/hist.h" #include "../util/util.h" @@ -79,7 +80,8 @@ static int __hpp__fmt(struct perf_hpp *hpp, struct hist_entry *he, } #define __HPP_HEADER_FN(_type, _str, _min_width, _unit_width) \ -static int hpp__header_##_type(struct perf_hpp *hpp) \ +static int hpp__header_##_type(struct perf_hpp_fmt *fmt __maybe_unused, \ + struct perf_hpp *hpp) \ { \ int len = _min_width; \ \ @@ -92,7 +94,8 @@ static int hpp__header_##_type(struct perf_hpp *hpp) \ } #define __HPP_WIDTH_FN(_type, _min_width, _unit_width) \ -static int hpp__width_##_type(struct perf_hpp *hpp __maybe_unused) \ +static int hpp__width_##_type(struct perf_hpp_fmt *fmt __maybe_unused, \ + struct perf_hpp *hpp __maybe_unused) \ { \ int len = _min_width; \ \ @@ -110,14 +113,16 @@ static u64 he_get_##_field(struct hist_entry *he) \ return he->stat._field; \ } \ \ -static int hpp__color_##_type(struct perf_hpp *hpp, struct hist_entry *he) \ +static int hpp__color_##_type(struct perf_hpp_fmt *fmt __maybe_unused, \ + struct perf_hpp *hpp, struct hist_entry *he) \ { \ return __hpp__fmt(hpp, he, he_get_##_field, " %6.2f%%", \ (hpp_snprint_fn)percent_color_snprintf, true); \ } #define __HPP_ENTRY_PERCENT_FN(_type, _field) \ -static int hpp__entry_##_type(struct perf_hpp *hpp, struct hist_entry *he) \ +static int hpp__entry_##_type(struct perf_hpp_fmt *_fmt __maybe_unused, \ + struct perf_hpp *hpp, struct hist_entry *he) \ { \ const char *fmt = symbol_conf.field_sep ? " %.2f" : " %6.2f%%"; \ return __hpp__fmt(hpp, he, he_get_##_field, fmt, \ @@ -130,7 +135,8 @@ static u64 he_get_raw_##_field(struct hist_entry *he) \ return he->stat._field; \ } \ \ -static int hpp__entry_##_type(struct perf_hpp *hpp, struct hist_entry *he) \ +static int hpp__entry_##_type(struct perf_hpp_fmt *_fmt __maybe_unused, \ + struct perf_hpp *hpp, struct hist_entry *he) \ { \ const char *fmt = symbol_conf.field_sep ? " %"PRIu64 : " %11"PRIu64; \ return __hpp__fmt(hpp, he, he_get_raw_##_field, fmt, scnprintf, false); \ @@ -157,196 +163,6 @@ HPP_PERCENT_FNS(overhead_guest_us, "guest usr", period_guest_us, 9, 8) HPP_RAW_FNS(samples, "Samples", nr_events, 12, 12) HPP_RAW_FNS(period, "Period", period, 12, 12) - -static int hpp__header_baseline(struct perf_hpp *hpp) -{ - return scnprintf(hpp->buf, hpp->size, "Baseline"); -} - -static int hpp__width_baseline(struct perf_hpp *hpp __maybe_unused) -{ - return 8; -} - -static double baseline_percent(struct hist_entry *he) -{ - struct hist_entry *pair = hist_entry__next_pair(he); - struct hists *pair_hists = pair ? pair->hists : NULL; - double percent = 0.0; - - if (pair) { - u64 total_period = pair_hists->stats.total_period; - u64 base_period = pair->stat.period; - - percent = 100.0 * base_period / total_period; - } - - return percent; -} - -static int hpp__color_baseline(struct perf_hpp *hpp, struct hist_entry *he) -{ - double percent = baseline_percent(he); - - if (hist_entry__has_pairs(he) || symbol_conf.field_sep) - return percent_color_snprintf(hpp->buf, hpp->size, " %6.2f%%", percent); - else - return scnprintf(hpp->buf, hpp->size, " "); -} - -static int hpp__entry_baseline(struct perf_hpp *hpp, struct hist_entry *he) -{ - double percent = baseline_percent(he); - const char *fmt = symbol_conf.field_sep ? "%.2f" : " %6.2f%%"; - - if (hist_entry__has_pairs(he) || symbol_conf.field_sep) - return scnprintf(hpp->buf, hpp->size, fmt, percent); - else - return scnprintf(hpp->buf, hpp->size, " "); -} - -static int hpp__header_period_baseline(struct perf_hpp *hpp) -{ - const char *fmt = symbol_conf.field_sep ? "%s" : "%12s"; - - return scnprintf(hpp->buf, hpp->size, fmt, "Period Base"); -} - -static int hpp__width_period_baseline(struct perf_hpp *hpp __maybe_unused) -{ - return 12; -} - -static int hpp__entry_period_baseline(struct perf_hpp *hpp, struct hist_entry *he) -{ - struct hist_entry *pair = hist_entry__next_pair(he); - u64 period = pair ? pair->stat.period : 0; - const char *fmt = symbol_conf.field_sep ? "%" PRIu64 : "%12" PRIu64; - - return scnprintf(hpp->buf, hpp->size, fmt, period); -} - -static int hpp__header_delta(struct perf_hpp *hpp) -{ - const char *fmt = symbol_conf.field_sep ? "%s" : "%7s"; - - return scnprintf(hpp->buf, hpp->size, fmt, "Delta"); -} - -static int hpp__width_delta(struct perf_hpp *hpp __maybe_unused) -{ - return 7; -} - -static int hpp__entry_delta(struct perf_hpp *hpp, struct hist_entry *he) -{ - struct hist_entry *pair = hist_entry__next_pair(he); - const char *fmt = symbol_conf.field_sep ? "%s" : "%7.7s"; - char buf[32] = " "; - double diff = 0.0; - - if (pair) { - if (he->diff.computed) - diff = he->diff.period_ratio_delta; - else - diff = perf_diff__compute_delta(he, pair); - } else - diff = perf_diff__period_percent(he, he->stat.period); - - if (fabs(diff) >= 0.01) - scnprintf(buf, sizeof(buf), "%+4.2F%%", diff); - - return scnprintf(hpp->buf, hpp->size, fmt, buf); -} - -static int hpp__header_ratio(struct perf_hpp *hpp) -{ - const char *fmt = symbol_conf.field_sep ? "%s" : "%14s"; - - return scnprintf(hpp->buf, hpp->size, fmt, "Ratio"); -} - -static int hpp__width_ratio(struct perf_hpp *hpp __maybe_unused) -{ - return 14; -} - -static int hpp__entry_ratio(struct perf_hpp *hpp, struct hist_entry *he) -{ - struct hist_entry *pair = hist_entry__next_pair(he); - const char *fmt = symbol_conf.field_sep ? "%s" : "%14s"; - char buf[32] = " "; - double ratio = 0.0; - - if (pair) { - if (he->diff.computed) - ratio = he->diff.period_ratio; - else - ratio = perf_diff__compute_ratio(he, pair); - } - - if (ratio > 0.0) - scnprintf(buf, sizeof(buf), "%+14.6F", ratio); - - return scnprintf(hpp->buf, hpp->size, fmt, buf); -} - -static int hpp__header_wdiff(struct perf_hpp *hpp) -{ - const char *fmt = symbol_conf.field_sep ? "%s" : "%14s"; - - return scnprintf(hpp->buf, hpp->size, fmt, "Weighted diff"); -} - -static int hpp__width_wdiff(struct perf_hpp *hpp __maybe_unused) -{ - return 14; -} - -static int hpp__entry_wdiff(struct perf_hpp *hpp, struct hist_entry *he) -{ - struct hist_entry *pair = hist_entry__next_pair(he); - const char *fmt = symbol_conf.field_sep ? "%s" : "%14s"; - char buf[32] = " "; - s64 wdiff = 0; - - if (pair) { - if (he->diff.computed) - wdiff = he->diff.wdiff; - else - wdiff = perf_diff__compute_wdiff(he, pair); - } - - if (wdiff != 0) - scnprintf(buf, sizeof(buf), "%14ld", wdiff); - - return scnprintf(hpp->buf, hpp->size, fmt, buf); -} - -static int hpp__header_formula(struct perf_hpp *hpp) -{ - const char *fmt = symbol_conf.field_sep ? "%s" : "%70s"; - - return scnprintf(hpp->buf, hpp->size, fmt, "Formula"); -} - -static int hpp__width_formula(struct perf_hpp *hpp __maybe_unused) -{ - return 70; -} - -static int hpp__entry_formula(struct perf_hpp *hpp, struct hist_entry *he) -{ - struct hist_entry *pair = hist_entry__next_pair(he); - const char *fmt = symbol_conf.field_sep ? "%s" : "%-70s"; - char buf[96] = " "; - - if (pair) - perf_diff__formula(he, pair, buf, sizeof(buf)); - - return scnprintf(hpp->buf, hpp->size, fmt, buf); -} - #define HPP__COLOR_PRINT_FNS(_name) \ { \ .header = hpp__header_ ## _name, \ @@ -363,19 +179,13 @@ static int hpp__entry_formula(struct perf_hpp *hpp, struct hist_entry *he) } struct perf_hpp_fmt perf_hpp__format[] = { - HPP__COLOR_PRINT_FNS(baseline), HPP__COLOR_PRINT_FNS(overhead), HPP__COLOR_PRINT_FNS(overhead_sys), HPP__COLOR_PRINT_FNS(overhead_us), HPP__COLOR_PRINT_FNS(overhead_guest_sys), HPP__COLOR_PRINT_FNS(overhead_guest_us), HPP__PRINT_FNS(samples), - HPP__PRINT_FNS(period), - HPP__PRINT_FNS(period_baseline), - HPP__PRINT_FNS(delta), - HPP__PRINT_FNS(ratio), - HPP__PRINT_FNS(wdiff), - HPP__PRINT_FNS(formula) + HPP__PRINT_FNS(period) }; LIST_HEAD(perf_hpp__list); @@ -396,6 +206,8 @@ LIST_HEAD(perf_hpp__list); void perf_hpp__init(void) { + perf_hpp__column_enable(PERF_HPP__OVERHEAD); + if (symbol_conf.show_cpu_utilization) { perf_hpp__column_enable(PERF_HPP__OVERHEAD_SYS); perf_hpp__column_enable(PERF_HPP__OVERHEAD_US); @@ -424,46 +236,6 @@ void perf_hpp__column_enable(unsigned col) perf_hpp__column_register(&perf_hpp__format[col]); } -static inline void advance_hpp(struct perf_hpp *hpp, int inc) -{ - hpp->buf += inc; - hpp->size -= inc; -} - -int hist_entry__period_snprintf(struct perf_hpp *hpp, struct hist_entry *he, - bool color) -{ - const char *sep = symbol_conf.field_sep; - struct perf_hpp_fmt *fmt; - char *start = hpp->buf; - int ret; - bool first = true; - - if (symbol_conf.exclude_other && !he->parent) - return 0; - - perf_hpp__for_each_format(fmt) { - /* - * If there's no field_sep, we still need - * to display initial ' '. - */ - if (!sep || !first) { - ret = scnprintf(hpp->buf, hpp->size, "%s", sep ?: " "); - advance_hpp(hpp, ret); - } else - first = false; - - if (color && fmt->color) - ret = fmt->color(hpp, he); - else - ret = fmt->entry(hpp, he); - - advance_hpp(hpp, ret); - } - - return hpp->buf - start; -} - int hist_entry__sort_snprintf(struct hist_entry *he, char *s, size_t size, struct hists *hists) { @@ -499,7 +271,7 @@ unsigned int hists__sort_list_width(struct hists *hists) if (i) ret += 2; - ret += fmt->width(&dummy_hpp); + ret += fmt->width(fmt, &dummy_hpp); } list_for_each_entry(se, &hist_entry__sort_list, list) diff --git a/tools/perf/ui/setup.c b/tools/perf/ui/setup.c index ae6a789cb0f6..47d9a571f261 100644 --- a/tools/perf/ui/setup.c +++ b/tools/perf/ui/setup.c @@ -30,7 +30,6 @@ void setup_browser(bool fallback_to_pager) if (fallback_to_pager) setup_pager(); - perf_hpp__column_enable(PERF_HPP__OVERHEAD); perf_hpp__init(); break; } diff --git a/tools/perf/ui/stdio/hist.c b/tools/perf/ui/stdio/hist.c index ae7a75432249..5b4fb330f656 100644 --- a/tools/perf/ui/stdio/hist.c +++ b/tools/perf/ui/stdio/hist.c @@ -308,6 +308,47 @@ static size_t hist_entry__callchain_fprintf(struct hist_entry *he, return hist_entry_callchain__fprintf(he, total_period, left_margin, fp); } +static inline void advance_hpp(struct perf_hpp *hpp, int inc) +{ + hpp->buf += inc; + hpp->size -= inc; +} + +static int hist_entry__period_snprintf(struct perf_hpp *hpp, + struct hist_entry *he, + bool color) +{ + const char *sep = symbol_conf.field_sep; + struct perf_hpp_fmt *fmt; + char *start = hpp->buf; + int ret; + bool first = true; + + if (symbol_conf.exclude_other && !he->parent) + return 0; + + perf_hpp__for_each_format(fmt) { + /* + * If there's no field_sep, we still need + * to display initial ' '. + */ + if (!sep || !first) { + ret = scnprintf(hpp->buf, hpp->size, "%s", sep ?: " "); + advance_hpp(hpp, ret); + } else + first = false; + + if (color && fmt->color) + ret = fmt->color(fmt, hpp, he); + else + ret = fmt->entry(fmt, hpp, he); + + advance_hpp(hpp, ret); + } + + return hpp->buf - start; +} + static int hist_entry__fprintf(struct hist_entry *he, size_t size, struct hists *hists, FILE *fp) { @@ -365,7 +406,7 @@ size_t hists__fprintf(struct hists *hists, bool show_header, int max_rows, else first = false; - fmt->header(&dummy_hpp); + fmt->header(fmt, &dummy_hpp); fprintf(fp, "%s", bf); } @@ -410,7 +451,7 @@ size_t hists__fprintf(struct hists *hists, bool show_header, int max_rows, else first = false; - width = fmt->width(&dummy_hpp); + width = fmt->width(fmt, &dummy_hpp); for (i = 0; i < width; i++) fprintf(fp, "."); } diff --git a/tools/perf/util/annotate.c b/tools/perf/util/annotate.c index d102716c43a1..bfc5a27597d6 100644 --- a/tools/perf/util/annotate.c +++ b/tools/perf/util/annotate.c @@ -110,10 +110,10 @@ static int jump__parse(struct ins_operands *ops) { const char *s = strchr(ops->raw, '+'); - ops->target.addr = strtoll(ops->raw, NULL, 16); + ops->target.addr = strtoull(ops->raw, NULL, 16); if (s++ != NULL) - ops->target.offset = strtoll(s, NULL, 16); + ops->target.offset = strtoull(s, NULL, 16); else ops->target.offset = UINT64_MAX; @@ -821,11 +821,55 @@ static int symbol__parse_objdump_line(struct symbol *sym, struct map *map, if (dl == NULL) return -1; + if (dl->ops.target.offset == UINT64_MAX) + dl->ops.target.offset = dl->ops.target.addr - + map__rip_2objdump(map, sym->start); + + /* + * kcore has no symbols, so add the call target name if it is on the + * same map. + */ + if (dl->ins && ins__is_call(dl->ins) && !dl->ops.target.name) { + struct symbol *s; + u64 ip = dl->ops.target.addr; + + if (ip >= map->start && ip <= map->end) { + ip = map->map_ip(map, ip); + s = map__find_symbol(map, ip, NULL); + if (s && s->start == ip) + dl->ops.target.name = strdup(s->name); + } + } + disasm__add(¬es->src->source, dl); return 0; } +static void delete_last_nop(struct symbol *sym) +{ + struct annotation *notes = symbol__annotation(sym); + struct list_head *list = ¬es->src->source; + struct disasm_line *dl; + + while (!list_empty(list)) { + dl = list_entry(list->prev, struct disasm_line, node); + + if (dl->ins && dl->ins->ops) { + if (dl->ins->ops != &nop_ops) + return; + } else { + if (!strstr(dl->line, " nop ") && + !strstr(dl->line, " nopl ") && + !strstr(dl->line, " nopw ")) + return; + } + + list_del(&dl->node); + disasm_line__free(dl); + } +} + int symbol__annotate(struct symbol *sym, struct map *map, size_t privsize) { struct dso *dso = map->dso; @@ -864,7 +908,8 @@ fallback: free_filename = false; } - if (dso->symtab_type == DSO_BINARY_TYPE__KALLSYMS) { + if (dso->symtab_type == DSO_BINARY_TYPE__KALLSYMS && + !dso__is_kcore(dso)) { char bf[BUILD_ID_SIZE * 2 + 16] = " with build id "; char *build_id_msg = NULL; @@ -898,7 +943,7 @@ fallback: snprintf(command, sizeof(command), "%s %s%s --start-address=0x%016" PRIx64 " --stop-address=0x%016" PRIx64 - " -d %s %s -C %s|grep -v %s|expand", + " -d %s %s -C %s 2>/dev/null|grep -v %s|expand", objdump_path ? objdump_path : "objdump", disassembler_style ? "-M " : "", disassembler_style ? disassembler_style : "", @@ -918,6 +963,13 @@ fallback: if (symbol__parse_objdump_line(sym, map, file, privsize) < 0) break; + /* + * kallsyms does not have symbol sizes so there may a nop at the end. + * Remove it. + */ + if (dso__is_kcore(dso)) + delete_last_nop(sym); + pclose(file); out_free_filename: if (free_filename) diff --git a/tools/perf/util/build-id.c b/tools/perf/util/build-id.c index 5295625c0c00..fb584092eb88 100644 --- a/tools/perf/util/build-id.c +++ b/tools/perf/util/build-id.c @@ -18,13 +18,14 @@ int build_id__mark_dso_hit(struct perf_tool *tool __maybe_unused, union perf_event *event, - struct perf_sample *sample __maybe_unused, + struct perf_sample *sample, struct perf_evsel *evsel __maybe_unused, struct machine *machine) { struct addr_location al; u8 cpumode = event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK; - struct thread *thread = machine__findnew_thread(machine, event->ip.pid); + struct thread *thread = machine__findnew_thread(machine, sample->pid, + sample->pid); if (thread == NULL) { pr_err("problem processing %d event, skipping it.\n", @@ -33,7 +34,7 @@ int build_id__mark_dso_hit(struct perf_tool *tool __maybe_unused, } thread__find_addr_map(thread, machine, cpumode, MAP__FUNCTION, - event->ip.ip, &al); + sample->ip, &al); if (al.map != NULL) al.map->dso->hit = 1; @@ -47,7 +48,9 @@ static int perf_event__exit_del_thread(struct perf_tool *tool __maybe_unused, __maybe_unused, struct machine *machine) { - struct thread *thread = machine__findnew_thread(machine, event->fork.tid); + struct thread *thread = machine__findnew_thread(machine, + event->fork.pid, + event->fork.tid); dump_printf("(%d:%d):(%d:%d)\n", event->fork.pid, event->fork.tid, event->fork.ppid, event->fork.ptid); diff --git a/tools/perf/util/callchain.c b/tools/perf/util/callchain.c index 42b6a632fe7b..482f68081cd8 100644 --- a/tools/perf/util/callchain.c +++ b/tools/perf/util/callchain.c @@ -15,19 +15,12 @@ #include <errno.h> #include <math.h> +#include "hist.h" #include "util.h" #include "callchain.h" __thread struct callchain_cursor callchain_cursor; -bool ip_callchain__valid(struct ip_callchain *chain, - const union perf_event *event) -{ - unsigned int chain_size = event->header.size; - chain_size -= (unsigned long)&event->ip.__more_data - (unsigned long)event; - return chain->nr * sizeof(u64) <= chain_size; -} - #define chain_for_each_child(child, parent) \ list_for_each_entry(child, &parent->children, siblings) @@ -327,7 +320,8 @@ append_chain(struct callchain_node *root, /* * Lookup in the current node * If we have a symbol, then compare the start to match - * anywhere inside a function. + * anywhere inside a function, unless function + * mode is disabled. */ list_for_each_entry(cnode, &root->val, list) { struct callchain_cursor_node *node; @@ -339,7 +333,8 @@ append_chain(struct callchain_node *root, sym = node->sym; - if (cnode->ms.sym && sym) { + if (cnode->ms.sym && sym && + callchain_param.key == CCKEY_FUNCTION) { if (cnode->ms.sym->start != sym->start) break; } else if (cnode->ip != node->ip) diff --git a/tools/perf/util/callchain.h b/tools/perf/util/callchain.h index 3ee9f67d5af0..2b585bc308cf 100644 --- a/tools/perf/util/callchain.h +++ b/tools/perf/util/callchain.h @@ -41,12 +41,18 @@ struct callchain_param; typedef void (*sort_chain_func_t)(struct rb_root *, struct callchain_root *, u64, struct callchain_param *); +enum chain_key { + CCKEY_FUNCTION, + CCKEY_ADDRESS +}; + struct callchain_param { enum chain_mode mode; u32 print_limit; double min_percent; sort_chain_func_t sort; enum chain_order order; + enum chain_key key; }; struct callchain_list { @@ -103,11 +109,6 @@ int callchain_append(struct callchain_root *root, int callchain_merge(struct callchain_cursor *cursor, struct callchain_root *dst, struct callchain_root *src); -struct ip_callchain; -union perf_event; - -bool ip_callchain__valid(struct ip_callchain *chain, - const union perf_event *event); /* * Initialize a cursor before adding entries inside, but keep * the previously allocated entries as a cache. diff --git a/tools/perf/util/cpumap.h b/tools/perf/util/cpumap.h index 9bed02e5fb3d..b123bb9d6f55 100644 --- a/tools/perf/util/cpumap.h +++ b/tools/perf/util/cpumap.h @@ -41,7 +41,7 @@ static inline int cpu_map__nr(const struct cpu_map *map) return map ? map->nr : 1; } -static inline bool cpu_map__all(const struct cpu_map *map) +static inline bool cpu_map__empty(const struct cpu_map *map) { return map ? map->map[0] == -1 : true; } diff --git a/tools/perf/util/dso.c b/tools/perf/util/dso.c index c4374f07603c..e3c1ff8512c8 100644 --- a/tools/perf/util/dso.c +++ b/tools/perf/util/dso.c @@ -78,6 +78,8 @@ int dso__binary_type_file(struct dso *dso, enum dso_binary_type type, symbol_conf.symfs, build_id_hex, build_id_hex + 2); break; + case DSO_BINARY_TYPE__VMLINUX: + case DSO_BINARY_TYPE__GUEST_VMLINUX: case DSO_BINARY_TYPE__SYSTEM_PATH_DSO: snprintf(file, size, "%s%s", symbol_conf.symfs, dso->long_name); @@ -93,11 +95,14 @@ int dso__binary_type_file(struct dso *dso, enum dso_binary_type type, dso->long_name); break; + case DSO_BINARY_TYPE__KCORE: + case DSO_BINARY_TYPE__GUEST_KCORE: + snprintf(file, size, "%s", dso->long_name); + break; + default: case DSO_BINARY_TYPE__KALLSYMS: - case DSO_BINARY_TYPE__VMLINUX: case DSO_BINARY_TYPE__GUEST_KALLSYMS: - case DSO_BINARY_TYPE__GUEST_VMLINUX: case DSO_BINARY_TYPE__JAVA_JIT: case DSO_BINARY_TYPE__NOT_FOUND: ret = -1; @@ -419,6 +424,7 @@ struct dso *dso__new(const char *name) dso->symtab_type = DSO_BINARY_TYPE__NOT_FOUND; dso->data_type = DSO_BINARY_TYPE__NOT_FOUND; dso->loaded = 0; + dso->rel = 0; dso->sorted_by_name = 0; dso->has_build_id = 0; dso->kernel = DSO_TYPE_USER; diff --git a/tools/perf/util/dso.h b/tools/perf/util/dso.h index d51aaf272c68..b793053335d6 100644 --- a/tools/perf/util/dso.h +++ b/tools/perf/util/dso.h @@ -3,6 +3,7 @@ #include <linux/types.h> #include <linux/rbtree.h> +#include <stdbool.h> #include "types.h" #include "map.h" @@ -20,6 +21,8 @@ enum dso_binary_type { DSO_BINARY_TYPE__SYSTEM_PATH_DSO, DSO_BINARY_TYPE__GUEST_KMODULE, DSO_BINARY_TYPE__SYSTEM_PATH_KMODULE, + DSO_BINARY_TYPE__KCORE, + DSO_BINARY_TYPE__GUEST_KCORE, DSO_BINARY_TYPE__NOT_FOUND, }; @@ -84,6 +87,7 @@ struct dso { u8 lname_alloc:1; u8 sorted_by_name; u8 loaded; + u8 rel; u8 build_id[BUILD_ID_SIZE]; const char *short_name; char *long_name; @@ -146,4 +150,17 @@ size_t dso__fprintf_buildid(struct dso *dso, FILE *fp); size_t dso__fprintf_symbols_by_name(struct dso *dso, enum map_type type, FILE *fp); size_t dso__fprintf(struct dso *dso, enum map_type type, FILE *fp); + +static inline bool dso__is_vmlinux(struct dso *dso) +{ + return dso->data_type == DSO_BINARY_TYPE__VMLINUX || + dso->data_type == DSO_BINARY_TYPE__GUEST_VMLINUX; +} + +static inline bool dso__is_kcore(struct dso *dso) +{ + return dso->data_type == DSO_BINARY_TYPE__KCORE || + dso->data_type == DSO_BINARY_TYPE__GUEST_KCORE; +} + #endif /* __PERF_DSO */ diff --git a/tools/perf/util/event.c b/tools/perf/util/event.c index 5cd13d768cec..8d51f21107aa 100644 --- a/tools/perf/util/event.c +++ b/tools/perf/util/event.c @@ -595,6 +595,7 @@ void thread__find_addr_map(struct thread *self, struct addr_location *al) { struct map_groups *mg = &self->mg; + bool load_map = false; al->thread = self; al->addr = addr; @@ -609,11 +610,13 @@ void thread__find_addr_map(struct thread *self, if (cpumode == PERF_RECORD_MISC_KERNEL && perf_host) { al->level = 'k'; mg = &machine->kmaps; + load_map = true; } else if (cpumode == PERF_RECORD_MISC_USER && perf_host) { al->level = '.'; } else if (cpumode == PERF_RECORD_MISC_GUEST_KERNEL && perf_guest) { al->level = 'g'; mg = &machine->kmaps; + load_map = true; } else { /* * 'u' means guest os user space. @@ -654,18 +657,25 @@ try_again: mg = &machine->kmaps; goto try_again; } - } else + } else { + /* + * Kernel maps might be changed when loading symbols so loading + * must be done prior to using kernel maps. + */ + if (load_map) + map__load(al->map, machine->symbol_filter); al->addr = al->map->map_ip(al->map, al->addr); + } } void thread__find_addr_location(struct thread *thread, struct machine *machine, u8 cpumode, enum map_type type, u64 addr, - struct addr_location *al, - symbol_filter_t filter) + struct addr_location *al) { thread__find_addr_map(thread, machine, cpumode, type, addr, al); if (al->map != NULL) - al->sym = map__find_symbol(al->map, al->addr, filter); + al->sym = map__find_symbol(al->map, al->addr, + machine->symbol_filter); else al->sym = NULL; } @@ -673,11 +683,11 @@ void thread__find_addr_location(struct thread *thread, struct machine *machine, int perf_event__preprocess_sample(const union perf_event *event, struct machine *machine, struct addr_location *al, - struct perf_sample *sample, - symbol_filter_t filter) + struct perf_sample *sample) { u8 cpumode = event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK; - struct thread *thread = machine__findnew_thread(machine, event->ip.pid); + struct thread *thread = machine__findnew_thread(machine, sample->pid, + sample->pid); if (thread == NULL) return -1; @@ -686,7 +696,7 @@ int perf_event__preprocess_sample(const union perf_event *event, !strlist__has_entry(symbol_conf.comm_list, thread->comm)) goto out_filtered; - dump_printf(" ... thread: %s:%d\n", thread->comm, thread->pid); + dump_printf(" ... thread: %s:%d\n", thread->comm, thread->tid); /* * Have we already created the kernel maps for this machine? * @@ -699,7 +709,7 @@ int perf_event__preprocess_sample(const union perf_event *event, machine__create_kernel_maps(machine); thread__find_addr_map(thread, machine, cpumode, MAP__FUNCTION, - event->ip.ip, al); + sample->ip, al); dump_printf(" ...... dso: %s\n", al->map ? al->map->dso->long_name : al->level == 'H' ? "[hypervisor]" : "<not found>"); @@ -717,7 +727,8 @@ int perf_event__preprocess_sample(const union perf_event *event, dso->long_name))))) goto out_filtered; - al->sym = map__find_symbol(al->map, al->addr, filter); + al->sym = map__find_symbol(al->map, al->addr, + machine->symbol_filter); } if (symbol_conf.sym_list && diff --git a/tools/perf/util/event.h b/tools/perf/util/event.h index 181389535c0c..93130d856bf0 100644 --- a/tools/perf/util/event.h +++ b/tools/perf/util/event.h @@ -8,16 +8,6 @@ #include "map.h" #include "build-id.h" -/* - * PERF_SAMPLE_IP | PERF_SAMPLE_TID | * - */ -struct ip_event { - struct perf_event_header header; - u64 ip; - u32 pid, tid; - unsigned char __more_data[]; -}; - struct mmap_event { struct perf_event_header header; u32 pid, tid; @@ -63,7 +53,8 @@ struct read_event { (PERF_SAMPLE_IP | PERF_SAMPLE_TID | \ PERF_SAMPLE_TIME | PERF_SAMPLE_ADDR | \ PERF_SAMPLE_ID | PERF_SAMPLE_STREAM_ID | \ - PERF_SAMPLE_CPU | PERF_SAMPLE_PERIOD) + PERF_SAMPLE_CPU | PERF_SAMPLE_PERIOD | \ + PERF_SAMPLE_IDENTIFIER) struct sample_event { struct perf_event_header header; @@ -71,6 +62,7 @@ struct sample_event { }; struct regs_dump { + u64 abi; u64 *regs; }; @@ -80,6 +72,23 @@ struct stack_dump { char *data; }; +struct sample_read_value { + u64 value; + u64 id; +}; + +struct sample_read { + u64 time_enabled; + u64 time_running; + union { + struct { + u64 nr; + struct sample_read_value *values; + } group; + struct sample_read_value one; + }; +}; + struct perf_sample { u64 ip; u32 pid, tid; @@ -97,6 +106,7 @@ struct perf_sample { struct branch_stack *branch_stack; struct regs_dump user_regs; struct stack_dump user_stack; + struct sample_read read; }; #define PERF_MEM_DATA_SRC_NONE \ @@ -116,7 +126,7 @@ struct build_id_event { enum perf_user_event_type { /* above any possible kernel type */ PERF_RECORD_USER_TYPE_START = 64, PERF_RECORD_HEADER_ATTR = 64, - PERF_RECORD_HEADER_EVENT_TYPE = 65, + PERF_RECORD_HEADER_EVENT_TYPE = 65, /* depreceated */ PERF_RECORD_HEADER_TRACING_DATA = 66, PERF_RECORD_HEADER_BUILD_ID = 67, PERF_RECORD_FINISHED_ROUND = 68, @@ -148,7 +158,6 @@ struct tracing_data_event { union perf_event { struct perf_event_header header; - struct ip_event ip; struct mmap_event mmap; struct comm_event comm; struct fork_event fork; @@ -216,12 +225,14 @@ struct addr_location; int perf_event__preprocess_sample(const union perf_event *self, struct machine *machine, struct addr_location *al, - struct perf_sample *sample, - symbol_filter_t filter); + struct perf_sample *sample); const char *perf_event__name(unsigned int id); +size_t perf_event__sample_event_size(const struct perf_sample *sample, u64 type, + u64 sample_regs_user, u64 read_format); int perf_event__synthesize_sample(union perf_event *event, u64 type, + u64 sample_regs_user, u64 read_format, const struct perf_sample *sample, bool swapped); diff --git a/tools/perf/util/evlist.c b/tools/perf/util/evlist.c index 8065ce8fa9a5..b8727ae45e3b 100644 --- a/tools/perf/util/evlist.c +++ b/tools/perf/util/evlist.c @@ -14,6 +14,7 @@ #include "target.h" #include "evlist.h" #include "evsel.h" +#include "debug.h" #include <unistd.h> #include "parse-events.h" @@ -48,26 +49,19 @@ struct perf_evlist *perf_evlist__new(void) return evlist; } -void perf_evlist__config(struct perf_evlist *evlist, - struct perf_record_opts *opts) +/** + * perf_evlist__set_id_pos - set the positions of event ids. + * @evlist: selected event list + * + * Events with compatible sample types all have the same id_pos + * and is_pos. For convenience, put a copy on evlist. + */ +void perf_evlist__set_id_pos(struct perf_evlist *evlist) { - struct perf_evsel *evsel; - /* - * Set the evsel leader links before we configure attributes, - * since some might depend on this info. - */ - if (opts->group) - perf_evlist__set_leader(evlist); - - if (evlist->cpus->map[0] < 0) - opts->no_inherit = true; - - list_for_each_entry(evsel, &evlist->entries, node) { - perf_evsel__config(evsel, opts); + struct perf_evsel *first = perf_evlist__first(evlist); - if (evlist->nr_entries > 1) - perf_evsel__set_sample_id(evsel); - } + evlist->id_pos = first->id_pos; + evlist->is_pos = first->is_pos; } static void perf_evlist__purge(struct perf_evlist *evlist) @@ -100,15 +94,20 @@ void perf_evlist__delete(struct perf_evlist *evlist) void perf_evlist__add(struct perf_evlist *evlist, struct perf_evsel *entry) { list_add_tail(&entry->node, &evlist->entries); - ++evlist->nr_entries; + if (!evlist->nr_entries++) + perf_evlist__set_id_pos(evlist); } void perf_evlist__splice_list_tail(struct perf_evlist *evlist, struct list_head *list, int nr_entries) { + bool set_id_pos = !evlist->nr_entries; + list_splice_tail(list, &evlist->entries); evlist->nr_entries += nr_entries; + if (set_id_pos) + perf_evlist__set_id_pos(evlist); } void __perf_evlist__set_leader(struct list_head *list) @@ -209,6 +208,21 @@ perf_evlist__find_tracepoint_by_id(struct perf_evlist *evlist, int id) return NULL; } +struct perf_evsel * +perf_evlist__find_tracepoint_by_name(struct perf_evlist *evlist, + const char *name) +{ + struct perf_evsel *evsel; + + list_for_each_entry(evsel, &evlist->entries, node) { + if ((evsel->attr.type == PERF_TYPE_TRACEPOINT) && + (strcmp(evsel->name, name) == 0)) + return evsel; + } + + return NULL; +} + int perf_evlist__add_newtp(struct perf_evlist *evlist, const char *sys, const char *name, void *handler) { @@ -232,7 +246,7 @@ void perf_evlist__disable(struct perf_evlist *evlist) for (cpu = 0; cpu < nr_cpus; cpu++) { list_for_each_entry(pos, &evlist->entries, node) { - if (!perf_evsel__is_group_leader(pos)) + if (!perf_evsel__is_group_leader(pos) || !pos->fd) continue; for (thread = 0; thread < nr_threads; thread++) ioctl(FD(pos, cpu, thread), @@ -250,7 +264,7 @@ void perf_evlist__enable(struct perf_evlist *evlist) for (cpu = 0; cpu < nr_cpus; cpu++) { list_for_each_entry(pos, &evlist->entries, node) { - if (!perf_evsel__is_group_leader(pos)) + if (!perf_evsel__is_group_leader(pos) || !pos->fd) continue; for (thread = 0; thread < nr_threads; thread++) ioctl(FD(pos, cpu, thread), @@ -259,6 +273,44 @@ void perf_evlist__enable(struct perf_evlist *evlist) } } +int perf_evlist__disable_event(struct perf_evlist *evlist, + struct perf_evsel *evsel) +{ + int cpu, thread, err; + + if (!evsel->fd) + return 0; + + for (cpu = 0; cpu < evlist->cpus->nr; cpu++) { + for (thread = 0; thread < evlist->threads->nr; thread++) { + err = ioctl(FD(evsel, cpu, thread), + PERF_EVENT_IOC_DISABLE, 0); + if (err) + return err; + } + } + return 0; +} + +int perf_evlist__enable_event(struct perf_evlist *evlist, + struct perf_evsel *evsel) +{ + int cpu, thread, err; + + if (!evsel->fd) + return -EINVAL; + + for (cpu = 0; cpu < evlist->cpus->nr; cpu++) { + for (thread = 0; thread < evlist->threads->nr; thread++) { + err = ioctl(FD(evsel, cpu, thread), + PERF_EVENT_IOC_ENABLE, 0); + if (err) + return err; + } + } + return 0; +} + static int perf_evlist__alloc_pollfd(struct perf_evlist *evlist) { int nr_cpus = cpu_map__nr(evlist->cpus); @@ -302,6 +354,24 @@ static int perf_evlist__id_add_fd(struct perf_evlist *evlist, { u64 read_data[4] = { 0, }; int id_idx = 1; /* The first entry is the counter value */ + u64 id; + int ret; + + ret = ioctl(fd, PERF_EVENT_IOC_ID, &id); + if (!ret) + goto add; + + if (errno != ENOTTY) + return -1; + + /* Legacy way to get event id.. All hail to old kernels! */ + + /* + * This way does not work with group format read, so bail + * out in that case. + */ + if (perf_evlist__read_format(evlist) & PERF_FORMAT_GROUP) + return -1; if (!(evsel->attr.read_format & PERF_FORMAT_ID) || read(fd, &read_data, sizeof(read_data)) == -1) @@ -312,25 +382,39 @@ static int perf_evlist__id_add_fd(struct perf_evlist *evlist, if (evsel->attr.read_format & PERF_FORMAT_TOTAL_TIME_RUNNING) ++id_idx; - perf_evlist__id_add(evlist, evsel, cpu, thread, read_data[id_idx]); + id = read_data[id_idx]; + + add: + perf_evlist__id_add(evlist, evsel, cpu, thread, id); return 0; } -struct perf_evsel *perf_evlist__id2evsel(struct perf_evlist *evlist, u64 id) +struct perf_sample_id *perf_evlist__id2sid(struct perf_evlist *evlist, u64 id) { struct hlist_head *head; struct perf_sample_id *sid; int hash; - if (evlist->nr_entries == 1) - return perf_evlist__first(evlist); - hash = hash_64(id, PERF_EVLIST__HLIST_BITS); head = &evlist->heads[hash]; hlist_for_each_entry(sid, head, node) if (sid->id == id) - return sid->evsel; + return sid; + + return NULL; +} + +struct perf_evsel *perf_evlist__id2evsel(struct perf_evlist *evlist, u64 id) +{ + struct perf_sample_id *sid; + + if (evlist->nr_entries == 1) + return perf_evlist__first(evlist); + + sid = perf_evlist__id2sid(evlist, id); + if (sid) + return sid->evsel; if (!perf_evlist__sample_id_all(evlist)) return perf_evlist__first(evlist); @@ -338,6 +422,55 @@ struct perf_evsel *perf_evlist__id2evsel(struct perf_evlist *evlist, u64 id) return NULL; } +static int perf_evlist__event2id(struct perf_evlist *evlist, + union perf_event *event, u64 *id) +{ + const u64 *array = event->sample.array; + ssize_t n; + + n = (event->header.size - sizeof(event->header)) >> 3; + + if (event->header.type == PERF_RECORD_SAMPLE) { + if (evlist->id_pos >= n) + return -1; + *id = array[evlist->id_pos]; + } else { + if (evlist->is_pos > n) + return -1; + n -= evlist->is_pos; + *id = array[n]; + } + return 0; +} + +static struct perf_evsel *perf_evlist__event2evsel(struct perf_evlist *evlist, + union perf_event *event) +{ + struct hlist_head *head; + struct perf_sample_id *sid; + int hash; + u64 id; + + if (evlist->nr_entries == 1) + return perf_evlist__first(evlist); + + if (perf_evlist__event2id(evlist, event, &id)) + return NULL; + + /* Synthesized events have an id of zero */ + if (!id) + return perf_evlist__first(evlist); + + hash = hash_64(id, PERF_EVLIST__HLIST_BITS); + head = &evlist->heads[hash]; + + hlist_for_each_entry(sid, head, node) { + if (sid->id == id) + return sid->evsel; + } + return NULL; +} + union perf_event *perf_evlist__mmap_read(struct perf_evlist *evlist, int idx) { struct perf_mmap *md = &evlist->mmap[idx]; @@ -403,16 +536,20 @@ union perf_event *perf_evlist__mmap_read(struct perf_evlist *evlist, int idx) return event; } +static void __perf_evlist__munmap(struct perf_evlist *evlist, int idx) +{ + if (evlist->mmap[idx].base != NULL) { + munmap(evlist->mmap[idx].base, evlist->mmap_len); + evlist->mmap[idx].base = NULL; + } +} + void perf_evlist__munmap(struct perf_evlist *evlist) { int i; - for (i = 0; i < evlist->nr_mmaps; i++) { - if (evlist->mmap[i].base != NULL) { - munmap(evlist->mmap[i].base, evlist->mmap_len); - evlist->mmap[i].base = NULL; - } - } + for (i = 0; i < evlist->nr_mmaps; i++) + __perf_evlist__munmap(evlist, i); free(evlist->mmap); evlist->mmap = NULL; @@ -421,7 +558,7 @@ void perf_evlist__munmap(struct perf_evlist *evlist) static int perf_evlist__alloc_mmap(struct perf_evlist *evlist) { evlist->nr_mmaps = cpu_map__nr(evlist->cpus); - if (cpu_map__all(evlist->cpus)) + if (cpu_map__empty(evlist->cpus)) evlist->nr_mmaps = thread_map__nr(evlist->threads); evlist->mmap = zalloc(evlist->nr_mmaps * sizeof(struct perf_mmap)); return evlist->mmap != NULL ? 0 : -ENOMEM; @@ -450,6 +587,7 @@ static int perf_evlist__mmap_per_cpu(struct perf_evlist *evlist, int prot, int m int nr_cpus = cpu_map__nr(evlist->cpus); int nr_threads = thread_map__nr(evlist->threads); + pr_debug2("perf event ring buffer mmapped per cpu\n"); for (cpu = 0; cpu < nr_cpus; cpu++) { int output = -1; @@ -477,12 +615,8 @@ static int perf_evlist__mmap_per_cpu(struct perf_evlist *evlist, int prot, int m return 0; out_unmap: - for (cpu = 0; cpu < nr_cpus; cpu++) { - if (evlist->mmap[cpu].base != NULL) { - munmap(evlist->mmap[cpu].base, evlist->mmap_len); - evlist->mmap[cpu].base = NULL; - } - } + for (cpu = 0; cpu < nr_cpus; cpu++) + __perf_evlist__munmap(evlist, cpu); return -1; } @@ -492,6 +626,7 @@ static int perf_evlist__mmap_per_thread(struct perf_evlist *evlist, int prot, in int thread; int nr_threads = thread_map__nr(evlist->threads); + pr_debug2("perf event ring buffer mmapped per thread\n"); for (thread = 0; thread < nr_threads; thread++) { int output = -1; @@ -517,12 +652,8 @@ static int perf_evlist__mmap_per_thread(struct perf_evlist *evlist, int prot, in return 0; out_unmap: - for (thread = 0; thread < nr_threads; thread++) { - if (evlist->mmap[thread].base != NULL) { - munmap(evlist->mmap[thread].base, evlist->mmap_len); - evlist->mmap[thread].base = NULL; - } - } + for (thread = 0; thread < nr_threads; thread++) + __perf_evlist__munmap(evlist, thread); return -1; } @@ -573,7 +704,7 @@ int perf_evlist__mmap(struct perf_evlist *evlist, unsigned int pages, return -ENOMEM; } - if (cpu_map__all(cpus)) + if (cpu_map__empty(cpus)) return perf_evlist__mmap_per_thread(evlist, prot, mask); return perf_evlist__mmap_per_cpu(evlist, prot, mask); @@ -650,20 +781,66 @@ int perf_evlist__set_filter(struct perf_evlist *evlist, const char *filter) bool perf_evlist__valid_sample_type(struct perf_evlist *evlist) { + struct perf_evsel *pos; + + if (evlist->nr_entries == 1) + return true; + + if (evlist->id_pos < 0 || evlist->is_pos < 0) + return false; + + list_for_each_entry(pos, &evlist->entries, node) { + if (pos->id_pos != evlist->id_pos || + pos->is_pos != evlist->is_pos) + return false; + } + + return true; +} + +u64 __perf_evlist__combined_sample_type(struct perf_evlist *evlist) +{ + struct perf_evsel *evsel; + + if (evlist->combined_sample_type) + return evlist->combined_sample_type; + + list_for_each_entry(evsel, &evlist->entries, node) + evlist->combined_sample_type |= evsel->attr.sample_type; + + return evlist->combined_sample_type; +} + +u64 perf_evlist__combined_sample_type(struct perf_evlist *evlist) +{ + evlist->combined_sample_type = 0; + return __perf_evlist__combined_sample_type(evlist); +} + +bool perf_evlist__valid_read_format(struct perf_evlist *evlist) +{ struct perf_evsel *first = perf_evlist__first(evlist), *pos = first; + u64 read_format = first->attr.read_format; + u64 sample_type = first->attr.sample_type; list_for_each_entry_continue(pos, &evlist->entries, node) { - if (first->attr.sample_type != pos->attr.sample_type) + if (read_format != pos->attr.read_format) return false; } + /* PERF_SAMPLE_READ imples PERF_FORMAT_ID. */ + if ((sample_type & PERF_SAMPLE_READ) && + !(read_format & PERF_FORMAT_ID)) { + return false; + } + return true; } -u64 perf_evlist__sample_type(struct perf_evlist *evlist) +u64 perf_evlist__read_format(struct perf_evlist *evlist) { struct perf_evsel *first = perf_evlist__first(evlist); - return first->attr.sample_type; + return first->attr.read_format; } u16 perf_evlist__id_hdr_size(struct perf_evlist *evlist) @@ -692,6 +869,9 @@ u16 perf_evlist__id_hdr_size(struct perf_evlist *evlist) if (sample_type & PERF_SAMPLE_CPU) size += sizeof(data->cpu) * 2; + + if (sample_type & PERF_SAMPLE_IDENTIFIER) + size += sizeof(data->id); out: return size; } @@ -783,13 +963,6 @@ int perf_evlist__prepare_workload(struct perf_evlist *evlist, fcntl(go_pipe[0], F_SETFD, FD_CLOEXEC); /* - * Do a dummy execvp to get the PLT entry resolved, - * so we avoid the resolver overhead on the real - * execvp call. - */ - execvp("", (char **)argv); - - /* * Tell the parent we're ready to go */ close(child_ready_pipe[1]); @@ -838,7 +1011,7 @@ out_close_ready_pipe: int perf_evlist__start_workload(struct perf_evlist *evlist) { if (evlist->workload.cork_fd > 0) { - char bf; + char bf = 0; int ret; /* * Remove the cork, let it rip! @@ -857,7 +1030,10 @@ int perf_evlist__start_workload(struct perf_evlist *evlist) int perf_evlist__parse_sample(struct perf_evlist *evlist, union perf_event *event, struct perf_sample *sample) { - struct perf_evsel *evsel = perf_evlist__first(evlist); + struct perf_evsel *evsel = perf_evlist__event2evsel(evlist, event); + + if (!evsel) + return -EFAULT; return perf_evsel__parse_sample(evsel, event, sample); } diff --git a/tools/perf/util/evlist.h b/tools/perf/util/evlist.h index 0583d36252be..880d7139d2fb 100644 --- a/tools/perf/util/evlist.h +++ b/tools/perf/util/evlist.h @@ -32,6 +32,9 @@ struct perf_evlist { int nr_fds; int nr_mmaps; int mmap_len; + int id_pos; + int is_pos; + u64 combined_sample_type; struct { int cork_fd; pid_t pid; @@ -71,6 +74,10 @@ int perf_evlist__set_filter(struct perf_evlist *evlist, const char *filter); struct perf_evsel * perf_evlist__find_tracepoint_by_id(struct perf_evlist *evlist, int id); +struct perf_evsel * +perf_evlist__find_tracepoint_by_name(struct perf_evlist *evlist, + const char *name); + void perf_evlist__id_add(struct perf_evlist *evlist, struct perf_evsel *evsel, int cpu, int thread, u64 id); @@ -78,11 +85,15 @@ void perf_evlist__add_pollfd(struct perf_evlist *evlist, int fd); struct perf_evsel *perf_evlist__id2evsel(struct perf_evlist *evlist, u64 id); +struct perf_sample_id *perf_evlist__id2sid(struct perf_evlist *evlist, u64 id); + union perf_event *perf_evlist__mmap_read(struct perf_evlist *self, int idx); int perf_evlist__open(struct perf_evlist *evlist); void perf_evlist__close(struct perf_evlist *evlist); +void perf_evlist__set_id_pos(struct perf_evlist *evlist); +bool perf_can_sample_identifier(void); void perf_evlist__config(struct perf_evlist *evlist, struct perf_record_opts *opts); @@ -99,6 +110,11 @@ void perf_evlist__munmap(struct perf_evlist *evlist); void perf_evlist__disable(struct perf_evlist *evlist); void perf_evlist__enable(struct perf_evlist *evlist); +int perf_evlist__disable_event(struct perf_evlist *evlist, + struct perf_evsel *evsel); +int perf_evlist__enable_event(struct perf_evlist *evlist, + struct perf_evsel *evsel); + void perf_evlist__set_selected(struct perf_evlist *evlist, struct perf_evsel *evsel); @@ -118,7 +134,9 @@ int perf_evlist__apply_filters(struct perf_evlist *evlist); void __perf_evlist__set_leader(struct list_head *list); void perf_evlist__set_leader(struct perf_evlist *evlist); -u64 perf_evlist__sample_type(struct perf_evlist *evlist); +u64 perf_evlist__read_format(struct perf_evlist *evlist); +u64 __perf_evlist__combined_sample_type(struct perf_evlist *evlist); +u64 perf_evlist__combined_sample_type(struct perf_evlist *evlist); bool perf_evlist__sample_id_all(struct perf_evlist *evlist); u16 perf_evlist__id_hdr_size(struct perf_evlist *evlist); @@ -127,6 +145,7 @@ int perf_evlist__parse_sample(struct perf_evlist *evlist, union perf_event *even bool perf_evlist__valid_sample_type(struct perf_evlist *evlist); bool perf_evlist__valid_sample_id_all(struct perf_evlist *evlist); +bool perf_evlist__valid_read_format(struct perf_evlist *evlist); void perf_evlist__splice_list_tail(struct perf_evlist *evlist, struct list_head *list, diff --git a/tools/perf/util/evsel.c b/tools/perf/util/evsel.c index c9c7494506a1..3612183e2cc5 100644 --- a/tools/perf/util/evsel.c +++ b/tools/perf/util/evsel.c @@ -9,18 +9,20 @@ #include <byteswap.h> #include <linux/bitops.h> -#include "asm/bug.h" #include <lk/debugfs.h> -#include "event-parse.h" +#include <traceevent/event-parse.h> +#include <linux/hw_breakpoint.h> +#include <linux/perf_event.h> +#include <sys/resource.h> +#include "asm/bug.h" #include "evsel.h" #include "evlist.h" #include "util.h" #include "cpumap.h" #include "thread_map.h" #include "target.h" -#include <linux/hw_breakpoint.h> -#include <linux/perf_event.h> #include "perf_regs.h" +#include "debug.h" static struct { bool sample_id_all; @@ -29,7 +31,7 @@ static struct { #define FD(e, x, y) (*(int *)xyarray__entry(e->fd, x, y)) -static int __perf_evsel__sample_size(u64 sample_type) +int __perf_evsel__sample_size(u64 sample_type) { u64 mask = sample_type & PERF_SAMPLE_MASK; int size = 0; @@ -45,6 +47,72 @@ static int __perf_evsel__sample_size(u64 sample_type) return size; } +/** + * __perf_evsel__calc_id_pos - calculate id_pos. + * @sample_type: sample type + * + * This function returns the position of the event id (PERF_SAMPLE_ID or + * PERF_SAMPLE_IDENTIFIER) in a sample event i.e. in the array of struct + * sample_event. + */ +static int __perf_evsel__calc_id_pos(u64 sample_type) +{ + int idx = 0; + + if (sample_type & PERF_SAMPLE_IDENTIFIER) + return 0; + + if (!(sample_type & PERF_SAMPLE_ID)) + return -1; + + if (sample_type & PERF_SAMPLE_IP) + idx += 1; + + if (sample_type & PERF_SAMPLE_TID) + idx += 1; + + if (sample_type & PERF_SAMPLE_TIME) + idx += 1; + + if (sample_type & PERF_SAMPLE_ADDR) + idx += 1; + + return idx; +} + +/** + * __perf_evsel__calc_is_pos - calculate is_pos. + * @sample_type: sample type + * + * This function returns the position (counting backwards) of the event id + * (PERF_SAMPLE_ID or PERF_SAMPLE_IDENTIFIER) in a non-sample event i.e. if + * sample_id_all is used there is an id sample appended to non-sample events. + */ +static int __perf_evsel__calc_is_pos(u64 sample_type) +{ + int idx = 1; + + if (sample_type & PERF_SAMPLE_IDENTIFIER) + return 1; + + if (!(sample_type & PERF_SAMPLE_ID)) + return -1; + + if (sample_type & PERF_SAMPLE_CPU) + idx += 1; + + if (sample_type & PERF_SAMPLE_STREAM_ID) + idx += 1; + + return idx; +} + +void perf_evsel__calc_id_pos(struct perf_evsel *evsel) +{ + evsel->id_pos = __perf_evsel__calc_id_pos(evsel->attr.sample_type); + evsel->is_pos = __perf_evsel__calc_is_pos(evsel->attr.sample_type); +} + void hists__init(struct hists *hists) { memset(hists, 0, sizeof(*hists)); @@ -61,6 +129,7 @@ void __perf_evsel__set_sample_bit(struct perf_evsel *evsel, if (!(evsel->attr.sample_type & bit)) { evsel->attr.sample_type |= bit; evsel->sample_size += sizeof(u64); + perf_evsel__calc_id_pos(evsel); } } @@ -70,12 +139,19 @@ void __perf_evsel__reset_sample_bit(struct perf_evsel *evsel, if (evsel->attr.sample_type & bit) { evsel->attr.sample_type &= ~bit; evsel->sample_size -= sizeof(u64); + perf_evsel__calc_id_pos(evsel); } } -void perf_evsel__set_sample_id(struct perf_evsel *evsel) +void perf_evsel__set_sample_id(struct perf_evsel *evsel, + bool can_sample_identifier) { - perf_evsel__set_sample_bit(evsel, ID); + if (can_sample_identifier) { + perf_evsel__reset_sample_bit(evsel, ID); + perf_evsel__set_sample_bit(evsel, IDENTIFIER); + } else { + perf_evsel__set_sample_bit(evsel, ID); + } evsel->attr.read_format |= PERF_FORMAT_ID; } @@ -88,6 +164,7 @@ void perf_evsel__init(struct perf_evsel *evsel, INIT_LIST_HEAD(&evsel->node); hists__init(&evsel->hists); evsel->sample_size = __perf_evsel__sample_size(attr->sample_type); + perf_evsel__calc_id_pos(evsel); } struct perf_evsel *perf_evsel__new(struct perf_event_attr *attr, int idx) @@ -246,6 +323,7 @@ const char *perf_evsel__sw_names[PERF_COUNT_SW_MAX] = { "major-faults", "alignment-faults", "emulation-faults", + "dummy", }; static const char *__perf_evsel__sw_name(u64 config) @@ -490,6 +568,7 @@ int perf_evsel__group_desc(struct perf_evsel *evsel, char *buf, size_t size) void perf_evsel__config(struct perf_evsel *evsel, struct perf_record_opts *opts) { + struct perf_evsel *leader = evsel->leader; struct perf_event_attr *attr = &evsel->attr; int track = !evsel->idx; /* only the first counter needs these */ @@ -499,6 +578,25 @@ void perf_evsel__config(struct perf_evsel *evsel, perf_evsel__set_sample_bit(evsel, IP); perf_evsel__set_sample_bit(evsel, TID); + if (evsel->sample_read) { + perf_evsel__set_sample_bit(evsel, READ); + + /* + * We need ID even in case of single event, because + * PERF_SAMPLE_READ process ID specific data. + */ + perf_evsel__set_sample_id(evsel, false); + + /* + * Apply group format only if we belong to group + * with more than one members. + */ + if (leader->nr_members > 1) { + attr->read_format |= PERF_FORMAT_GROUP; + attr->inherit = 0; + } + } + /* * We default some events to a 1 default interval. But keep * it a weak assumption overridable by the user. @@ -514,6 +612,15 @@ void perf_evsel__config(struct perf_evsel *evsel, } } + /* + * Disable sampling for all group members other + * than leader in case leader 'leads' the sampling. + */ + if ((leader != evsel) && leader->sample_read) { + attr->sample_freq = 0; + attr->sample_period = 0; + } + if (opts->no_samples) attr->sample_freq = 0; @@ -605,15 +712,15 @@ int perf_evsel__alloc_fd(struct perf_evsel *evsel, int ncpus, int nthreads) return evsel->fd != NULL ? 0 : -ENOMEM; } -int perf_evsel__set_filter(struct perf_evsel *evsel, int ncpus, int nthreads, - const char *filter) +static int perf_evsel__run_ioctl(struct perf_evsel *evsel, int ncpus, int nthreads, + int ioc, void *arg) { int cpu, thread; for (cpu = 0; cpu < ncpus; cpu++) { for (thread = 0; thread < nthreads; thread++) { int fd = FD(evsel, cpu, thread), - err = ioctl(fd, PERF_EVENT_IOC_SET_FILTER, filter); + err = ioctl(fd, ioc, arg); if (err) return err; @@ -623,6 +730,21 @@ int perf_evsel__set_filter(struct perf_evsel *evsel, int ncpus, int nthreads, return 0; } +int perf_evsel__set_filter(struct perf_evsel *evsel, int ncpus, int nthreads, + const char *filter) +{ + return perf_evsel__run_ioctl(evsel, ncpus, nthreads, + PERF_EVENT_IOC_SET_FILTER, + (void *)filter); +} + +int perf_evsel__enable(struct perf_evsel *evsel, int ncpus, int nthreads) +{ + return perf_evsel__run_ioctl(evsel, ncpus, nthreads, + PERF_EVENT_IOC_ENABLE, + 0); +} + int perf_evsel__alloc_id(struct perf_evsel *evsel, int ncpus, int nthreads) { evsel->sample_id = xyarray__new(ncpus, nthreads, sizeof(struct perf_sample_id)); @@ -817,12 +939,72 @@ static int get_group_fd(struct perf_evsel *evsel, int cpu, int thread) return fd; } +#define __PRINT_ATTR(fmt, cast, field) \ + fprintf(fp, " %-19s "fmt"\n", #field, cast attr->field) + +#define PRINT_ATTR_U32(field) __PRINT_ATTR("%u" , , field) +#define PRINT_ATTR_X32(field) __PRINT_ATTR("%#x", , field) +#define PRINT_ATTR_U64(field) __PRINT_ATTR("%" PRIu64, (uint64_t), field) +#define PRINT_ATTR_X64(field) __PRINT_ATTR("%#"PRIx64, (uint64_t), field) + +#define PRINT_ATTR2N(name1, field1, name2, field2) \ + fprintf(fp, " %-19s %u %-19s %u\n", \ + name1, attr->field1, name2, attr->field2) + +#define PRINT_ATTR2(field1, field2) \ + PRINT_ATTR2N(#field1, field1, #field2, field2) + +static size_t perf_event_attr__fprintf(struct perf_event_attr *attr, FILE *fp) +{ + size_t ret = 0; + + ret += fprintf(fp, "%.60s\n", graph_dotted_line); + ret += fprintf(fp, "perf_event_attr:\n"); + + ret += PRINT_ATTR_U32(type); + ret += PRINT_ATTR_U32(size); + ret += PRINT_ATTR_X64(config); + ret += PRINT_ATTR_U64(sample_period); + ret += PRINT_ATTR_U64(sample_freq); + ret += PRINT_ATTR_X64(sample_type); + ret += PRINT_ATTR_X64(read_format); + + ret += PRINT_ATTR2(disabled, inherit); + ret += PRINT_ATTR2(pinned, exclusive); + ret += PRINT_ATTR2(exclude_user, exclude_kernel); + ret += PRINT_ATTR2(exclude_hv, exclude_idle); + ret += PRINT_ATTR2(mmap, comm); + ret += PRINT_ATTR2(freq, inherit_stat); + ret += PRINT_ATTR2(enable_on_exec, task); + ret += PRINT_ATTR2(watermark, precise_ip); + ret += PRINT_ATTR2(mmap_data, sample_id_all); + ret += PRINT_ATTR2(exclude_host, exclude_guest); + ret += PRINT_ATTR2N("excl.callchain_kern", exclude_callchain_kernel, + "excl.callchain_user", exclude_callchain_user); + + ret += PRINT_ATTR_U32(wakeup_events); + ret += PRINT_ATTR_U32(wakeup_watermark); + ret += PRINT_ATTR_X32(bp_type); + ret += PRINT_ATTR_X64(bp_addr); + ret += PRINT_ATTR_X64(config1); + ret += PRINT_ATTR_U64(bp_len); + ret += PRINT_ATTR_X64(config2); + ret += PRINT_ATTR_X64(branch_sample_type); + ret += PRINT_ATTR_X64(sample_regs_user); + ret += PRINT_ATTR_U32(sample_stack_user); + + ret += fprintf(fp, "%.60s\n", graph_dotted_line); + + return ret; +} + static int __perf_evsel__open(struct perf_evsel *evsel, struct cpu_map *cpus, struct thread_map *threads) { int cpu, thread; unsigned long flags = 0; int pid = -1, err; + enum { NO_CHANGE, SET_TO_MAX, INCREASED_MAX } set_rlimit = NO_CHANGE; if (evsel->fd == NULL && perf_evsel__alloc_fd(evsel, cpus->nr, threads->nr) < 0) @@ -840,6 +1022,9 @@ retry_sample_id: if (perf_missing_features.sample_id_all) evsel->attr.sample_id_all = 0; + if (verbose >= 2) + perf_event_attr__fprintf(&evsel->attr, stderr); + for (cpu = 0; cpu < cpus->nr; cpu++) { for (thread = 0; thread < threads->nr; thread++) { @@ -849,6 +1034,9 @@ retry_sample_id: pid = threads->map[thread]; group_fd = get_group_fd(evsel, cpu, thread); +retry_open: + pr_debug2("perf_event_open: pid %d cpu %d group_fd %d flags %#lx\n", + pid, cpus->map[cpu], group_fd, flags); FD(evsel, cpu, thread) = sys_perf_event_open(&evsel->attr, pid, @@ -858,12 +1046,37 @@ retry_sample_id: err = -errno; goto try_fallback; } + set_rlimit = NO_CHANGE; } } return 0; try_fallback: + /* + * perf stat needs between 5 and 22 fds per CPU. When we run out + * of them try to increase the limits. + */ + if (err == -EMFILE && set_rlimit < INCREASED_MAX) { + struct rlimit l; + int old_errno = errno; + + if (getrlimit(RLIMIT_NOFILE, &l) == 0) { + if (set_rlimit == NO_CHANGE) + l.rlim_cur = l.rlim_max; + else { + l.rlim_cur = l.rlim_max + 1000; + l.rlim_max = l.rlim_cur; + } + if (setrlimit(RLIMIT_NOFILE, &l) == 0) { + set_rlimit++; + errno = old_errno; + goto retry_open; + } + } + errno = old_errno; + } + if (err != -EINVAL || cpu > 0 || thread > 0) goto out_close; @@ -951,6 +1164,11 @@ static int perf_evsel__parse_id_sample(const struct perf_evsel *evsel, array += ((event->header.size - sizeof(event->header)) / sizeof(u64)) - 1; + if (type & PERF_SAMPLE_IDENTIFIER) { + sample->id = *array; + array--; + } + if (type & PERF_SAMPLE_CPU) { u.val64 = *array; if (swapped) { @@ -994,24 +1212,30 @@ static int perf_evsel__parse_id_sample(const struct perf_evsel *evsel, return 0; } -static bool sample_overlap(const union perf_event *event, - const void *offset, u64 size) +static inline bool overflow(const void *endp, u16 max_size, const void *offset, + u64 size) { - const void *base = event; + return size > max_size || offset + size > endp; +} - if (offset + size > base + event->header.size) - return true; +#define OVERFLOW_CHECK(offset, size, max_size) \ + do { \ + if (overflow(endp, (max_size), (offset), (size))) \ + return -EFAULT; \ + } while (0) - return false; -} +#define OVERFLOW_CHECK_u64(offset) \ + OVERFLOW_CHECK(offset, sizeof(u64), sizeof(u64)) int perf_evsel__parse_sample(struct perf_evsel *evsel, union perf_event *event, struct perf_sample *data) { u64 type = evsel->attr.sample_type; - u64 regs_user = evsel->attr.sample_regs_user; bool swapped = evsel->needs_swap; const u64 *array; + u16 max_size = event->header.size; + const void *endp = (void *)event + max_size; + u64 sz; /* * used for cross-endian analysis. See git commit 65014ab3 @@ -1033,11 +1257,22 @@ int perf_evsel__parse_sample(struct perf_evsel *evsel, union perf_event *event, array = event->sample.array; + /* + * The evsel's sample_size is based on PERF_SAMPLE_MASK which includes + * up to PERF_SAMPLE_PERIOD. After that overflow() must be used to + * check the format does not go past the end of the event. + */ if (evsel->sample_size + sizeof(event->header) > event->header.size) return -EFAULT; + data->id = -1ULL; + if (type & PERF_SAMPLE_IDENTIFIER) { + data->id = *array; + array++; + } + if (type & PERF_SAMPLE_IP) { - data->ip = event->ip.ip; + data->ip = *array; array++; } @@ -1066,7 +1301,6 @@ int perf_evsel__parse_sample(struct perf_evsel *evsel, union perf_event *event, array++; } - data->id = -1ULL; if (type & PERF_SAMPLE_ID) { data->id = *array; array++; @@ -1096,25 +1330,62 @@ int perf_evsel__parse_sample(struct perf_evsel *evsel, union perf_event *event, } if (type & PERF_SAMPLE_READ) { - fprintf(stderr, "PERF_SAMPLE_READ is unsupported for now\n"); - return -1; + u64 read_format = evsel->attr.read_format; + + OVERFLOW_CHECK_u64(array); + if (read_format & PERF_FORMAT_GROUP) + data->read.group.nr = *array; + else + data->read.one.value = *array; + + array++; + + if (read_format & PERF_FORMAT_TOTAL_TIME_ENABLED) { + OVERFLOW_CHECK_u64(array); + data->read.time_enabled = *array; + array++; + } + + if (read_format & PERF_FORMAT_TOTAL_TIME_RUNNING) { + OVERFLOW_CHECK_u64(array); + data->read.time_running = *array; + array++; + } + + /* PERF_FORMAT_ID is forced for PERF_SAMPLE_READ */ + if (read_format & PERF_FORMAT_GROUP) { + const u64 max_group_nr = UINT64_MAX / + sizeof(struct sample_read_value); + + if (data->read.group.nr > max_group_nr) + return -EFAULT; + sz = data->read.group.nr * + sizeof(struct sample_read_value); + OVERFLOW_CHECK(array, sz, max_size); + data->read.group.values = + (struct sample_read_value *)array; + array = (void *)array + sz; + } else { + OVERFLOW_CHECK_u64(array); + data->read.one.id = *array; + array++; + } } if (type & PERF_SAMPLE_CALLCHAIN) { - if (sample_overlap(event, array, sizeof(data->callchain->nr))) - return -EFAULT; - - data->callchain = (struct ip_callchain *)array; + const u64 max_callchain_nr = UINT64_MAX / sizeof(u64); - if (sample_overlap(event, array, data->callchain->nr)) + OVERFLOW_CHECK_u64(array); + data->callchain = (struct ip_callchain *)array++; + if (data->callchain->nr > max_callchain_nr) return -EFAULT; - - array += 1 + data->callchain->nr; + sz = data->callchain->nr * sizeof(u64); + OVERFLOW_CHECK(array, sz, max_size); + array = (void *)array + sz; } if (type & PERF_SAMPLE_RAW) { - const u64 *pdata; - + OVERFLOW_CHECK_u64(array); u.val64 = *array; if (WARN_ONCE(swapped, "Endianness of raw data not corrected!\n")) { @@ -1123,65 +1394,71 @@ int perf_evsel__parse_sample(struct perf_evsel *evsel, union perf_event *event, u.val32[0] = bswap_32(u.val32[0]); u.val32[1] = bswap_32(u.val32[1]); } - - if (sample_overlap(event, array, sizeof(u32))) - return -EFAULT; - data->raw_size = u.val32[0]; - pdata = (void *) array + sizeof(u32); + array = (void *)array + sizeof(u32); - if (sample_overlap(event, pdata, data->raw_size)) - return -EFAULT; - - data->raw_data = (void *) pdata; - - array = (void *)array + data->raw_size + sizeof(u32); + OVERFLOW_CHECK(array, data->raw_size, max_size); + data->raw_data = (void *)array; + array = (void *)array + data->raw_size; } if (type & PERF_SAMPLE_BRANCH_STACK) { - u64 sz; + const u64 max_branch_nr = UINT64_MAX / + sizeof(struct branch_entry); - data->branch_stack = (struct branch_stack *)array; - array++; /* nr */ + OVERFLOW_CHECK_u64(array); + data->branch_stack = (struct branch_stack *)array++; + if (data->branch_stack->nr > max_branch_nr) + return -EFAULT; sz = data->branch_stack->nr * sizeof(struct branch_entry); - sz /= sizeof(u64); - array += sz; + OVERFLOW_CHECK(array, sz, max_size); + array = (void *)array + sz; } if (type & PERF_SAMPLE_REGS_USER) { - /* First u64 tells us if we have any regs in sample. */ - u64 avail = *array++; + OVERFLOW_CHECK_u64(array); + data->user_regs.abi = *array; + array++; + + if (data->user_regs.abi) { + u64 regs_user = evsel->attr.sample_regs_user; - if (avail) { + sz = hweight_long(regs_user) * sizeof(u64); + OVERFLOW_CHECK(array, sz, max_size); data->user_regs.regs = (u64 *)array; - array += hweight_long(regs_user); + array = (void *)array + sz; } } if (type & PERF_SAMPLE_STACK_USER) { - u64 size = *array++; + OVERFLOW_CHECK_u64(array); + sz = *array++; data->user_stack.offset = ((char *)(array - 1) - (char *) event); - if (!size) { + if (!sz) { data->user_stack.size = 0; } else { + OVERFLOW_CHECK(array, sz, max_size); data->user_stack.data = (char *)array; - array += size / sizeof(*array); + array = (void *)array + sz; + OVERFLOW_CHECK_u64(array); data->user_stack.size = *array++; } } data->weight = 0; if (type & PERF_SAMPLE_WEIGHT) { + OVERFLOW_CHECK_u64(array); data->weight = *array; array++; } data->data_src = PERF_MEM_DATA_SRC_NONE; if (type & PERF_SAMPLE_DATA_SRC) { + OVERFLOW_CHECK_u64(array); data->data_src = *array; array++; } @@ -1189,12 +1466,105 @@ int perf_evsel__parse_sample(struct perf_evsel *evsel, union perf_event *event, return 0; } +size_t perf_event__sample_event_size(const struct perf_sample *sample, u64 type, + u64 sample_regs_user, u64 read_format) +{ + size_t sz, result = sizeof(struct sample_event); + + if (type & PERF_SAMPLE_IDENTIFIER) + result += sizeof(u64); + + if (type & PERF_SAMPLE_IP) + result += sizeof(u64); + + if (type & PERF_SAMPLE_TID) + result += sizeof(u64); + + if (type & PERF_SAMPLE_TIME) + result += sizeof(u64); + + if (type & PERF_SAMPLE_ADDR) + result += sizeof(u64); + + if (type & PERF_SAMPLE_ID) + result += sizeof(u64); + + if (type & PERF_SAMPLE_STREAM_ID) + result += sizeof(u64); + + if (type & PERF_SAMPLE_CPU) + result += sizeof(u64); + + if (type & PERF_SAMPLE_PERIOD) + result += sizeof(u64); + + if (type & PERF_SAMPLE_READ) { + result += sizeof(u64); + if (read_format & PERF_FORMAT_TOTAL_TIME_ENABLED) + result += sizeof(u64); + if (read_format & PERF_FORMAT_TOTAL_TIME_RUNNING) + result += sizeof(u64); + /* PERF_FORMAT_ID is forced for PERF_SAMPLE_READ */ + if (read_format & PERF_FORMAT_GROUP) { + sz = sample->read.group.nr * + sizeof(struct sample_read_value); + result += sz; + } else { + result += sizeof(u64); + } + } + + if (type & PERF_SAMPLE_CALLCHAIN) { + sz = (sample->callchain->nr + 1) * sizeof(u64); + result += sz; + } + + if (type & PERF_SAMPLE_RAW) { + result += sizeof(u32); + result += sample->raw_size; + } + + if (type & PERF_SAMPLE_BRANCH_STACK) { + sz = sample->branch_stack->nr * sizeof(struct branch_entry); + sz += sizeof(u64); + result += sz; + } + + if (type & PERF_SAMPLE_REGS_USER) { + if (sample->user_regs.abi) { + result += sizeof(u64); + sz = hweight_long(sample_regs_user) * sizeof(u64); + result += sz; + } else { + result += sizeof(u64); + } + } + + if (type & PERF_SAMPLE_STACK_USER) { + sz = sample->user_stack.size; + result += sizeof(u64); + if (sz) { + result += sz; + result += sizeof(u64); + } + } + + if (type & PERF_SAMPLE_WEIGHT) + result += sizeof(u64); + + if (type & PERF_SAMPLE_DATA_SRC) + result += sizeof(u64); + + return result; +} + int perf_event__synthesize_sample(union perf_event *event, u64 type, + u64 sample_regs_user, u64 read_format, const struct perf_sample *sample, bool swapped) { u64 *array; - + size_t sz; /* * used for cross-endian analysis. See git commit 65014ab3 * for why this goofiness is needed. @@ -1203,8 +1573,13 @@ int perf_event__synthesize_sample(union perf_event *event, u64 type, array = event->sample.array; + if (type & PERF_SAMPLE_IDENTIFIER) { + *array = sample->id; + array++; + } + if (type & PERF_SAMPLE_IP) { - event->ip.ip = sample->ip; + *array = sample->ip; array++; } @@ -1262,6 +1637,97 @@ int perf_event__synthesize_sample(union perf_event *event, u64 type, array++; } + if (type & PERF_SAMPLE_READ) { + if (read_format & PERF_FORMAT_GROUP) + *array = sample->read.group.nr; + else + *array = sample->read.one.value; + array++; + + if (read_format & PERF_FORMAT_TOTAL_TIME_ENABLED) { + *array = sample->read.time_enabled; + array++; + } + + if (read_format & PERF_FORMAT_TOTAL_TIME_RUNNING) { + *array = sample->read.time_running; + array++; + } + + /* PERF_FORMAT_ID is forced for PERF_SAMPLE_READ */ + if (read_format & PERF_FORMAT_GROUP) { + sz = sample->read.group.nr * + sizeof(struct sample_read_value); + memcpy(array, sample->read.group.values, sz); + array = (void *)array + sz; + } else { + *array = sample->read.one.id; + array++; + } + } + + if (type & PERF_SAMPLE_CALLCHAIN) { + sz = (sample->callchain->nr + 1) * sizeof(u64); + memcpy(array, sample->callchain, sz); + array = (void *)array + sz; + } + + if (type & PERF_SAMPLE_RAW) { + u.val32[0] = sample->raw_size; + if (WARN_ONCE(swapped, + "Endianness of raw data not corrected!\n")) { + /* + * Inverse of what is done in perf_evsel__parse_sample + */ + u.val32[0] = bswap_32(u.val32[0]); + u.val32[1] = bswap_32(u.val32[1]); + u.val64 = bswap_64(u.val64); + } + *array = u.val64; + array = (void *)array + sizeof(u32); + + memcpy(array, sample->raw_data, sample->raw_size); + array = (void *)array + sample->raw_size; + } + + if (type & PERF_SAMPLE_BRANCH_STACK) { + sz = sample->branch_stack->nr * sizeof(struct branch_entry); + sz += sizeof(u64); + memcpy(array, sample->branch_stack, sz); + array = (void *)array + sz; + } + + if (type & PERF_SAMPLE_REGS_USER) { + if (sample->user_regs.abi) { + *array++ = sample->user_regs.abi; + sz = hweight_long(sample_regs_user) * sizeof(u64); + memcpy(array, sample->user_regs.regs, sz); + array = (void *)array + sz; + } else { + *array++ = 0; + } + } + + if (type & PERF_SAMPLE_STACK_USER) { + sz = sample->user_stack.size; + *array++ = sz; + if (sz) { + memcpy(array, sample->user_stack.data, sz); + array = (void *)array + sz; + *array++ = sz; + } + } + + if (type & PERF_SAMPLE_WEIGHT) { + *array = sample->weight; + array++; + } + + if (type & PERF_SAMPLE_DATA_SRC) { + *array = sample->data_src; + array++; + } + return 0; } @@ -1391,6 +1857,7 @@ static int sample_type__fprintf(FILE *fp, bool *first, u64 value) bit_name(READ), bit_name(CALLCHAIN), bit_name(ID), bit_name(CPU), bit_name(PERIOD), bit_name(STREAM_ID), bit_name(RAW), bit_name(BRANCH_STACK), bit_name(REGS_USER), bit_name(STACK_USER), + bit_name(IDENTIFIER), { .name = NULL, } }; #undef bit_name @@ -1482,7 +1949,7 @@ out: bool perf_evsel__fallback(struct perf_evsel *evsel, int err, char *msg, size_t msgsize) { - if ((err == ENOENT || err == ENXIO) && + if ((err == ENOENT || err == ENXIO || err == ENODEV) && evsel->attr.type == PERF_TYPE_HARDWARE && evsel->attr.config == PERF_COUNT_HW_CPU_CYCLES) { /* diff --git a/tools/perf/util/evsel.h b/tools/perf/util/evsel.h index 3f156ccc1acb..4a7bdc713bab 100644 --- a/tools/perf/util/evsel.h +++ b/tools/perf/util/evsel.h @@ -38,6 +38,9 @@ struct perf_sample_id { struct hlist_node node; u64 id; struct perf_evsel *evsel; + + /* Holds total ID period value for PERF_SAMPLE_READ processing. */ + u64 period; }; /** struct perf_evsel - event selector @@ -45,6 +48,12 @@ struct perf_sample_id { * @name - Can be set to retain the original event name passed by the user, * so that when showing results in tools such as 'perf stat', we * show the name used, not some alias. + * @id_pos: the position of the event id (PERF_SAMPLE_ID or + * PERF_SAMPLE_IDENTIFIER) in a sample event i.e. in the array of + * struct sample_event + * @is_pos: the position (counting backwards) of the event id (PERF_SAMPLE_ID or + * PERF_SAMPLE_IDENTIFIER) in a non-sample event i.e. if sample_id_all + * is used there is an id sample appended to non-sample events */ struct perf_evsel { struct list_head node; @@ -71,11 +80,14 @@ struct perf_evsel { } handler; struct cpu_map *cpus; unsigned int sample_size; + int id_pos; + int is_pos; bool supported; bool needs_swap; /* parse modifier helper */ int exclude_GH; int nr_members; + int sample_read; struct perf_evsel *leader; char *group_name; }; @@ -100,6 +112,9 @@ void perf_evsel__delete(struct perf_evsel *evsel); void perf_evsel__config(struct perf_evsel *evsel, struct perf_record_opts *opts); +int __perf_evsel__sample_size(u64 sample_type); +void perf_evsel__calc_id_pos(struct perf_evsel *evsel); + bool perf_evsel__is_cache_op_valid(u8 type, u8 op); #define PERF_EVSEL__MAX_ALIASES 8 @@ -138,10 +153,12 @@ void __perf_evsel__reset_sample_bit(struct perf_evsel *evsel, #define perf_evsel__reset_sample_bit(evsel, bit) \ __perf_evsel__reset_sample_bit(evsel, PERF_SAMPLE_##bit) -void perf_evsel__set_sample_id(struct perf_evsel *evsel); +void perf_evsel__set_sample_id(struct perf_evsel *evsel, + bool use_sample_identifier); int perf_evsel__set_filter(struct perf_evsel *evsel, int ncpus, int nthreads, const char *filter); +int perf_evsel__enable(struct perf_evsel *evsel, int ncpus, int nthreads); int perf_evsel__open_per_cpu(struct perf_evsel *evsel, struct cpu_map *cpus); diff --git a/tools/perf/util/header.c b/tools/perf/util/header.c index a4dafbee2511..a33197a4fd21 100644 --- a/tools/perf/util/header.c +++ b/tools/perf/util/header.c @@ -25,41 +25,9 @@ static bool no_buildid_cache = false; -static int trace_event_count; -static struct perf_trace_event_type *trace_events; - static u32 header_argc; static const char **header_argv; -int perf_header__push_event(u64 id, const char *name) -{ - struct perf_trace_event_type *nevents; - - if (strlen(name) > MAX_EVENT_NAME) - pr_warning("Event %s will be truncated\n", name); - - nevents = realloc(trace_events, (trace_event_count + 1) * sizeof(*trace_events)); - if (nevents == NULL) - return -ENOMEM; - trace_events = nevents; - - memset(&trace_events[trace_event_count], 0, sizeof(struct perf_trace_event_type)); - trace_events[trace_event_count].event_id = id; - strncpy(trace_events[trace_event_count].name, name, MAX_EVENT_NAME - 1); - trace_event_count++; - return 0; -} - -char *perf_header__find_event(u64 id) -{ - int i; - for (i = 0 ; i < trace_event_count; i++) { - if (trace_events[i].event_id == id) - return trace_events[i].name; - } - return NULL; -} - /* * magic2 = "PERFILE2" * must be a numerical value to let the endianness @@ -748,18 +716,19 @@ static int build_cpu_topo(struct cpu_topo *tp, int cpu) char filename[MAXPATHLEN]; char *buf = NULL, *p; size_t len = 0; + ssize_t sret; u32 i = 0; int ret = -1; sprintf(filename, CORE_SIB_FMT, cpu); fp = fopen(filename, "r"); if (!fp) - return -1; - - if (getline(&buf, &len, fp) <= 0) - goto done; + goto try_threads; + sret = getline(&buf, &len, fp); fclose(fp); + if (sret <= 0) + goto try_threads; p = strchr(buf, '\n'); if (p) @@ -775,7 +744,9 @@ static int build_cpu_topo(struct cpu_topo *tp, int cpu) buf = NULL; len = 0; } + ret = 0; +try_threads: sprintf(filename, THRD_SIB_FMT, cpu); fp = fopen(filename, "r"); if (!fp) @@ -2257,7 +2228,7 @@ static int perf_header__adds_write(struct perf_header *header, sec_size = sizeof(*feat_sec) * nr_sections; - sec_start = header->data_offset + header->data_size; + sec_start = header->feat_offset; lseek(fd, sec_start + sec_size, SEEK_SET); for_each_set_bit(feat, header->adds_features, HEADER_FEAT_BITS) { @@ -2304,6 +2275,7 @@ int perf_session__write_header(struct perf_session *session, struct perf_file_attr f_attr; struct perf_header *header = &session->header; struct perf_evsel *evsel; + u64 attr_offset; int err; lseek(fd, sizeof(f_header), SEEK_SET); @@ -2317,7 +2289,7 @@ int perf_session__write_header(struct perf_session *session, } } - header->attr_offset = lseek(fd, 0, SEEK_CUR); + attr_offset = lseek(fd, 0, SEEK_CUR); list_for_each_entry(evsel, &evlist->entries, node) { f_attr = (struct perf_file_attr){ @@ -2334,17 +2306,8 @@ int perf_session__write_header(struct perf_session *session, } } - header->event_offset = lseek(fd, 0, SEEK_CUR); - header->event_size = trace_event_count * sizeof(struct perf_trace_event_type); - if (trace_events) { - err = do_write(fd, trace_events, header->event_size); - if (err < 0) { - pr_debug("failed to write perf header events\n"); - return err; - } - } - header->data_offset = lseek(fd, 0, SEEK_CUR); + header->feat_offset = header->data_offset + header->data_size; if (at_exit) { err = perf_header__adds_write(header, evlist, fd); @@ -2357,17 +2320,14 @@ int perf_session__write_header(struct perf_session *session, .size = sizeof(f_header), .attr_size = sizeof(f_attr), .attrs = { - .offset = header->attr_offset, + .offset = attr_offset, .size = evlist->nr_entries * sizeof(f_attr), }, .data = { .offset = header->data_offset, .size = header->data_size, }, - .event_types = { - .offset = header->event_offset, - .size = header->event_size, - }, + /* event_types is ignored, store zeros */ }; memcpy(&f_header.adds_features, &header->adds_features, sizeof(header->adds_features)); @@ -2417,7 +2377,7 @@ int perf_header__process_sections(struct perf_header *header, int fd, sec_size = sizeof(*feat_sec) * nr_sections; - lseek(fd, header->data_offset + header->data_size, SEEK_SET); + lseek(fd, header->feat_offset, SEEK_SET); err = perf_header__getbuffer64(header, fd, feat_sec, sec_size); if (err < 0) @@ -2523,6 +2483,7 @@ static int check_magic_endian(u64 magic, uint64_t hdr_sz, /* check for legacy format */ ret = memcmp(&magic, __perf_magic1, sizeof(magic)); if (ret == 0) { + ph->version = PERF_HEADER_VERSION_1; pr_debug("legacy perf.data format\n"); if (is_pipe) return try_all_pipe_abis(hdr_sz, ph); @@ -2544,6 +2505,7 @@ static int check_magic_endian(u64 magic, uint64_t hdr_sz, return -1; ph->needs_swap = true; + ph->version = PERF_HEADER_VERSION_2; return 0; } @@ -2614,10 +2576,9 @@ int perf_file_header__read(struct perf_file_header *header, memcpy(&ph->adds_features, &header->adds_features, sizeof(ph->adds_features)); - ph->event_offset = header->event_types.offset; - ph->event_size = header->event_types.size; ph->data_offset = header->data.offset; ph->data_size = header->data.size; + ph->feat_offset = header->data.offset + header->data.size; return 0; } @@ -2666,19 +2627,17 @@ static int perf_file_header__read_pipe(struct perf_pipe_file_header *header, return 0; } -static int perf_header__read_pipe(struct perf_session *session, int fd) +static int perf_header__read_pipe(struct perf_session *session) { struct perf_header *header = &session->header; struct perf_pipe_file_header f_header; - if (perf_file_header__read_pipe(&f_header, header, fd, + if (perf_file_header__read_pipe(&f_header, header, session->fd, session->repipe) < 0) { pr_debug("incompatible file format\n"); return -EINVAL; } - session->fd = fd; - return 0; } @@ -2772,20 +2731,21 @@ static int perf_evlist__prepare_tracepoint_events(struct perf_evlist *evlist, return 0; } -int perf_session__read_header(struct perf_session *session, int fd) +int perf_session__read_header(struct perf_session *session) { struct perf_header *header = &session->header; struct perf_file_header f_header; struct perf_file_attr f_attr; u64 f_id; int nr_attrs, nr_ids, i, j; + int fd = session->fd; session->evlist = perf_evlist__new(); if (session->evlist == NULL) return -ENOMEM; if (session->fd_pipe) - return perf_header__read_pipe(session, fd); + return perf_header__read_pipe(session); if (perf_file_header__read(&f_header, header, fd) < 0) return -EINVAL; @@ -2839,22 +2799,9 @@ int perf_session__read_header(struct perf_session *session, int fd) symbol_conf.nr_events = nr_attrs; - if (f_header.event_types.size) { - lseek(fd, f_header.event_types.offset, SEEK_SET); - trace_events = malloc(f_header.event_types.size); - if (trace_events == NULL) - return -ENOMEM; - if (perf_header__getbuffer64(header, fd, trace_events, - f_header.event_types.size)) - goto out_errno; - trace_event_count = f_header.event_types.size / sizeof(struct perf_trace_event_type); - } - perf_header__process_sections(header, fd, &session->pevent, perf_file_section__process); - lseek(fd, header->data_offset, SEEK_SET); - if (perf_evlist__prepare_tracepoint_events(session->evlist, session->pevent)) goto out_delete_evlist; @@ -2922,7 +2869,8 @@ int perf_event__synthesize_attrs(struct perf_tool *tool, return err; } -int perf_event__process_attr(union perf_event *event, +int perf_event__process_attr(struct perf_tool *tool __maybe_unused, + union perf_event *event, struct perf_evlist **pevlist) { u32 i, ids, n_ids; @@ -2961,64 +2909,6 @@ int perf_event__process_attr(union perf_event *event, return 0; } -int perf_event__synthesize_event_type(struct perf_tool *tool, - u64 event_id, char *name, - perf_event__handler_t process, - struct machine *machine) -{ - union perf_event ev; - size_t size = 0; - int err = 0; - - memset(&ev, 0, sizeof(ev)); - - ev.event_type.event_type.event_id = event_id; - memset(ev.event_type.event_type.name, 0, MAX_EVENT_NAME); - strncpy(ev.event_type.event_type.name, name, MAX_EVENT_NAME - 1); - - ev.event_type.header.type = PERF_RECORD_HEADER_EVENT_TYPE; - size = strlen(ev.event_type.event_type.name); - size = PERF_ALIGN(size, sizeof(u64)); - ev.event_type.header.size = sizeof(ev.event_type) - - (sizeof(ev.event_type.event_type.name) - size); - - err = process(tool, &ev, NULL, machine); - - return err; -} - -int perf_event__synthesize_event_types(struct perf_tool *tool, - perf_event__handler_t process, - struct machine *machine) -{ - struct perf_trace_event_type *type; - int i, err = 0; - - for (i = 0; i < trace_event_count; i++) { - type = &trace_events[i]; - - err = perf_event__synthesize_event_type(tool, type->event_id, - type->name, process, - machine); - if (err) { - pr_debug("failed to create perf header event type\n"); - return err; - } - } - - return err; -} - -int perf_event__process_event_type(struct perf_tool *tool __maybe_unused, - union perf_event *event) -{ - if (perf_header__push_event(event->event_type.event_type.event_id, - event->event_type.event_type.name) < 0) - return -ENOMEM; - - return 0; -} - int perf_event__synthesize_tracing_data(struct perf_tool *tool, int fd, struct perf_evlist *evlist, perf_event__handler_t process) @@ -3065,7 +2955,8 @@ int perf_event__synthesize_tracing_data(struct perf_tool *tool, int fd, return aligned_size; } -int perf_event__process_tracing_data(union perf_event *event, +int perf_event__process_tracing_data(struct perf_tool *tool __maybe_unused, + union perf_event *event, struct perf_session *session) { ssize_t size_read, padding, size = event->tracing_data.size; diff --git a/tools/perf/util/header.h b/tools/perf/util/header.h index 16a3e83c584e..307c9aed972e 100644 --- a/tools/perf/util/header.h +++ b/tools/perf/util/header.h @@ -34,6 +34,11 @@ enum { HEADER_FEAT_BITS = 256, }; +enum perf_header_version { + PERF_HEADER_VERSION_1, + PERF_HEADER_VERSION_2, +}; + struct perf_file_section { u64 offset; u64 size; @@ -45,6 +50,7 @@ struct perf_file_header { u64 attr_size; struct perf_file_section attrs; struct perf_file_section data; + /* event_types is ignored */ struct perf_file_section event_types; DECLARE_BITMAP(adds_features, HEADER_FEAT_BITS); }; @@ -84,28 +90,24 @@ struct perf_session_env { }; struct perf_header { - bool needs_swap; - s64 attr_offset; - u64 data_offset; - u64 data_size; - u64 event_offset; - u64 event_size; + enum perf_header_version version; + bool needs_swap; + u64 data_offset; + u64 data_size; + u64 feat_offset; DECLARE_BITMAP(adds_features, HEADER_FEAT_BITS); - struct perf_session_env env; + struct perf_session_env env; }; struct perf_evlist; struct perf_session; -int perf_session__read_header(struct perf_session *session, int fd); +int perf_session__read_header(struct perf_session *session); int perf_session__write_header(struct perf_session *session, struct perf_evlist *evlist, int fd, bool at_exit); int perf_header__write_pipe(int fd); -int perf_header__push_event(u64 id, const char *name); -char *perf_header__find_event(u64 id); - void perf_header__set_feat(struct perf_header *header, int feat); void perf_header__clear_feat(struct perf_header *header, int feat); bool perf_header__has_feat(const struct perf_header *header, int feat); @@ -130,22 +132,14 @@ int perf_event__synthesize_attr(struct perf_tool *tool, int perf_event__synthesize_attrs(struct perf_tool *tool, struct perf_session *session, perf_event__handler_t process); -int perf_event__process_attr(union perf_event *event, struct perf_evlist **pevlist); - -int perf_event__synthesize_event_type(struct perf_tool *tool, - u64 event_id, char *name, - perf_event__handler_t process, - struct machine *machine); -int perf_event__synthesize_event_types(struct perf_tool *tool, - perf_event__handler_t process, - struct machine *machine); -int perf_event__process_event_type(struct perf_tool *tool, - union perf_event *event); +int perf_event__process_attr(struct perf_tool *tool, union perf_event *event, + struct perf_evlist **pevlist); int perf_event__synthesize_tracing_data(struct perf_tool *tool, int fd, struct perf_evlist *evlist, perf_event__handler_t process); -int perf_event__process_tracing_data(union perf_event *event, +int perf_event__process_tracing_data(struct perf_tool *tool, + union perf_event *event, struct perf_session *session); int perf_event__synthesize_build_id(struct perf_tool *tool, diff --git a/tools/perf/util/hist.c b/tools/perf/util/hist.c index b11a6cfdb414..46a0d35a05e1 100644 --- a/tools/perf/util/hist.c +++ b/tools/perf/util/hist.c @@ -24,7 +24,8 @@ enum hist_filter { struct callchain_param callchain_param = { .mode = CHAIN_GRAPH_REL, .min_percent = 0.5, - .order = ORDER_CALLEE + .order = ORDER_CALLEE, + .key = CCKEY_FUNCTION }; u16 hists__col_len(struct hists *hists, enum hist_column col) @@ -912,6 +913,7 @@ static struct hist_entry *hists__add_dummy_entry(struct hists *hists, rb_link_node(&he->rb_node_in, parent, p); rb_insert_color(&he->rb_node_in, root); hists__inc_nr_entries(hists, he); + he->dummy = true; } out: return he; diff --git a/tools/perf/util/hist.h b/tools/perf/util/hist.h index 2d3790fd99bb..1329b6b6ffe6 100644 --- a/tools/perf/util/hist.h +++ b/tools/perf/util/hist.h @@ -141,10 +141,12 @@ struct perf_hpp { }; struct perf_hpp_fmt { - int (*header)(struct perf_hpp *hpp); - int (*width)(struct perf_hpp *hpp); - int (*color)(struct perf_hpp *hpp, struct hist_entry *he); - int (*entry)(struct perf_hpp *hpp, struct hist_entry *he); + int (*header)(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp); + int (*width)(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp); + int (*color)(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp, + struct hist_entry *he); + int (*entry)(struct perf_hpp_fmt *fmt, struct perf_hpp *hpp, + struct hist_entry *he); struct list_head list; }; @@ -157,7 +159,7 @@ extern struct list_head perf_hpp__list; extern struct perf_hpp_fmt perf_hpp__format[]; enum { - PERF_HPP__BASELINE, + /* Matches perf_hpp__format array. */ PERF_HPP__OVERHEAD, PERF_HPP__OVERHEAD_SYS, PERF_HPP__OVERHEAD_US, @@ -165,11 +167,6 @@ enum { PERF_HPP__OVERHEAD_GUEST_US, PERF_HPP__SAMPLES, PERF_HPP__PERIOD, - PERF_HPP__PERIOD_BASELINE, - PERF_HPP__DELTA, - PERF_HPP__RATIO, - PERF_HPP__WEIGHTED_DIFF, - PERF_HPP__FORMULA, PERF_HPP__MAX_INDEX }; @@ -177,8 +174,6 @@ enum { void perf_hpp__init(void); void perf_hpp__column_register(struct perf_hpp_fmt *format); void perf_hpp__column_enable(unsigned col); -int hist_entry__period_snprintf(struct perf_hpp *hpp, struct hist_entry *he, - bool color); struct perf_evlist; @@ -245,11 +240,4 @@ int perf_evlist__gtk_browse_hists(struct perf_evlist *evlist __maybe_unused, #endif unsigned int hists__sort_list_width(struct hists *self); - -double perf_diff__compute_delta(struct hist_entry *he, struct hist_entry *pair); -double perf_diff__compute_ratio(struct hist_entry *he, struct hist_entry *pair); -s64 perf_diff__compute_wdiff(struct hist_entry *he, struct hist_entry *pair); -int perf_diff__formula(struct hist_entry *he, struct hist_entry *pair, - char *buf, size_t size); -double perf_diff__period_percent(struct hist_entry *he, u64 period); #endif /* __PERF_HIST_H */ diff --git a/tools/perf/util/include/linux/string.h b/tools/perf/util/include/linux/string.h index 6f19c548ecc0..97a800738226 100644 --- a/tools/perf/util/include/linux/string.h +++ b/tools/perf/util/include/linux/string.h @@ -1,3 +1,4 @@ #include <string.h> void *memdup(const void *src, size_t len); +int str_append(char **s, int *len, const char *a); diff --git a/tools/perf/util/machine.c b/tools/perf/util/machine.c index b2ecad6ec46b..1dca61f0512d 100644 --- a/tools/perf/util/machine.c +++ b/tools/perf/util/machine.c @@ -25,12 +25,15 @@ int machine__init(struct machine *machine, const char *root_dir, pid_t pid) machine->kmaps.machine = machine; machine->pid = pid; + machine->symbol_filter = NULL; + machine->root_dir = strdup(root_dir); if (machine->root_dir == NULL) return -ENOMEM; if (pid != HOST_KERNEL_ID) { - struct thread *thread = machine__findnew_thread(machine, pid); + struct thread *thread = machine__findnew_thread(machine, 0, + pid); char comm[64]; if (thread == NULL) @@ -95,6 +98,7 @@ void machines__init(struct machines *machines) { machine__init(&machines->host, "", HOST_KERNEL_ID); machines->guests = RB_ROOT; + machines->symbol_filter = NULL; } void machines__exit(struct machines *machines) @@ -118,6 +122,8 @@ struct machine *machines__add(struct machines *machines, pid_t pid, return NULL; } + machine->symbol_filter = machines->symbol_filter; + while (*p != NULL) { parent = *p; pos = rb_entry(parent, struct machine, rb_node); @@ -133,6 +139,21 @@ struct machine *machines__add(struct machines *machines, pid_t pid, return machine; } +void machines__set_symbol_filter(struct machines *machines, + symbol_filter_t symbol_filter) +{ + struct rb_node *nd; + + machines->symbol_filter = symbol_filter; + machines->host.symbol_filter = symbol_filter; + + for (nd = rb_first(&machines->guests); nd; nd = rb_next(nd)) { + struct machine *machine = rb_entry(nd, struct machine, rb_node); + + machine->symbol_filter = symbol_filter; + } +} + struct machine *machines__find(struct machines *machines, pid_t pid) { struct rb_node **p = &machines->guests.rb_node; @@ -233,7 +254,8 @@ void machines__set_id_hdr_size(struct machines *machines, u16 id_hdr_size) return; } -static struct thread *__machine__findnew_thread(struct machine *machine, pid_t pid, +static struct thread *__machine__findnew_thread(struct machine *machine, + pid_t pid, pid_t tid, bool create) { struct rb_node **p = &machine->threads.rb_node; @@ -241,23 +263,28 @@ static struct thread *__machine__findnew_thread(struct machine *machine, pid_t p struct thread *th; /* - * Font-end cache - PID lookups come in blocks, + * Front-end cache - TID lookups come in blocks, * so most of the time we dont have to look up * the full rbtree: */ - if (machine->last_match && machine->last_match->pid == pid) + if (machine->last_match && machine->last_match->tid == tid) { + if (pid && pid != machine->last_match->pid_) + machine->last_match->pid_ = pid; return machine->last_match; + } while (*p != NULL) { parent = *p; th = rb_entry(parent, struct thread, rb_node); - if (th->pid == pid) { + if (th->tid == tid) { machine->last_match = th; + if (pid && pid != th->pid_) + th->pid_ = pid; return th; } - if (pid < th->pid) + if (tid < th->tid) p = &(*p)->rb_left; else p = &(*p)->rb_right; @@ -266,7 +293,7 @@ static struct thread *__machine__findnew_thread(struct machine *machine, pid_t p if (!create) return NULL; - th = thread__new(pid); + th = thread__new(pid, tid); if (th != NULL) { rb_link_node(&th->rb_node, parent, p); rb_insert_color(&th->rb_node, &machine->threads); @@ -276,19 +303,22 @@ static struct thread *__machine__findnew_thread(struct machine *machine, pid_t p return th; } -struct thread *machine__findnew_thread(struct machine *machine, pid_t pid) +struct thread *machine__findnew_thread(struct machine *machine, pid_t pid, + pid_t tid) { - return __machine__findnew_thread(machine, pid, true); + return __machine__findnew_thread(machine, pid, tid, true); } -struct thread *machine__find_thread(struct machine *machine, pid_t pid) +struct thread *machine__find_thread(struct machine *machine, pid_t tid) { - return __machine__findnew_thread(machine, pid, false); + return __machine__findnew_thread(machine, 0, tid, false); } int machine__process_comm_event(struct machine *machine, union perf_event *event) { - struct thread *thread = machine__findnew_thread(machine, event->comm.tid); + struct thread *thread = machine__findnew_thread(machine, + event->comm.pid, + event->comm.tid); if (dump_trace) perf_event__fprintf_comm(event, stdout); @@ -628,10 +658,8 @@ int machine__load_vmlinux_path(struct machine *machine, enum map_type type, struct map *map = machine->vmlinux_maps[type]; int ret = dso__load_vmlinux_path(map->dso, map, filter); - if (ret > 0) { + if (ret > 0) dso__set_loaded(map->dso, type); - map__reloc_vmlinux(map); - } return ret; } @@ -808,7 +836,10 @@ static int machine__create_modules(struct machine *machine) free(line); fclose(file); - return machine__set_modules_path(machine); + if (machine__set_modules_path(machine) < 0) { + pr_debug("Problems setting modules path maps, continuing anyway...\n"); + } + return 0; out_delete_line: free(line); @@ -858,6 +889,18 @@ static void machine__set_kernel_mmap_len(struct machine *machine, } } +static bool machine__uses_kcore(struct machine *machine) +{ + struct dso *dso; + + list_for_each_entry(dso, &machine->kernel_dsos, node) { + if (dso__is_kcore(dso)) + return true; + } + + return false; +} + static int machine__process_kernel_mmap_event(struct machine *machine, union perf_event *event) { @@ -866,6 +909,10 @@ static int machine__process_kernel_mmap_event(struct machine *machine, enum dso_kernel_type kernel_type; bool is_kernel_mmap; + /* If we have maps from kcore then we do not need or want any others */ + if (machine__uses_kcore(machine)) + return 0; + machine__mmap_name(machine, kmmap_prefix, sizeof(kmmap_prefix)); if (machine__is_host(machine)) kernel_type = DSO_TYPE_KERNEL; @@ -969,7 +1016,8 @@ int machine__process_mmap_event(struct machine *machine, union perf_event *event return 0; } - thread = machine__findnew_thread(machine, event->mmap.pid); + thread = machine__findnew_thread(machine, event->mmap.pid, + event->mmap.pid); if (thread == NULL) goto out_problem; @@ -994,11 +1042,30 @@ out_problem: return 0; } +static void machine__remove_thread(struct machine *machine, struct thread *th) +{ + machine->last_match = NULL; + rb_erase(&th->rb_node, &machine->threads); + /* + * We may have references to this thread, for instance in some hist_entry + * instances, so just move them to a separate list. + */ + list_add_tail(&th->node, &machine->dead_threads); +} + int machine__process_fork_event(struct machine *machine, union perf_event *event) { - struct thread *thread = machine__findnew_thread(machine, event->fork.tid); - struct thread *parent = machine__findnew_thread(machine, event->fork.ptid); + struct thread *thread = machine__find_thread(machine, event->fork.tid); + struct thread *parent = machine__findnew_thread(machine, + event->fork.ppid, + event->fork.ptid); + /* if a thread currently exists for the thread id remove it */ + if (thread != NULL) + machine__remove_thread(machine, thread); + + thread = machine__findnew_thread(machine, event->fork.pid, + event->fork.tid); if (dump_trace) perf_event__fprintf_task(event, stdout); @@ -1011,18 +1078,8 @@ int machine__process_fork_event(struct machine *machine, union perf_event *event return 0; } -static void machine__remove_thread(struct machine *machine, struct thread *th) -{ - machine->last_match = NULL; - rb_erase(&th->rb_node, &machine->threads); - /* - * We may have references to this thread, for instance in some hist_entry - * instances, so just move them to a separate list. - */ - list_add_tail(&th->node, &machine->dead_threads); -} - -int machine__process_exit_event(struct machine *machine, union perf_event *event) +int machine__process_exit_event(struct machine *machine __maybe_unused, + union perf_event *event) { struct thread *thread = machine__find_thread(machine, event->fork.tid); @@ -1030,7 +1087,7 @@ int machine__process_exit_event(struct machine *machine, union perf_event *event perf_event__fprintf_task(event, stdout); if (thread != NULL) - machine__remove_thread(machine, thread); + thread__exited(thread); return 0; } @@ -1058,11 +1115,10 @@ int machine__process_event(struct machine *machine, union perf_event *event) return ret; } -static bool symbol__match_parent_regex(struct symbol *sym) +static bool symbol__match_regex(struct symbol *sym, regex_t *regex) { - if (sym->name && !regexec(&parent_regex, sym->name, 0, NULL, 0)) + if (sym->name && !regexec(regex, sym->name, 0, NULL, 0)) return 1; - return 0; } @@ -1094,7 +1150,7 @@ static void ip__resolve_ams(struct machine *machine, struct thread *thread, * or else, the symbol is unknown */ thread__find_addr_location(thread, machine, m, MAP__FUNCTION, - ip, &al, NULL); + ip, &al); if (al.sym) goto found; } @@ -1112,8 +1168,8 @@ static void ip__resolve_data(struct machine *machine, struct thread *thread, memset(&al, 0, sizeof(al)); - thread__find_addr_location(thread, machine, m, MAP__VARIABLE, addr, &al, - NULL); + thread__find_addr_location(thread, machine, m, MAP__VARIABLE, addr, + &al); ams->addr = addr; ams->al_addr = al.addr; ams->sym = al.sym; @@ -1159,8 +1215,8 @@ struct branch_info *machine__resolve_bstack(struct machine *machine, static int machine__resolve_callchain_sample(struct machine *machine, struct thread *thread, struct ip_callchain *chain, - struct symbol **parent) - + struct symbol **parent, + struct addr_location *root_al) { u8 cpumode = PERF_RECORD_MISC_USER; unsigned int i; @@ -1208,11 +1264,18 @@ static int machine__resolve_callchain_sample(struct machine *machine, al.filtered = false; thread__find_addr_location(thread, machine, cpumode, - MAP__FUNCTION, ip, &al, NULL); + MAP__FUNCTION, ip, &al); if (al.sym != NULL) { if (sort__has_parent && !*parent && - symbol__match_parent_regex(al.sym)) + symbol__match_regex(al.sym, &parent_regex)) *parent = al.sym; + else if (have_ignore_callees && root_al && + symbol__match_regex(al.sym, &ignore_callees_regex)) { + /* Treat this symbol as the root, + forgetting its callees. */ + *root_al = al; + callchain_cursor_reset(&callchain_cursor); + } if (!symbol_conf.use_callchain) break; } @@ -1237,15 +1300,13 @@ int machine__resolve_callchain(struct machine *machine, struct perf_evsel *evsel, struct thread *thread, struct perf_sample *sample, - struct symbol **parent) - + struct symbol **parent, + struct addr_location *root_al) { int ret; - callchain_cursor_reset(&callchain_cursor); - ret = machine__resolve_callchain_sample(machine, thread, - sample->callchain, parent); + sample->callchain, parent, root_al); if (ret) return ret; diff --git a/tools/perf/util/machine.h b/tools/perf/util/machine.h index 77940680f1fc..0df925ba6a44 100644 --- a/tools/perf/util/machine.h +++ b/tools/perf/util/machine.h @@ -5,6 +5,7 @@ #include <linux/rbtree.h> #include "map.h" +struct addr_location; struct branch_stack; struct perf_evsel; struct perf_sample; @@ -28,6 +29,7 @@ struct machine { struct list_head kernel_dsos; struct map_groups kmaps; struct map *vmlinux_maps[MAP__NR_TYPES]; + symbol_filter_t symbol_filter; }; static inline @@ -36,7 +38,7 @@ struct map *machine__kernel_map(struct machine *machine, enum map_type type) return machine->vmlinux_maps[type]; } -struct thread *machine__find_thread(struct machine *machine, pid_t pid); +struct thread *machine__find_thread(struct machine *machine, pid_t tid); int machine__process_comm_event(struct machine *machine, union perf_event *event); int machine__process_exit_event(struct machine *machine, union perf_event *event); @@ -50,6 +52,7 @@ typedef void (*machine__process_t)(struct machine *machine, void *data); struct machines { struct machine host; struct rb_root guests; + symbol_filter_t symbol_filter; }; void machines__init(struct machines *machines); @@ -67,6 +70,9 @@ struct machine *machines__findnew(struct machines *machines, pid_t pid); void machines__set_id_hdr_size(struct machines *machines, u16 id_hdr_size); char *machine__mmap_name(struct machine *machine, char *bf, size_t size); +void machines__set_symbol_filter(struct machines *machines, + symbol_filter_t symbol_filter); + int machine__init(struct machine *machine, const char *root_dir, pid_t pid); void machine__exit(struct machine *machine); void machine__delete_dead_threads(struct machine *machine); @@ -83,7 +89,8 @@ int machine__resolve_callchain(struct machine *machine, struct perf_evsel *evsel, struct thread *thread, struct perf_sample *sample, - struct symbol **parent); + struct symbol **parent, + struct addr_location *root_al); /* * Default guest kernel is defined by parameter --guestkallsyms @@ -99,7 +106,8 @@ static inline bool machine__is_host(struct machine *machine) return machine ? machine->pid == HOST_KERNEL_ID : false; } -struct thread *machine__findnew_thread(struct machine *machine, pid_t pid); +struct thread *machine__findnew_thread(struct machine *machine, pid_t pid, + pid_t tid); size_t machine__fprintf(struct machine *machine, FILE *fp); diff --git a/tools/perf/util/map.c b/tools/perf/util/map.c index 8bcdf9e54089..9e8304ca343e 100644 --- a/tools/perf/util/map.c +++ b/tools/perf/util/map.c @@ -182,12 +182,6 @@ int map__load(struct map *map, symbol_filter_t filter) #endif return -1; } - /* - * Only applies to the kernel, as its symtabs aren't relative like the - * module ones. - */ - if (map->dso->kernel) - map__reloc_vmlinux(map); return 0; } @@ -254,14 +248,18 @@ size_t map__fprintf_dsoname(struct map *map, FILE *fp) /* * objdump wants/reports absolute IPs for ET_EXEC, and RIPs for ET_DYN. - * map->dso->adjust_symbols==1 for ET_EXEC-like cases. + * map->dso->adjust_symbols==1 for ET_EXEC-like cases except ET_REL which is + * relative to section start. */ u64 map__rip_2objdump(struct map *map, u64 rip) { - u64 addr = map->dso->adjust_symbols ? - map->unmap_ip(map, rip) : /* RIP -> IP */ - rip; - return addr; + if (!map->dso->adjust_symbols) + return rip; + + if (map->dso->rel) + return rip - map->pgoff; + + return map->unmap_ip(map, rip); } void map_groups__init(struct map_groups *mg) @@ -513,35 +511,6 @@ int map_groups__clone(struct map_groups *mg, return 0; } -static u64 map__reloc_map_ip(struct map *map, u64 ip) -{ - return ip + (s64)map->pgoff; -} - -static u64 map__reloc_unmap_ip(struct map *map, u64 ip) -{ - return ip - (s64)map->pgoff; -} - -void map__reloc_vmlinux(struct map *map) -{ - struct kmap *kmap = map__kmap(map); - s64 reloc; - - if (!kmap->ref_reloc_sym || !kmap->ref_reloc_sym->unrelocated_addr) - return; - - reloc = (kmap->ref_reloc_sym->unrelocated_addr - - kmap->ref_reloc_sym->addr); - - if (!reloc) - return; - - map->map_ip = map__reloc_map_ip; - map->unmap_ip = map__reloc_unmap_ip; - map->pgoff = reloc; -} - void maps__insert(struct rb_root *maps, struct map *map) { struct rb_node **p = &maps->rb_node; @@ -586,3 +555,21 @@ struct map *maps__find(struct rb_root *maps, u64 ip) return NULL; } + +struct map *maps__first(struct rb_root *maps) +{ + struct rb_node *first = rb_first(maps); + + if (first) + return rb_entry(first, struct map, rb_node); + return NULL; +} + +struct map *maps__next(struct map *map) +{ + struct rb_node *next = rb_next(&map->rb_node); + + if (next) + return rb_entry(next, struct map, rb_node); + return NULL; +} diff --git a/tools/perf/util/map.h b/tools/perf/util/map.h index a887f2c9dfbb..2cc93cbf0e17 100644 --- a/tools/perf/util/map.h +++ b/tools/perf/util/map.h @@ -112,6 +112,8 @@ size_t __map_groups__fprintf_maps(struct map_groups *mg, void maps__insert(struct rb_root *maps, struct map *map); void maps__remove(struct rb_root *maps, struct map *map); struct map *maps__find(struct rb_root *maps, u64 addr); +struct map *maps__first(struct rb_root *maps); +struct map *maps__next(struct map *map); void map_groups__init(struct map_groups *mg); void map_groups__exit(struct map_groups *mg); int map_groups__clone(struct map_groups *mg, @@ -139,6 +141,17 @@ static inline struct map *map_groups__find(struct map_groups *mg, return maps__find(&mg->maps[type], addr); } +static inline struct map *map_groups__first(struct map_groups *mg, + enum map_type type) +{ + return maps__first(&mg->maps[type]); +} + +static inline struct map *map_groups__next(struct map *map) +{ + return maps__next(map); +} + struct symbol *map_groups__find_symbol(struct map_groups *mg, enum map_type type, u64 addr, struct map **mapp, diff --git a/tools/perf/util/parse-events.c b/tools/perf/util/parse-events.c index 995fc25db8c6..98125319b158 100644 --- a/tools/perf/util/parse-events.c +++ b/tools/perf/util/parse-events.c @@ -6,7 +6,7 @@ #include "parse-options.h" #include "parse-events.h" #include "exec_cmd.h" -#include "string.h" +#include "linux/string.h" #include "symbol.h" #include "cache.h" #include "header.h" @@ -15,6 +15,7 @@ #define YY_EXTRA_TYPE int #include "parse-events-flex.h" #include "pmu.h" +#include "thread_map.h" #define MAX_NAME_LEN 100 @@ -108,6 +109,10 @@ static struct event_symbol event_symbols_sw[PERF_COUNT_SW_MAX] = { .symbol = "emulation-faults", .alias = "", }, + [PERF_COUNT_SW_DUMMY] = { + .symbol = "dummy", + .alias = "", + }, }; #define __PERF_EVENT_FIELD(config, name) \ @@ -217,6 +222,29 @@ struct tracepoint_path *tracepoint_id_to_path(u64 config) return NULL; } +struct tracepoint_path *tracepoint_name_to_path(const char *name) +{ + struct tracepoint_path *path = zalloc(sizeof(*path)); + char *str = strchr(name, ':'); + + if (path == NULL || str == NULL) { + free(path); + return NULL; + } + + path->system = strndup(name, str - name); + path->name = strdup(str+1); + + if (path->system == NULL || path->name == NULL) { + free(path->system); + free(path->name); + free(path); + path = NULL; + } + + return path; +} + const char *event_type(int type) { switch (type) { @@ -241,40 +269,29 @@ const char *event_type(int type) -static int __add_event(struct list_head **_list, int *idx, +static int __add_event(struct list_head *list, int *idx, struct perf_event_attr *attr, char *name, struct cpu_map *cpus) { struct perf_evsel *evsel; - struct list_head *list = *_list; - - if (!list) { - list = malloc(sizeof(*list)); - if (!list) - return -ENOMEM; - INIT_LIST_HEAD(list); - } event_attr_init(attr); evsel = perf_evsel__new(attr, (*idx)++); - if (!evsel) { - free(list); + if (!evsel) return -ENOMEM; - } evsel->cpus = cpus; if (name) evsel->name = strdup(name); list_add_tail(&evsel->node, list); - *_list = list; return 0; } -static int add_event(struct list_head **_list, int *idx, +static int add_event(struct list_head *list, int *idx, struct perf_event_attr *attr, char *name) { - return __add_event(_list, idx, attr, name, NULL); + return __add_event(list, idx, attr, name, NULL); } static int parse_aliases(char *str, const char *names[][PERF_EVSEL__MAX_ALIASES], int size) @@ -295,7 +312,7 @@ static int parse_aliases(char *str, const char *names[][PERF_EVSEL__MAX_ALIASES] return -1; } -int parse_events_add_cache(struct list_head **list, int *idx, +int parse_events_add_cache(struct list_head *list, int *idx, char *type, char *op_result1, char *op_result2) { struct perf_event_attr attr; @@ -356,31 +373,21 @@ int parse_events_add_cache(struct list_head **list, int *idx, return add_event(list, idx, &attr, name); } -static int add_tracepoint(struct list_head **listp, int *idx, +static int add_tracepoint(struct list_head *list, int *idx, char *sys_name, char *evt_name) { struct perf_evsel *evsel; - struct list_head *list = *listp; - - if (!list) { - list = malloc(sizeof(*list)); - if (!list) - return -ENOMEM; - INIT_LIST_HEAD(list); - } evsel = perf_evsel__newtp(sys_name, evt_name, (*idx)++); - if (!evsel) { - free(list); + if (!evsel) return -ENOMEM; - } list_add_tail(&evsel->node, list); - *listp = list; + return 0; } -static int add_tracepoint_multi_event(struct list_head **list, int *idx, +static int add_tracepoint_multi_event(struct list_head *list, int *idx, char *sys_name, char *evt_name) { char evt_path[MAXPATHLEN]; @@ -412,7 +419,7 @@ static int add_tracepoint_multi_event(struct list_head **list, int *idx, return ret; } -static int add_tracepoint_event(struct list_head **list, int *idx, +static int add_tracepoint_event(struct list_head *list, int *idx, char *sys_name, char *evt_name) { return strpbrk(evt_name, "*?") ? @@ -420,7 +427,7 @@ static int add_tracepoint_event(struct list_head **list, int *idx, add_tracepoint(list, idx, sys_name, evt_name); } -static int add_tracepoint_multi_sys(struct list_head **list, int *idx, +static int add_tracepoint_multi_sys(struct list_head *list, int *idx, char *sys_name, char *evt_name) { struct dirent *events_ent; @@ -452,7 +459,7 @@ static int add_tracepoint_multi_sys(struct list_head **list, int *idx, return ret; } -int parse_events_add_tracepoint(struct list_head **list, int *idx, +int parse_events_add_tracepoint(struct list_head *list, int *idx, char *sys, char *event) { int ret; @@ -507,7 +514,7 @@ do { \ return 0; } -int parse_events_add_breakpoint(struct list_head **list, int *idx, +int parse_events_add_breakpoint(struct list_head *list, int *idx, void *ptr, char *type) { struct perf_event_attr attr; @@ -588,7 +595,7 @@ static int config_attr(struct perf_event_attr *attr, return 0; } -int parse_events_add_numeric(struct list_head **list, int *idx, +int parse_events_add_numeric(struct list_head *list, int *idx, u32 type, u64 config, struct list_head *head_config) { @@ -621,7 +628,7 @@ static char *pmu_event_name(struct list_head *head_terms) return NULL; } -int parse_events_add_pmu(struct list_head **list, int *idx, +int parse_events_add_pmu(struct list_head *list, int *idx, char *name, struct list_head *head_config) { struct perf_event_attr attr; @@ -664,6 +671,7 @@ void parse_events__set_leader(char *name, struct list_head *list) leader->group_name = name ? strdup(name) : NULL; } +/* list_event is assumed to point to malloc'ed memory */ void parse_events_update_lists(struct list_head *list_event, struct list_head *list_all) { @@ -684,6 +692,8 @@ struct event_modifier { int eG; int precise; int exclude_GH; + int sample_read; + int pinned; }; static int get_event_modifier(struct event_modifier *mod, char *str, @@ -695,6 +705,8 @@ static int get_event_modifier(struct event_modifier *mod, char *str, int eH = evsel ? evsel->attr.exclude_host : 0; int eG = evsel ? evsel->attr.exclude_guest : 0; int precise = evsel ? evsel->attr.precise_ip : 0; + int sample_read = 0; + int pinned = evsel ? evsel->attr.pinned : 0; int exclude = eu | ek | eh; int exclude_GH = evsel ? evsel->exclude_GH : 0; @@ -727,6 +739,10 @@ static int get_event_modifier(struct event_modifier *mod, char *str, /* use of precise requires exclude_guest */ if (!exclude_GH) eG = 1; + } else if (*str == 'S') { + sample_read = 1; + } else if (*str == 'D') { + pinned = 1; } else break; @@ -753,6 +769,9 @@ static int get_event_modifier(struct event_modifier *mod, char *str, mod->eG = eG; mod->precise = precise; mod->exclude_GH = exclude_GH; + mod->sample_read = sample_read; + mod->pinned = pinned; + return 0; } @@ -765,7 +784,7 @@ static int check_modifier(char *str) char *p = str; /* The sizeof includes 0 byte as well. */ - if (strlen(str) > (sizeof("ukhGHppp") - 1)) + if (strlen(str) > (sizeof("ukhGHpppSD") - 1)) return -1; while (*p) { @@ -803,6 +822,10 @@ int parse_events__modifier_event(struct list_head *list, char *str, bool add) evsel->attr.exclude_host = mod.eH; evsel->attr.exclude_guest = mod.eG; evsel->exclude_GH = mod.exclude_GH; + evsel->sample_read = mod.sample_read; + + if (perf_evsel__is_group_leader(evsel)) + evsel->attr.pinned = mod.pinned; } return 0; @@ -820,6 +843,32 @@ int parse_events_name(struct list_head *list, char *name) return 0; } +static int parse_events__scanner(const char *str, void *data, int start_token); + +static int parse_events_fixup(int ret, const char *str, void *data, + int start_token) +{ + char *o = strdup(str); + char *s = NULL; + char *t = o; + char *p; + int len = 0; + + if (!o) + return ret; + while ((p = strsep(&t, ",")) != NULL) { + if (s) + str_append(&s, &len, ","); + str_append(&s, &len, "cpu/"); + str_append(&s, &len, p); + str_append(&s, &len, "/"); + } + free(o); + if (!s) + return -ENOMEM; + return parse_events__scanner(s, data, start_token); +} + static int parse_events__scanner(const char *str, void *data, int start_token) { YY_BUFFER_STATE buffer; @@ -840,6 +889,8 @@ static int parse_events__scanner(const char *str, void *data, int start_token) parse_events__flush_buffer(buffer, scanner); parse_events__delete_buffer(buffer, scanner); parse_events_lex_destroy(scanner); + if (ret && !strchr(str, '/')) + ret = parse_events_fixup(ret, str, data, start_token); return ret; } @@ -1026,6 +1077,33 @@ int is_valid_tracepoint(const char *event_string) return 0; } +static bool is_event_supported(u8 type, unsigned config) +{ + bool ret = true; + struct perf_evsel *evsel; + struct perf_event_attr attr = { + .type = type, + .config = config, + .disabled = 1, + .exclude_kernel = 1, + }; + struct { + struct thread_map map; + int threads[1]; + } tmap = { + .map.nr = 1, + .threads = { 0 }, + }; + + evsel = perf_evsel__new(&attr, 0); + if (evsel) { + ret = perf_evsel__open(evsel, NULL, &tmap.map) >= 0; + perf_evsel__delete(evsel); + } + + return ret; +} + static void __print_events_type(u8 type, struct event_symbol *syms, unsigned max) { @@ -1033,14 +1111,16 @@ static void __print_events_type(u8 type, struct event_symbol *syms, unsigned i; for (i = 0; i < max ; i++, syms++) { + if (!is_event_supported(type, i)) + continue; + if (strlen(syms->alias)) snprintf(name, sizeof(name), "%s OR %s", syms->symbol, syms->alias); else snprintf(name, sizeof(name), "%s", syms->symbol); - printf(" %-50s [%s]\n", name, - event_type_descriptors[type]); + printf(" %-50s [%s]\n", name, event_type_descriptors[type]); } } @@ -1069,6 +1149,10 @@ int print_hwcache_events(const char *event_glob, bool name_only) if (event_glob != NULL && !strglobmatch(name, event_glob)) continue; + if (!is_event_supported(PERF_TYPE_HW_CACHE, + type | (op << 8) | (i << 16))) + continue; + if (name_only) printf("%s ", name); else @@ -1079,6 +1163,8 @@ int print_hwcache_events(const char *event_glob, bool name_only) } } + if (printed) + printf("\n"); return printed; } @@ -1096,6 +1182,9 @@ static void print_symbol_events(const char *event_glob, unsigned type, (syms->alias && strglobmatch(syms->alias, event_glob)))) continue; + if (!is_event_supported(type, i)) + continue; + if (name_only) { printf("%s ", syms->symbol); continue; @@ -1133,11 +1222,12 @@ void print_events(const char *event_glob, bool name_only) print_hwcache_events(event_glob, name_only); + print_pmu_events(event_glob, name_only); + if (event_glob != NULL) return; if (!name_only) { - printf("\n"); printf(" %-50s [%s]\n", "rNNN", event_type_descriptors[PERF_TYPE_RAW]); @@ -1237,6 +1327,4 @@ void parse_events__free_terms(struct list_head *terms) list_for_each_entry_safe(term, h, terms, list) free(term); - - free(terms); } diff --git a/tools/perf/util/parse-events.h b/tools/perf/util/parse-events.h index 8a4859315fd9..f1cb4c4b3c70 100644 --- a/tools/perf/util/parse-events.h +++ b/tools/perf/util/parse-events.h @@ -23,6 +23,7 @@ struct tracepoint_path { }; extern struct tracepoint_path *tracepoint_id_to_path(u64 config); +extern struct tracepoint_path *tracepoint_name_to_path(const char *name); extern bool have_tracepoints(struct list_head *evlist); const char *event_type(int type); @@ -84,16 +85,16 @@ void parse_events__free_terms(struct list_head *terms); int parse_events__modifier_event(struct list_head *list, char *str, bool add); int parse_events__modifier_group(struct list_head *list, char *event_mod); int parse_events_name(struct list_head *list, char *name); -int parse_events_add_tracepoint(struct list_head **list, int *idx, +int parse_events_add_tracepoint(struct list_head *list, int *idx, char *sys, char *event); -int parse_events_add_numeric(struct list_head **list, int *idx, +int parse_events_add_numeric(struct list_head *list, int *idx, u32 type, u64 config, struct list_head *head_config); -int parse_events_add_cache(struct list_head **list, int *idx, +int parse_events_add_cache(struct list_head *list, int *idx, char *type, char *op_result1, char *op_result2); -int parse_events_add_breakpoint(struct list_head **list, int *idx, +int parse_events_add_breakpoint(struct list_head *list, int *idx, void *ptr, char *type); -int parse_events_add_pmu(struct list_head **list, int *idx, +int parse_events_add_pmu(struct list_head *list, int *idx, char *pmu , struct list_head *head_config); void parse_events__set_leader(char *name, struct list_head *list); void parse_events_update_lists(struct list_head *list_event, diff --git a/tools/perf/util/parse-events.l b/tools/perf/util/parse-events.l index e9d1134c2c68..91346b753960 100644 --- a/tools/perf/util/parse-events.l +++ b/tools/perf/util/parse-events.l @@ -82,7 +82,8 @@ num_hex 0x[a-fA-F0-9]+ num_raw_hex [a-fA-F0-9]+ name [a-zA-Z_*?][a-zA-Z0-9_*?]* name_minus [a-zA-Z_*?][a-zA-Z0-9\-_*?]* -modifier_event [ukhpGH]+ +/* If you add a modifier you need to update check_modifier() */ +modifier_event [ukhpGHSD]+ modifier_bp [rwx]{1,3} %% @@ -144,6 +145,7 @@ context-switches|cs { return sym(yyscanner, PERF_TYPE_SOFTWARE, PERF_COUNT_SW cpu-migrations|migrations { return sym(yyscanner, PERF_TYPE_SOFTWARE, PERF_COUNT_SW_CPU_MIGRATIONS); } alignment-faults { return sym(yyscanner, PERF_TYPE_SOFTWARE, PERF_COUNT_SW_ALIGNMENT_FAULTS); } emulation-faults { return sym(yyscanner, PERF_TYPE_SOFTWARE, PERF_COUNT_SW_EMULATION_FAULTS); } +dummy { return sym(yyscanner, PERF_TYPE_SOFTWARE, PERF_COUNT_SW_DUMMY); } L1-dcache|l1-d|l1d|L1-data | L1-icache|l1-i|l1i|L1-instruction | diff --git a/tools/perf/util/parse-events.y b/tools/perf/util/parse-events.y index afc44c18dfe1..4eb67ec333f1 100644 --- a/tools/perf/util/parse-events.y +++ b/tools/perf/util/parse-events.y @@ -22,6 +22,13 @@ do { \ YYABORT; \ } while (0) +#define ALLOC_LIST(list) \ +do { \ + list = malloc(sizeof(*list)); \ + ABORT_ON(!list); \ + INIT_LIST_HEAD(list); \ +} while (0) + static inc_group_count(struct list_head *list, struct parse_events_evlist *data) { @@ -196,9 +203,10 @@ event_pmu: PE_NAME '/' event_config '/' { struct parse_events_evlist *data = _data; - struct list_head *list = NULL; + struct list_head *list; - ABORT_ON(parse_events_add_pmu(&list, &data->idx, $1, $3)); + ALLOC_LIST(list); + ABORT_ON(parse_events_add_pmu(list, &data->idx, $1, $3)); parse_events__free_terms($3); $$ = list; } @@ -212,11 +220,12 @@ event_legacy_symbol: value_sym '/' event_config '/' { struct parse_events_evlist *data = _data; - struct list_head *list = NULL; + struct list_head *list; int type = $1 >> 16; int config = $1 & 255; - ABORT_ON(parse_events_add_numeric(&list, &data->idx, + ALLOC_LIST(list); + ABORT_ON(parse_events_add_numeric(list, &data->idx, type, config, $3)); parse_events__free_terms($3); $$ = list; @@ -225,11 +234,12 @@ value_sym '/' event_config '/' value_sym sep_slash_dc { struct parse_events_evlist *data = _data; - struct list_head *list = NULL; + struct list_head *list; int type = $1 >> 16; int config = $1 & 255; - ABORT_ON(parse_events_add_numeric(&list, &data->idx, + ALLOC_LIST(list); + ABORT_ON(parse_events_add_numeric(list, &data->idx, type, config, NULL)); $$ = list; } @@ -238,27 +248,30 @@ event_legacy_cache: PE_NAME_CACHE_TYPE '-' PE_NAME_CACHE_OP_RESULT '-' PE_NAME_CACHE_OP_RESULT { struct parse_events_evlist *data = _data; - struct list_head *list = NULL; + struct list_head *list; - ABORT_ON(parse_events_add_cache(&list, &data->idx, $1, $3, $5)); + ALLOC_LIST(list); + ABORT_ON(parse_events_add_cache(list, &data->idx, $1, $3, $5)); $$ = list; } | PE_NAME_CACHE_TYPE '-' PE_NAME_CACHE_OP_RESULT { struct parse_events_evlist *data = _data; - struct list_head *list = NULL; + struct list_head *list; - ABORT_ON(parse_events_add_cache(&list, &data->idx, $1, $3, NULL)); + ALLOC_LIST(list); + ABORT_ON(parse_events_add_cache(list, &data->idx, $1, $3, NULL)); $$ = list; } | PE_NAME_CACHE_TYPE { struct parse_events_evlist *data = _data; - struct list_head *list = NULL; + struct list_head *list; - ABORT_ON(parse_events_add_cache(&list, &data->idx, $1, NULL, NULL)); + ALLOC_LIST(list); + ABORT_ON(parse_events_add_cache(list, &data->idx, $1, NULL, NULL)); $$ = list; } @@ -266,9 +279,10 @@ event_legacy_mem: PE_PREFIX_MEM PE_VALUE ':' PE_MODIFIER_BP sep_dc { struct parse_events_evlist *data = _data; - struct list_head *list = NULL; + struct list_head *list; - ABORT_ON(parse_events_add_breakpoint(&list, &data->idx, + ALLOC_LIST(list); + ABORT_ON(parse_events_add_breakpoint(list, &data->idx, (void *) $2, $4)); $$ = list; } @@ -276,9 +290,10 @@ PE_PREFIX_MEM PE_VALUE ':' PE_MODIFIER_BP sep_dc PE_PREFIX_MEM PE_VALUE sep_dc { struct parse_events_evlist *data = _data; - struct list_head *list = NULL; + struct list_head *list; - ABORT_ON(parse_events_add_breakpoint(&list, &data->idx, + ALLOC_LIST(list); + ABORT_ON(parse_events_add_breakpoint(list, &data->idx, (void *) $2, NULL)); $$ = list; } @@ -287,9 +302,10 @@ event_legacy_tracepoint: PE_NAME ':' PE_NAME { struct parse_events_evlist *data = _data; - struct list_head *list = NULL; + struct list_head *list; - ABORT_ON(parse_events_add_tracepoint(&list, &data->idx, $1, $3)); + ALLOC_LIST(list); + ABORT_ON(parse_events_add_tracepoint(list, &data->idx, $1, $3)); $$ = list; } @@ -297,9 +313,10 @@ event_legacy_numeric: PE_VALUE ':' PE_VALUE { struct parse_events_evlist *data = _data; - struct list_head *list = NULL; + struct list_head *list; - ABORT_ON(parse_events_add_numeric(&list, &data->idx, (u32)$1, $3, NULL)); + ALLOC_LIST(list); + ABORT_ON(parse_events_add_numeric(list, &data->idx, (u32)$1, $3, NULL)); $$ = list; } @@ -307,9 +324,10 @@ event_legacy_raw: PE_RAW { struct parse_events_evlist *data = _data; - struct list_head *list = NULL; + struct list_head *list; - ABORT_ON(parse_events_add_numeric(&list, &data->idx, + ALLOC_LIST(list); + ABORT_ON(parse_events_add_numeric(list, &data->idx, PERF_TYPE_RAW, $1, NULL)); $$ = list; } diff --git a/tools/perf/util/pmu.c b/tools/perf/util/pmu.c index 4c6f9c490a8d..bc9d8069d376 100644 --- a/tools/perf/util/pmu.c +++ b/tools/perf/util/pmu.c @@ -73,7 +73,7 @@ int perf_pmu__format_parse(char *dir, struct list_head *head) * located at: * /sys/bus/event_source/devices/<dev>/format as sysfs group attributes. */ -static int pmu_format(char *name, struct list_head *format) +static int pmu_format(const char *name, struct list_head *format) { struct stat st; char path[PATH_MAX]; @@ -162,7 +162,7 @@ static int pmu_aliases_parse(char *dir, struct list_head *head) * Reading the pmu event aliases definition, which should be located at: * /sys/bus/event_source/devices/<dev>/events as sysfs group attributes. */ -static int pmu_aliases(char *name, struct list_head *head) +static int pmu_aliases(const char *name, struct list_head *head) { struct stat st; char path[PATH_MAX]; @@ -208,7 +208,7 @@ static int pmu_alias_terms(struct perf_pmu_alias *alias, * located at: * /sys/bus/event_source/devices/<dev>/type as sysfs attribute. */ -static int pmu_type(char *name, __u32 *type) +static int pmu_type(const char *name, __u32 *type) { struct stat st; char path[PATH_MAX]; @@ -266,7 +266,7 @@ static void pmu_read_sysfs(void) closedir(dir); } -static struct cpu_map *pmu_cpumask(char *name) +static struct cpu_map *pmu_cpumask(const char *name) { struct stat st; char path[PATH_MAX]; @@ -293,7 +293,7 @@ static struct cpu_map *pmu_cpumask(char *name) return cpus; } -static struct perf_pmu *pmu_lookup(char *name) +static struct perf_pmu *pmu_lookup(const char *name) { struct perf_pmu *pmu; LIST_HEAD(format); @@ -330,7 +330,7 @@ static struct perf_pmu *pmu_lookup(char *name) return pmu; } -static struct perf_pmu *pmu_find(char *name) +static struct perf_pmu *pmu_find(const char *name) { struct perf_pmu *pmu; @@ -356,7 +356,7 @@ struct perf_pmu *perf_pmu__scan(struct perf_pmu *pmu) return NULL; } -struct perf_pmu *perf_pmu__find(char *name) +struct perf_pmu *perf_pmu__find(const char *name) { struct perf_pmu *pmu; @@ -564,3 +564,76 @@ void perf_pmu__set_format(unsigned long *bits, long from, long to) for (b = from; b <= to; b++) set_bit(b, bits); } + +static char *format_alias(char *buf, int len, struct perf_pmu *pmu, + struct perf_pmu_alias *alias) +{ + snprintf(buf, len, "%s/%s/", pmu->name, alias->name); + return buf; +} + +static char *format_alias_or(char *buf, int len, struct perf_pmu *pmu, + struct perf_pmu_alias *alias) +{ + snprintf(buf, len, "%s OR %s/%s/", alias->name, pmu->name, alias->name); + return buf; +} + +static int cmp_string(const void *a, const void *b) +{ + const char * const *as = a; + const char * const *bs = b; + return strcmp(*as, *bs); +} + +void print_pmu_events(const char *event_glob, bool name_only) +{ + struct perf_pmu *pmu; + struct perf_pmu_alias *alias; + char buf[1024]; + int printed = 0; + int len, j; + char **aliases; + + pmu = NULL; + len = 0; + while ((pmu = perf_pmu__scan(pmu)) != NULL) + list_for_each_entry(alias, &pmu->aliases, list) + len++; + aliases = malloc(sizeof(char *) * len); + if (!aliases) + return; + pmu = NULL; + j = 0; + while ((pmu = perf_pmu__scan(pmu)) != NULL) + list_for_each_entry(alias, &pmu->aliases, list) { + char *name = format_alias(buf, sizeof(buf), pmu, alias); + bool is_cpu = !strcmp(pmu->name, "cpu"); + + if (event_glob != NULL && + !(strglobmatch(name, event_glob) || + (!is_cpu && strglobmatch(alias->name, + event_glob)))) + continue; + aliases[j] = name; + if (is_cpu && !name_only) + aliases[j] = format_alias_or(buf, sizeof(buf), + pmu, alias); + aliases[j] = strdup(aliases[j]); + j++; + } + len = j; + qsort(aliases, len, sizeof(char *), cmp_string); + for (j = 0; j < len; j++) { + if (name_only) { + printf("%s ", aliases[j]); + continue; + } + printf(" %-50s [Kernel PMU event]\n", aliases[j]); + free(aliases[j]); + printed++; + } + if (printed) + printf("\n"); + free(aliases); +} diff --git a/tools/perf/util/pmu.h b/tools/perf/util/pmu.h index 32fe55b659fa..6b2cbe2d4cc3 100644 --- a/tools/perf/util/pmu.h +++ b/tools/perf/util/pmu.h @@ -3,6 +3,7 @@ #include <linux/bitops.h> #include <linux/perf_event.h> +#include <stdbool.h> enum { PERF_PMU_FORMAT_VALUE_CONFIG, @@ -21,7 +22,7 @@ struct perf_pmu { struct list_head list; }; -struct perf_pmu *perf_pmu__find(char *name); +struct perf_pmu *perf_pmu__find(const char *name); int perf_pmu__config(struct perf_pmu *pmu, struct perf_event_attr *attr, struct list_head *head_terms); int perf_pmu__config_terms(struct list_head *formats, @@ -40,5 +41,7 @@ int perf_pmu__format_parse(char *dir, struct list_head *head); struct perf_pmu *perf_pmu__scan(struct perf_pmu *pmu); +void print_pmu_events(const char *event_glob, bool name_only); + int perf_pmu__test(void); #endif /* __PMU_H */ diff --git a/tools/perf/util/python.c b/tools/perf/util/python.c index 925e0c3e6d91..71b5412bbbb9 100644 --- a/tools/perf/util/python.c +++ b/tools/perf/util/python.c @@ -8,6 +8,26 @@ #include "cpumap.h" #include "thread_map.h" +/* + * Support debug printing even though util/debug.c is not linked. That means + * implementing 'verbose' and 'eprintf'. + */ +int verbose; + +int eprintf(int level, const char *fmt, ...) +{ + va_list args; + int ret = 0; + + if (verbose >= level) { + va_start(args, fmt); + ret = vfprintf(stderr, fmt, args); + va_end(args); + } + + return ret; +} + /* Define PyVarObject_HEAD_INIT for python 2.5 */ #ifndef PyVarObject_HEAD_INIT # define PyVarObject_HEAD_INIT(type, size) PyObject_HEAD_INIT(type) size, @@ -967,6 +987,7 @@ static struct { { "COUNT_SW_PAGE_FAULTS_MAJ", PERF_COUNT_SW_PAGE_FAULTS_MAJ }, { "COUNT_SW_ALIGNMENT_FAULTS", PERF_COUNT_SW_ALIGNMENT_FAULTS }, { "COUNT_SW_EMULATION_FAULTS", PERF_COUNT_SW_EMULATION_FAULTS }, + { "COUNT_SW_DUMMY", PERF_COUNT_SW_DUMMY }, { "SAMPLE_IP", PERF_SAMPLE_IP }, { "SAMPLE_TID", PERF_SAMPLE_TID }, diff --git a/tools/perf/util/record.c b/tools/perf/util/record.c new file mode 100644 index 000000000000..18d73aa2f0f8 --- /dev/null +++ b/tools/perf/util/record.c @@ -0,0 +1,108 @@ +#include "evlist.h" +#include "evsel.h" +#include "cpumap.h" +#include "parse-events.h" + +typedef void (*setup_probe_fn_t)(struct perf_evsel *evsel); + +static int perf_do_probe_api(setup_probe_fn_t fn, int cpu, const char *str) +{ + struct perf_evlist *evlist; + struct perf_evsel *evsel; + int err = -EAGAIN, fd; + + evlist = perf_evlist__new(); + if (!evlist) + return -ENOMEM; + + if (parse_events(evlist, str)) + goto out_delete; + + evsel = perf_evlist__first(evlist); + + fd = sys_perf_event_open(&evsel->attr, -1, cpu, -1, 0); + if (fd < 0) + goto out_delete; + close(fd); + + fn(evsel); + + fd = sys_perf_event_open(&evsel->attr, -1, cpu, -1, 0); + if (fd < 0) { + if (errno == EINVAL) + err = -EINVAL; + goto out_delete; + } + close(fd); + err = 0; + +out_delete: + perf_evlist__delete(evlist); + return err; +} + +static bool perf_probe_api(setup_probe_fn_t fn) +{ + const char *try[] = {"cycles:u", "instructions:u", "cpu-clock", NULL}; + struct cpu_map *cpus; + int cpu, ret, i = 0; + + cpus = cpu_map__new(NULL); + if (!cpus) + return false; + cpu = cpus->map[0]; + cpu_map__delete(cpus); + + do { + ret = perf_do_probe_api(fn, cpu, try[i++]); + if (!ret) + return true; + } while (ret == -EAGAIN && try[i]); + + return false; +} + +static void perf_probe_sample_identifier(struct perf_evsel *evsel) +{ + evsel->attr.sample_type |= PERF_SAMPLE_IDENTIFIER; +} + +bool perf_can_sample_identifier(void) +{ + return perf_probe_api(perf_probe_sample_identifier); +} + +void perf_evlist__config(struct perf_evlist *evlist, + struct perf_record_opts *opts) +{ + struct perf_evsel *evsel; + bool use_sample_identifier = false; + + /* + * Set the evsel leader links before we configure attributes, + * since some might depend on this info. + */ + if (opts->group) + perf_evlist__set_leader(evlist); + + if (evlist->cpus->map[0] < 0) + opts->no_inherit = true; + + list_for_each_entry(evsel, &evlist->entries, node) + perf_evsel__config(evsel, opts); + + if (evlist->nr_entries > 1) { + struct perf_evsel *first = perf_evlist__first(evlist); + + list_for_each_entry(evsel, &evlist->entries, node) { + if (evsel->attr.sample_type == first->attr.sample_type) + continue; + use_sample_identifier = perf_can_sample_identifier(); + break; + } + list_for_each_entry(evsel, &evlist->entries, node) + perf_evsel__set_sample_id(evsel, use_sample_identifier); + } + + perf_evlist__set_id_pos(evlist); +} diff --git a/tools/perf/util/scripting-engines/trace-event-perl.c b/tools/perf/util/scripting-engines/trace-event-perl.c index eacec859f299..a85e4ae5f3ac 100644 --- a/tools/perf/util/scripting-engines/trace-event-perl.c +++ b/tools/perf/util/scripting-engines/trace-event-perl.c @@ -261,7 +261,8 @@ static void perl_process_tracepoint(union perf_event *perf_event __maybe_unused, struct perf_sample *sample, struct perf_evsel *evsel, struct machine *machine __maybe_unused, - struct addr_location *al) + struct thread *thread, + struct addr_location *al) { struct format_field *field; static char handler[256]; @@ -272,7 +273,6 @@ static void perl_process_tracepoint(union perf_event *perf_event __maybe_unused, int cpu = sample->cpu; void *data = sample->raw_data; unsigned long long nsecs = sample->time; - struct thread *thread = al->thread; char *comm = thread->comm; dSP; @@ -351,7 +351,8 @@ static void perl_process_event_generic(union perf_event *event, struct perf_sample *sample, struct perf_evsel *evsel, struct machine *machine __maybe_unused, - struct addr_location *al __maybe_unused) + struct thread *thread __maybe_unused, + struct addr_location *al __maybe_unused) { dSP; @@ -377,10 +378,11 @@ static void perl_process_event(union perf_event *event, struct perf_sample *sample, struct perf_evsel *evsel, struct machine *machine, - struct addr_location *al) + struct thread *thread, + struct addr_location *al) { - perl_process_tracepoint(event, sample, evsel, machine, al); - perl_process_event_generic(event, sample, evsel, machine, al); + perl_process_tracepoint(event, sample, evsel, machine, thread, al); + perl_process_event_generic(event, sample, evsel, machine, thread, al); } static void run_start_sub(void) diff --git a/tools/perf/util/scripting-engines/trace-event-python.c b/tools/perf/util/scripting-engines/trace-event-python.c index e87aa5d9696b..cc75a3cef388 100644 --- a/tools/perf/util/scripting-engines/trace-event-python.c +++ b/tools/perf/util/scripting-engines/trace-event-python.c @@ -225,6 +225,7 @@ static void python_process_tracepoint(union perf_event *perf_event struct perf_sample *sample, struct perf_evsel *evsel, struct machine *machine __maybe_unused, + struct thread *thread, struct addr_location *al) { PyObject *handler, *retval, *context, *t, *obj, *dict = NULL; @@ -238,7 +239,6 @@ static void python_process_tracepoint(union perf_event *perf_event int cpu = sample->cpu; void *data = sample->raw_data; unsigned long long nsecs = sample->time; - struct thread *thread = al->thread; char *comm = thread->comm; t = PyTuple_New(MAX_FIELDS); @@ -345,12 +345,12 @@ static void python_process_general_event(union perf_event *perf_event struct perf_sample *sample, struct perf_evsel *evsel, struct machine *machine __maybe_unused, + struct thread *thread, struct addr_location *al) { PyObject *handler, *retval, *t, *dict; static char handler_name[64]; unsigned n = 0; - struct thread *thread = al->thread; /* * Use the MAX_FIELDS to make the function expandable, though @@ -404,17 +404,18 @@ static void python_process_event(union perf_event *perf_event, struct perf_sample *sample, struct perf_evsel *evsel, struct machine *machine, + struct thread *thread, struct addr_location *al) { switch (evsel->attr.type) { case PERF_TYPE_TRACEPOINT: python_process_tracepoint(perf_event, sample, evsel, - machine, al); + machine, thread, al); break; /* Reserve for future process_hw/sw/raw APIs */ default: python_process_general_event(perf_event, sample, evsel, - machine, al); + machine, thread, al); } } diff --git a/tools/perf/util/session.c b/tools/perf/util/session.c index cf1fe01b7e89..1fc0c628683e 100644 --- a/tools/perf/util/session.c +++ b/tools/perf/util/session.c @@ -1,4 +1,5 @@ #include <linux/kernel.h> +#include <traceevent/event-parse.h> #include <byteswap.h> #include <unistd.h> @@ -12,7 +13,6 @@ #include "sort.h" #include "util.h" #include "cpumap.h" -#include "event-parse.h" #include "perf_regs.h" #include "vdso.h" @@ -24,7 +24,7 @@ static int perf_session__open(struct perf_session *self, bool force) self->fd_pipe = true; self->fd = STDIN_FILENO; - if (perf_session__read_header(self, self->fd) < 0) + if (perf_session__read_header(self) < 0) pr_err("incompatible file format (rerun with -v to learn more)"); return 0; @@ -56,7 +56,7 @@ static int perf_session__open(struct perf_session *self, bool force) goto out_close; } - if (perf_session__read_header(self, self->fd) < 0) { + if (perf_session__read_header(self) < 0) { pr_err("incompatible file format (rerun with -v to learn more)"); goto out_close; } @@ -71,6 +71,11 @@ static int perf_session__open(struct perf_session *self, bool force) goto out_close; } + if (!perf_evlist__valid_read_format(self->evlist)) { + pr_err("non matching read_format"); + goto out_close; + } + self->size = input_stat.st_size; return 0; @@ -193,7 +198,9 @@ void perf_session__delete(struct perf_session *self) vdso__exit(); } -static int process_event_synth_tracing_data_stub(union perf_event *event +static int process_event_synth_tracing_data_stub(struct perf_tool *tool + __maybe_unused, + union perf_event *event __maybe_unused, struct perf_session *session __maybe_unused) @@ -202,7 +209,8 @@ static int process_event_synth_tracing_data_stub(union perf_event *event return 0; } -static int process_event_synth_attr_stub(union perf_event *event __maybe_unused, +static int process_event_synth_attr_stub(struct perf_tool *tool __maybe_unused, + union perf_event *event __maybe_unused, struct perf_evlist **pevlist __maybe_unused) { @@ -238,18 +246,11 @@ static int process_finished_round_stub(struct perf_tool *tool __maybe_unused, return 0; } -static int process_event_type_stub(struct perf_tool *tool __maybe_unused, - union perf_event *event __maybe_unused) -{ - dump_printf(": unhandled!\n"); - return 0; -} - static int process_finished_round(struct perf_tool *tool, union perf_event *event, struct perf_session *session); -static void perf_tool__fill_defaults(struct perf_tool *tool) +void perf_tool__fill_defaults(struct perf_tool *tool) { if (tool->sample == NULL) tool->sample = process_event_sample_stub; @@ -271,8 +272,6 @@ static void perf_tool__fill_defaults(struct perf_tool *tool) tool->unthrottle = process_event_stub; if (tool->attr == NULL) tool->attr = process_event_synth_attr_stub; - if (tool->event_type == NULL) - tool->event_type = process_event_type_stub; if (tool->tracing_data == NULL) tool->tracing_data = process_event_synth_tracing_data_stub; if (tool->build_id == NULL) @@ -496,7 +495,7 @@ static int perf_session_deliver_event(struct perf_session *session, u64 file_offset); static int flush_sample_queue(struct perf_session *s, - struct perf_tool *tool) + struct perf_tool *tool) { struct ordered_samples *os = &s->ordered_samples; struct list_head *head = &os->samples; @@ -644,7 +643,7 @@ static void __queue_event(struct sample_queue *new, struct perf_session *s) #define MAX_SAMPLE_BUFFER (64 * 1024 / sizeof(struct sample_queue)) -static int perf_session_queue_event(struct perf_session *s, union perf_event *event, +int perf_session_queue_event(struct perf_session *s, union perf_event *event, struct perf_sample *sample, u64 file_offset) { struct ordered_samples *os = &s->ordered_samples; @@ -740,7 +739,7 @@ static void perf_session__print_tstamp(struct perf_session *session, union perf_event *event, struct perf_sample *sample) { - u64 sample_type = perf_evlist__sample_type(session->evlist); + u64 sample_type = __perf_evlist__combined_sample_type(session->evlist); if (event->header.type != PERF_RECORD_SAMPLE && !perf_evlist__sample_id_all(session->evlist)) { @@ -755,6 +754,36 @@ static void perf_session__print_tstamp(struct perf_session *session, printf("%" PRIu64 " ", sample->time); } +static void sample_read__printf(struct perf_sample *sample, u64 read_format) +{ + printf("... sample_read:\n"); + + if (read_format & PERF_FORMAT_TOTAL_TIME_ENABLED) + printf("...... time enabled %016" PRIx64 "\n", + sample->read.time_enabled); + + if (read_format & PERF_FORMAT_TOTAL_TIME_RUNNING) + printf("...... time running %016" PRIx64 "\n", + sample->read.time_running); + + if (read_format & PERF_FORMAT_GROUP) { + u64 i; + + printf(".... group nr %" PRIu64 "\n", sample->read.group.nr); + + for (i = 0; i < sample->read.group.nr; i++) { + struct sample_read_value *value; + + value = &sample->read.group.values[i]; + printf("..... id %016" PRIx64 + ", value %016" PRIx64 "\n", + value->id, value->value); + } + } else + printf("..... id %016" PRIx64 ", value %016" PRIx64 "\n", + sample->read.one.id, sample->read.one.value); +} + static void dump_event(struct perf_session *session, union perf_event *event, u64 file_offset, struct perf_sample *sample) { @@ -804,11 +833,15 @@ static void dump_sample(struct perf_evsel *evsel, union perf_event *event, if (sample_type & PERF_SAMPLE_DATA_SRC) printf(" . data_src: 0x%"PRIx64"\n", sample->data_src); + + if (sample_type & PERF_SAMPLE_READ) + sample_read__printf(sample, evsel->attr.read_format); } static struct machine * perf_session__find_machine_for_cpumode(struct perf_session *session, - union perf_event *event) + union perf_event *event, + struct perf_sample *sample) { const u8 cpumode = event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK; @@ -820,7 +853,7 @@ static struct machine * if (event->header.type == PERF_RECORD_MMAP) pid = event->mmap.pid; else - pid = event->ip.pid; + pid = sample->pid; return perf_session__findnew_machine(session, pid); } @@ -828,6 +861,75 @@ static struct machine * return &session->machines.host; } +static int deliver_sample_value(struct perf_session *session, + struct perf_tool *tool, + union perf_event *event, + struct perf_sample *sample, + struct sample_read_value *v, + struct machine *machine) +{ + struct perf_sample_id *sid; + + sid = perf_evlist__id2sid(session->evlist, v->id); + if (sid) { + sample->id = v->id; + sample->period = v->value - sid->period; + sid->period = v->value; + } + + if (!sid || sid->evsel == NULL) { + ++session->stats.nr_unknown_id; + return 0; + } + + return tool->sample(tool, event, sample, sid->evsel, machine); +} + +static int deliver_sample_group(struct perf_session *session, + struct perf_tool *tool, + union perf_event *event, + struct perf_sample *sample, + struct machine *machine) +{ + int ret = -EINVAL; + u64 i; + + for (i = 0; i < sample->read.group.nr; i++) { + ret = deliver_sample_value(session, tool, event, sample, + &sample->read.group.values[i], + machine); + if (ret) + break; + } + + return ret; +} + +static int +perf_session__deliver_sample(struct perf_session *session, + struct perf_tool *tool, + union perf_event *event, + struct perf_sample *sample, + struct perf_evsel *evsel, + struct machine *machine) +{ + /* We know evsel != NULL. */ + u64 sample_type = evsel->attr.sample_type; + u64 read_format = evsel->attr.read_format; + + /* Standard sample delievery. */ + if (!(sample_type & PERF_SAMPLE_READ)) + return tool->sample(tool, event, sample, evsel, machine); + + /* For PERF_SAMPLE_READ we have either single or group mode. */ + if (read_format & PERF_FORMAT_GROUP) + return deliver_sample_group(session, tool, event, sample, + machine); + else + return deliver_sample_value(session, tool, event, sample, + &sample->read.one, machine); +} + static int perf_session_deliver_event(struct perf_session *session, union perf_event *event, struct perf_sample *sample, @@ -857,7 +959,8 @@ static int perf_session_deliver_event(struct perf_session *session, hists__inc_nr_events(&evsel->hists, event->header.type); } - machine = perf_session__find_machine_for_cpumode(session, event); + machine = perf_session__find_machine_for_cpumode(session, event, + sample); switch (event->header.type) { case PERF_RECORD_SAMPLE: @@ -870,7 +973,8 @@ static int perf_session_deliver_event(struct perf_session *session, ++session->stats.nr_unprocessable_samples; return 0; } - return tool->sample(tool, event, sample, evsel, machine); + return perf_session__deliver_sample(session, tool, event, + sample, evsel, machine); case PERF_RECORD_MMAP: return tool->mmap(tool, event, sample, machine); case PERF_RECORD_COMM: @@ -895,22 +999,6 @@ static int perf_session_deliver_event(struct perf_session *session, } } -static int perf_session__preprocess_sample(struct perf_session *session, - union perf_event *event, struct perf_sample *sample) -{ - if (event->header.type != PERF_RECORD_SAMPLE || - !(perf_evlist__sample_type(session->evlist) & PERF_SAMPLE_CALLCHAIN)) - return 0; - - if (!ip_callchain__valid(sample->callchain, event)) { - pr_debug("call-chain problem with event, skipping it.\n"); - ++session->stats.nr_invalid_chains; - session->stats.total_invalid_chains += sample->period; - return -EINVAL; - } - return 0; -} - static int perf_session__process_user_event(struct perf_session *session, union perf_event *event, struct perf_tool *tool, u64 file_offset) { @@ -921,16 +1009,14 @@ static int perf_session__process_user_event(struct perf_session *session, union /* These events are processed right away */ switch (event->header.type) { case PERF_RECORD_HEADER_ATTR: - err = tool->attr(event, &session->evlist); + err = tool->attr(tool, event, &session->evlist); if (err == 0) perf_session__set_id_hdr_size(session); return err; - case PERF_RECORD_HEADER_EVENT_TYPE: - return tool->event_type(tool, event); case PERF_RECORD_HEADER_TRACING_DATA: /* setup for reading amidst mmap */ lseek(session->fd, file_offset, SEEK_SET); - return tool->tracing_data(event, session); + return tool->tracing_data(tool, event, session); case PERF_RECORD_HEADER_BUILD_ID: return tool->build_id(tool, event, session); case PERF_RECORD_FINISHED_ROUND: @@ -975,10 +1061,6 @@ static int perf_session__process_event(struct perf_session *session, if (ret) return ret; - /* Preprocess sample records - precheck callchains */ - if (perf_session__preprocess_sample(session, event, &sample)) - return 0; - if (tool->ordered_samples) { ret = perf_session_queue_event(session, event, &sample, file_offset); @@ -999,7 +1081,7 @@ void perf_event_header__bswap(struct perf_event_header *self) struct thread *perf_session__findnew(struct perf_session *session, pid_t pid) { - return machine__findnew_thread(&session->machines.host, pid); + return machine__findnew_thread(&session->machines.host, 0, pid); } static struct thread *perf_session__register_idle_thread(struct perf_session *self) @@ -1091,8 +1173,10 @@ more: perf_event_header__bswap(&event->header); size = event->header.size; - if (size == 0) - size = 8; + if (size < sizeof(struct perf_event_header)) { + pr_err("bad event header size\n"); + goto out_err; + } if (size > cur_size) { void *new = realloc(buf, size); @@ -1161,8 +1245,12 @@ fetch_mmaped_event(struct perf_session *session, if (session->header.needs_swap) perf_event_header__bswap(&event->header); - if (head + event->header.size > mmap_size) + if (head + event->header.size > mmap_size) { + /* We're not fetching the event so swap back again */ + if (session->header.needs_swap) + perf_event_header__bswap(&event->header); return NULL; + } return event; } @@ -1242,7 +1330,7 @@ more: size = event->header.size; - if (size == 0 || + if (size < sizeof(struct perf_event_header) || perf_session__process_event(session, event, tool, file_pos) < 0) { pr_err("%#" PRIx64 " [%#x]: failed to process type: %d\n", file_offset + head, event->header.size, @@ -1295,12 +1383,15 @@ int perf_session__process_events(struct perf_session *self, bool perf_session__has_traces(struct perf_session *session, const char *msg) { - if (!(perf_evlist__sample_type(session->evlist) & PERF_SAMPLE_RAW)) { - pr_err("No trace sample to read. Did you call 'perf %s'?\n", msg); - return false; + struct perf_evsel *evsel; + + list_for_each_entry(evsel, &session->evlist->entries, node) { + if (evsel->attr.type == PERF_TYPE_TRACEPOINT) + return true; } - return true; + pr_err("No trace sample to read. Did you call 'perf %s'?\n", msg); + return false; } int maps__set_kallsyms_ref_reloc_sym(struct map **maps, @@ -1383,13 +1474,18 @@ struct perf_evsel *perf_session__find_first_evtype(struct perf_session *session, void perf_evsel__print_ip(struct perf_evsel *evsel, union perf_event *event, struct perf_sample *sample, struct machine *machine, - int print_sym, int print_dso, int print_symoffset) + unsigned int print_opts, unsigned int stack_depth) { struct addr_location al; struct callchain_cursor_node *node; - - if (perf_event__preprocess_sample(event, machine, &al, sample, - NULL) < 0) { + int print_ip = print_opts & PRINT_IP_OPT_IP; + int print_sym = print_opts & PRINT_IP_OPT_SYM; + int print_dso = print_opts & PRINT_IP_OPT_DSO; + int print_symoffset = print_opts & PRINT_IP_OPT_SYMOFFSET; + int print_oneline = print_opts & PRINT_IP_OPT_ONELINE; + char s = print_oneline ? ' ' : '\t'; + + if (perf_event__preprocess_sample(event, machine, &al, sample) < 0) { error("problem processing %d event, skipping it.\n", event->header.type); return; @@ -1397,37 +1493,50 @@ void perf_evsel__print_ip(struct perf_evsel *evsel, union perf_event *event, if (symbol_conf.use_callchain && sample->callchain) { - if (machine__resolve_callchain(machine, evsel, al.thread, - sample, NULL) != 0) { + sample, NULL, NULL) != 0) { if (verbose) error("Failed to resolve callchain. Skipping\n"); return; } callchain_cursor_commit(&callchain_cursor); - while (1) { + while (stack_depth) { node = callchain_cursor_current(&callchain_cursor); if (!node) break; - printf("\t%16" PRIx64, node->ip); + if (print_ip) + printf("%c%16" PRIx64, s, node->ip); + if (print_sym) { printf(" "); - symbol__fprintf_symname(node->sym, stdout); + if (print_symoffset) { + al.addr = node->ip; + al.map = node->map; + symbol__fprintf_symname_offs(node->sym, &al, stdout); + } else + symbol__fprintf_symname(node->sym, stdout); } + if (print_dso) { printf(" ("); map__fprintf_dsoname(node->map, stdout); printf(")"); } - printf("\n"); + + if (!print_oneline) + printf("\n"); callchain_cursor_advance(&callchain_cursor); + + stack_depth--; } } else { - printf("%16" PRIx64, sample->ip); + if (print_ip) + printf("%16" PRIx64, sample->ip); + if (print_sym) { printf(" "); if (print_symoffset) diff --git a/tools/perf/util/session.h b/tools/perf/util/session.h index f3b235ec7bf4..3aa75fb2225f 100644 --- a/tools/perf/util/session.h +++ b/tools/perf/util/session.h @@ -37,11 +37,16 @@ struct perf_session { int fd; bool fd_pipe; bool repipe; - char *cwd; struct ordered_samples ordered_samples; char filename[1]; }; +#define PRINT_IP_OPT_IP (1<<0) +#define PRINT_IP_OPT_SYM (1<<1) +#define PRINT_IP_OPT_DSO (1<<2) +#define PRINT_IP_OPT_SYMOFFSET (1<<3) +#define PRINT_IP_OPT_ONELINE (1<<4) + struct perf_tool; struct perf_session *perf_session__new(const char *filename, int mode, @@ -57,6 +62,11 @@ int __perf_session__process_events(struct perf_session *self, int perf_session__process_events(struct perf_session *self, struct perf_tool *tool); +int perf_session_queue_event(struct perf_session *s, union perf_event *event, + struct perf_sample *sample, u64 file_offset); + +void perf_tool__fill_defaults(struct perf_tool *tool); + int perf_session__resolve_callchain(struct perf_session *self, struct perf_evsel *evsel, struct thread *thread, struct ip_callchain *chain, @@ -99,7 +109,7 @@ struct perf_evsel *perf_session__find_first_evtype(struct perf_session *session, void perf_evsel__print_ip(struct perf_evsel *evsel, union perf_event *event, struct perf_sample *sample, struct machine *machine, - int print_sym, int print_dso, int print_symoffset); + unsigned int print_opts, unsigned int stack_depth); int perf_session__cpu_bitmap(struct perf_session *session, const char *cpu_list, unsigned long *cpu_bitmap); diff --git a/tools/perf/util/sort.c b/tools/perf/util/sort.c index 313a5a730112..5f118a089519 100644 --- a/tools/perf/util/sort.c +++ b/tools/perf/util/sort.c @@ -7,6 +7,8 @@ const char default_parent_pattern[] = "^sys_|^do_page_fault"; const char *parent_pattern = default_parent_pattern; const char default_sort_order[] = "comm,dso,symbol"; const char *sort_order = default_sort_order; +regex_t ignore_callees_regex; +int have_ignore_callees = 0; int sort__need_collapse = 0; int sort__has_parent = 0; int sort__has_sym = 0; @@ -55,14 +57,14 @@ static int64_t cmp_null(void *l, void *r) static int64_t sort__thread_cmp(struct hist_entry *left, struct hist_entry *right) { - return right->thread->pid - left->thread->pid; + return right->thread->tid - left->thread->tid; } static int hist_entry__thread_snprintf(struct hist_entry *self, char *bf, size_t size, unsigned int width) { return repsep_snprintf(bf, size, "%*s:%5d", width - 6, - self->thread->comm ?: "", self->thread->pid); + self->thread->comm ?: "", self->thread->tid); } struct sort_entry sort_thread = { @@ -77,7 +79,7 @@ struct sort_entry sort_thread = { static int64_t sort__comm_cmp(struct hist_entry *left, struct hist_entry *right) { - return right->thread->pid - left->thread->pid; + return right->thread->tid - left->thread->tid; } static int64_t @@ -872,6 +874,8 @@ static struct sort_dimension common_sort_dimensions[] = { DIM(SORT_PARENT, "parent", sort_parent), DIM(SORT_CPU, "cpu", sort_cpu), DIM(SORT_SRCLINE, "srcline", sort_srcline), + DIM(SORT_LOCAL_WEIGHT, "local_weight", sort_local_weight), + DIM(SORT_GLOBAL_WEIGHT, "weight", sort_global_weight), }; #undef DIM @@ -891,8 +895,6 @@ static struct sort_dimension bstack_sort_dimensions[] = { #define DIM(d, n, func) [d - __SORT_MEMORY_MODE] = { .name = n, .entry = &(func) } static struct sort_dimension memory_sort_dimensions[] = { - DIM(SORT_LOCAL_WEIGHT, "local_weight", sort_local_weight), - DIM(SORT_GLOBAL_WEIGHT, "weight", sort_global_weight), DIM(SORT_MEM_DADDR_SYMBOL, "symbol_daddr", sort_mem_daddr_sym), DIM(SORT_MEM_DADDR_DSO, "dso_daddr", sort_mem_daddr_dso), DIM(SORT_MEM_LOCKED, "locked", sort_mem_locked), diff --git a/tools/perf/util/sort.h b/tools/perf/util/sort.h index 45ac84c1e037..4e80dbd271e7 100644 --- a/tools/perf/util/sort.h +++ b/tools/perf/util/sort.h @@ -29,6 +29,8 @@ extern const char *sort_order; extern const char default_parent_pattern[]; extern const char *parent_pattern; extern const char default_sort_order[]; +extern regex_t ignore_callees_regex; +extern int have_ignore_callees; extern int sort__need_collapse; extern int sort__has_parent; extern int sort__has_sym; @@ -87,6 +89,9 @@ struct hist_entry { struct hist_entry_diff diff; + /* We are added by hists__add_dummy_entry. */ + bool dummy; + /* XXX These two should move to some tree widget lib */ u16 row_offset; u16 nr_rows; @@ -138,6 +143,8 @@ enum sort_type { SORT_PARENT, SORT_CPU, SORT_SRCLINE, + SORT_LOCAL_WEIGHT, + SORT_GLOBAL_WEIGHT, /* branch stack specific sort keys */ __SORT_BRANCH_STACK, @@ -149,9 +156,7 @@ enum sort_type { /* memory mode specific sort keys */ __SORT_MEMORY_MODE, - SORT_LOCAL_WEIGHT = __SORT_MEMORY_MODE, - SORT_GLOBAL_WEIGHT, - SORT_MEM_DADDR_SYMBOL, + SORT_MEM_DADDR_SYMBOL = __SORT_MEMORY_MODE, SORT_MEM_DADDR_DSO, SORT_MEM_LOCKED, SORT_MEM_TLB, @@ -183,4 +188,6 @@ int setup_sorting(void); extern int sort_dimension__add(const char *); void sort__setup_elide(FILE *fp); +int report_parse_ignore_callees_opt(const struct option *opt, const char *arg, int unset); + #endif /* __PERF_SORT_H */ diff --git a/tools/perf/util/stat.c b/tools/perf/util/stat.c index 7c59c28afcc5..6506b3dfb605 100644 --- a/tools/perf/util/stat.c +++ b/tools/perf/util/stat.c @@ -10,6 +10,12 @@ void update_stats(struct stats *stats, u64 val) delta = val - stats->mean; stats->mean += delta / stats->n; stats->M2 += delta*(val - stats->mean); + + if (val > stats->max) + stats->max = val; + + if (val < stats->min) + stats->min = val; } double avg_stats(struct stats *stats) diff --git a/tools/perf/util/stat.h b/tools/perf/util/stat.h index 588367c3c767..ae8ccd7227cf 100644 --- a/tools/perf/util/stat.h +++ b/tools/perf/util/stat.h @@ -6,6 +6,7 @@ struct stats { double n, mean, M2; + u64 max, min; }; void update_stats(struct stats *stats, u64 val); @@ -13,4 +14,12 @@ double avg_stats(struct stats *stats); double stddev_stats(struct stats *stats); double rel_stddev_stats(double stddev, double avg); +static inline void init_stats(struct stats *stats) +{ + stats->n = 0.0; + stats->mean = 0.0; + stats->M2 = 0.0; + stats->min = (u64) -1; + stats->max = 0; +} #endif diff --git a/tools/perf/util/string.c b/tools/perf/util/string.c index 29c7b2cb2521..f0b0c008c507 100644 --- a/tools/perf/util/string.c +++ b/tools/perf/util/string.c @@ -387,3 +387,27 @@ void *memdup(const void *src, size_t len) return p; } + +/** + * str_append - reallocate string and append another + * @s: pointer to string pointer + * @len: pointer to len (initialized) + * @a: string to append. + */ +int str_append(char **s, int *len, const char *a) +{ + int olen = *s ? strlen(*s) : 0; + int nlen = olen + strlen(a) + 1; + if (*len < nlen) { + *len = *len * 2; + if (*len < nlen) + *len = nlen; + *s = realloc(*s, *len); + if (!*s) + return -ENOMEM; + if (olen == 0) + **s = 0; + } + strcat(*s, a); + return 0; +} diff --git a/tools/perf/util/symbol-elf.c b/tools/perf/util/symbol-elf.c index 4b12bf850325..a7b9ab557380 100644 --- a/tools/perf/util/symbol-elf.c +++ b/tools/perf/util/symbol-elf.c @@ -599,11 +599,13 @@ int symsrc__init(struct symsrc *ss, struct dso *dso, const char *name, if (dso->kernel == DSO_TYPE_USER) { GElf_Shdr shdr; ss->adjust_symbols = (ehdr.e_type == ET_EXEC || + ehdr.e_type == ET_REL || elf_section_by_name(elf, &ehdr, &shdr, ".gnu.prelink_undo", NULL) != NULL); } else { - ss->adjust_symbols = 0; + ss->adjust_symbols = ehdr.e_type == ET_EXEC || + ehdr.e_type == ET_REL; } ss->name = strdup(name); @@ -624,6 +626,37 @@ out_close: return err; } +/** + * ref_reloc_sym_not_found - has kernel relocation symbol been found. + * @kmap: kernel maps and relocation reference symbol + * + * This function returns %true if we are dealing with the kernel maps and the + * relocation reference symbol has not yet been found. Otherwise %false is + * returned. + */ +static bool ref_reloc_sym_not_found(struct kmap *kmap) +{ + return kmap && kmap->ref_reloc_sym && kmap->ref_reloc_sym->name && + !kmap->ref_reloc_sym->unrelocated_addr; +} + +/** + * ref_reloc - kernel relocation offset. + * @kmap: kernel maps and relocation reference symbol + * + * This function returns the offset of kernel addresses as determined by using + * the relocation reference symbol i.e. if the kernel has not been relocated + * then the return value is zero. + */ +static u64 ref_reloc(struct kmap *kmap) +{ + if (kmap && kmap->ref_reloc_sym && + kmap->ref_reloc_sym->unrelocated_addr) + return kmap->ref_reloc_sym->addr - + kmap->ref_reloc_sym->unrelocated_addr; + return 0; +} + int dso__load_sym(struct dso *dso, struct map *map, struct symsrc *syms_ss, struct symsrc *runtime_ss, symbol_filter_t filter, int kmodule) @@ -642,8 +675,17 @@ int dso__load_sym(struct dso *dso, struct map *map, Elf_Scn *sec, *sec_strndx; Elf *elf; int nr = 0; + bool remap_kernel = false, adjust_kernel_syms = false; dso->symtab_type = syms_ss->type; + dso->rel = syms_ss->ehdr.e_type == ET_REL; + + /* + * Modules may already have symbols from kallsyms, but those symbols + * have the wrong values for the dso maps, so remove them. + */ + if (kmodule && syms_ss->symtab) + symbols__delete(&dso->symbols[map->type]); if (!syms_ss->symtab) { syms_ss->symtab = syms_ss->dynsym; @@ -681,7 +723,31 @@ int dso__load_sym(struct dso *dso, struct map *map, nr_syms = shdr.sh_size / shdr.sh_entsize; memset(&sym, 0, sizeof(sym)); - dso->adjust_symbols = runtime_ss->adjust_symbols; + + /* + * The kernel relocation symbol is needed in advance in order to adjust + * kernel maps correctly. + */ + if (ref_reloc_sym_not_found(kmap)) { + elf_symtab__for_each_symbol(syms, nr_syms, idx, sym) { + const char *elf_name = elf_sym__name(&sym, symstrs); + + if (strcmp(elf_name, kmap->ref_reloc_sym->name)) + continue; + kmap->ref_reloc_sym->unrelocated_addr = sym.st_value; + break; + } + } + + dso->adjust_symbols = runtime_ss->adjust_symbols || ref_reloc(kmap); + /* + * Initial kernel and module mappings do not map to the dso. For + * function mappings, flag the fixups. + */ + if (map->type == MAP__FUNCTION && (dso->kernel || kmodule)) { + remap_kernel = true; + adjust_kernel_syms = dso->adjust_symbols; + } elf_symtab__for_each_symbol(syms, nr_syms, idx, sym) { struct symbol *f; const char *elf_name = elf_sym__name(&sym, symstrs); @@ -690,10 +756,6 @@ int dso__load_sym(struct dso *dso, struct map *map, const char *section_name; bool used_opd = false; - if (kmap && kmap->ref_reloc_sym && kmap->ref_reloc_sym->name && - strcmp(elf_name, kmap->ref_reloc_sym->name) == 0) - kmap->ref_reloc_sym->unrelocated_addr = sym.st_value; - if (!is_label && !elf_sym__is_a(&sym, map->type)) continue; @@ -745,20 +807,55 @@ int dso__load_sym(struct dso *dso, struct map *map, (sym.st_value & 1)) --sym.st_value; - if (dso->kernel != DSO_TYPE_USER || kmodule) { + if (dso->kernel || kmodule) { char dso_name[PATH_MAX]; + /* Adjust symbol to map to file offset */ + if (adjust_kernel_syms) + sym.st_value -= shdr.sh_addr - shdr.sh_offset; + if (strcmp(section_name, (curr_dso->short_name + dso->short_name_len)) == 0) goto new_symbol; if (strcmp(section_name, ".text") == 0) { + /* + * The initial kernel mapping is based on + * kallsyms and identity maps. Overwrite it to + * map to the kernel dso. + */ + if (remap_kernel && dso->kernel) { + remap_kernel = false; + map->start = shdr.sh_addr + + ref_reloc(kmap); + map->end = map->start + shdr.sh_size; + map->pgoff = shdr.sh_offset; + map->map_ip = map__map_ip; + map->unmap_ip = map__unmap_ip; + /* Ensure maps are correctly ordered */ + map_groups__remove(kmap->kmaps, map); + map_groups__insert(kmap->kmaps, map); + } + + /* + * The initial module mapping is based on + * /proc/modules mapped to offset zero. + * Overwrite it to map to the module dso. + */ + if (remap_kernel && kmodule) { + remap_kernel = false; + map->pgoff = shdr.sh_offset; + } + curr_map = map; curr_dso = dso; goto new_symbol; } + if (!kmap) + goto new_symbol; + snprintf(dso_name, sizeof(dso_name), "%s%s", dso->short_name, section_name); @@ -781,8 +878,16 @@ int dso__load_sym(struct dso *dso, struct map *map, dso__delete(curr_dso); goto out_elf_end; } - curr_map->map_ip = identity__map_ip; - curr_map->unmap_ip = identity__map_ip; + if (adjust_kernel_syms) { + curr_map->start = shdr.sh_addr + + ref_reloc(kmap); + curr_map->end = curr_map->start + + shdr.sh_size; + curr_map->pgoff = shdr.sh_offset; + } else { + curr_map->map_ip = identity__map_ip; + curr_map->unmap_ip = identity__map_ip; + } curr_dso->symtab_type = dso->symtab_type; map_groups__insert(kmap->kmaps, curr_map); dsos__add(&dso->node, curr_dso); @@ -846,6 +951,57 @@ out_elf_end: return err; } +static int elf_read_maps(Elf *elf, bool exe, mapfn_t mapfn, void *data) +{ + GElf_Phdr phdr; + size_t i, phdrnum; + int err; + u64 sz; + + if (elf_getphdrnum(elf, &phdrnum)) + return -1; + + for (i = 0; i < phdrnum; i++) { + if (gelf_getphdr(elf, i, &phdr) == NULL) + return -1; + if (phdr.p_type != PT_LOAD) + continue; + if (exe) { + if (!(phdr.p_flags & PF_X)) + continue; + } else { + if (!(phdr.p_flags & PF_R)) + continue; + } + sz = min(phdr.p_memsz, phdr.p_filesz); + if (!sz) + continue; + err = mapfn(phdr.p_vaddr, sz, phdr.p_offset, data); + if (err) + return err; + } + return 0; +} + +int file__read_maps(int fd, bool exe, mapfn_t mapfn, void *data, + bool *is_64_bit) +{ + int err; + Elf *elf; + + elf = elf_begin(fd, PERF_ELF_C_READ_MMAP, NULL); + if (elf == NULL) + return -1; + + if (is_64_bit) + *is_64_bit = (gelf_getclass(elf) == ELFCLASS64); + + err = elf_read_maps(elf, exe, mapfn, data); + + elf_end(elf); + return err; +} + void symbol__elf_init(void) { elf_version(EV_CURRENT); diff --git a/tools/perf/util/symbol-minimal.c b/tools/perf/util/symbol-minimal.c index a7390cde63bc..3a802c300fc5 100644 --- a/tools/perf/util/symbol-minimal.c +++ b/tools/perf/util/symbol-minimal.c @@ -301,6 +301,13 @@ int dso__load_sym(struct dso *dso, struct map *map __maybe_unused, return 0; } +int file__read_maps(int fd __maybe_unused, bool exe __maybe_unused, + mapfn_t mapfn __maybe_unused, void *data __maybe_unused, + bool *is_64_bit __maybe_unused) +{ + return -1; +} + void symbol__elf_init(void) { } diff --git a/tools/perf/util/symbol.c b/tools/perf/util/symbol.c index d5528e1cc03a..7eb0362f4ffd 100644 --- a/tools/perf/util/symbol.c +++ b/tools/perf/util/symbol.c @@ -87,6 +87,7 @@ static int choose_best_symbol(struct symbol *syma, struct symbol *symb) { s64 a; s64 b; + size_t na, nb; /* Prefer a symbol with non zero length */ a = syma->end - syma->start; @@ -120,11 +121,21 @@ static int choose_best_symbol(struct symbol *syma, struct symbol *symb) else if (a > b) return SYMBOL_B; - /* If all else fails, choose the symbol with the longest name */ - if (strlen(syma->name) >= strlen(symb->name)) + /* Choose the symbol with the longest name */ + na = strlen(syma->name); + nb = strlen(symb->name); + if (na > nb) return SYMBOL_A; - else + else if (na < nb) + return SYMBOL_B; + + /* Avoid "SyS" kernel syscall aliases */ + if (na >= 3 && !strncmp(syma->name, "SyS", 3)) + return SYMBOL_B; + if (na >= 10 && !strncmp(syma->name, "compat_SyS", 10)) return SYMBOL_B; + + return SYMBOL_A; } void symbols__fixup_duplicate(struct rb_root *symbols) @@ -248,7 +259,10 @@ size_t symbol__fprintf_symname_offs(const struct symbol *sym, if (sym && sym->name) { length = fprintf(fp, "%s", sym->name); if (al) { - offset = al->addr - sym->start; + if (al->addr < sym->end) + offset = al->addr - sym->start; + else + offset = al->addr - al->map->start - sym->start; length += fprintf(fp, "+0x%lx", offset); } return length; @@ -316,6 +330,16 @@ static struct symbol *symbols__find(struct rb_root *symbols, u64 ip) return NULL; } +static struct symbol *symbols__first(struct rb_root *symbols) +{ + struct rb_node *n = rb_first(symbols); + + if (n) + return rb_entry(n, struct symbol, rb_node); + + return NULL; +} + struct symbol_name_rb_node { struct rb_node rb_node; struct symbol sym; @@ -386,6 +410,11 @@ struct symbol *dso__find_symbol(struct dso *dso, return symbols__find(&dso->symbols[type], addr); } +struct symbol *dso__first_symbol(struct dso *dso, enum map_type type) +{ + return symbols__first(&dso->symbols[type]); +} + struct symbol *dso__find_symbol_by_name(struct dso *dso, enum map_type type, const char *name) { @@ -522,6 +551,53 @@ static int dso__load_all_kallsyms(struct dso *dso, const char *filename, return kallsyms__parse(filename, &args, map__process_kallsym_symbol); } +static int dso__split_kallsyms_for_kcore(struct dso *dso, struct map *map, + symbol_filter_t filter) +{ + struct map_groups *kmaps = map__kmap(map)->kmaps; + struct map *curr_map; + struct symbol *pos; + int count = 0, moved = 0; + struct rb_root *root = &dso->symbols[map->type]; + struct rb_node *next = rb_first(root); + + while (next) { + char *module; + + pos = rb_entry(next, struct symbol, rb_node); + next = rb_next(&pos->rb_node); + + module = strchr(pos->name, '\t'); + if (module) + *module = '\0'; + + curr_map = map_groups__find(kmaps, map->type, pos->start); + + if (!curr_map || (filter && filter(curr_map, pos))) { + rb_erase(&pos->rb_node, root); + symbol__delete(pos); + } else { + pos->start -= curr_map->start - curr_map->pgoff; + if (pos->end) + pos->end -= curr_map->start - curr_map->pgoff; + if (curr_map != map) { + rb_erase(&pos->rb_node, root); + symbols__insert( + &curr_map->dso->symbols[curr_map->type], + pos); + ++moved; + } else { + ++count; + } + } + } + + /* Symbols have been adjusted */ + dso->adjust_symbols = 1; + + return count + moved; +} + /* * Split the symbols into maps, making sure there are no overlaps, i.e. the * kernel range is broken in several maps, named [kernel].N, as we don't have @@ -663,6 +739,161 @@ bool symbol__restricted_filename(const char *filename, return restricted; } +struct kcore_mapfn_data { + struct dso *dso; + enum map_type type; + struct list_head maps; +}; + +static int kcore_mapfn(u64 start, u64 len, u64 pgoff, void *data) +{ + struct kcore_mapfn_data *md = data; + struct map *map; + + map = map__new2(start, md->dso, md->type); + if (map == NULL) + return -ENOMEM; + + map->end = map->start + len; + map->pgoff = pgoff; + + list_add(&map->node, &md->maps); + + return 0; +} + +/* + * If kallsyms is referenced by name then we look for kcore in the same + * directory. + */ +static bool kcore_filename_from_kallsyms_filename(char *kcore_filename, + const char *kallsyms_filename) +{ + char *name; + + strcpy(kcore_filename, kallsyms_filename); + name = strrchr(kcore_filename, '/'); + if (!name) + return false; + + if (!strcmp(name, "/kallsyms")) { + strcpy(name, "/kcore"); + return true; + } + + return false; +} + +static int dso__load_kcore(struct dso *dso, struct map *map, + const char *kallsyms_filename) +{ + struct map_groups *kmaps = map__kmap(map)->kmaps; + struct machine *machine = kmaps->machine; + struct kcore_mapfn_data md; + struct map *old_map, *new_map, *replacement_map = NULL; + bool is_64_bit; + int err, fd; + char kcore_filename[PATH_MAX]; + struct symbol *sym; + + /* This function requires that the map is the kernel map */ + if (map != machine->vmlinux_maps[map->type]) + return -EINVAL; + + if (!kcore_filename_from_kallsyms_filename(kcore_filename, + kallsyms_filename)) + return -EINVAL; + + md.dso = dso; + md.type = map->type; + INIT_LIST_HEAD(&md.maps); + + fd = open(kcore_filename, O_RDONLY); + if (fd < 0) + return -EINVAL; + + /* Read new maps into temporary lists */ + err = file__read_maps(fd, md.type == MAP__FUNCTION, kcore_mapfn, &md, + &is_64_bit); + if (err) + goto out_err; + + if (list_empty(&md.maps)) { + err = -EINVAL; + goto out_err; + } + + /* Remove old maps */ + old_map = map_groups__first(kmaps, map->type); + while (old_map) { + struct map *next = map_groups__next(old_map); + + if (old_map != map) + map_groups__remove(kmaps, old_map); + old_map = next; + } + + /* Find the kernel map using the first symbol */ + sym = dso__first_symbol(dso, map->type); + list_for_each_entry(new_map, &md.maps, node) { + if (sym && sym->start >= new_map->start && + sym->start < new_map->end) { + replacement_map = new_map; + break; + } + } + + if (!replacement_map) + replacement_map = list_entry(md.maps.next, struct map, node); + + /* Add new maps */ + while (!list_empty(&md.maps)) { + new_map = list_entry(md.maps.next, struct map, node); + list_del(&new_map->node); + if (new_map == replacement_map) { + map->start = new_map->start; + map->end = new_map->end; + map->pgoff = new_map->pgoff; + map->map_ip = new_map->map_ip; + map->unmap_ip = new_map->unmap_ip; + map__delete(new_map); + /* Ensure maps are correctly ordered */ + map_groups__remove(kmaps, map); + map_groups__insert(kmaps, map); + } else { + map_groups__insert(kmaps, new_map); + } + } + + /* + * Set the data type and long name so that kcore can be read via + * dso__data_read_addr(). + */ + if (dso->kernel == DSO_TYPE_GUEST_KERNEL) + dso->data_type = DSO_BINARY_TYPE__GUEST_KCORE; + else + dso->data_type = DSO_BINARY_TYPE__KCORE; + dso__set_long_name(dso, strdup(kcore_filename)); + + close(fd); + + if (map->type == MAP__FUNCTION) + pr_debug("Using %s for kernel object code\n", kcore_filename); + else + pr_debug("Using %s for kernel data\n", kcore_filename); + + return 0; + +out_err: + while (!list_empty(&md.maps)) { + map = list_entry(md.maps.next, struct map, node); + list_del(&map->node); + map__delete(map); + } + close(fd); + return -EINVAL; +} + int dso__load_kallsyms(struct dso *dso, const char *filename, struct map *map, symbol_filter_t filter) { @@ -680,7 +911,10 @@ int dso__load_kallsyms(struct dso *dso, const char *filename, else dso->symtab_type = DSO_BINARY_TYPE__KALLSYMS; - return dso__split_kallsyms(dso, map, filter); + if (!dso__load_kcore(dso, map, filename)) + return dso__split_kallsyms_for_kcore(dso, map, filter); + else + return dso__split_kallsyms(dso, map, filter); } static int dso__load_perf_map(struct dso *dso, struct map *map, @@ -843,10 +1077,15 @@ int dso__load(struct dso *dso, struct map *map, symbol_filter_t filter) if (!runtime_ss && syms_ss) runtime_ss = syms_ss; - if (syms_ss) - ret = dso__load_sym(dso, map, syms_ss, runtime_ss, filter, 0); - else + if (syms_ss) { + int km; + + km = dso->symtab_type == DSO_BINARY_TYPE__SYSTEM_PATH_KMODULE || + dso->symtab_type == DSO_BINARY_TYPE__GUEST_KMODULE; + ret = dso__load_sym(dso, map, syms_ss, runtime_ss, filter, km); + } else { ret = -1; + } if (ret > 0) { int nr_plt; @@ -888,8 +1127,11 @@ int dso__load_vmlinux(struct dso *dso, struct map *map, char symfs_vmlinux[PATH_MAX]; enum dso_binary_type symtab_type; - snprintf(symfs_vmlinux, sizeof(symfs_vmlinux), "%s%s", - symbol_conf.symfs, vmlinux); + if (vmlinux[0] == '/') + snprintf(symfs_vmlinux, sizeof(symfs_vmlinux), "%s", vmlinux); + else + snprintf(symfs_vmlinux, sizeof(symfs_vmlinux), "%s%s", + symbol_conf.symfs, vmlinux); if (dso->kernel == DSO_TYPE_GUEST_KERNEL) symtab_type = DSO_BINARY_TYPE__GUEST_VMLINUX; @@ -903,6 +1145,10 @@ int dso__load_vmlinux(struct dso *dso, struct map *map, symsrc__destroy(&ss); if (err > 0) { + if (dso->kernel == DSO_TYPE_GUEST_KERNEL) + dso->data_type = DSO_BINARY_TYPE__GUEST_VMLINUX; + else + dso->data_type = DSO_BINARY_TYPE__VMLINUX; dso__set_long_name(dso, (char *)vmlinux); dso__set_loaded(dso, map->type); pr_debug("Using %s for symbols\n", symfs_vmlinux); @@ -975,7 +1221,7 @@ static int dso__load_kernel_sym(struct dso *dso, struct map *map, dso__set_long_name(dso, strdup(symbol_conf.vmlinux_name)); dso->lname_alloc = 1; - goto out_fixup; + return err; } return err; } @@ -983,7 +1229,7 @@ static int dso__load_kernel_sym(struct dso *dso, struct map *map, if (vmlinux_path != NULL) { err = dso__load_vmlinux_path(dso, map, filter); if (err > 0) - goto out_fixup; + return err; } /* do not try local files if a symfs was given */ @@ -1042,9 +1288,8 @@ do_kallsyms: pr_debug("Using %s for symbols\n", kallsyms_filename); free(kallsyms_allocated_filename); - if (err > 0) { + if (err > 0 && !dso__is_kcore(dso)) { dso__set_long_name(dso, strdup("[kernel.kallsyms]")); -out_fixup: map__fixup_start(map); map__fixup_end(map); } @@ -1075,7 +1320,7 @@ static int dso__load_guest_kernel_sym(struct dso *dso, struct map *map, if (symbol_conf.default_guest_vmlinux_name != NULL) { err = dso__load_vmlinux(dso, map, symbol_conf.default_guest_vmlinux_name, filter); - goto out_try_fixup; + return err; } kallsyms_filename = symbol_conf.default_guest_kallsyms; @@ -1089,13 +1334,9 @@ static int dso__load_guest_kernel_sym(struct dso *dso, struct map *map, err = dso__load_kallsyms(dso, kallsyms_filename, map, filter); if (err > 0) pr_debug("Using %s for symbols\n", kallsyms_filename); - -out_try_fixup: - if (err > 0) { - if (kallsyms_filename != NULL) { - machine__mmap_name(machine, path, sizeof(path)); - dso__set_long_name(dso, strdup(path)); - } + if (err > 0 && !dso__is_kcore(dso)) { + machine__mmap_name(machine, path, sizeof(path)); + dso__set_long_name(dso, strdup(path)); map__fixup_start(map); map__fixup_end(map); } diff --git a/tools/perf/util/symbol.h b/tools/perf/util/symbol.h index 5f720dc076da..fd5b70ea2981 100644 --- a/tools/perf/util/symbol.h +++ b/tools/perf/util/symbol.h @@ -215,6 +215,7 @@ struct symbol *dso__find_symbol(struct dso *dso, enum map_type type, u64 addr); struct symbol *dso__find_symbol_by_name(struct dso *dso, enum map_type type, const char *name); +struct symbol *dso__first_symbol(struct dso *dso, enum map_type type); int filename__read_build_id(const char *filename, void *bf, size_t size); int sysfs__read_build_id(const char *filename, void *bf, size_t size); @@ -247,4 +248,8 @@ void symbols__fixup_duplicate(struct rb_root *symbols); void symbols__fixup_end(struct rb_root *symbols); void __map_groups__fixup_end(struct map_groups *mg, enum map_type type); +typedef int (*mapfn_t)(u64 start, u64 len, u64 pgoff, void *data); +int file__read_maps(int fd, bool exe, mapfn_t mapfn, void *data, + bool *is_64_bit); + #endif /* __PERF_SYMBOL */ diff --git a/tools/perf/util/thread.c b/tools/perf/util/thread.c index 40399cbcca77..e3d4a550a703 100644 --- a/tools/perf/util/thread.c +++ b/tools/perf/util/thread.c @@ -7,17 +7,18 @@ #include "util.h" #include "debug.h" -struct thread *thread__new(pid_t pid) +struct thread *thread__new(pid_t pid, pid_t tid) { struct thread *self = zalloc(sizeof(*self)); if (self != NULL) { map_groups__init(&self->mg); - self->pid = pid; + self->pid_ = pid; + self->tid = tid; self->ppid = -1; self->comm = malloc(32); if (self->comm) - snprintf(self->comm, 32, ":%d", self->pid); + snprintf(self->comm, 32, ":%d", self->tid); } return self; @@ -57,7 +58,7 @@ int thread__comm_len(struct thread *self) size_t thread__fprintf(struct thread *thread, FILE *fp) { - return fprintf(fp, "Thread %d %s\n", thread->pid, thread->comm) + + return fprintf(fp, "Thread %d %s\n", thread->tid, thread->comm) + map_groups__fprintf(&thread->mg, verbose, fp); } @@ -84,7 +85,7 @@ int thread__fork(struct thread *self, struct thread *parent) if (map_groups__clone(&self->mg, &parent->mg, i) < 0) return -ENOMEM; - self->ppid = parent->pid; + self->ppid = parent->tid; return 0; } diff --git a/tools/perf/util/thread.h b/tools/perf/util/thread.h index eeb7ac62b9e3..4ebbb40d46d4 100644 --- a/tools/perf/util/thread.h +++ b/tools/perf/util/thread.h @@ -12,10 +12,12 @@ struct thread { struct list_head node; }; struct map_groups mg; - pid_t pid; + pid_t pid_; /* Not all tools update this */ + pid_t tid; pid_t ppid; char shortname[3]; bool comm_set; + bool dead; /* if set thread has exited */ char *comm; int comm_len; @@ -24,8 +26,12 @@ struct thread { struct machine; -struct thread *thread__new(pid_t pid); +struct thread *thread__new(pid_t pid, pid_t tid); void thread__delete(struct thread *self); +static inline void thread__exited(struct thread *thread) +{ + thread->dead = true; +} int thread__set_comm(struct thread *self, const char *comm); int thread__comm_len(struct thread *self); @@ -45,6 +51,15 @@ void thread__find_addr_map(struct thread *thread, struct machine *machine, void thread__find_addr_location(struct thread *thread, struct machine *machine, u8 cpumode, enum map_type type, u64 addr, - struct addr_location *al, - symbol_filter_t filter); + struct addr_location *al); + +static inline void *thread__priv(struct thread *thread) +{ + return thread->priv; +} + +static inline void thread__set_priv(struct thread *thread, void *p) +{ + thread->priv = p; +} #endif /* __PERF_THREAD_H */ diff --git a/tools/perf/util/tool.h b/tools/perf/util/tool.h index b0e1aadba8d5..62b16b6165ba 100644 --- a/tools/perf/util/tool.h +++ b/tools/perf/util/tool.h @@ -18,12 +18,9 @@ typedef int (*event_sample)(struct perf_tool *tool, union perf_event *event, typedef int (*event_op)(struct perf_tool *tool, union perf_event *event, struct perf_sample *sample, struct machine *machine); -typedef int (*event_attr_op)(union perf_event *event, +typedef int (*event_attr_op)(struct perf_tool *tool, + union perf_event *event, struct perf_evlist **pevlist); -typedef int (*event_simple_op)(struct perf_tool *tool, union perf_event *event); - -typedef int (*event_synth_op)(union perf_event *event, - struct perf_session *session); typedef int (*event_op2)(struct perf_tool *tool, union perf_event *event, struct perf_session *session); @@ -39,8 +36,7 @@ struct perf_tool { throttle, unthrottle; event_attr_op attr; - event_synth_op tracing_data; - event_simple_op event_type; + event_op2 tracing_data; event_op2 finished_round, build_id; bool ordered_samples; diff --git a/tools/perf/util/top.h b/tools/perf/util/top.h index df46be93d902..b554ffc462b6 100644 --- a/tools/perf/util/top.h +++ b/tools/perf/util/top.h @@ -39,6 +39,8 @@ struct perf_top { float min_percent; }; +#define CONSOLE_CLEAR "[H[2J" + size_t perf_top__header_snprintf(struct perf_top *top, char *bf, size_t size); void perf_top__reset_sample_counters(struct perf_top *top); #endif /* __PERF_TOP_H */ diff --git a/tools/perf/util/trace-event-info.c b/tools/perf/util/trace-event-info.c index 3917eb9a8479..f3c9e551bd35 100644 --- a/tools/perf/util/trace-event-info.c +++ b/tools/perf/util/trace-event-info.c @@ -46,65 +46,6 @@ static int output_fd; -static const char *find_debugfs(void) -{ - const char *path = perf_debugfs_mount(NULL); - - if (!path) - pr_debug("Your kernel does not support the debugfs filesystem"); - - return path; -} - -/* - * Finds the path to the debugfs/tracing - * Allocates the string and stores it. - */ -static const char *find_tracing_dir(void) -{ - static char *tracing; - static int tracing_found; - const char *debugfs; - - if (tracing_found) - return tracing; - - debugfs = find_debugfs(); - if (!debugfs) - return NULL; - - tracing = malloc(strlen(debugfs) + 9); - if (!tracing) - return NULL; - - sprintf(tracing, "%s/tracing", debugfs); - - tracing_found = 1; - return tracing; -} - -static char *get_tracing_file(const char *name) -{ - const char *tracing; - char *file; - - tracing = find_tracing_dir(); - if (!tracing) - return NULL; - - file = malloc(strlen(tracing) + strlen(name) + 2); - if (!file) - return NULL; - - sprintf(file, "%s/%s", tracing, name); - return file; -} - -static void put_tracing_file(char *file) -{ - free(file); -} - int bigendian(void) { unsigned char str[] = { 0x1, 0x2, 0x3, 0x4, 0x0, 0x0, 0x0, 0x0}; @@ -160,7 +101,7 @@ out: return err; } -static int read_header_files(void) +static int record_header_files(void) { char *path; struct stat st; @@ -299,7 +240,7 @@ out: return err; } -static int read_ftrace_files(struct tracepoint_path *tps) +static int record_ftrace_files(struct tracepoint_path *tps) { char *path; int ret; @@ -328,7 +269,7 @@ static bool system_in_tp_list(char *sys, struct tracepoint_path *tps) return false; } -static int read_event_files(struct tracepoint_path *tps) +static int record_event_files(struct tracepoint_path *tps) { struct dirent *dent; struct stat st; @@ -403,7 +344,7 @@ out: return err; } -static int read_proc_kallsyms(void) +static int record_proc_kallsyms(void) { unsigned int size; const char *path = "/proc/kallsyms"; @@ -421,7 +362,7 @@ static int read_proc_kallsyms(void) return record_file(path, 4); } -static int read_ftrace_printk(void) +static int record_ftrace_printk(void) { unsigned int size; char *path; @@ -473,12 +414,27 @@ get_tracepoints_path(struct list_head *pattrs) if (pos->attr.type != PERF_TYPE_TRACEPOINT) continue; ++nr_tracepoints; + + if (pos->name) { + ppath->next = tracepoint_name_to_path(pos->name); + if (ppath->next) + goto next; + + if (strchr(pos->name, ':') == NULL) + goto try_id; + + goto error; + } + +try_id: ppath->next = tracepoint_id_to_path(pos->attr.config); if (!ppath->next) { +error: pr_debug("No memory to alloc tracepoints list\n"); put_tracepoints_path(&path); return NULL; } +next: ppath = ppath->next; } @@ -520,8 +476,6 @@ static int tracing_data_header(void) else buf[0] = 0; - read_trace_init(buf[0], buf[0]); - if (write(output_fd, buf, 1) != 1) return -1; @@ -583,19 +537,19 @@ struct tracing_data *tracing_data_get(struct list_head *pattrs, err = tracing_data_header(); if (err) goto out; - err = read_header_files(); + err = record_header_files(); if (err) goto out; - err = read_ftrace_files(tps); + err = record_ftrace_files(tps); if (err) goto out; - err = read_event_files(tps); + err = record_event_files(tps); if (err) goto out; - err = read_proc_kallsyms(); + err = record_proc_kallsyms(); if (err) goto out; - err = read_ftrace_printk(); + err = record_ftrace_printk(); out: /* diff --git a/tools/perf/util/trace-event-parse.c b/tools/perf/util/trace-event-parse.c index 4454835a9ebc..fe7a27d67d2b 100644 --- a/tools/perf/util/trace-event-parse.c +++ b/tools/perf/util/trace-event-parse.c @@ -28,12 +28,6 @@ #include "util.h" #include "trace-event.h" -int header_page_size_size; -int header_page_ts_size; -int header_page_data_offset; - -bool latency_format; - struct pevent *read_trace_init(int file_bigendian, int host_bigendian) { struct pevent *pevent = pevent_alloc(); diff --git a/tools/perf/util/trace-event-read.c b/tools/perf/util/trace-event-read.c index af215c0d2379..f2112270c663 100644 --- a/tools/perf/util/trace-event-read.c +++ b/tools/perf/util/trace-event-read.c @@ -39,10 +39,6 @@ static int input_fd; -int file_bigendian; -int host_bigendian; -static int long_size; - static ssize_t trace_data_size; static bool repipe; @@ -216,7 +212,7 @@ static int read_ftrace_printk(struct pevent *pevent) static int read_header_files(struct pevent *pevent) { unsigned long long size; - char *header_event; + char *header_page; char buf[BUFSIZ]; int ret = 0; @@ -229,13 +225,26 @@ static int read_header_files(struct pevent *pevent) } size = read8(pevent); - skip(size); - /* - * The size field in the page is of type long, - * use that instead, since it represents the kernel. - */ - long_size = header_page_size_size; + header_page = malloc(size); + if (header_page == NULL) + return -1; + + if (do_read(header_page, size) < 0) { + pr_debug("did not read header page"); + free(header_page); + return -1; + } + + if (!pevent_parse_header_page(pevent, header_page, size, + pevent_get_long_size(pevent))) { + /* + * The commit field in the page is of type long, + * use that instead, since it represents the kernel. + */ + pevent_set_long_size(pevent, pevent->header_page_size_size); + } + free(header_page); if (do_read(buf, 13) < 0) return -1; @@ -246,14 +255,8 @@ static int read_header_files(struct pevent *pevent) } size = read8(pevent); - header_event = malloc(size); - if (header_event == NULL) - return -1; - - if (do_read(header_event, size) < 0) - ret = -1; + skip(size); - free(header_event); return ret; } @@ -349,6 +352,10 @@ ssize_t trace_report(int fd, struct pevent **ppevent, bool __repipe) int show_funcs = 0; int show_printk = 0; ssize_t size = -1; + int file_bigendian; + int host_bigendian; + int file_long_size; + int file_page_size; struct pevent *pevent; int err; @@ -391,12 +398,15 @@ ssize_t trace_report(int fd, struct pevent **ppevent, bool __repipe) if (do_read(buf, 1) < 0) goto out; - long_size = buf[0]; + file_long_size = buf[0]; - page_size = read4(pevent); - if (!page_size) + file_page_size = read4(pevent); + if (!file_page_size) goto out; + pevent_set_long_size(pevent, file_long_size); + pevent_set_page_size(pevent, file_page_size); + err = read_header_files(pevent); if (err) goto out; diff --git a/tools/perf/util/trace-event-scripting.c b/tools/perf/util/trace-event-scripting.c index 8715a1006d00..95199e4eea97 100644 --- a/tools/perf/util/trace-event-scripting.c +++ b/tools/perf/util/trace-event-scripting.c @@ -39,7 +39,8 @@ static void process_event_unsupported(union perf_event *event __maybe_unused, struct perf_sample *sample __maybe_unused, struct perf_evsel *evsel __maybe_unused, struct machine *machine __maybe_unused, - struct addr_location *al __maybe_unused) + struct thread *thread __maybe_unused, + struct addr_location *al __maybe_unused) { } diff --git a/tools/perf/util/trace-event.h b/tools/perf/util/trace-event.h index 1978c398ad87..fafe1a40444a 100644 --- a/tools/perf/util/trace-event.h +++ b/tools/perf/util/trace-event.h @@ -1,32 +1,18 @@ #ifndef _PERF_UTIL_TRACE_EVENT_H #define _PERF_UTIL_TRACE_EVENT_H +#include <traceevent/event-parse.h> #include "parse-events.h" -#include "event-parse.h" #include "session.h" struct machine; struct perf_sample; union perf_event; struct perf_tool; +struct thread; -extern int header_page_size_size; -extern int header_page_ts_size; -extern int header_page_data_offset; - -extern bool latency_format; extern struct pevent *perf_pevent; -enum { - RINGBUF_TYPE_PADDING = 29, - RINGBUF_TYPE_TIME_EXTEND = 30, - RINGBUF_TYPE_TIME_STAMP = 31, -}; - -#ifndef TS_SHIFT -#define TS_SHIFT 27 -#endif - int bigendian(void); struct pevent *read_trace_init(int file_bigendian, int host_bigendian); @@ -83,7 +69,8 @@ struct scripting_ops { struct perf_sample *sample, struct perf_evsel *evsel, struct machine *machine, - struct addr_location *al); + struct thread *thread, + struct addr_location *al); int (*generate_script) (struct pevent *pevent, const char *outfile); }; diff --git a/tools/perf/util/unwind.c b/tools/perf/util/unwind.c index 958723ba3d2e..2f891f7e70bf 100644 --- a/tools/perf/util/unwind.c +++ b/tools/perf/util/unwind.c @@ -473,7 +473,7 @@ static int entry(u64 ip, struct thread *thread, struct machine *machine, thread__find_addr_location(thread, machine, PERF_RECORD_MISC_USER, - MAP__FUNCTION, ip, &al, NULL); + MAP__FUNCTION, ip, &al); e.ip = ip; e.map = al.map; diff --git a/tools/perf/util/util.c b/tools/perf/util/util.c index 59d868add275..6d17b18e915d 100644 --- a/tools/perf/util/util.c +++ b/tools/perf/util/util.c @@ -269,3 +269,95 @@ void perf_debugfs_set_path(const char *mntpt) snprintf(debugfs_mountpoint, strlen(debugfs_mountpoint), "%s", mntpt); set_tracing_events_path(mntpt); } + +static const char *find_debugfs(void) +{ + const char *path = perf_debugfs_mount(NULL); + + if (!path) + fprintf(stderr, "Your kernel does not support the debugfs filesystem"); + + return path; +} + +/* + * Finds the path to the debugfs/tracing + * Allocates the string and stores it. + */ +const char *find_tracing_dir(void) +{ + static char *tracing; + static int tracing_found; + const char *debugfs; + + if (tracing_found) + return tracing; + + debugfs = find_debugfs(); + if (!debugfs) + return NULL; + + tracing = malloc(strlen(debugfs) + 9); + if (!tracing) + return NULL; + + sprintf(tracing, "%s/tracing", debugfs); + + tracing_found = 1; + return tracing; +} + +char *get_tracing_file(const char *name) +{ + const char *tracing; + char *file; + + tracing = find_tracing_dir(); + if (!tracing) + return NULL; + + file = malloc(strlen(tracing) + strlen(name) + 2); + if (!file) + return NULL; + + sprintf(file, "%s/%s", tracing, name); + return file; +} + +void put_tracing_file(char *file) +{ + free(file); +} + +int parse_nsec_time(const char *str, u64 *ptime) +{ + u64 time_sec, time_nsec; + char *end; + + time_sec = strtoul(str, &end, 10); + if (*end != '.' && *end != '\0') + return -1; + + if (*end == '.') { + int i; + char nsec_buf[10]; + + if (strlen(++end) > 9) + return -1; + + strncpy(nsec_buf, end, 9); + nsec_buf[9] = '\0'; + + /* make it nsec precision */ + for (i = strlen(nsec_buf); i < 9; i++) + nsec_buf[i] = '0'; + + time_nsec = strtoul(nsec_buf, &end, 10); + if (*end != '\0') + return -1; + } else + time_nsec = 0; + + *ptime = time_sec * NSEC_PER_SEC + time_nsec; + return 0; +} diff --git a/tools/perf/util/util.h b/tools/perf/util/util.h index 2732fad03908..a53535949043 100644 --- a/tools/perf/util/util.h +++ b/tools/perf/util/util.h @@ -80,6 +80,9 @@ extern char buildid_dir[]; extern char tracing_events_path[]; extern void perf_debugfs_set_path(const char *mountpoint); const char *perf_debugfs_mount(const char *mountpoint); +const char *find_tracing_dir(void); +char *get_tracing_file(const char *name); +void put_tracing_file(char *file); /* On most systems <limits.h> would have given us this, but * not on some systems (e.g. GNU/Hurd). @@ -205,6 +208,8 @@ static inline int has_extension(const char *filename, const char *ext) #define NSEC_PER_MSEC 1000000L #endif +int parse_nsec_time(const char *str, u64 *ptime); + extern unsigned char sane_ctype[256]; #define GIT_SPACE 0x01 #define GIT_DIGIT 0x02 |