diff options
author | David Ahern <dsahern@gmail.com> | 2016-11-16 01:06:29 -0500 |
---|---|---|
committer | Arnaldo Carvalho de Melo <acme@redhat.com> | 2016-11-23 08:44:07 -0500 |
commit | 49394a2a24c78ce034fe0e732b1b54922c23fd92 (patch) | |
tree | 02707b0cb678b1e91b8f177d9b4b2f46ebdacc24 | |
parent | 69b7e48070ca2ecee7498166259b5826b22e8b2e (diff) |
perf sched timehist: Introduce timehist command
'perf sched timehist' provides an analysis of scheduling events.
Example usage:
perf sched record -- sleep 1
perf sched timehist
By default it shows the individual schedule events, including the wait
time (time between sched-out and next sched-in events for the task), the
task scheduling delay (time between wakeup and actually running) and run
time for the task:
time cpu task name wait time sch delay run time
[tid/pid] (msec) (msec) (msec)
-------------- ------ -------------------- --------- --------- ---------
79371.874569 [0011] gcc[31949] 0.014 0.000 1.148
79371.874591 [0010] gcc[31951] 0.000 0.000 0.024
79371.874603 [0010] migration/10[59] 3.350 0.004 0.011
79371.874604 [0011] <idle> 1.148 0.000 0.035
79371.874723 [0005] <idle> 0.016 0.000 1.383
79371.874746 [0005] gcc[31949] 0.153 0.078 0.022
...
Times are in msec.usec.
Committer note:
Add above explanation as the 'perf sched timehist' entry for 'man
perf-sched'.
Signed-off-by: David Ahern <dsahern@gmail.com>
Signed-off-by: Namhyung Kim <namhyung@kernel.org>
Acked-by: Ingo Molnar <mingo@kernel.org>
Acked-by: Jiri Olsa <jolsa@kernel.org>
Tested-by: Arnaldo Carvalho de Melo <acme@redhat.com>
Cc: Andi Kleen <andi@firstfloor.org>
Cc: Peter Zijlstra <a.p.zijlstra@chello.nl>
Cc: Stephane Eranian <eranian@google.com>
Link: http://lkml.kernel.org/r/20161116060634.28477-4-namhyung@kernel.org
Signed-off-by: Arnaldo Carvalho de Melo <acme@redhat.com>
-rw-r--r-- | tools/perf/Documentation/perf-sched.txt | 50 | ||||
-rw-r--r-- | tools/perf/builtin-sched.c | 594 |
2 files changed, 637 insertions, 7 deletions
diff --git a/tools/perf/Documentation/perf-sched.txt b/tools/perf/Documentation/perf-sched.txt index 1cc08cc47ac5..72730da307b9 100644 --- a/tools/perf/Documentation/perf-sched.txt +++ b/tools/perf/Documentation/perf-sched.txt | |||
@@ -8,11 +8,11 @@ perf-sched - Tool to trace/measure scheduler properties (latencies) | |||
8 | SYNOPSIS | 8 | SYNOPSIS |
9 | -------- | 9 | -------- |
10 | [verse] | 10 | [verse] |
11 | 'perf sched' {record|latency|map|replay|script} | 11 | 'perf sched' {record|latency|map|replay|script|timehist} |
12 | 12 | ||
13 | DESCRIPTION | 13 | DESCRIPTION |
14 | ----------- | 14 | ----------- |
15 | There are five variants of perf sched: | 15 | There are several variants of 'perf sched': |
16 | 16 | ||
17 | 'perf sched record <command>' to record the scheduling events | 17 | 'perf sched record <command>' to record the scheduling events |
18 | of an arbitrary workload. | 18 | of an arbitrary workload. |
@@ -36,6 +36,30 @@ There are five variants of perf sched: | |||
36 | are running on a CPU. A '*' denotes the CPU that had the event, and | 36 | are running on a CPU. A '*' denotes the CPU that had the event, and |
37 | a dot signals an idle CPU. | 37 | a dot signals an idle CPU. |
38 | 38 | ||
39 | 'perf sched timehist' provides an analysis of scheduling events. | ||
40 | |||
41 | Example usage: | ||
42 | perf sched record -- sleep 1 | ||
43 | perf sched timehist | ||
44 | |||
45 | By default it shows the individual schedule events, including the wait | ||
46 | time (time between sched-out and next sched-in events for the task), the | ||
47 | task scheduling delay (time between wakeup and actually running) and run | ||
48 | time for the task: | ||
49 | |||
50 | time cpu task name wait time sch delay run time | ||
51 | [tid/pid] (msec) (msec) (msec) | ||
52 | -------------- ------ -------------------- --------- --------- --------- | ||
53 | 79371.874569 [0011] gcc[31949] 0.014 0.000 1.148 | ||
54 | 79371.874591 [0010] gcc[31951] 0.000 0.000 0.024 | ||
55 | 79371.874603 [0010] migration/10[59] 3.350 0.004 0.011 | ||
56 | 79371.874604 [0011] <idle> 1.148 0.000 0.035 | ||
57 | 79371.874723 [0005] <idle> 0.016 0.000 1.383 | ||
58 | 79371.874746 [0005] gcc[31949] 0.153 0.078 0.022 | ||
59 | ... | ||
60 | |||
61 | Times are in msec.usec. | ||
62 | |||
39 | OPTIONS | 63 | OPTIONS |
40 | ------- | 64 | ------- |
41 | -i:: | 65 | -i:: |
@@ -66,6 +90,28 @@ OPTIONS for 'perf sched map' | |||
66 | --color-pids:: | 90 | --color-pids:: |
67 | Highlight the given pids. | 91 | Highlight the given pids. |
68 | 92 | ||
93 | OPTIONS for 'perf sched timehist' | ||
94 | --------------------------------- | ||
95 | -k:: | ||
96 | --vmlinux=<file>:: | ||
97 | vmlinux pathname | ||
98 | |||
99 | --kallsyms=<file>:: | ||
100 | kallsyms pathname | ||
101 | |||
102 | -s:: | ||
103 | --summary:: | ||
104 | Show only a summary of scheduling by thread with min, max, and average | ||
105 | run times (in sec) and relative stddev. | ||
106 | |||
107 | -S:: | ||
108 | --with-summary:: | ||
109 | Show all scheduling events followed by a summary by thread with min, | ||
110 | max, and average run times (in sec) and relative stddev. | ||
111 | |||
112 | --symfs=<directory>:: | ||
113 | Look for files with symbols relative to this directory. | ||
114 | |||
69 | SEE ALSO | 115 | SEE ALSO |
70 | -------- | 116 | -------- |
71 | linkperf:perf-record[1] | 117 | linkperf:perf-record[1] |
diff --git a/tools/perf/builtin-sched.c b/tools/perf/builtin-sched.c index fb3441211e4b..c0ac0c9557e8 100644 --- a/tools/perf/builtin-sched.c +++ b/tools/perf/builtin-sched.c | |||
@@ -13,12 +13,14 @@ | |||
13 | #include "util/cloexec.h" | 13 | #include "util/cloexec.h" |
14 | #include "util/thread_map.h" | 14 | #include "util/thread_map.h" |
15 | #include "util/color.h" | 15 | #include "util/color.h" |
16 | #include "util/stat.h" | ||
16 | 17 | ||
17 | #include <subcmd/parse-options.h> | 18 | #include <subcmd/parse-options.h> |
18 | #include "util/trace-event.h" | 19 | #include "util/trace-event.h" |
19 | 20 | ||
20 | #include "util/debug.h" | 21 | #include "util/debug.h" |
21 | 22 | ||
23 | #include <linux/log2.h> | ||
22 | #include <sys/prctl.h> | 24 | #include <sys/prctl.h> |
23 | #include <sys/resource.h> | 25 | #include <sys/resource.h> |
24 | 26 | ||
@@ -194,6 +196,29 @@ struct perf_sched { | |||
194 | struct perf_sched_map map; | 196 | struct perf_sched_map map; |
195 | }; | 197 | }; |
196 | 198 | ||
199 | /* per thread run time data */ | ||
200 | struct thread_runtime { | ||
201 | u64 last_time; /* time of previous sched in/out event */ | ||
202 | u64 dt_run; /* run time */ | ||
203 | u64 dt_wait; /* time between CPU access (off cpu) */ | ||
204 | u64 dt_delay; /* time between wakeup and sched-in */ | ||
205 | u64 ready_to_run; /* time of wakeup */ | ||
206 | |||
207 | struct stats run_stats; | ||
208 | u64 total_run_time; | ||
209 | }; | ||
210 | |||
211 | /* per event run time data */ | ||
212 | struct evsel_runtime { | ||
213 | u64 *last_time; /* time this event was last seen per cpu */ | ||
214 | u32 ncpu; /* highest cpu slot allocated */ | ||
215 | }; | ||
216 | |||
217 | /* track idle times per cpu */ | ||
218 | static struct thread **idle_threads; | ||
219 | static int idle_max_cpu; | ||
220 | static char idle_comm[] = "<idle>"; | ||
221 | |||
197 | static u64 get_nsecs(void) | 222 | static u64 get_nsecs(void) |
198 | { | 223 | { |
199 | struct timespec ts; | 224 | struct timespec ts; |
@@ -1654,6 +1679,546 @@ out_delete: | |||
1654 | return rc; | 1679 | return rc; |
1655 | } | 1680 | } |
1656 | 1681 | ||
1682 | /* | ||
1683 | * scheduling times are printed as msec.usec | ||
1684 | */ | ||
1685 | static inline void print_sched_time(unsigned long long nsecs, int width) | ||
1686 | { | ||
1687 | unsigned long msecs; | ||
1688 | unsigned long usecs; | ||
1689 | |||
1690 | msecs = nsecs / NSEC_PER_MSEC; | ||
1691 | nsecs -= msecs * NSEC_PER_MSEC; | ||
1692 | usecs = nsecs / NSEC_PER_USEC; | ||
1693 | printf("%*lu.%03lu ", width, msecs, usecs); | ||
1694 | } | ||
1695 | |||
1696 | /* | ||
1697 | * returns runtime data for event, allocating memory for it the | ||
1698 | * first time it is used. | ||
1699 | */ | ||
1700 | static struct evsel_runtime *perf_evsel__get_runtime(struct perf_evsel *evsel) | ||
1701 | { | ||
1702 | struct evsel_runtime *r = evsel->priv; | ||
1703 | |||
1704 | if (r == NULL) { | ||
1705 | r = zalloc(sizeof(struct evsel_runtime)); | ||
1706 | evsel->priv = r; | ||
1707 | } | ||
1708 | |||
1709 | return r; | ||
1710 | } | ||
1711 | |||
1712 | /* | ||
1713 | * save last time event was seen per cpu | ||
1714 | */ | ||
1715 | static void perf_evsel__save_time(struct perf_evsel *evsel, | ||
1716 | u64 timestamp, u32 cpu) | ||
1717 | { | ||
1718 | struct evsel_runtime *r = perf_evsel__get_runtime(evsel); | ||
1719 | |||
1720 | if (r == NULL) | ||
1721 | return; | ||
1722 | |||
1723 | if ((cpu >= r->ncpu) || (r->last_time == NULL)) { | ||
1724 | int i, n = __roundup_pow_of_two(cpu+1); | ||
1725 | void *p = r->last_time; | ||
1726 | |||
1727 | p = realloc(r->last_time, n * sizeof(u64)); | ||
1728 | if (!p) | ||
1729 | return; | ||
1730 | |||
1731 | r->last_time = p; | ||
1732 | for (i = r->ncpu; i < n; ++i) | ||
1733 | r->last_time[i] = (u64) 0; | ||
1734 | |||
1735 | r->ncpu = n; | ||
1736 | } | ||
1737 | |||
1738 | r->last_time[cpu] = timestamp; | ||
1739 | } | ||
1740 | |||
1741 | /* returns last time this event was seen on the given cpu */ | ||
1742 | static u64 perf_evsel__get_time(struct perf_evsel *evsel, u32 cpu) | ||
1743 | { | ||
1744 | struct evsel_runtime *r = perf_evsel__get_runtime(evsel); | ||
1745 | |||
1746 | if ((r == NULL) || (r->last_time == NULL) || (cpu >= r->ncpu)) | ||
1747 | return 0; | ||
1748 | |||
1749 | return r->last_time[cpu]; | ||
1750 | } | ||
1751 | |||
1752 | static int comm_width = 20; | ||
1753 | |||
1754 | static char *timehist_get_commstr(struct thread *thread) | ||
1755 | { | ||
1756 | static char str[32]; | ||
1757 | const char *comm = thread__comm_str(thread); | ||
1758 | pid_t tid = thread->tid; | ||
1759 | pid_t pid = thread->pid_; | ||
1760 | int n; | ||
1761 | |||
1762 | if (pid == 0) | ||
1763 | n = scnprintf(str, sizeof(str), "%s", comm); | ||
1764 | |||
1765 | else if (tid != pid) | ||
1766 | n = scnprintf(str, sizeof(str), "%s[%d/%d]", comm, tid, pid); | ||
1767 | |||
1768 | else | ||
1769 | n = scnprintf(str, sizeof(str), "%s[%d]", comm, tid); | ||
1770 | |||
1771 | if (n > comm_width) | ||
1772 | comm_width = n; | ||
1773 | |||
1774 | return str; | ||
1775 | } | ||
1776 | |||
1777 | static void timehist_header(void) | ||
1778 | { | ||
1779 | printf("%15s %6s ", "time", "cpu"); | ||
1780 | |||
1781 | printf(" %-20s %9s %9s %9s", | ||
1782 | "task name", "wait time", "sch delay", "run time"); | ||
1783 | |||
1784 | printf("\n"); | ||
1785 | |||
1786 | /* | ||
1787 | * units row | ||
1788 | */ | ||
1789 | printf("%15s %-6s ", "", ""); | ||
1790 | |||
1791 | printf(" %-20s %9s %9s %9s\n", "[tid/pid]", "(msec)", "(msec)", "(msec)"); | ||
1792 | |||
1793 | /* | ||
1794 | * separator | ||
1795 | */ | ||
1796 | printf("%.15s %.6s ", graph_dotted_line, graph_dotted_line); | ||
1797 | |||
1798 | printf(" %.20s %.9s %.9s %.9s", | ||
1799 | graph_dotted_line, graph_dotted_line, graph_dotted_line, | ||
1800 | graph_dotted_line); | ||
1801 | |||
1802 | printf("\n"); | ||
1803 | } | ||
1804 | |||
1805 | static void timehist_print_sample(struct perf_sample *sample, | ||
1806 | struct thread *thread) | ||
1807 | { | ||
1808 | struct thread_runtime *tr = thread__priv(thread); | ||
1809 | char tstr[64]; | ||
1810 | |||
1811 | timestamp__scnprintf_usec(sample->time, tstr, sizeof(tstr)); | ||
1812 | printf("%15s [%04d] ", tstr, sample->cpu); | ||
1813 | |||
1814 | printf(" %-*s ", comm_width, timehist_get_commstr(thread)); | ||
1815 | |||
1816 | print_sched_time(tr->dt_wait, 6); | ||
1817 | print_sched_time(tr->dt_delay, 6); | ||
1818 | print_sched_time(tr->dt_run, 6); | ||
1819 | printf("\n"); | ||
1820 | } | ||
1821 | |||
1822 | /* | ||
1823 | * Explanation of delta-time stats: | ||
1824 | * | ||
1825 | * t = time of current schedule out event | ||
1826 | * tprev = time of previous sched out event | ||
1827 | * also time of schedule-in event for current task | ||
1828 | * last_time = time of last sched change event for current task | ||
1829 | * (i.e, time process was last scheduled out) | ||
1830 | * ready_to_run = time of wakeup for current task | ||
1831 | * | ||
1832 | * -----|------------|------------|------------|------ | ||
1833 | * last ready tprev t | ||
1834 | * time to run | ||
1835 | * | ||
1836 | * |-------- dt_wait --------| | ||
1837 | * |- dt_delay -|-- dt_run --| | ||
1838 | * | ||
1839 | * dt_run = run time of current task | ||
1840 | * dt_wait = time between last schedule out event for task and tprev | ||
1841 | * represents time spent off the cpu | ||
1842 | * dt_delay = time between wakeup and schedule-in of task | ||
1843 | */ | ||
1844 | |||
1845 | static void timehist_update_runtime_stats(struct thread_runtime *r, | ||
1846 | u64 t, u64 tprev) | ||
1847 | { | ||
1848 | r->dt_delay = 0; | ||
1849 | r->dt_wait = 0; | ||
1850 | r->dt_run = 0; | ||
1851 | if (tprev) { | ||
1852 | r->dt_run = t - tprev; | ||
1853 | if (r->ready_to_run) { | ||
1854 | if (r->ready_to_run > tprev) | ||
1855 | pr_debug("time travel: wakeup time for task > previous sched_switch event\n"); | ||
1856 | else | ||
1857 | r->dt_delay = tprev - r->ready_to_run; | ||
1858 | } | ||
1859 | |||
1860 | if (r->last_time > tprev) | ||
1861 | pr_debug("time travel: last sched out time for task > previous sched_switch event\n"); | ||
1862 | else if (r->last_time) | ||
1863 | r->dt_wait = tprev - r->last_time; | ||
1864 | } | ||
1865 | |||
1866 | update_stats(&r->run_stats, r->dt_run); | ||
1867 | r->total_run_time += r->dt_run; | ||
1868 | } | ||
1869 | |||
1870 | static bool is_idle_sample(struct perf_sample *sample, | ||
1871 | struct perf_evsel *evsel) | ||
1872 | { | ||
1873 | /* pid 0 == swapper == idle task */ | ||
1874 | if (sample->pid == 0) | ||
1875 | return true; | ||
1876 | |||
1877 | if (strcmp(perf_evsel__name(evsel), "sched:sched_switch") == 0) { | ||
1878 | if (perf_evsel__intval(evsel, sample, "prev_pid") == 0) | ||
1879 | return true; | ||
1880 | } | ||
1881 | return false; | ||
1882 | } | ||
1883 | |||
1884 | /* | ||
1885 | * Track idle stats per cpu by maintaining a local thread | ||
1886 | * struct for the idle task on each cpu. | ||
1887 | */ | ||
1888 | static int init_idle_threads(int ncpu) | ||
1889 | { | ||
1890 | int i; | ||
1891 | |||
1892 | idle_threads = zalloc(ncpu * sizeof(struct thread *)); | ||
1893 | if (!idle_threads) | ||
1894 | return -ENOMEM; | ||
1895 | |||
1896 | idle_max_cpu = ncpu - 1; | ||
1897 | |||
1898 | /* allocate the actual thread struct if needed */ | ||
1899 | for (i = 0; i < ncpu; ++i) { | ||
1900 | idle_threads[i] = thread__new(0, 0); | ||
1901 | if (idle_threads[i] == NULL) | ||
1902 | return -ENOMEM; | ||
1903 | |||
1904 | thread__set_comm(idle_threads[i], idle_comm, 0); | ||
1905 | } | ||
1906 | |||
1907 | return 0; | ||
1908 | } | ||
1909 | |||
1910 | static void free_idle_threads(void) | ||
1911 | { | ||
1912 | int i; | ||
1913 | |||
1914 | if (idle_threads == NULL) | ||
1915 | return; | ||
1916 | |||
1917 | for (i = 0; i <= idle_max_cpu; ++i) { | ||
1918 | if ((idle_threads[i])) | ||
1919 | thread__delete(idle_threads[i]); | ||
1920 | } | ||
1921 | |||
1922 | free(idle_threads); | ||
1923 | } | ||
1924 | |||
1925 | static struct thread *get_idle_thread(int cpu) | ||
1926 | { | ||
1927 | /* | ||
1928 | * expand/allocate array of pointers to local thread | ||
1929 | * structs if needed | ||
1930 | */ | ||
1931 | if ((cpu >= idle_max_cpu) || (idle_threads == NULL)) { | ||
1932 | int i, j = __roundup_pow_of_two(cpu+1); | ||
1933 | void *p; | ||
1934 | |||
1935 | p = realloc(idle_threads, j * sizeof(struct thread *)); | ||
1936 | if (!p) | ||
1937 | return NULL; | ||
1938 | |||
1939 | idle_threads = (struct thread **) p; | ||
1940 | i = idle_max_cpu ? idle_max_cpu + 1 : 0; | ||
1941 | for (; i < j; ++i) | ||
1942 | idle_threads[i] = NULL; | ||
1943 | |||
1944 | idle_max_cpu = j; | ||
1945 | } | ||
1946 | |||
1947 | /* allocate a new thread struct if needed */ | ||
1948 | if (idle_threads[cpu] == NULL) { | ||
1949 | idle_threads[cpu] = thread__new(0, 0); | ||
1950 | |||
1951 | if (idle_threads[cpu]) { | ||
1952 | idle_threads[cpu]->tid = 0; | ||
1953 | thread__set_comm(idle_threads[cpu], idle_comm, 0); | ||
1954 | } | ||
1955 | } | ||
1956 | |||
1957 | return idle_threads[cpu]; | ||
1958 | } | ||
1959 | |||
1960 | /* | ||
1961 | * handle runtime stats saved per thread | ||
1962 | */ | ||
1963 | static struct thread_runtime *thread__init_runtime(struct thread *thread) | ||
1964 | { | ||
1965 | struct thread_runtime *r; | ||
1966 | |||
1967 | r = zalloc(sizeof(struct thread_runtime)); | ||
1968 | if (!r) | ||
1969 | return NULL; | ||
1970 | |||
1971 | init_stats(&r->run_stats); | ||
1972 | thread__set_priv(thread, r); | ||
1973 | |||
1974 | return r; | ||
1975 | } | ||
1976 | |||
1977 | static struct thread_runtime *thread__get_runtime(struct thread *thread) | ||
1978 | { | ||
1979 | struct thread_runtime *tr; | ||
1980 | |||
1981 | tr = thread__priv(thread); | ||
1982 | if (tr == NULL) { | ||
1983 | tr = thread__init_runtime(thread); | ||
1984 | if (tr == NULL) | ||
1985 | pr_debug("Failed to malloc memory for runtime data.\n"); | ||
1986 | } | ||
1987 | |||
1988 | return tr; | ||
1989 | } | ||
1990 | |||
1991 | static struct thread *timehist_get_thread(struct perf_sample *sample, | ||
1992 | struct machine *machine, | ||
1993 | struct perf_evsel *evsel) | ||
1994 | { | ||
1995 | struct thread *thread; | ||
1996 | |||
1997 | if (is_idle_sample(sample, evsel)) { | ||
1998 | thread = get_idle_thread(sample->cpu); | ||
1999 | if (thread == NULL) | ||
2000 | pr_err("Failed to get idle thread for cpu %d.\n", sample->cpu); | ||
2001 | |||
2002 | } else { | ||
2003 | thread = machine__findnew_thread(machine, sample->pid, sample->tid); | ||
2004 | if (thread == NULL) { | ||
2005 | pr_debug("Failed to get thread for tid %d. skipping sample.\n", | ||
2006 | sample->tid); | ||
2007 | } | ||
2008 | } | ||
2009 | |||
2010 | return thread; | ||
2011 | } | ||
2012 | |||
2013 | static bool timehist_skip_sample(struct thread *thread) | ||
2014 | { | ||
2015 | bool rc = false; | ||
2016 | |||
2017 | if (thread__is_filtered(thread)) | ||
2018 | rc = true; | ||
2019 | |||
2020 | return rc; | ||
2021 | } | ||
2022 | |||
2023 | static int timehist_sched_wakeup_event(struct perf_tool *tool __maybe_unused, | ||
2024 | union perf_event *event __maybe_unused, | ||
2025 | struct perf_evsel *evsel, | ||
2026 | struct perf_sample *sample, | ||
2027 | struct machine *machine) | ||
2028 | { | ||
2029 | struct thread *thread; | ||
2030 | struct thread_runtime *tr = NULL; | ||
2031 | /* want pid of awakened task not pid in sample */ | ||
2032 | const u32 pid = perf_evsel__intval(evsel, sample, "pid"); | ||
2033 | |||
2034 | thread = machine__findnew_thread(machine, 0, pid); | ||
2035 | if (thread == NULL) | ||
2036 | return -1; | ||
2037 | |||
2038 | tr = thread__get_runtime(thread); | ||
2039 | if (tr == NULL) | ||
2040 | return -1; | ||
2041 | |||
2042 | if (tr->ready_to_run == 0) | ||
2043 | tr->ready_to_run = sample->time; | ||
2044 | |||
2045 | return 0; | ||
2046 | } | ||
2047 | |||
2048 | static int timehist_sched_change_event(struct perf_tool *tool __maybe_unused, | ||
2049 | union perf_event *event, | ||
2050 | struct perf_evsel *evsel, | ||
2051 | struct perf_sample *sample, | ||
2052 | struct machine *machine) | ||
2053 | { | ||
2054 | struct addr_location al; | ||
2055 | struct thread *thread; | ||
2056 | struct thread_runtime *tr = NULL; | ||
2057 | u64 tprev; | ||
2058 | int rc = 0; | ||
2059 | |||
2060 | if (machine__resolve(machine, &al, sample) < 0) { | ||
2061 | pr_err("problem processing %d event. skipping it\n", | ||
2062 | event->header.type); | ||
2063 | rc = -1; | ||
2064 | goto out; | ||
2065 | } | ||
2066 | |||
2067 | thread = timehist_get_thread(sample, machine, evsel); | ||
2068 | if (thread == NULL) { | ||
2069 | rc = -1; | ||
2070 | goto out; | ||
2071 | } | ||
2072 | |||
2073 | if (timehist_skip_sample(thread)) | ||
2074 | goto out; | ||
2075 | |||
2076 | tr = thread__get_runtime(thread); | ||
2077 | if (tr == NULL) { | ||
2078 | rc = -1; | ||
2079 | goto out; | ||
2080 | } | ||
2081 | |||
2082 | tprev = perf_evsel__get_time(evsel, sample->cpu); | ||
2083 | |||
2084 | timehist_update_runtime_stats(tr, sample->time, tprev); | ||
2085 | timehist_print_sample(sample, thread); | ||
2086 | |||
2087 | out: | ||
2088 | if (tr) { | ||
2089 | /* time of this sched_switch event becomes last time task seen */ | ||
2090 | tr->last_time = sample->time; | ||
2091 | |||
2092 | /* sched out event for task so reset ready to run time */ | ||
2093 | tr->ready_to_run = 0; | ||
2094 | } | ||
2095 | |||
2096 | perf_evsel__save_time(evsel, sample->time, sample->cpu); | ||
2097 | |||
2098 | return rc; | ||
2099 | } | ||
2100 | |||
2101 | static int timehist_sched_switch_event(struct perf_tool *tool, | ||
2102 | union perf_event *event, | ||
2103 | struct perf_evsel *evsel, | ||
2104 | struct perf_sample *sample, | ||
2105 | struct machine *machine __maybe_unused) | ||
2106 | { | ||
2107 | return timehist_sched_change_event(tool, event, evsel, sample, machine); | ||
2108 | } | ||
2109 | |||
2110 | static int process_lost(struct perf_tool *tool __maybe_unused, | ||
2111 | union perf_event *event, | ||
2112 | struct perf_sample *sample, | ||
2113 | struct machine *machine __maybe_unused) | ||
2114 | { | ||
2115 | char tstr[64]; | ||
2116 | |||
2117 | timestamp__scnprintf_usec(sample->time, tstr, sizeof(tstr)); | ||
2118 | printf("%15s ", tstr); | ||
2119 | printf("lost %" PRIu64 " events on cpu %d\n", event->lost.lost, sample->cpu); | ||
2120 | |||
2121 | return 0; | ||
2122 | } | ||
2123 | |||
2124 | |||
2125 | typedef int (*sched_handler)(struct perf_tool *tool, | ||
2126 | union perf_event *event, | ||
2127 | struct perf_evsel *evsel, | ||
2128 | struct perf_sample *sample, | ||
2129 | struct machine *machine); | ||
2130 | |||
2131 | static int perf_timehist__process_sample(struct perf_tool *tool, | ||
2132 | union perf_event *event, | ||
2133 | struct perf_sample *sample, | ||
2134 | struct perf_evsel *evsel, | ||
2135 | struct machine *machine) | ||
2136 | { | ||
2137 | struct perf_sched *sched = container_of(tool, struct perf_sched, tool); | ||
2138 | int err = 0; | ||
2139 | int this_cpu = sample->cpu; | ||
2140 | |||
2141 | if (this_cpu > sched->max_cpu) | ||
2142 | sched->max_cpu = this_cpu; | ||
2143 | |||
2144 | if (evsel->handler != NULL) { | ||
2145 | sched_handler f = evsel->handler; | ||
2146 | |||
2147 | err = f(tool, event, evsel, sample, machine); | ||
2148 | } | ||
2149 | |||
2150 | return err; | ||
2151 | } | ||
2152 | |||
2153 | static int perf_sched__timehist(struct perf_sched *sched) | ||
2154 | { | ||
2155 | const struct perf_evsel_str_handler handlers[] = { | ||
2156 | { "sched:sched_switch", timehist_sched_switch_event, }, | ||
2157 | { "sched:sched_wakeup", timehist_sched_wakeup_event, }, | ||
2158 | { "sched:sched_wakeup_new", timehist_sched_wakeup_event, }, | ||
2159 | }; | ||
2160 | struct perf_data_file file = { | ||
2161 | .path = input_name, | ||
2162 | .mode = PERF_DATA_MODE_READ, | ||
2163 | }; | ||
2164 | |||
2165 | struct perf_session *session; | ||
2166 | int err = -1; | ||
2167 | |||
2168 | /* | ||
2169 | * event handlers for timehist option | ||
2170 | */ | ||
2171 | sched->tool.sample = perf_timehist__process_sample; | ||
2172 | sched->tool.mmap = perf_event__process_mmap; | ||
2173 | sched->tool.comm = perf_event__process_comm; | ||
2174 | sched->tool.exit = perf_event__process_exit; | ||
2175 | sched->tool.fork = perf_event__process_fork; | ||
2176 | sched->tool.lost = process_lost; | ||
2177 | sched->tool.attr = perf_event__process_attr; | ||
2178 | sched->tool.tracing_data = perf_event__process_tracing_data; | ||
2179 | sched->tool.build_id = perf_event__process_build_id; | ||
2180 | |||
2181 | sched->tool.ordered_events = true; | ||
2182 | sched->tool.ordering_requires_timestamps = true; | ||
2183 | |||
2184 | session = perf_session__new(&file, false, &sched->tool); | ||
2185 | if (session == NULL) | ||
2186 | return -ENOMEM; | ||
2187 | |||
2188 | symbol__init(&session->header.env); | ||
2189 | |||
2190 | setup_pager(); | ||
2191 | |||
2192 | /* setup per-evsel handlers */ | ||
2193 | if (perf_session__set_tracepoints_handlers(session, handlers)) | ||
2194 | goto out; | ||
2195 | |||
2196 | if (!perf_session__has_traces(session, "record -R")) | ||
2197 | goto out; | ||
2198 | |||
2199 | /* pre-allocate struct for per-CPU idle stats */ | ||
2200 | sched->max_cpu = session->header.env.nr_cpus_online; | ||
2201 | if (sched->max_cpu == 0) | ||
2202 | sched->max_cpu = 4; | ||
2203 | if (init_idle_threads(sched->max_cpu)) | ||
2204 | goto out; | ||
2205 | |||
2206 | timehist_header(); | ||
2207 | |||
2208 | err = perf_session__process_events(session); | ||
2209 | if (err) { | ||
2210 | pr_err("Failed to process events, error %d", err); | ||
2211 | goto out; | ||
2212 | } | ||
2213 | |||
2214 | out: | ||
2215 | free_idle_threads(); | ||
2216 | perf_session__delete(session); | ||
2217 | |||
2218 | return err; | ||
2219 | } | ||
2220 | |||
2221 | |||
1657 | static void print_bad_events(struct perf_sched *sched) | 2222 | static void print_bad_events(struct perf_sched *sched) |
1658 | { | 2223 | { |
1659 | if (sched->nr_unordered_timestamps && sched->nr_timestamps) { | 2224 | if (sched->nr_unordered_timestamps && sched->nr_timestamps) { |
@@ -1970,8 +2535,6 @@ int cmd_sched(int argc, const char **argv, const char *prefix __maybe_unused) | |||
1970 | const struct option latency_options[] = { | 2535 | const struct option latency_options[] = { |
1971 | OPT_STRING('s', "sort", &sched.sort_order, "key[,key2...]", | 2536 | OPT_STRING('s', "sort", &sched.sort_order, "key[,key2...]", |
1972 | "sort by key(s): runtime, switch, avg, max"), | 2537 | "sort by key(s): runtime, switch, avg, max"), |
1973 | OPT_INCR('v', "verbose", &verbose, | ||
1974 | "be more verbose (show symbol address, etc)"), | ||
1975 | OPT_INTEGER('C', "CPU", &sched.profile_cpu, | 2538 | OPT_INTEGER('C', "CPU", &sched.profile_cpu, |
1976 | "CPU to profile on"), | 2539 | "CPU to profile on"), |
1977 | OPT_BOOLEAN('D', "dump-raw-trace", &dump_trace, | 2540 | OPT_BOOLEAN('D', "dump-raw-trace", &dump_trace, |
@@ -1983,8 +2546,6 @@ int cmd_sched(int argc, const char **argv, const char *prefix __maybe_unused) | |||
1983 | const struct option replay_options[] = { | 2546 | const struct option replay_options[] = { |
1984 | OPT_UINTEGER('r', "repeat", &sched.replay_repeat, | 2547 | OPT_UINTEGER('r', "repeat", &sched.replay_repeat, |
1985 | "repeat the workload replay N times (-1: infinite)"), | 2548 | "repeat the workload replay N times (-1: infinite)"), |
1986 | OPT_INCR('v', "verbose", &verbose, | ||
1987 | "be more verbose (show symbol address, etc)"), | ||
1988 | OPT_BOOLEAN('D', "dump-raw-trace", &dump_trace, | 2549 | OPT_BOOLEAN('D', "dump-raw-trace", &dump_trace, |
1989 | "dump raw trace in ASCII"), | 2550 | "dump raw trace in ASCII"), |
1990 | OPT_BOOLEAN('f', "force", &sched.force, "don't complain, do it"), | 2551 | OPT_BOOLEAN('f', "force", &sched.force, "don't complain, do it"), |
@@ -2001,6 +2562,16 @@ int cmd_sched(int argc, const char **argv, const char *prefix __maybe_unused) | |||
2001 | "display given CPUs in map"), | 2562 | "display given CPUs in map"), |
2002 | OPT_PARENT(sched_options) | 2563 | OPT_PARENT(sched_options) |
2003 | }; | 2564 | }; |
2565 | const struct option timehist_options[] = { | ||
2566 | OPT_STRING('k', "vmlinux", &symbol_conf.vmlinux_name, | ||
2567 | "file", "vmlinux pathname"), | ||
2568 | OPT_STRING(0, "kallsyms", &symbol_conf.kallsyms_name, | ||
2569 | "file", "kallsyms pathname"), | ||
2570 | OPT_STRING(0, "symfs", &symbol_conf.symfs, "directory", | ||
2571 | "Look for files with symbols relative to this directory"), | ||
2572 | OPT_PARENT(sched_options) | ||
2573 | }; | ||
2574 | |||
2004 | const char * const latency_usage[] = { | 2575 | const char * const latency_usage[] = { |
2005 | "perf sched latency [<options>]", | 2576 | "perf sched latency [<options>]", |
2006 | NULL | 2577 | NULL |
@@ -2013,8 +2584,13 @@ int cmd_sched(int argc, const char **argv, const char *prefix __maybe_unused) | |||
2013 | "perf sched map [<options>]", | 2584 | "perf sched map [<options>]", |
2014 | NULL | 2585 | NULL |
2015 | }; | 2586 | }; |
2587 | const char * const timehist_usage[] = { | ||
2588 | "perf sched timehist [<options>]", | ||
2589 | NULL | ||
2590 | }; | ||
2016 | const char *const sched_subcommands[] = { "record", "latency", "map", | 2591 | const char *const sched_subcommands[] = { "record", "latency", "map", |
2017 | "replay", "script", NULL }; | 2592 | "replay", "script", |
2593 | "timehist", NULL }; | ||
2018 | const char *sched_usage[] = { | 2594 | const char *sched_usage[] = { |
2019 | NULL, | 2595 | NULL, |
2020 | NULL | 2596 | NULL |
@@ -2077,6 +2653,14 @@ int cmd_sched(int argc, const char **argv, const char *prefix __maybe_unused) | |||
2077 | usage_with_options(replay_usage, replay_options); | 2653 | usage_with_options(replay_usage, replay_options); |
2078 | } | 2654 | } |
2079 | return perf_sched__replay(&sched); | 2655 | return perf_sched__replay(&sched); |
2656 | } else if (!strcmp(argv[0], "timehist")) { | ||
2657 | if (argc) { | ||
2658 | argc = parse_options(argc, argv, timehist_options, | ||
2659 | timehist_usage, 0); | ||
2660 | if (argc) | ||
2661 | usage_with_options(timehist_usage, timehist_options); | ||
2662 | } | ||
2663 | return perf_sched__timehist(&sched); | ||
2080 | } else { | 2664 | } else { |
2081 | usage_with_options(sched_usage, sched_options); | 2665 | usage_with_options(sched_usage, sched_options); |
2082 | } | 2666 | } |