diff options
| author | Ingo Molnar <mingo@elte.hu> | 2012-03-12 15:46:35 -0400 |
|---|---|---|
| committer | Ingo Molnar <mingo@elte.hu> | 2012-03-12 15:47:05 -0400 |
| commit | bea95c152dee1791dd02cbc708afbb115bb00f9a (patch) | |
| tree | af9994c42c5fdd81ba3dadd7b812e2fa85273353 /tools/perf/util | |
| parent | f9b4eeb809c6d031cc9561cc34dd691701cb2c2a (diff) | |
| parent | 24bff2dc0f77b1f186b7bdf30060caf3df191a68 (diff) | |
Merge branch 'perf/hw-branch-sampling' into perf/core
Merge reason: The 'perf record -b' hardware branch sampling feature is ready for upstream.
Signed-off-by: Ingo Molnar <mingo@elte.hu>
Diffstat (limited to 'tools/perf/util')
| -rw-r--r-- | tools/perf/util/event.h | 1 | ||||
| -rw-r--r-- | tools/perf/util/evsel.c | 14 | ||||
| -rw-r--r-- | tools/perf/util/header.c | 207 | ||||
| -rw-r--r-- | tools/perf/util/header.h | 2 | ||||
| -rw-r--r-- | tools/perf/util/hist.c | 122 | ||||
| -rw-r--r-- | tools/perf/util/hist.h | 11 | ||||
| -rw-r--r-- | tools/perf/util/session.c | 77 | ||||
| -rw-r--r-- | tools/perf/util/session.h | 4 | ||||
| -rw-r--r-- | tools/perf/util/sort.c | 287 | ||||
| -rw-r--r-- | tools/perf/util/sort.h | 11 | ||||
| -rw-r--r-- | tools/perf/util/symbol.h | 20 | ||||
| -rw-r--r-- | tools/perf/util/ui/browsers/hists.c | 102 |
12 files changed, 714 insertions, 144 deletions
diff --git a/tools/perf/util/event.h b/tools/perf/util/event.h index cbdeaad9c5e5..1b197280c621 100644 --- a/tools/perf/util/event.h +++ b/tools/perf/util/event.h | |||
| @@ -81,6 +81,7 @@ struct perf_sample { | |||
| 81 | u32 raw_size; | 81 | u32 raw_size; |
| 82 | void *raw_data; | 82 | void *raw_data; |
| 83 | struct ip_callchain *callchain; | 83 | struct ip_callchain *callchain; |
| 84 | struct branch_stack *branch_stack; | ||
| 84 | }; | 85 | }; |
| 85 | 86 | ||
| 86 | #define BUILD_ID_SIZE 20 | 87 | #define BUILD_ID_SIZE 20 |
diff --git a/tools/perf/util/evsel.c b/tools/perf/util/evsel.c index 302d49a9f985..f421f7cbc0d3 100644 --- a/tools/perf/util/evsel.c +++ b/tools/perf/util/evsel.c | |||
| @@ -126,6 +126,10 @@ void perf_evsel__config(struct perf_evsel *evsel, struct perf_record_opts *opts) | |||
| 126 | attr->watermark = 0; | 126 | attr->watermark = 0; |
| 127 | attr->wakeup_events = 1; | 127 | attr->wakeup_events = 1; |
| 128 | } | 128 | } |
| 129 | if (opts->branch_stack) { | ||
| 130 | attr->sample_type |= PERF_SAMPLE_BRANCH_STACK; | ||
| 131 | attr->branch_sample_type = opts->branch_stack; | ||
| 132 | } | ||
| 129 | 133 | ||
| 130 | attr->mmap = track; | 134 | attr->mmap = track; |
| 131 | attr->comm = track; | 135 | attr->comm = track; |
| @@ -576,6 +580,16 @@ int perf_event__parse_sample(const union perf_event *event, u64 type, | |||
| 576 | data->raw_data = (void *) pdata; | 580 | data->raw_data = (void *) pdata; |
| 577 | } | 581 | } |
| 578 | 582 | ||
| 583 | if (type & PERF_SAMPLE_BRANCH_STACK) { | ||
| 584 | u64 sz; | ||
| 585 | |||
| 586 | data->branch_stack = (struct branch_stack *)array; | ||
| 587 | array++; /* nr */ | ||
| 588 | |||
| 589 | sz = data->branch_stack->nr * sizeof(struct branch_entry); | ||
| 590 | sz /= sizeof(u64); | ||
| 591 | array += sz; | ||
| 592 | } | ||
| 579 | return 0; | 593 | return 0; |
| 580 | } | 594 | } |
| 581 | 595 | ||
diff --git a/tools/perf/util/header.c b/tools/perf/util/header.c index 9f867d96c6a5..0d9b6da86a39 100644 --- a/tools/perf/util/header.c +++ b/tools/perf/util/header.c | |||
| @@ -1023,6 +1023,12 @@ write_it: | |||
| 1023 | return do_write_string(fd, buffer); | 1023 | return do_write_string(fd, buffer); |
| 1024 | } | 1024 | } |
| 1025 | 1025 | ||
| 1026 | static int write_branch_stack(int fd __used, struct perf_header *h __used, | ||
| 1027 | struct perf_evlist *evlist __used) | ||
| 1028 | { | ||
| 1029 | return 0; | ||
| 1030 | } | ||
| 1031 | |||
| 1026 | static void print_hostname(struct perf_header *ph, int fd, FILE *fp) | 1032 | static void print_hostname(struct perf_header *ph, int fd, FILE *fp) |
| 1027 | { | 1033 | { |
| 1028 | char *str = do_read_string(fd, ph); | 1034 | char *str = do_read_string(fd, ph); |
| @@ -1144,8 +1150,9 @@ static void print_event_desc(struct perf_header *ph, int fd, FILE *fp) | |||
| 1144 | uint64_t id; | 1150 | uint64_t id; |
| 1145 | void *buf = NULL; | 1151 | void *buf = NULL; |
| 1146 | char *str; | 1152 | char *str; |
| 1147 | u32 nre, sz, nr, i, j, msz; | 1153 | u32 nre, sz, nr, i, j; |
| 1148 | int ret; | 1154 | ssize_t ret; |
| 1155 | size_t msz; | ||
| 1149 | 1156 | ||
| 1150 | /* number of events */ | 1157 | /* number of events */ |
| 1151 | ret = read(fd, &nre, sizeof(nre)); | 1158 | ret = read(fd, &nre, sizeof(nre)); |
| @@ -1162,25 +1169,23 @@ static void print_event_desc(struct perf_header *ph, int fd, FILE *fp) | |||
| 1162 | if (ph->needs_swap) | 1169 | if (ph->needs_swap) |
| 1163 | sz = bswap_32(sz); | 1170 | sz = bswap_32(sz); |
| 1164 | 1171 | ||
| 1165 | /* | ||
| 1166 | * ensure it is at least to our ABI rev | ||
| 1167 | */ | ||
| 1168 | if (sz < (u32)sizeof(attr)) | ||
| 1169 | goto error; | ||
| 1170 | |||
| 1171 | memset(&attr, 0, sizeof(attr)); | 1172 | memset(&attr, 0, sizeof(attr)); |
| 1172 | 1173 | ||
| 1173 | /* read entire region to sync up to next field */ | 1174 | /* buffer to hold on file attr struct */ |
| 1174 | buf = malloc(sz); | 1175 | buf = malloc(sz); |
| 1175 | if (!buf) | 1176 | if (!buf) |
| 1176 | goto error; | 1177 | goto error; |
| 1177 | 1178 | ||
| 1178 | msz = sizeof(attr); | 1179 | msz = sizeof(attr); |
| 1179 | if (sz < msz) | 1180 | if (sz < (ssize_t)msz) |
| 1180 | msz = sz; | 1181 | msz = sz; |
| 1181 | 1182 | ||
| 1182 | for (i = 0 ; i < nre; i++) { | 1183 | for (i = 0 ; i < nre; i++) { |
| 1183 | 1184 | ||
| 1185 | /* | ||
| 1186 | * must read entire on-file attr struct to | ||
| 1187 | * sync up with layout. | ||
| 1188 | */ | ||
| 1184 | ret = read(fd, buf, sz); | 1189 | ret = read(fd, buf, sz); |
| 1185 | if (ret != (ssize_t)sz) | 1190 | if (ret != (ssize_t)sz) |
| 1186 | goto error; | 1191 | goto error; |
| @@ -1316,6 +1321,12 @@ static void print_cpuid(struct perf_header *ph, int fd, FILE *fp) | |||
| 1316 | free(str); | 1321 | free(str); |
| 1317 | } | 1322 | } |
| 1318 | 1323 | ||
| 1324 | static void print_branch_stack(struct perf_header *ph __used, int fd __used, | ||
| 1325 | FILE *fp) | ||
| 1326 | { | ||
| 1327 | fprintf(fp, "# contains samples with branch stack\n"); | ||
| 1328 | } | ||
| 1329 | |||
| 1319 | static int __event_process_build_id(struct build_id_event *bev, | 1330 | static int __event_process_build_id(struct build_id_event *bev, |
| 1320 | char *filename, | 1331 | char *filename, |
| 1321 | struct perf_session *session) | 1332 | struct perf_session *session) |
| @@ -1520,6 +1531,7 @@ static const struct feature_ops feat_ops[HEADER_LAST_FEATURE] = { | |||
| 1520 | FEAT_OPA(HEADER_CMDLINE, cmdline), | 1531 | FEAT_OPA(HEADER_CMDLINE, cmdline), |
| 1521 | FEAT_OPF(HEADER_CPU_TOPOLOGY, cpu_topology), | 1532 | FEAT_OPF(HEADER_CPU_TOPOLOGY, cpu_topology), |
| 1522 | FEAT_OPF(HEADER_NUMA_TOPOLOGY, numa_topology), | 1533 | FEAT_OPF(HEADER_NUMA_TOPOLOGY, numa_topology), |
| 1534 | FEAT_OPA(HEADER_BRANCH_STACK, branch_stack), | ||
| 1523 | }; | 1535 | }; |
| 1524 | 1536 | ||
| 1525 | struct header_print_data { | 1537 | struct header_print_data { |
| @@ -1804,35 +1816,101 @@ out_free: | |||
| 1804 | return err; | 1816 | return err; |
| 1805 | } | 1817 | } |
| 1806 | 1818 | ||
| 1807 | static int check_magic_endian(u64 *magic, struct perf_file_header *header, | 1819 | static const int attr_file_abi_sizes[] = { |
| 1808 | struct perf_header *ph) | 1820 | [0] = PERF_ATTR_SIZE_VER0, |
| 1821 | [1] = PERF_ATTR_SIZE_VER1, | ||
| 1822 | 0, | ||
| 1823 | }; | ||
| 1824 | |||
| 1825 | /* | ||
| 1826 | * In the legacy file format, the magic number is not used to encode endianness. | ||
| 1827 | * hdr_sz was used to encode endianness. But given that hdr_sz can vary based | ||
| 1828 | * on ABI revisions, we need to try all combinations for all endianness to | ||
| 1829 | * detect the endianness. | ||
| 1830 | */ | ||
| 1831 | static int try_all_file_abis(uint64_t hdr_sz, struct perf_header *ph) | ||
| 1809 | { | 1832 | { |
| 1810 | int ret; | 1833 | uint64_t ref_size, attr_size; |
| 1834 | int i; | ||
| 1811 | 1835 | ||
| 1812 | /* check for legacy format */ | 1836 | for (i = 0 ; attr_file_abi_sizes[i]; i++) { |
| 1813 | ret = memcmp(magic, __perf_magic1, sizeof(*magic)); | 1837 | ref_size = attr_file_abi_sizes[i] |
| 1814 | if (ret == 0) { | 1838 | + sizeof(struct perf_file_section); |
| 1815 | pr_debug("legacy perf.data format\n"); | 1839 | if (hdr_sz != ref_size) { |
| 1816 | if (!header) | 1840 | attr_size = bswap_64(hdr_sz); |
| 1817 | return -1; | 1841 | if (attr_size != ref_size) |
| 1842 | continue; | ||
| 1818 | 1843 | ||
| 1819 | if (header->attr_size != sizeof(struct perf_file_attr)) { | 1844 | ph->needs_swap = true; |
| 1820 | u64 attr_size = bswap_64(header->attr_size); | 1845 | } |
| 1846 | pr_debug("ABI%d perf.data file detected, need_swap=%d\n", | ||
| 1847 | i, | ||
| 1848 | ph->needs_swap); | ||
| 1849 | return 0; | ||
| 1850 | } | ||
| 1851 | /* could not determine endianness */ | ||
| 1852 | return -1; | ||
| 1853 | } | ||
| 1821 | 1854 | ||
| 1822 | if (attr_size != sizeof(struct perf_file_attr)) | 1855 | #define PERF_PIPE_HDR_VER0 16 |
| 1823 | return -1; | 1856 | |
| 1857 | static const size_t attr_pipe_abi_sizes[] = { | ||
| 1858 | [0] = PERF_PIPE_HDR_VER0, | ||
| 1859 | 0, | ||
| 1860 | }; | ||
| 1861 | |||
| 1862 | /* | ||
| 1863 | * In the legacy pipe format, there is an implicit assumption that endiannesss | ||
| 1864 | * between host recording the samples, and host parsing the samples is the | ||
| 1865 | * same. This is not always the case given that the pipe output may always be | ||
| 1866 | * redirected into a file and analyzed on a different machine with possibly a | ||
| 1867 | * different endianness and perf_event ABI revsions in the perf tool itself. | ||
| 1868 | */ | ||
| 1869 | static int try_all_pipe_abis(uint64_t hdr_sz, struct perf_header *ph) | ||
| 1870 | { | ||
| 1871 | u64 attr_size; | ||
| 1872 | int i; | ||
| 1873 | |||
| 1874 | for (i = 0 ; attr_pipe_abi_sizes[i]; i++) { | ||
| 1875 | if (hdr_sz != attr_pipe_abi_sizes[i]) { | ||
| 1876 | attr_size = bswap_64(hdr_sz); | ||
| 1877 | if (attr_size != hdr_sz) | ||
| 1878 | continue; | ||
| 1824 | 1879 | ||
| 1825 | ph->needs_swap = true; | 1880 | ph->needs_swap = true; |
| 1826 | } | 1881 | } |
| 1882 | pr_debug("Pipe ABI%d perf.data file detected\n", i); | ||
| 1827 | return 0; | 1883 | return 0; |
| 1828 | } | 1884 | } |
| 1885 | return -1; | ||
| 1886 | } | ||
| 1887 | |||
| 1888 | static int check_magic_endian(u64 magic, uint64_t hdr_sz, | ||
| 1889 | bool is_pipe, struct perf_header *ph) | ||
| 1890 | { | ||
| 1891 | int ret; | ||
| 1892 | |||
| 1893 | /* check for legacy format */ | ||
| 1894 | ret = memcmp(&magic, __perf_magic1, sizeof(magic)); | ||
| 1895 | if (ret == 0) { | ||
| 1896 | pr_debug("legacy perf.data format\n"); | ||
| 1897 | if (is_pipe) | ||
| 1898 | return try_all_pipe_abis(hdr_sz, ph); | ||
| 1899 | |||
| 1900 | return try_all_file_abis(hdr_sz, ph); | ||
| 1901 | } | ||
| 1902 | /* | ||
| 1903 | * the new magic number serves two purposes: | ||
| 1904 | * - unique number to identify actual perf.data files | ||
| 1905 | * - encode endianness of file | ||
| 1906 | */ | ||
| 1829 | 1907 | ||
| 1830 | /* check magic number with same endianness */ | 1908 | /* check magic number with one endianness */ |
| 1831 | if (*magic == __perf_magic2) | 1909 | if (magic == __perf_magic2) |
| 1832 | return 0; | 1910 | return 0; |
| 1833 | 1911 | ||
| 1834 | /* check magic number but opposite endianness */ | 1912 | /* check magic number with opposite endianness */ |
| 1835 | if (*magic != __perf_magic2_sw) | 1913 | if (magic != __perf_magic2_sw) |
| 1836 | return -1; | 1914 | return -1; |
| 1837 | 1915 | ||
| 1838 | ph->needs_swap = true; | 1916 | ph->needs_swap = true; |
| @@ -1851,8 +1929,11 @@ int perf_file_header__read(struct perf_file_header *header, | |||
| 1851 | if (ret <= 0) | 1929 | if (ret <= 0) |
| 1852 | return -1; | 1930 | return -1; |
| 1853 | 1931 | ||
| 1854 | if (check_magic_endian(&header->magic, header, ph) < 0) | 1932 | if (check_magic_endian(header->magic, |
| 1933 | header->attr_size, false, ph) < 0) { | ||
| 1934 | pr_debug("magic/endian check failed\n"); | ||
| 1855 | return -1; | 1935 | return -1; |
| 1936 | } | ||
| 1856 | 1937 | ||
| 1857 | if (ph->needs_swap) { | 1938 | if (ph->needs_swap) { |
| 1858 | mem_bswap_64(header, offsetof(struct perf_file_header, | 1939 | mem_bswap_64(header, offsetof(struct perf_file_header, |
| @@ -1939,21 +2020,17 @@ static int perf_file_header__read_pipe(struct perf_pipe_file_header *header, | |||
| 1939 | if (ret <= 0) | 2020 | if (ret <= 0) |
| 1940 | return -1; | 2021 | return -1; |
| 1941 | 2022 | ||
| 1942 | if (check_magic_endian(&header->magic, NULL, ph) < 0) | 2023 | if (check_magic_endian(header->magic, header->size, true, ph) < 0) { |
| 2024 | pr_debug("endian/magic failed\n"); | ||
| 1943 | return -1; | 2025 | return -1; |
| 2026 | } | ||
| 2027 | |||
| 2028 | if (ph->needs_swap) | ||
| 2029 | header->size = bswap_64(header->size); | ||
| 1944 | 2030 | ||
| 1945 | if (repipe && do_write(STDOUT_FILENO, header, sizeof(*header)) < 0) | 2031 | if (repipe && do_write(STDOUT_FILENO, header, sizeof(*header)) < 0) |
| 1946 | return -1; | 2032 | return -1; |
| 1947 | 2033 | ||
| 1948 | if (header->size != sizeof(*header)) { | ||
| 1949 | u64 size = bswap_64(header->size); | ||
| 1950 | |||
| 1951 | if (size != sizeof(*header)) | ||
| 1952 | return -1; | ||
| 1953 | |||
| 1954 | ph->needs_swap = true; | ||
| 1955 | } | ||
| 1956 | |||
| 1957 | return 0; | 2034 | return 0; |
| 1958 | } | 2035 | } |
| 1959 | 2036 | ||
| @@ -1973,6 +2050,52 @@ static int perf_header__read_pipe(struct perf_session *session, int fd) | |||
| 1973 | return 0; | 2050 | return 0; |
| 1974 | } | 2051 | } |
| 1975 | 2052 | ||
| 2053 | static int read_attr(int fd, struct perf_header *ph, | ||
| 2054 | struct perf_file_attr *f_attr) | ||
| 2055 | { | ||
| 2056 | struct perf_event_attr *attr = &f_attr->attr; | ||
| 2057 | size_t sz, left; | ||
| 2058 | size_t our_sz = sizeof(f_attr->attr); | ||
| 2059 | int ret; | ||
| 2060 | |||
| 2061 | memset(f_attr, 0, sizeof(*f_attr)); | ||
| 2062 | |||
| 2063 | /* read minimal guaranteed structure */ | ||
| 2064 | ret = readn(fd, attr, PERF_ATTR_SIZE_VER0); | ||
| 2065 | if (ret <= 0) { | ||
| 2066 | pr_debug("cannot read %d bytes of header attr\n", | ||
| 2067 | PERF_ATTR_SIZE_VER0); | ||
| 2068 | return -1; | ||
| 2069 | } | ||
| 2070 | |||
| 2071 | /* on file perf_event_attr size */ | ||
| 2072 | sz = attr->size; | ||
| 2073 | |||
| 2074 | if (ph->needs_swap) | ||
| 2075 | sz = bswap_32(sz); | ||
| 2076 | |||
| 2077 | if (sz == 0) { | ||
| 2078 | /* assume ABI0 */ | ||
| 2079 | sz = PERF_ATTR_SIZE_VER0; | ||
| 2080 | } else if (sz > our_sz) { | ||
| 2081 | pr_debug("file uses a more recent and unsupported ABI" | ||
| 2082 | " (%zu bytes extra)\n", sz - our_sz); | ||
| 2083 | return -1; | ||
| 2084 | } | ||
| 2085 | /* what we have not yet read and that we know about */ | ||
| 2086 | left = sz - PERF_ATTR_SIZE_VER0; | ||
| 2087 | if (left) { | ||
| 2088 | void *ptr = attr; | ||
| 2089 | ptr += PERF_ATTR_SIZE_VER0; | ||
| 2090 | |||
| 2091 | ret = readn(fd, ptr, left); | ||
| 2092 | } | ||
| 2093 | /* read perf_file_section, ids are read in caller */ | ||
| 2094 | ret = readn(fd, &f_attr->ids, sizeof(f_attr->ids)); | ||
| 2095 | |||
| 2096 | return ret <= 0 ? -1 : 0; | ||
| 2097 | } | ||
| 2098 | |||
| 1976 | int perf_session__read_header(struct perf_session *session, int fd) | 2099 | int perf_session__read_header(struct perf_session *session, int fd) |
| 1977 | { | 2100 | { |
| 1978 | struct perf_header *header = &session->header; | 2101 | struct perf_header *header = &session->header; |
| @@ -1988,19 +2111,17 @@ int perf_session__read_header(struct perf_session *session, int fd) | |||
| 1988 | if (session->fd_pipe) | 2111 | if (session->fd_pipe) |
| 1989 | return perf_header__read_pipe(session, fd); | 2112 | return perf_header__read_pipe(session, fd); |
| 1990 | 2113 | ||
| 1991 | if (perf_file_header__read(&f_header, header, fd) < 0) { | 2114 | if (perf_file_header__read(&f_header, header, fd) < 0) |
| 1992 | pr_debug("incompatible file format\n"); | ||
| 1993 | return -EINVAL; | 2115 | return -EINVAL; |
| 1994 | } | ||
| 1995 | 2116 | ||
| 1996 | nr_attrs = f_header.attrs.size / sizeof(f_attr); | 2117 | nr_attrs = f_header.attrs.size / f_header.attr_size; |
| 1997 | lseek(fd, f_header.attrs.offset, SEEK_SET); | 2118 | lseek(fd, f_header.attrs.offset, SEEK_SET); |
| 1998 | 2119 | ||
| 1999 | for (i = 0; i < nr_attrs; i++) { | 2120 | for (i = 0; i < nr_attrs; i++) { |
| 2000 | struct perf_evsel *evsel; | 2121 | struct perf_evsel *evsel; |
| 2001 | off_t tmp; | 2122 | off_t tmp; |
| 2002 | 2123 | ||
| 2003 | if (readn(fd, &f_attr, sizeof(f_attr)) <= 0) | 2124 | if (read_attr(fd, header, &f_attr) < 0) |
| 2004 | goto out_errno; | 2125 | goto out_errno; |
| 2005 | 2126 | ||
| 2006 | if (header->needs_swap) | 2127 | if (header->needs_swap) |
diff --git a/tools/perf/util/header.h b/tools/perf/util/header.h index e68f617d082f..21a6be09c129 100644 --- a/tools/perf/util/header.h +++ b/tools/perf/util/header.h | |||
| @@ -27,7 +27,7 @@ enum { | |||
| 27 | HEADER_EVENT_DESC, | 27 | HEADER_EVENT_DESC, |
| 28 | HEADER_CPU_TOPOLOGY, | 28 | HEADER_CPU_TOPOLOGY, |
| 29 | HEADER_NUMA_TOPOLOGY, | 29 | HEADER_NUMA_TOPOLOGY, |
| 30 | 30 | HEADER_BRANCH_STACK, | |
| 31 | HEADER_LAST_FEATURE, | 31 | HEADER_LAST_FEATURE, |
| 32 | HEADER_FEAT_BITS = 256, | 32 | HEADER_FEAT_BITS = 256, |
| 33 | }; | 33 | }; |
diff --git a/tools/perf/util/hist.c b/tools/perf/util/hist.c index 6f505d1abac7..8380c3db1c92 100644 --- a/tools/perf/util/hist.c +++ b/tools/perf/util/hist.c | |||
| @@ -50,21 +50,25 @@ static void hists__reset_col_len(struct hists *hists) | |||
| 50 | hists__set_col_len(hists, col, 0); | 50 | hists__set_col_len(hists, col, 0); |
| 51 | } | 51 | } |
| 52 | 52 | ||
| 53 | static void hists__set_unres_dso_col_len(struct hists *hists, int dso) | ||
| 54 | { | ||
| 55 | const unsigned int unresolved_col_width = BITS_PER_LONG / 4; | ||
| 56 | |||
| 57 | if (hists__col_len(hists, dso) < unresolved_col_width && | ||
| 58 | !symbol_conf.col_width_list_str && !symbol_conf.field_sep && | ||
| 59 | !symbol_conf.dso_list) | ||
| 60 | hists__set_col_len(hists, dso, unresolved_col_width); | ||
| 61 | } | ||
| 62 | |||
| 53 | static void hists__calc_col_len(struct hists *hists, struct hist_entry *h) | 63 | static void hists__calc_col_len(struct hists *hists, struct hist_entry *h) |
| 54 | { | 64 | { |
| 65 | const unsigned int unresolved_col_width = BITS_PER_LONG / 4; | ||
| 55 | u16 len; | 66 | u16 len; |
| 56 | 67 | ||
| 57 | if (h->ms.sym) | 68 | if (h->ms.sym) |
| 58 | hists__new_col_len(hists, HISTC_SYMBOL, h->ms.sym->namelen); | 69 | hists__new_col_len(hists, HISTC_SYMBOL, h->ms.sym->namelen + 4); |
| 59 | else { | 70 | else |
| 60 | const unsigned int unresolved_col_width = BITS_PER_LONG / 4; | 71 | hists__set_unres_dso_col_len(hists, HISTC_DSO); |
| 61 | |||
| 62 | if (hists__col_len(hists, HISTC_DSO) < unresolved_col_width && | ||
| 63 | !symbol_conf.col_width_list_str && !symbol_conf.field_sep && | ||
| 64 | !symbol_conf.dso_list) | ||
| 65 | hists__set_col_len(hists, HISTC_DSO, | ||
| 66 | unresolved_col_width); | ||
| 67 | } | ||
| 68 | 72 | ||
| 69 | len = thread__comm_len(h->thread); | 73 | len = thread__comm_len(h->thread); |
| 70 | if (hists__new_col_len(hists, HISTC_COMM, len)) | 74 | if (hists__new_col_len(hists, HISTC_COMM, len)) |
| @@ -74,6 +78,37 @@ static void hists__calc_col_len(struct hists *hists, struct hist_entry *h) | |||
| 74 | len = dso__name_len(h->ms.map->dso); | 78 | len = dso__name_len(h->ms.map->dso); |
| 75 | hists__new_col_len(hists, HISTC_DSO, len); | 79 | hists__new_col_len(hists, HISTC_DSO, len); |
| 76 | } | 80 | } |
| 81 | |||
| 82 | if (h->branch_info) { | ||
| 83 | int symlen; | ||
| 84 | /* | ||
| 85 | * +4 accounts for '[x] ' priv level info | ||
| 86 | * +2 account of 0x prefix on raw addresses | ||
| 87 | */ | ||
| 88 | if (h->branch_info->from.sym) { | ||
| 89 | symlen = (int)h->branch_info->from.sym->namelen + 4; | ||
| 90 | hists__new_col_len(hists, HISTC_SYMBOL_FROM, symlen); | ||
| 91 | |||
| 92 | symlen = dso__name_len(h->branch_info->from.map->dso); | ||
| 93 | hists__new_col_len(hists, HISTC_DSO_FROM, symlen); | ||
| 94 | } else { | ||
| 95 | symlen = unresolved_col_width + 4 + 2; | ||
| 96 | hists__new_col_len(hists, HISTC_SYMBOL_FROM, symlen); | ||
| 97 | hists__set_unres_dso_col_len(hists, HISTC_DSO_FROM); | ||
| 98 | } | ||
| 99 | |||
| 100 | if (h->branch_info->to.sym) { | ||
| 101 | symlen = (int)h->branch_info->to.sym->namelen + 4; | ||
| 102 | hists__new_col_len(hists, HISTC_SYMBOL_TO, symlen); | ||
| 103 | |||
| 104 | symlen = dso__name_len(h->branch_info->to.map->dso); | ||
| 105 | hists__new_col_len(hists, HISTC_DSO_TO, symlen); | ||
| 106 | } else { | ||
| 107 | symlen = unresolved_col_width + 4 + 2; | ||
| 108 | hists__new_col_len(hists, HISTC_SYMBOL_TO, symlen); | ||
| 109 | hists__set_unres_dso_col_len(hists, HISTC_DSO_TO); | ||
| 110 | } | ||
| 111 | } | ||
| 77 | } | 112 | } |
| 78 | 113 | ||
| 79 | static void hist_entry__add_cpumode_period(struct hist_entry *he, | 114 | static void hist_entry__add_cpumode_period(struct hist_entry *he, |
| @@ -195,26 +230,14 @@ static u8 symbol__parent_filter(const struct symbol *parent) | |||
| 195 | return 0; | 230 | return 0; |
| 196 | } | 231 | } |
| 197 | 232 | ||
| 198 | struct hist_entry *__hists__add_entry(struct hists *hists, | 233 | static struct hist_entry *add_hist_entry(struct hists *hists, |
| 234 | struct hist_entry *entry, | ||
| 199 | struct addr_location *al, | 235 | struct addr_location *al, |
| 200 | struct symbol *sym_parent, u64 period) | 236 | u64 period) |
| 201 | { | 237 | { |
| 202 | struct rb_node **p; | 238 | struct rb_node **p; |
| 203 | struct rb_node *parent = NULL; | 239 | struct rb_node *parent = NULL; |
| 204 | struct hist_entry *he; | 240 | struct hist_entry *he; |
| 205 | struct hist_entry entry = { | ||
| 206 | .thread = al->thread, | ||
| 207 | .ms = { | ||
| 208 | .map = al->map, | ||
| 209 | .sym = al->sym, | ||
| 210 | }, | ||
| 211 | .cpu = al->cpu, | ||
| 212 | .ip = al->addr, | ||
| 213 | .level = al->level, | ||
| 214 | .period = period, | ||
| 215 | .parent = sym_parent, | ||
| 216 | .filtered = symbol__parent_filter(sym_parent), | ||
| 217 | }; | ||
| 218 | int cmp; | 241 | int cmp; |
| 219 | 242 | ||
| 220 | pthread_mutex_lock(&hists->lock); | 243 | pthread_mutex_lock(&hists->lock); |
| @@ -225,7 +248,7 @@ struct hist_entry *__hists__add_entry(struct hists *hists, | |||
| 225 | parent = *p; | 248 | parent = *p; |
| 226 | he = rb_entry(parent, struct hist_entry, rb_node_in); | 249 | he = rb_entry(parent, struct hist_entry, rb_node_in); |
| 227 | 250 | ||
| 228 | cmp = hist_entry__cmp(&entry, he); | 251 | cmp = hist_entry__cmp(entry, he); |
| 229 | 252 | ||
| 230 | if (!cmp) { | 253 | if (!cmp) { |
| 231 | he->period += period; | 254 | he->period += period; |
| @@ -239,7 +262,7 @@ struct hist_entry *__hists__add_entry(struct hists *hists, | |||
| 239 | p = &(*p)->rb_right; | 262 | p = &(*p)->rb_right; |
| 240 | } | 263 | } |
| 241 | 264 | ||
| 242 | he = hist_entry__new(&entry); | 265 | he = hist_entry__new(entry); |
| 243 | if (!he) | 266 | if (!he) |
| 244 | goto out_unlock; | 267 | goto out_unlock; |
| 245 | 268 | ||
| @@ -252,6 +275,51 @@ out_unlock: | |||
| 252 | return he; | 275 | return he; |
| 253 | } | 276 | } |
| 254 | 277 | ||
| 278 | struct hist_entry *__hists__add_branch_entry(struct hists *self, | ||
| 279 | struct addr_location *al, | ||
| 280 | struct symbol *sym_parent, | ||
| 281 | struct branch_info *bi, | ||
| 282 | u64 period) | ||
| 283 | { | ||
| 284 | struct hist_entry entry = { | ||
| 285 | .thread = al->thread, | ||
| 286 | .ms = { | ||
| 287 | .map = bi->to.map, | ||
| 288 | .sym = bi->to.sym, | ||
| 289 | }, | ||
| 290 | .cpu = al->cpu, | ||
| 291 | .ip = bi->to.addr, | ||
| 292 | .level = al->level, | ||
| 293 | .period = period, | ||
| 294 | .parent = sym_parent, | ||
| 295 | .filtered = symbol__parent_filter(sym_parent), | ||
| 296 | .branch_info = bi, | ||
| 297 | }; | ||
| 298 | |||
| 299 | return add_hist_entry(self, &entry, al, period); | ||
| 300 | } | ||
| 301 | |||
| 302 | struct hist_entry *__hists__add_entry(struct hists *self, | ||
| 303 | struct addr_location *al, | ||
| 304 | struct symbol *sym_parent, u64 period) | ||
| 305 | { | ||
| 306 | struct hist_entry entry = { | ||
| 307 | .thread = al->thread, | ||
| 308 | .ms = { | ||
| 309 | .map = al->map, | ||
| 310 | .sym = al->sym, | ||
| 311 | }, | ||
| 312 | .cpu = al->cpu, | ||
| 313 | .ip = al->addr, | ||
| 314 | .level = al->level, | ||
| 315 | .period = period, | ||
| 316 | .parent = sym_parent, | ||
| 317 | .filtered = symbol__parent_filter(sym_parent), | ||
| 318 | }; | ||
| 319 | |||
| 320 | return add_hist_entry(self, &entry, al, period); | ||
| 321 | } | ||
| 322 | |||
| 255 | int64_t | 323 | int64_t |
| 256 | hist_entry__cmp(struct hist_entry *left, struct hist_entry *right) | 324 | hist_entry__cmp(struct hist_entry *left, struct hist_entry *right) |
| 257 | { | 325 | { |
diff --git a/tools/perf/util/hist.h b/tools/perf/util/hist.h index 48e5acd1e862..9413f3e31fea 100644 --- a/tools/perf/util/hist.h +++ b/tools/perf/util/hist.h | |||
| @@ -42,6 +42,11 @@ enum hist_column { | |||
| 42 | HISTC_COMM, | 42 | HISTC_COMM, |
| 43 | HISTC_PARENT, | 43 | HISTC_PARENT, |
| 44 | HISTC_CPU, | 44 | HISTC_CPU, |
| 45 | HISTC_MISPREDICT, | ||
| 46 | HISTC_SYMBOL_FROM, | ||
| 47 | HISTC_SYMBOL_TO, | ||
| 48 | HISTC_DSO_FROM, | ||
| 49 | HISTC_DSO_TO, | ||
| 45 | HISTC_NR_COLS, /* Last entry */ | 50 | HISTC_NR_COLS, /* Last entry */ |
| 46 | }; | 51 | }; |
| 47 | 52 | ||
| @@ -74,6 +79,12 @@ int hist_entry__snprintf(struct hist_entry *self, char *bf, size_t size, | |||
| 74 | struct hists *hists); | 79 | struct hists *hists); |
| 75 | void hist_entry__free(struct hist_entry *); | 80 | void hist_entry__free(struct hist_entry *); |
| 76 | 81 | ||
| 82 | struct hist_entry *__hists__add_branch_entry(struct hists *self, | ||
| 83 | struct addr_location *al, | ||
| 84 | struct symbol *sym_parent, | ||
| 85 | struct branch_info *bi, | ||
| 86 | u64 period); | ||
| 87 | |||
| 77 | void hists__output_resort(struct hists *self); | 88 | void hists__output_resort(struct hists *self); |
| 78 | void hists__output_resort_threaded(struct hists *hists); | 89 | void hists__output_resort_threaded(struct hists *hists); |
| 79 | void hists__collapse_resort(struct hists *self); | 90 | void hists__collapse_resort(struct hists *self); |
diff --git a/tools/perf/util/session.c b/tools/perf/util/session.c index 9f833cf9c6a9..002ebbf59f48 100644 --- a/tools/perf/util/session.c +++ b/tools/perf/util/session.c | |||
| @@ -24,7 +24,7 @@ static int perf_session__open(struct perf_session *self, bool force) | |||
| 24 | self->fd = STDIN_FILENO; | 24 | self->fd = STDIN_FILENO; |
| 25 | 25 | ||
| 26 | if (perf_session__read_header(self, self->fd) < 0) | 26 | if (perf_session__read_header(self, self->fd) < 0) |
| 27 | pr_err("incompatible file format"); | 27 | pr_err("incompatible file format (rerun with -v to learn more)"); |
| 28 | 28 | ||
| 29 | return 0; | 29 | return 0; |
| 30 | } | 30 | } |
| @@ -56,7 +56,7 @@ static int perf_session__open(struct perf_session *self, bool force) | |||
| 56 | } | 56 | } |
| 57 | 57 | ||
| 58 | if (perf_session__read_header(self, self->fd) < 0) { | 58 | if (perf_session__read_header(self, self->fd) < 0) { |
| 59 | pr_err("incompatible file format"); | 59 | pr_err("incompatible file format (rerun with -v to learn more)"); |
| 60 | goto out_close; | 60 | goto out_close; |
| 61 | } | 61 | } |
| 62 | 62 | ||
| @@ -229,6 +229,64 @@ static bool symbol__match_parent_regex(struct symbol *sym) | |||
| 229 | return 0; | 229 | return 0; |
| 230 | } | 230 | } |
| 231 | 231 | ||
| 232 | static const u8 cpumodes[] = { | ||
| 233 | PERF_RECORD_MISC_USER, | ||
| 234 | PERF_RECORD_MISC_KERNEL, | ||
| 235 | PERF_RECORD_MISC_GUEST_USER, | ||
| 236 | PERF_RECORD_MISC_GUEST_KERNEL | ||
| 237 | }; | ||
| 238 | #define NCPUMODES (sizeof(cpumodes)/sizeof(u8)) | ||
| 239 | |||
| 240 | static void ip__resolve_ams(struct machine *self, struct thread *thread, | ||
| 241 | struct addr_map_symbol *ams, | ||
| 242 | u64 ip) | ||
| 243 | { | ||
| 244 | struct addr_location al; | ||
| 245 | size_t i; | ||
| 246 | u8 m; | ||
| 247 | |||
| 248 | memset(&al, 0, sizeof(al)); | ||
| 249 | |||
| 250 | for (i = 0; i < NCPUMODES; i++) { | ||
| 251 | m = cpumodes[i]; | ||
| 252 | /* | ||
| 253 | * We cannot use the header.misc hint to determine whether a | ||
| 254 | * branch stack address is user, kernel, guest, hypervisor. | ||
| 255 | * Branches may straddle the kernel/user/hypervisor boundaries. | ||
| 256 | * Thus, we have to try consecutively until we find a match | ||
| 257 | * or else, the symbol is unknown | ||
| 258 | */ | ||
| 259 | thread__find_addr_location(thread, self, m, MAP__FUNCTION, | ||
| 260 | ip, &al, NULL); | ||
| 261 | if (al.sym) | ||
| 262 | goto found; | ||
| 263 | } | ||
| 264 | found: | ||
| 265 | ams->addr = ip; | ||
| 266 | ams->al_addr = al.addr; | ||
| 267 | ams->sym = al.sym; | ||
| 268 | ams->map = al.map; | ||
| 269 | } | ||
| 270 | |||
| 271 | struct branch_info *machine__resolve_bstack(struct machine *self, | ||
| 272 | struct thread *thr, | ||
| 273 | struct branch_stack *bs) | ||
| 274 | { | ||
| 275 | struct branch_info *bi; | ||
| 276 | unsigned int i; | ||
| 277 | |||
| 278 | bi = calloc(bs->nr, sizeof(struct branch_info)); | ||
| 279 | if (!bi) | ||
| 280 | return NULL; | ||
| 281 | |||
| 282 | for (i = 0; i < bs->nr; i++) { | ||
| 283 | ip__resolve_ams(self, thr, &bi[i].to, bs->entries[i].to); | ||
| 284 | ip__resolve_ams(self, thr, &bi[i].from, bs->entries[i].from); | ||
| 285 | bi[i].flags = bs->entries[i].flags; | ||
| 286 | } | ||
| 287 | return bi; | ||
| 288 | } | ||
| 289 | |||
| 232 | int machine__resolve_callchain(struct machine *self, struct perf_evsel *evsel, | 290 | int machine__resolve_callchain(struct machine *self, struct perf_evsel *evsel, |
| 233 | struct thread *thread, | 291 | struct thread *thread, |
| 234 | struct ip_callchain *chain, | 292 | struct ip_callchain *chain, |
| @@ -697,6 +755,18 @@ static void callchain__printf(struct perf_sample *sample) | |||
| 697 | i, sample->callchain->ips[i]); | 755 | i, sample->callchain->ips[i]); |
| 698 | } | 756 | } |
| 699 | 757 | ||
| 758 | static void branch_stack__printf(struct perf_sample *sample) | ||
| 759 | { | ||
| 760 | uint64_t i; | ||
| 761 | |||
| 762 | printf("... branch stack: nr:%" PRIu64 "\n", sample->branch_stack->nr); | ||
| 763 | |||
| 764 | for (i = 0; i < sample->branch_stack->nr; i++) | ||
| 765 | printf("..... %2"PRIu64": %016" PRIx64 " -> %016" PRIx64 "\n", | ||
| 766 | i, sample->branch_stack->entries[i].from, | ||
| 767 | sample->branch_stack->entries[i].to); | ||
| 768 | } | ||
| 769 | |||
| 700 | static void perf_session__print_tstamp(struct perf_session *session, | 770 | static void perf_session__print_tstamp(struct perf_session *session, |
| 701 | union perf_event *event, | 771 | union perf_event *event, |
| 702 | struct perf_sample *sample) | 772 | struct perf_sample *sample) |
| @@ -744,6 +814,9 @@ static void dump_sample(struct perf_session *session, union perf_event *event, | |||
| 744 | 814 | ||
| 745 | if (session->sample_type & PERF_SAMPLE_CALLCHAIN) | 815 | if (session->sample_type & PERF_SAMPLE_CALLCHAIN) |
| 746 | callchain__printf(sample); | 816 | callchain__printf(sample); |
| 817 | |||
| 818 | if (session->sample_type & PERF_SAMPLE_BRANCH_STACK) | ||
| 819 | branch_stack__printf(sample); | ||
| 747 | } | 820 | } |
| 748 | 821 | ||
| 749 | static struct machine * | 822 | static struct machine * |
diff --git a/tools/perf/util/session.h b/tools/perf/util/session.h index c8d90178e7de..7a5434c00565 100644 --- a/tools/perf/util/session.h +++ b/tools/perf/util/session.h | |||
| @@ -73,6 +73,10 @@ int perf_session__resolve_callchain(struct perf_session *self, struct perf_evsel | |||
| 73 | struct ip_callchain *chain, | 73 | struct ip_callchain *chain, |
| 74 | struct symbol **parent); | 74 | struct symbol **parent); |
| 75 | 75 | ||
| 76 | struct branch_info *machine__resolve_bstack(struct machine *self, | ||
| 77 | struct thread *thread, | ||
| 78 | struct branch_stack *bs); | ||
| 79 | |||
| 76 | bool perf_session__has_traces(struct perf_session *self, const char *msg); | 80 | bool perf_session__has_traces(struct perf_session *self, const char *msg); |
| 77 | 81 | ||
| 78 | void mem_bswap_64(void *src, int byte_size); | 82 | void mem_bswap_64(void *src, int byte_size); |
diff --git a/tools/perf/util/sort.c b/tools/perf/util/sort.c index 16da30d8d765..88dbcf6f9575 100644 --- a/tools/perf/util/sort.c +++ b/tools/perf/util/sort.c | |||
| @@ -8,6 +8,7 @@ const char default_sort_order[] = "comm,dso,symbol"; | |||
| 8 | const char *sort_order = default_sort_order; | 8 | const char *sort_order = default_sort_order; |
| 9 | int sort__need_collapse = 0; | 9 | int sort__need_collapse = 0; |
| 10 | int sort__has_parent = 0; | 10 | int sort__has_parent = 0; |
| 11 | int sort__branch_mode = -1; /* -1 = means not set */ | ||
| 11 | 12 | ||
| 12 | enum sort_type sort__first_dimension; | 13 | enum sort_type sort__first_dimension; |
| 13 | 14 | ||
| @@ -94,6 +95,26 @@ static int hist_entry__comm_snprintf(struct hist_entry *self, char *bf, | |||
| 94 | return repsep_snprintf(bf, size, "%*s", width, self->thread->comm); | 95 | return repsep_snprintf(bf, size, "%*s", width, self->thread->comm); |
| 95 | } | 96 | } |
| 96 | 97 | ||
| 98 | static int64_t _sort__dso_cmp(struct map *map_l, struct map *map_r) | ||
| 99 | { | ||
| 100 | struct dso *dso_l = map_l ? map_l->dso : NULL; | ||
| 101 | struct dso *dso_r = map_r ? map_r->dso : NULL; | ||
| 102 | const char *dso_name_l, *dso_name_r; | ||
| 103 | |||
| 104 | if (!dso_l || !dso_r) | ||
| 105 | return cmp_null(dso_l, dso_r); | ||
| 106 | |||
| 107 | if (verbose) { | ||
| 108 | dso_name_l = dso_l->long_name; | ||
| 109 | dso_name_r = dso_r->long_name; | ||
| 110 | } else { | ||
| 111 | dso_name_l = dso_l->short_name; | ||
| 112 | dso_name_r = dso_r->short_name; | ||
| 113 | } | ||
| 114 | |||
| 115 | return strcmp(dso_name_l, dso_name_r); | ||
| 116 | } | ||
| 117 | |||
| 97 | struct sort_entry sort_comm = { | 118 | struct sort_entry sort_comm = { |
| 98 | .se_header = "Command", | 119 | .se_header = "Command", |
| 99 | .se_cmp = sort__comm_cmp, | 120 | .se_cmp = sort__comm_cmp, |
| @@ -107,36 +128,74 @@ struct sort_entry sort_comm = { | |||
| 107 | static int64_t | 128 | static int64_t |
| 108 | sort__dso_cmp(struct hist_entry *left, struct hist_entry *right) | 129 | sort__dso_cmp(struct hist_entry *left, struct hist_entry *right) |
| 109 | { | 130 | { |
| 110 | struct dso *dso_l = left->ms.map ? left->ms.map->dso : NULL; | 131 | return _sort__dso_cmp(left->ms.map, right->ms.map); |
| 111 | struct dso *dso_r = right->ms.map ? right->ms.map->dso : NULL; | 132 | } |
| 112 | const char *dso_name_l, *dso_name_r; | ||
| 113 | 133 | ||
| 114 | if (!dso_l || !dso_r) | ||
| 115 | return cmp_null(dso_l, dso_r); | ||
| 116 | 134 | ||
| 117 | if (verbose) { | 135 | static int64_t _sort__sym_cmp(struct symbol *sym_l, struct symbol *sym_r, |
| 118 | dso_name_l = dso_l->long_name; | 136 | u64 ip_l, u64 ip_r) |
| 119 | dso_name_r = dso_r->long_name; | 137 | { |
| 120 | } else { | 138 | if (!sym_l || !sym_r) |
| 121 | dso_name_l = dso_l->short_name; | 139 | return cmp_null(sym_l, sym_r); |
| 122 | dso_name_r = dso_r->short_name; | 140 | |
| 141 | if (sym_l == sym_r) | ||
| 142 | return 0; | ||
| 143 | |||
| 144 | if (sym_l) | ||
| 145 | ip_l = sym_l->start; | ||
| 146 | if (sym_r) | ||
| 147 | ip_r = sym_r->start; | ||
| 148 | |||
| 149 | return (int64_t)(ip_r - ip_l); | ||
| 150 | } | ||
| 151 | |||
| 152 | static int _hist_entry__dso_snprintf(struct map *map, char *bf, | ||
| 153 | size_t size, unsigned int width) | ||
| 154 | { | ||
| 155 | if (map && map->dso) { | ||
| 156 | const char *dso_name = !verbose ? map->dso->short_name : | ||
| 157 | map->dso->long_name; | ||
| 158 | return repsep_snprintf(bf, size, "%-*s", width, dso_name); | ||
| 123 | } | 159 | } |
| 124 | 160 | ||
| 125 | return strcmp(dso_name_l, dso_name_r); | 161 | return repsep_snprintf(bf, size, "%-*s", width, "[unknown]"); |
| 126 | } | 162 | } |
| 127 | 163 | ||
| 128 | static int hist_entry__dso_snprintf(struct hist_entry *self, char *bf, | 164 | static int hist_entry__dso_snprintf(struct hist_entry *self, char *bf, |
| 129 | size_t size, unsigned int width) | 165 | size_t size, unsigned int width) |
| 130 | { | 166 | { |
| 131 | if (self->ms.map && self->ms.map->dso) { | 167 | return _hist_entry__dso_snprintf(self->ms.map, bf, size, width); |
| 132 | const char *dso_name = !verbose ? self->ms.map->dso->short_name : | 168 | } |
| 133 | self->ms.map->dso->long_name; | 169 | |
| 134 | return repsep_snprintf(bf, size, "%-*s", width, dso_name); | 170 | static int _hist_entry__sym_snprintf(struct map *map, struct symbol *sym, |
| 171 | u64 ip, char level, char *bf, size_t size, | ||
| 172 | unsigned int width __used) | ||
| 173 | { | ||
| 174 | size_t ret = 0; | ||
| 175 | |||
| 176 | if (verbose) { | ||
| 177 | char o = map ? dso__symtab_origin(map->dso) : '!'; | ||
| 178 | ret += repsep_snprintf(bf, size, "%-#*llx %c ", | ||
| 179 | BITS_PER_LONG / 4, ip, o); | ||
| 135 | } | 180 | } |
| 136 | 181 | ||
| 137 | return repsep_snprintf(bf, size, "%-*s", width, "[unknown]"); | 182 | ret += repsep_snprintf(bf + ret, size - ret, "[%c] ", level); |
| 183 | if (sym) | ||
| 184 | ret += repsep_snprintf(bf + ret, size - ret, "%-*s", | ||
| 185 | width - ret, | ||
| 186 | sym->name); | ||
| 187 | else { | ||
| 188 | size_t len = BITS_PER_LONG / 4; | ||
| 189 | ret += repsep_snprintf(bf + ret, size - ret, "%-#.*llx", | ||
| 190 | len, ip); | ||
| 191 | ret += repsep_snprintf(bf + ret, size - ret, "%-*s", | ||
| 192 | width - ret, ""); | ||
| 193 | } | ||
| 194 | |||
| 195 | return ret; | ||
| 138 | } | 196 | } |
| 139 | 197 | ||
| 198 | |||
| 140 | struct sort_entry sort_dso = { | 199 | struct sort_entry sort_dso = { |
| 141 | .se_header = "Shared Object", | 200 | .se_header = "Shared Object", |
| 142 | .se_cmp = sort__dso_cmp, | 201 | .se_cmp = sort__dso_cmp, |
| @@ -144,8 +203,14 @@ struct sort_entry sort_dso = { | |||
| 144 | .se_width_idx = HISTC_DSO, | 203 | .se_width_idx = HISTC_DSO, |
| 145 | }; | 204 | }; |
| 146 | 205 | ||
| 147 | /* --sort symbol */ | 206 | static int hist_entry__sym_snprintf(struct hist_entry *self, char *bf, |
| 207 | size_t size, unsigned int width __used) | ||
| 208 | { | ||
| 209 | return _hist_entry__sym_snprintf(self->ms.map, self->ms.sym, self->ip, | ||
| 210 | self->level, bf, size, width); | ||
| 211 | } | ||
| 148 | 212 | ||
| 213 | /* --sort symbol */ | ||
| 149 | static int64_t | 214 | static int64_t |
| 150 | sort__sym_cmp(struct hist_entry *left, struct hist_entry *right) | 215 | sort__sym_cmp(struct hist_entry *left, struct hist_entry *right) |
| 151 | { | 216 | { |
| @@ -163,31 +228,7 @@ sort__sym_cmp(struct hist_entry *left, struct hist_entry *right) | |||
| 163 | ip_l = left->ms.sym->start; | 228 | ip_l = left->ms.sym->start; |
| 164 | ip_r = right->ms.sym->start; | 229 | ip_r = right->ms.sym->start; |
| 165 | 230 | ||
| 166 | return (int64_t)(ip_r - ip_l); | 231 | return _sort__sym_cmp(left->ms.sym, right->ms.sym, ip_l, ip_r); |
| 167 | } | ||
| 168 | |||
| 169 | static int hist_entry__sym_snprintf(struct hist_entry *self, char *bf, | ||
| 170 | size_t size, unsigned int width __used) | ||
| 171 | { | ||
| 172 | size_t ret = 0; | ||
| 173 | |||
| 174 | if (verbose) { | ||
| 175 | char o = self->ms.map ? dso__symtab_origin(self->ms.map->dso) : '!'; | ||
| 176 | ret += repsep_snprintf(bf, size, "%-#*llx %c ", | ||
| 177 | BITS_PER_LONG / 4, self->ip, o); | ||
| 178 | } | ||
| 179 | |||
| 180 | if (!sort_dso.elide) | ||
| 181 | ret += repsep_snprintf(bf + ret, size - ret, "[%c] ", self->level); | ||
| 182 | |||
| 183 | if (self->ms.sym) | ||
| 184 | ret += repsep_snprintf(bf + ret, size - ret, "%s", | ||
| 185 | self->ms.sym->name); | ||
| 186 | else | ||
| 187 | ret += repsep_snprintf(bf + ret, size - ret, "%-#*llx", | ||
| 188 | BITS_PER_LONG / 4, self->ip); | ||
| 189 | |||
| 190 | return ret; | ||
| 191 | } | 232 | } |
| 192 | 233 | ||
| 193 | struct sort_entry sort_sym = { | 234 | struct sort_entry sort_sym = { |
| @@ -246,19 +287,155 @@ struct sort_entry sort_cpu = { | |||
| 246 | .se_width_idx = HISTC_CPU, | 287 | .se_width_idx = HISTC_CPU, |
| 247 | }; | 288 | }; |
| 248 | 289 | ||
| 290 | static int64_t | ||
| 291 | sort__dso_from_cmp(struct hist_entry *left, struct hist_entry *right) | ||
| 292 | { | ||
| 293 | return _sort__dso_cmp(left->branch_info->from.map, | ||
| 294 | right->branch_info->from.map); | ||
| 295 | } | ||
| 296 | |||
| 297 | static int hist_entry__dso_from_snprintf(struct hist_entry *self, char *bf, | ||
| 298 | size_t size, unsigned int width) | ||
| 299 | { | ||
| 300 | return _hist_entry__dso_snprintf(self->branch_info->from.map, | ||
| 301 | bf, size, width); | ||
| 302 | } | ||
| 303 | |||
| 304 | struct sort_entry sort_dso_from = { | ||
| 305 | .se_header = "Source Shared Object", | ||
| 306 | .se_cmp = sort__dso_from_cmp, | ||
| 307 | .se_snprintf = hist_entry__dso_from_snprintf, | ||
| 308 | .se_width_idx = HISTC_DSO_FROM, | ||
| 309 | }; | ||
| 310 | |||
| 311 | static int64_t | ||
| 312 | sort__dso_to_cmp(struct hist_entry *left, struct hist_entry *right) | ||
| 313 | { | ||
| 314 | return _sort__dso_cmp(left->branch_info->to.map, | ||
| 315 | right->branch_info->to.map); | ||
| 316 | } | ||
| 317 | |||
| 318 | static int hist_entry__dso_to_snprintf(struct hist_entry *self, char *bf, | ||
| 319 | size_t size, unsigned int width) | ||
| 320 | { | ||
| 321 | return _hist_entry__dso_snprintf(self->branch_info->to.map, | ||
| 322 | bf, size, width); | ||
| 323 | } | ||
| 324 | |||
| 325 | static int64_t | ||
| 326 | sort__sym_from_cmp(struct hist_entry *left, struct hist_entry *right) | ||
| 327 | { | ||
| 328 | struct addr_map_symbol *from_l = &left->branch_info->from; | ||
| 329 | struct addr_map_symbol *from_r = &right->branch_info->from; | ||
| 330 | |||
| 331 | if (!from_l->sym && !from_r->sym) | ||
| 332 | return right->level - left->level; | ||
| 333 | |||
| 334 | return _sort__sym_cmp(from_l->sym, from_r->sym, from_l->addr, | ||
| 335 | from_r->addr); | ||
| 336 | } | ||
| 337 | |||
| 338 | static int64_t | ||
| 339 | sort__sym_to_cmp(struct hist_entry *left, struct hist_entry *right) | ||
| 340 | { | ||
| 341 | struct addr_map_symbol *to_l = &left->branch_info->to; | ||
| 342 | struct addr_map_symbol *to_r = &right->branch_info->to; | ||
| 343 | |||
| 344 | if (!to_l->sym && !to_r->sym) | ||
| 345 | return right->level - left->level; | ||
| 346 | |||
| 347 | return _sort__sym_cmp(to_l->sym, to_r->sym, to_l->addr, to_r->addr); | ||
| 348 | } | ||
| 349 | |||
| 350 | static int hist_entry__sym_from_snprintf(struct hist_entry *self, char *bf, | ||
| 351 | size_t size, unsigned int width __used) | ||
| 352 | { | ||
| 353 | struct addr_map_symbol *from = &self->branch_info->from; | ||
| 354 | return _hist_entry__sym_snprintf(from->map, from->sym, from->addr, | ||
| 355 | self->level, bf, size, width); | ||
| 356 | |||
| 357 | } | ||
| 358 | |||
| 359 | static int hist_entry__sym_to_snprintf(struct hist_entry *self, char *bf, | ||
| 360 | size_t size, unsigned int width __used) | ||
| 361 | { | ||
| 362 | struct addr_map_symbol *to = &self->branch_info->to; | ||
| 363 | return _hist_entry__sym_snprintf(to->map, to->sym, to->addr, | ||
| 364 | self->level, bf, size, width); | ||
| 365 | |||
| 366 | } | ||
| 367 | |||
| 368 | struct sort_entry sort_dso_to = { | ||
| 369 | .se_header = "Target Shared Object", | ||
| 370 | .se_cmp = sort__dso_to_cmp, | ||
| 371 | .se_snprintf = hist_entry__dso_to_snprintf, | ||
| 372 | .se_width_idx = HISTC_DSO_TO, | ||
| 373 | }; | ||
| 374 | |||
| 375 | struct sort_entry sort_sym_from = { | ||
| 376 | .se_header = "Source Symbol", | ||
| 377 | .se_cmp = sort__sym_from_cmp, | ||
| 378 | .se_snprintf = hist_entry__sym_from_snprintf, | ||
| 379 | .se_width_idx = HISTC_SYMBOL_FROM, | ||
| 380 | }; | ||
| 381 | |||
| 382 | struct sort_entry sort_sym_to = { | ||
| 383 | .se_header = "Target Symbol", | ||
| 384 | .se_cmp = sort__sym_to_cmp, | ||
| 385 | .se_snprintf = hist_entry__sym_to_snprintf, | ||
| 386 | .se_width_idx = HISTC_SYMBOL_TO, | ||
| 387 | }; | ||
| 388 | |||
| 389 | static int64_t | ||
| 390 | sort__mispredict_cmp(struct hist_entry *left, struct hist_entry *right) | ||
| 391 | { | ||
| 392 | const unsigned char mp = left->branch_info->flags.mispred != | ||
| 393 | right->branch_info->flags.mispred; | ||
| 394 | const unsigned char p = left->branch_info->flags.predicted != | ||
| 395 | right->branch_info->flags.predicted; | ||
| 396 | |||
| 397 | return mp || p; | ||
| 398 | } | ||
| 399 | |||
| 400 | static int hist_entry__mispredict_snprintf(struct hist_entry *self, char *bf, | ||
| 401 | size_t size, unsigned int width){ | ||
| 402 | static const char *out = "N/A"; | ||
| 403 | |||
| 404 | if (self->branch_info->flags.predicted) | ||
| 405 | out = "N"; | ||
| 406 | else if (self->branch_info->flags.mispred) | ||
| 407 | out = "Y"; | ||
| 408 | |||
| 409 | return repsep_snprintf(bf, size, "%-*s", width, out); | ||
| 410 | } | ||
| 411 | |||
| 412 | struct sort_entry sort_mispredict = { | ||
| 413 | .se_header = "Branch Mispredicted", | ||
| 414 | .se_cmp = sort__mispredict_cmp, | ||
| 415 | .se_snprintf = hist_entry__mispredict_snprintf, | ||
| 416 | .se_width_idx = HISTC_MISPREDICT, | ||
| 417 | }; | ||
| 418 | |||
| 249 | struct sort_dimension { | 419 | struct sort_dimension { |
| 250 | const char *name; | 420 | const char *name; |
| 251 | struct sort_entry *entry; | 421 | struct sort_entry *entry; |
| 252 | int taken; | 422 | int taken; |
| 253 | }; | 423 | }; |
| 254 | 424 | ||
| 425 | #define DIM(d, n, func) [d] = { .name = n, .entry = &(func) } | ||
| 426 | |||
| 255 | static struct sort_dimension sort_dimensions[] = { | 427 | static struct sort_dimension sort_dimensions[] = { |
| 256 | { .name = "pid", .entry = &sort_thread, }, | 428 | DIM(SORT_PID, "pid", sort_thread), |
| 257 | { .name = "comm", .entry = &sort_comm, }, | 429 | DIM(SORT_COMM, "comm", sort_comm), |
| 258 | { .name = "dso", .entry = &sort_dso, }, | 430 | DIM(SORT_DSO, "dso", sort_dso), |
| 259 | { .name = "symbol", .entry = &sort_sym, }, | 431 | DIM(SORT_DSO_FROM, "dso_from", sort_dso_from), |
| 260 | { .name = "parent", .entry = &sort_parent, }, | 432 | DIM(SORT_DSO_TO, "dso_to", sort_dso_to), |
| 261 | { .name = "cpu", .entry = &sort_cpu, }, | 433 | DIM(SORT_SYM, "symbol", sort_sym), |
| 434 | DIM(SORT_SYM_TO, "symbol_from", sort_sym_from), | ||
| 435 | DIM(SORT_SYM_FROM, "symbol_to", sort_sym_to), | ||
| 436 | DIM(SORT_PARENT, "parent", sort_parent), | ||
| 437 | DIM(SORT_CPU, "cpu", sort_cpu), | ||
| 438 | DIM(SORT_MISPREDICT, "mispredict", sort_mispredict), | ||
| 262 | }; | 439 | }; |
| 263 | 440 | ||
| 264 | int sort_dimension__add(const char *tok) | 441 | int sort_dimension__add(const char *tok) |
| @@ -270,7 +447,6 @@ int sort_dimension__add(const char *tok) | |||
| 270 | 447 | ||
| 271 | if (strncasecmp(tok, sd->name, strlen(tok))) | 448 | if (strncasecmp(tok, sd->name, strlen(tok))) |
| 272 | continue; | 449 | continue; |
| 273 | |||
| 274 | if (sd->entry == &sort_parent) { | 450 | if (sd->entry == &sort_parent) { |
| 275 | int ret = regcomp(&parent_regex, parent_pattern, REG_EXTENDED); | 451 | int ret = regcomp(&parent_regex, parent_pattern, REG_EXTENDED); |
| 276 | if (ret) { | 452 | if (ret) { |
| @@ -302,6 +478,16 @@ int sort_dimension__add(const char *tok) | |||
| 302 | sort__first_dimension = SORT_PARENT; | 478 | sort__first_dimension = SORT_PARENT; |
| 303 | else if (!strcmp(sd->name, "cpu")) | 479 | else if (!strcmp(sd->name, "cpu")) |
| 304 | sort__first_dimension = SORT_CPU; | 480 | sort__first_dimension = SORT_CPU; |
| 481 | else if (!strcmp(sd->name, "symbol_from")) | ||
| 482 | sort__first_dimension = SORT_SYM_FROM; | ||
| 483 | else if (!strcmp(sd->name, "symbol_to")) | ||
| 484 | sort__first_dimension = SORT_SYM_TO; | ||
| 485 | else if (!strcmp(sd->name, "dso_from")) | ||
| 486 | sort__first_dimension = SORT_DSO_FROM; | ||
| 487 | else if (!strcmp(sd->name, "dso_to")) | ||
| 488 | sort__first_dimension = SORT_DSO_TO; | ||
| 489 | else if (!strcmp(sd->name, "mispredict")) | ||
| 490 | sort__first_dimension = SORT_MISPREDICT; | ||
| 305 | } | 491 | } |
| 306 | 492 | ||
| 307 | list_add_tail(&sd->entry->list, &hist_entry__sort_list); | 493 | list_add_tail(&sd->entry->list, &hist_entry__sort_list); |
| @@ -309,7 +495,6 @@ int sort_dimension__add(const char *tok) | |||
| 309 | 495 | ||
| 310 | return 0; | 496 | return 0; |
| 311 | } | 497 | } |
| 312 | |||
| 313 | return -ESRCH; | 498 | return -ESRCH; |
| 314 | } | 499 | } |
| 315 | 500 | ||
diff --git a/tools/perf/util/sort.h b/tools/perf/util/sort.h index 3f67ae395752..472aa5a63a58 100644 --- a/tools/perf/util/sort.h +++ b/tools/perf/util/sort.h | |||
| @@ -31,11 +31,16 @@ extern const char *parent_pattern; | |||
| 31 | extern const char default_sort_order[]; | 31 | extern const char default_sort_order[]; |
| 32 | extern int sort__need_collapse; | 32 | extern int sort__need_collapse; |
| 33 | extern int sort__has_parent; | 33 | extern int sort__has_parent; |
| 34 | extern int sort__branch_mode; | ||
| 34 | extern char *field_sep; | 35 | extern char *field_sep; |
| 35 | extern struct sort_entry sort_comm; | 36 | extern struct sort_entry sort_comm; |
| 36 | extern struct sort_entry sort_dso; | 37 | extern struct sort_entry sort_dso; |
| 37 | extern struct sort_entry sort_sym; | 38 | extern struct sort_entry sort_sym; |
| 38 | extern struct sort_entry sort_parent; | 39 | extern struct sort_entry sort_parent; |
| 40 | extern struct sort_entry sort_dso_from; | ||
| 41 | extern struct sort_entry sort_dso_to; | ||
| 42 | extern struct sort_entry sort_sym_from; | ||
| 43 | extern struct sort_entry sort_sym_to; | ||
| 39 | extern enum sort_type sort__first_dimension; | 44 | extern enum sort_type sort__first_dimension; |
| 40 | 45 | ||
| 41 | /** | 46 | /** |
| @@ -72,6 +77,7 @@ struct hist_entry { | |||
| 72 | struct hist_entry *pair; | 77 | struct hist_entry *pair; |
| 73 | struct rb_root sorted_chain; | 78 | struct rb_root sorted_chain; |
| 74 | }; | 79 | }; |
| 80 | struct branch_info *branch_info; | ||
| 75 | struct callchain_root callchain[0]; | 81 | struct callchain_root callchain[0]; |
| 76 | }; | 82 | }; |
| 77 | 83 | ||
| @@ -82,6 +88,11 @@ enum sort_type { | |||
| 82 | SORT_SYM, | 88 | SORT_SYM, |
| 83 | SORT_PARENT, | 89 | SORT_PARENT, |
| 84 | SORT_CPU, | 90 | SORT_CPU, |
| 91 | SORT_DSO_FROM, | ||
| 92 | SORT_DSO_TO, | ||
| 93 | SORT_SYM_FROM, | ||
| 94 | SORT_SYM_TO, | ||
| 95 | SORT_MISPREDICT, | ||
| 85 | }; | 96 | }; |
| 86 | 97 | ||
| 87 | /* | 98 | /* |
diff --git a/tools/perf/util/symbol.h b/tools/perf/util/symbol.h index 2a683d4fc918..ac49ef208a5f 100644 --- a/tools/perf/util/symbol.h +++ b/tools/perf/util/symbol.h | |||
| @@ -5,6 +5,7 @@ | |||
| 5 | #include <stdbool.h> | 5 | #include <stdbool.h> |
| 6 | #include <stdint.h> | 6 | #include <stdint.h> |
| 7 | #include "map.h" | 7 | #include "map.h" |
| 8 | #include "../perf.h" | ||
| 8 | #include <linux/list.h> | 9 | #include <linux/list.h> |
| 9 | #include <linux/rbtree.h> | 10 | #include <linux/rbtree.h> |
| 10 | #include <stdio.h> | 11 | #include <stdio.h> |
| @@ -96,7 +97,11 @@ struct symbol_conf { | |||
| 96 | *col_width_list_str; | 97 | *col_width_list_str; |
| 97 | struct strlist *dso_list, | 98 | struct strlist *dso_list, |
| 98 | *comm_list, | 99 | *comm_list, |
| 99 | *sym_list; | 100 | *sym_list, |
| 101 | *dso_from_list, | ||
| 102 | *dso_to_list, | ||
| 103 | *sym_from_list, | ||
| 104 | *sym_to_list; | ||
| 100 | const char *symfs; | 105 | const char *symfs; |
| 101 | }; | 106 | }; |
| 102 | 107 | ||
| @@ -120,6 +125,19 @@ struct map_symbol { | |||
| 120 | bool has_children; | 125 | bool has_children; |
| 121 | }; | 126 | }; |
| 122 | 127 | ||
| 128 | struct addr_map_symbol { | ||
| 129 | struct map *map; | ||
| 130 | struct symbol *sym; | ||
| 131 | u64 addr; | ||
| 132 | u64 al_addr; | ||
| 133 | }; | ||
| 134 | |||
| 135 | struct branch_info { | ||
| 136 | struct addr_map_symbol from; | ||
| 137 | struct addr_map_symbol to; | ||
| 138 | struct branch_flags flags; | ||
| 139 | }; | ||
| 140 | |||
| 123 | struct addr_location { | 141 | struct addr_location { |
| 124 | struct thread *thread; | 142 | struct thread *thread; |
| 125 | struct map *map; | 143 | struct map *map; |
diff --git a/tools/perf/util/ui/browsers/hists.c b/tools/perf/util/ui/browsers/hists.c index bfba0490c098..de8ece8bcce3 100644 --- a/tools/perf/util/ui/browsers/hists.c +++ b/tools/perf/util/ui/browsers/hists.c | |||
| @@ -805,8 +805,11 @@ static struct hist_browser *hist_browser__new(struct hists *hists) | |||
| 805 | self->hists = hists; | 805 | self->hists = hists; |
| 806 | self->b.refresh = hist_browser__refresh; | 806 | self->b.refresh = hist_browser__refresh; |
| 807 | self->b.seek = ui_browser__hists_seek; | 807 | self->b.seek = ui_browser__hists_seek; |
| 808 | self->b.use_navkeypressed = true, | 808 | self->b.use_navkeypressed = true; |
| 809 | self->has_symbols = sort_sym.list.next != NULL; | 809 | if (sort__branch_mode == 1) |
| 810 | self->has_symbols = sort_sym_from.list.next != NULL; | ||
| 811 | else | ||
| 812 | self->has_symbols = sort_sym.list.next != NULL; | ||
| 810 | } | 813 | } |
| 811 | 814 | ||
| 812 | return self; | 815 | return self; |
| @@ -853,6 +856,16 @@ static int hists__browser_title(struct hists *self, char *bf, size_t size, | |||
| 853 | return printed; | 856 | return printed; |
| 854 | } | 857 | } |
| 855 | 858 | ||
| 859 | static inline void free_popup_options(char **options, int n) | ||
| 860 | { | ||
| 861 | int i; | ||
| 862 | |||
| 863 | for (i = 0; i < n; ++i) { | ||
| 864 | free(options[i]); | ||
| 865 | options[i] = NULL; | ||
| 866 | } | ||
| 867 | } | ||
| 868 | |||
| 856 | static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events, | 869 | static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events, |
| 857 | const char *helpline, const char *ev_name, | 870 | const char *helpline, const char *ev_name, |
| 858 | bool left_exits, | 871 | bool left_exits, |
| @@ -861,7 +874,10 @@ static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events, | |||
| 861 | { | 874 | { |
| 862 | struct hists *self = &evsel->hists; | 875 | struct hists *self = &evsel->hists; |
| 863 | struct hist_browser *browser = hist_browser__new(self); | 876 | struct hist_browser *browser = hist_browser__new(self); |
| 877 | struct branch_info *bi; | ||
| 864 | struct pstack *fstack; | 878 | struct pstack *fstack; |
| 879 | char *options[16]; | ||
| 880 | int nr_options = 0; | ||
| 865 | int key = -1; | 881 | int key = -1; |
| 866 | 882 | ||
| 867 | if (browser == NULL) | 883 | if (browser == NULL) |
| @@ -873,13 +889,16 @@ static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events, | |||
| 873 | 889 | ||
| 874 | ui_helpline__push(helpline); | 890 | ui_helpline__push(helpline); |
| 875 | 891 | ||
| 892 | memset(options, 0, sizeof(options)); | ||
| 893 | |||
| 876 | while (1) { | 894 | while (1) { |
| 877 | const struct thread *thread = NULL; | 895 | const struct thread *thread = NULL; |
| 878 | const struct dso *dso = NULL; | 896 | const struct dso *dso = NULL; |
| 879 | char *options[16]; | 897 | int choice = 0, |
| 880 | int nr_options = 0, choice = 0, i, | ||
| 881 | annotate = -2, zoom_dso = -2, zoom_thread = -2, | 898 | annotate = -2, zoom_dso = -2, zoom_thread = -2, |
| 882 | browse_map = -2; | 899 | annotate_f = -2, annotate_t = -2, browse_map = -2; |
| 900 | |||
| 901 | nr_options = 0; | ||
| 883 | 902 | ||
| 884 | key = hist_browser__run(browser, ev_name, timer, arg, delay_secs); | 903 | key = hist_browser__run(browser, ev_name, timer, arg, delay_secs); |
| 885 | 904 | ||
| @@ -887,7 +906,6 @@ static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events, | |||
| 887 | thread = hist_browser__selected_thread(browser); | 906 | thread = hist_browser__selected_thread(browser); |
| 888 | dso = browser->selection->map ? browser->selection->map->dso : NULL; | 907 | dso = browser->selection->map ? browser->selection->map->dso : NULL; |
| 889 | } | 908 | } |
| 890 | |||
| 891 | switch (key) { | 909 | switch (key) { |
| 892 | case K_TAB: | 910 | case K_TAB: |
| 893 | case K_UNTAB: | 911 | case K_UNTAB: |
| @@ -902,7 +920,7 @@ static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events, | |||
| 902 | if (!browser->has_symbols) { | 920 | if (!browser->has_symbols) { |
| 903 | ui_browser__warning(&browser->b, delay_secs * 2, | 921 | ui_browser__warning(&browser->b, delay_secs * 2, |
| 904 | "Annotation is only available for symbolic views, " | 922 | "Annotation is only available for symbolic views, " |
| 905 | "include \"sym\" in --sort to use it."); | 923 | "include \"sym*\" in --sort to use it."); |
| 906 | continue; | 924 | continue; |
| 907 | } | 925 | } |
| 908 | 926 | ||
| @@ -972,12 +990,34 @@ static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events, | |||
| 972 | if (!browser->has_symbols) | 990 | if (!browser->has_symbols) |
| 973 | goto add_exit_option; | 991 | goto add_exit_option; |
| 974 | 992 | ||
| 975 | if (browser->selection != NULL && | 993 | if (sort__branch_mode == 1) { |
| 976 | browser->selection->sym != NULL && | 994 | bi = browser->he_selection->branch_info; |
| 977 | !browser->selection->map->dso->annotate_warned && | 995 | if (browser->selection != NULL && |
| 978 | asprintf(&options[nr_options], "Annotate %s", | 996 | bi && |
| 979 | browser->selection->sym->name) > 0) | 997 | bi->from.sym != NULL && |
| 980 | annotate = nr_options++; | 998 | !bi->from.map->dso->annotate_warned && |
| 999 | asprintf(&options[nr_options], "Annotate %s", | ||
| 1000 | bi->from.sym->name) > 0) | ||
| 1001 | annotate_f = nr_options++; | ||
| 1002 | |||
| 1003 | if (browser->selection != NULL && | ||
| 1004 | bi && | ||
| 1005 | bi->to.sym != NULL && | ||
| 1006 | !bi->to.map->dso->annotate_warned && | ||
| 1007 | (bi->to.sym != bi->from.sym || | ||
| 1008 | bi->to.map->dso != bi->from.map->dso) && | ||
| 1009 | asprintf(&options[nr_options], "Annotate %s", | ||
| 1010 | bi->to.sym->name) > 0) | ||
| 1011 | annotate_t = nr_options++; | ||
| 1012 | } else { | ||
| 1013 | |||
| 1014 | if (browser->selection != NULL && | ||
| 1015 | browser->selection->sym != NULL && | ||
| 1016 | !browser->selection->map->dso->annotate_warned && | ||
| 1017 | asprintf(&options[nr_options], "Annotate %s", | ||
| 1018 | browser->selection->sym->name) > 0) | ||
| 1019 | annotate = nr_options++; | ||
| 1020 | } | ||
| 981 | 1021 | ||
| 982 | if (thread != NULL && | 1022 | if (thread != NULL && |
| 983 | asprintf(&options[nr_options], "Zoom %s %s(%d) thread", | 1023 | asprintf(&options[nr_options], "Zoom %s %s(%d) thread", |
| @@ -998,25 +1038,39 @@ static int perf_evsel__hists_browse(struct perf_evsel *evsel, int nr_events, | |||
| 998 | browse_map = nr_options++; | 1038 | browse_map = nr_options++; |
| 999 | add_exit_option: | 1039 | add_exit_option: |
| 1000 | options[nr_options++] = (char *)"Exit"; | 1040 | options[nr_options++] = (char *)"Exit"; |
| 1001 | 1041 | retry_popup_menu: | |
| 1002 | choice = ui__popup_menu(nr_options, options); | 1042 | choice = ui__popup_menu(nr_options, options); |
| 1003 | 1043 | ||
| 1004 | for (i = 0; i < nr_options - 1; ++i) | ||
| 1005 | free(options[i]); | ||
| 1006 | |||
| 1007 | if (choice == nr_options - 1) | 1044 | if (choice == nr_options - 1) |
| 1008 | break; | 1045 | break; |
| 1009 | 1046 | ||
| 1010 | if (choice == -1) | 1047 | if (choice == -1) { |
| 1048 | free_popup_options(options, nr_options - 1); | ||
| 1011 | continue; | 1049 | continue; |
| 1050 | } | ||
| 1012 | 1051 | ||
| 1013 | if (choice == annotate) { | 1052 | if (choice == annotate || choice == annotate_t || choice == annotate_f) { |
| 1014 | struct hist_entry *he; | 1053 | struct hist_entry *he; |
| 1015 | int err; | 1054 | int err; |
| 1016 | do_annotate: | 1055 | do_annotate: |
| 1017 | he = hist_browser__selected_entry(browser); | 1056 | he = hist_browser__selected_entry(browser); |
| 1018 | if (he == NULL) | 1057 | if (he == NULL) |
| 1019 | continue; | 1058 | continue; |
| 1059 | |||
| 1060 | /* | ||
| 1061 | * we stash the branch_info symbol + map into the | ||
| 1062 | * the ms so we don't have to rewrite all the annotation | ||
| 1063 | * code to use branch_info. | ||
| 1064 | * in branch mode, the ms struct is not used | ||
| 1065 | */ | ||
| 1066 | if (choice == annotate_f) { | ||
| 1067 | he->ms.sym = he->branch_info->from.sym; | ||
| 1068 | he->ms.map = he->branch_info->from.map; | ||
| 1069 | } else if (choice == annotate_t) { | ||
| 1070 | he->ms.sym = he->branch_info->to.sym; | ||
| 1071 | he->ms.map = he->branch_info->to.map; | ||
| 1072 | } | ||
| 1073 | |||
| 1020 | /* | 1074 | /* |
| 1021 | * Don't let this be freed, say, by hists__decay_entry. | 1075 | * Don't let this be freed, say, by hists__decay_entry. |
| 1022 | */ | 1076 | */ |
| @@ -1024,9 +1078,18 @@ do_annotate: | |||
| 1024 | err = hist_entry__tui_annotate(he, evsel->idx, | 1078 | err = hist_entry__tui_annotate(he, evsel->idx, |
| 1025 | timer, arg, delay_secs); | 1079 | timer, arg, delay_secs); |
| 1026 | he->used = false; | 1080 | he->used = false; |
| 1081 | /* | ||
| 1082 | * offer option to annotate the other branch source or target | ||
| 1083 | * (if they exists) when returning from annotate | ||
| 1084 | */ | ||
| 1085 | if ((err == 'q' || err == CTRL('c')) | ||
| 1086 | && annotate_t != -2 && annotate_f != -2) | ||
| 1087 | goto retry_popup_menu; | ||
| 1088 | |||
| 1027 | ui_browser__update_nr_entries(&browser->b, browser->hists->nr_entries); | 1089 | ui_browser__update_nr_entries(&browser->b, browser->hists->nr_entries); |
| 1028 | if (err) | 1090 | if (err) |
| 1029 | ui_browser__handle_resize(&browser->b); | 1091 | ui_browser__handle_resize(&browser->b); |
| 1092 | |||
| 1030 | } else if (choice == browse_map) | 1093 | } else if (choice == browse_map) |
| 1031 | map__browse(browser->selection->map); | 1094 | map__browse(browser->selection->map); |
| 1032 | else if (choice == zoom_dso) { | 1095 | else if (choice == zoom_dso) { |
| @@ -1072,6 +1135,7 @@ out_free_stack: | |||
| 1072 | pstack__delete(fstack); | 1135 | pstack__delete(fstack); |
| 1073 | out: | 1136 | out: |
| 1074 | hist_browser__delete(browser); | 1137 | hist_browser__delete(browser); |
| 1138 | free_popup_options(options, nr_options - 1); | ||
| 1075 | return key; | 1139 | return key; |
| 1076 | } | 1140 | } |
| 1077 | 1141 | ||
