diff options
Diffstat (limited to 'tools')
130 files changed, 9627 insertions, 1480 deletions
diff --git a/tools/perf/Documentation/perf-annotate.txt b/tools/perf/Documentation/perf-annotate.txt index b2c63309a65..6f5a498608b 100644 --- a/tools/perf/Documentation/perf-annotate.txt +++ b/tools/perf/Documentation/perf-annotate.txt @@ -24,12 +24,47 @@ OPTIONS --input=:: Input file name. (default: perf.data) +-d:: +--dsos=<dso[,dso...]>:: + Only consider symbols in these dsos. +-s:: +--symbol=<symbol>:: + Symbol to annotate. + +-f:: +--force:: + Don't complain, do it. + +-v:: +--verbose:: + Be more verbose. (Show symbol address, etc) + +-D:: +--dump-raw-trace:: + Dump raw trace in ASCII. + +-k:: +--vmlinux=<file>:: + vmlinux pathname. + +-m:: +--modules:: + Load module symbols. WARNING: use only with -k and LIVE kernel. + +-l:: +--print-line:: + Print matching source lines (may be slow). + +-P:: +--full-paths:: + Don't shorten the displayed pathnames. + --stdio:: Use the stdio interface. --tui:: Use the TUI interface Use of --tui requires a tty, if one is not present, as when piping to other commands, the stdio interface is used. This interfaces starts by centering on the line with more - samples, TAB/UNTAB cycles thru the lines with more samples. + samples, TAB/UNTAB cycles through the lines with more samples. SEE ALSO -------- diff --git a/tools/perf/Documentation/perf-buildid-list.txt b/tools/perf/Documentation/perf-buildid-list.txt index 01b642c0bf8..5eaac6f26d5 100644 --- a/tools/perf/Documentation/perf-buildid-list.txt +++ b/tools/perf/Documentation/perf-buildid-list.txt @@ -18,6 +18,9 @@ perf report. OPTIONS ------- +-H:: +--with-hits:: + Show only DSOs with hits. -i:: --input=:: Input file name. (default: perf.data) diff --git a/tools/perf/Documentation/perf-diff.txt b/tools/perf/Documentation/perf-diff.txt index 20d97d84ea1..74d7481ed7a 100644 --- a/tools/perf/Documentation/perf-diff.txt +++ b/tools/perf/Documentation/perf-diff.txt @@ -19,6 +19,18 @@ If no parameters are passed it will assume perf.data.old and perf.data. OPTIONS ------- +-M:: +--displacement:: + Show position displacement relative to baseline. + +-D:: +--dump-raw-trace:: + Dump raw trace in ASCII. + +-m:: +--modules:: + Load module symbols. WARNING: use only with -k and LIVE kernel + -d:: --dsos=:: Only consider symbols in these dsos. CSV that understands @@ -42,7 +54,7 @@ OPTIONS --field-separator=:: Use a special separator character and don't pad with spaces, replacing - all occurances of this separator in symbol names (and other output) + all occurrences of this separator in symbol names (and other output) with a '.' character, that thus it's the only non valid separator. -v:: @@ -50,6 +62,13 @@ OPTIONS Be verbose, for instance, show the raw counts in addition to the diff. +-f:: +--force:: + Don't complain, do it. + +--symfs=<directory>:: + Look for files with symbols relative to this directory. + SEE ALSO -------- linkperf:perf-record[1] diff --git a/tools/perf/Documentation/perf-kvm.txt b/tools/perf/Documentation/perf-kvm.txt index d004e19fe6d..dd84cb2f0a8 100644 --- a/tools/perf/Documentation/perf-kvm.txt +++ b/tools/perf/Documentation/perf-kvm.txt @@ -22,7 +22,7 @@ There are a couple of variants of perf kvm: a performance counter profile of guest os in realtime of an arbitrary workload. - 'perf kvm record <command>' to record the performance couinter profile + 'perf kvm record <command>' to record the performance counter profile of an arbitrary workload and save it into a perf data file. If both --host and --guest are input, the perf data file name is perf.data.kvm. If there is no --host but --guest, the file name is perf.data.guest. @@ -40,6 +40,12 @@ There are a couple of variants of perf kvm: OPTIONS ------- +-i:: +--input=:: + Input file name. +-o:: +--output:: + Output file name. --host=:: Collect host side performance profile. --guest=:: diff --git a/tools/perf/Documentation/perf-lock.txt b/tools/perf/Documentation/perf-lock.txt index b317102138c..921de259ea1 100644 --- a/tools/perf/Documentation/perf-lock.txt +++ b/tools/perf/Documentation/perf-lock.txt @@ -24,6 +24,21 @@ and statistics with this 'perf lock' command. 'perf lock report' reports statistical data. +OPTIONS +------- + +-i:: +--input=<file>:: + Input file name. + +-v:: +--verbose:: + Be more verbose (show symbol address, etc). + +-D:: +--dump-raw-trace:: + Dump raw trace in ASCII. + SEE ALSO -------- linkperf:perf[1] diff --git a/tools/perf/Documentation/perf-probe.txt b/tools/perf/Documentation/perf-probe.txt index 62de1b7f4e7..86b797a35aa 100644 --- a/tools/perf/Documentation/perf-probe.txt +++ b/tools/perf/Documentation/perf-probe.txt @@ -115,9 +115,9 @@ Each probe argument follows below syntax. LINE SYNTAX ----------- -Line range is descripted by following syntax. +Line range is described by following syntax. - "FUNC[:RLN[+NUM|-RLN2]]|SRC:ALN[+NUM|-ALN2]" + "FUNC[:RLN[+NUM|-RLN2]]|SRC[:ALN[+NUM|-ALN2]]" FUNC specifies the function name of showing lines. 'RLN' is the start line number from function entry line, and 'RLN2' is the end line number. As same as diff --git a/tools/perf/Documentation/perf-record.txt b/tools/perf/Documentation/perf-record.txt index a91f9f9e6e5..52462ae2645 100644 --- a/tools/perf/Documentation/perf-record.txt +++ b/tools/perf/Documentation/perf-record.txt @@ -39,15 +39,24 @@ OPTIONS be passed as follows: '\mem:addr[:[r][w][x]]'. If you want to profile read-write accesses in 0x1000, just set 'mem:0x1000:rw'. + +--filter=<filter>:: + Event filter. + -a:: - System-wide collection. +--all-cpus:: + System-wide collection from all CPUs. -l:: Scale counter values. -p:: --pid=:: - Record events on existing pid. + Record events on existing process ID. + +-t:: +--tid=:: + Record events on existing thread ID. -r:: --realtime=:: @@ -99,6 +108,11 @@ OPTIONS --data:: Sample addresses. +-T:: +--timestamp:: + Sample timestamps. Use it with 'perf report -D' to see the timestamps, + for instance. + -n:: --no-samples:: Don't sample. @@ -109,8 +123,8 @@ Collect raw sample records from all opened counters (default for tracepoint coun -C:: --cpu:: -Collect samples only on the list of cpus provided. Multiple CPUs can be provided as a -comma-sperated list with no space: 0,1. Ranges of CPUs are specified with -: 0-2. +Collect samples only on the list of CPUs provided. Multiple CPUs can be provided as a +comma-separated list with no space: 0,1. Ranges of CPUs are specified with -: 0-2. In per-thread mode with inheritance mode on (default), samples are captured only when the thread executes on the designated CPUs. Default is to monitor all CPUs. diff --git a/tools/perf/Documentation/perf-report.txt b/tools/perf/Documentation/perf-report.txt index 12052c9ed0b..8ba03d6e539 100644 --- a/tools/perf/Documentation/perf-report.txt +++ b/tools/perf/Documentation/perf-report.txt @@ -20,6 +20,11 @@ OPTIONS -i:: --input=:: Input file name. (default: perf.data) + +-v:: +--verbose:: + Be more verbose. (show symbol address, etc) + -d:: --dsos=:: Only consider symbols in these dsos. CSV that understands @@ -27,6 +32,10 @@ OPTIONS -n:: --show-nr-samples:: Show the number of samples for each symbol + +--showcpuutilization:: + Show sample percentage for different cpu modes. + -T:: --threads:: Show per-thread event counters @@ -39,12 +48,24 @@ OPTIONS Only consider these symbols. CSV that understands file://filename entries. +-U:: +--hide-unresolved:: + Only display entries resolved to a symbol. + -s:: --sort=:: Sort by key(s): pid, comm, dso, symbol, parent. +-p:: +--parent=<regex>:: + regex filter to identify parent, see: '--sort parent' + +-x:: +--exclude-other:: + Only display entries with parent-match. + -w:: ---field-width=:: +--column-widths=<width[,width...]>:: Force each column width to the provided list, for large terminal readability. @@ -52,19 +73,26 @@ OPTIONS --field-separator=:: Use a special separator character and don't pad with spaces, replacing - all occurances of this separator in symbol names (and other output) + all occurrences of this separator in symbol names (and other output) with a '.' character, that thus it's the only non valid separator. +-D:: +--dump-raw-trace:: + Dump raw trace in ASCII. + -g [type,min]:: --call-graph:: - Display callchains using type and min percent threshold. + Display call chains using type and min percent threshold. type can be either: - - flat: single column, linear exposure of callchains. + - flat: single column, linear exposure of call chains. - graph: use a graph tree, displaying absolute overhead rates. - fractal: like graph, but displays relative rates. Each branch of the tree is considered as a new profiled object. + Default: fractal,0.5. +--pretty=<key>:: + Pretty printing style. key: normal, raw + --stdio:: Use the stdio interface. --tui:: Use the TUI interface, that is integrated with annotate and allows @@ -72,6 +100,25 @@ OPTIONS requires a tty, if one is not present, as when piping to other commands, the stdio interface is used. +-k:: +--vmlinux=<file>:: + vmlinux pathname + +--kallsyms=<file>:: + kallsyms pathname + +-m:: +--modules:: + Load module symbols. WARNING: This should only be used with -k and + a LIVE kernel. + +-f:: +--force:: + Don't complain, do it. + +--symfs=<directory>:: + Look for files with symbols relative to this directory. + SEE ALSO -------- linkperf:perf-stat[1] diff --git a/tools/perf/Documentation/perf-sched.txt b/tools/perf/Documentation/perf-sched.txt index 8417644a616..46822d5fde1 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) SYNOPSIS -------- [verse] -'perf sched' {record|latency|replay|trace} +'perf sched' {record|latency|map|replay|trace} DESCRIPTION ----------- -There are four variants of perf sched: +There are five variants of perf sched: 'perf sched record <command>' to record the scheduling events of an arbitrary workload. @@ -30,8 +30,22 @@ There are four variants of perf sched: of the workload as it occurred when it was recorded - and can repeat it a number of times, measuring its performance.) + 'perf sched map' to print a textual context-switching outline of + workload captured via perf sched record. Columns stand for + individual CPUs, and the two-letter shortcuts stand for tasks that + are running on a CPU. A '*' denotes the CPU that had the event, and + a dot signals an idle CPU. + OPTIONS ------- +-i:: +--input=<file>:: + Input file name. (default: perf.data) + +-v:: +--verbose:: + Be more verbose. (show symbol address, etc) + -D:: --dump-raw-trace=:: Display verbose dump of the sched data. diff --git a/tools/perf/Documentation/perf-trace-perl.txt b/tools/perf/Documentation/perf-script-perl.txt index ee6525ee6d6..5bb41e55a3a 100644 --- a/tools/perf/Documentation/perf-trace-perl.txt +++ b/tools/perf/Documentation/perf-script-perl.txt @@ -1,19 +1,19 @@ -perf-trace-perl(1) +perf-script-perl(1) ================== NAME ---- -perf-trace-perl - Process trace data with a Perl script +perf-script-perl - Process trace data with a Perl script SYNOPSIS -------- [verse] -'perf trace' [-s [Perl]:script[.pl] ] +'perf script' [-s [Perl]:script[.pl] ] DESCRIPTION ----------- -This perf trace option is used to process perf trace data using perf's +This perf script option is used to process perf script data using perf's built-in Perl interpreter. It reads and processes the input file and displays the results of the trace analysis implemented in the given Perl script, if any. @@ -21,7 +21,7 @@ Perl script, if any. STARTER SCRIPTS --------------- -You can avoid reading the rest of this document by running 'perf trace +You can avoid reading the rest of this document by running 'perf script -g perl' in the same directory as an existing perf.data trace file. That will generate a starter script containing a handler for each of the event types in the trace file; it simply prints every available @@ -30,13 +30,13 @@ field for each event in the trace file. You can also look at the existing scripts in ~/libexec/perf-core/scripts/perl for typical examples showing how to do basic things like aggregate event data, print results, etc. Also, -the check-perf-trace.pl script, while not interesting for its results, +the check-perf-script.pl script, while not interesting for its results, attempts to exercise all of the main scripting features. EVENT HANDLERS -------------- -When perf trace is invoked using a trace script, a user-defined +When perf script is invoked using a trace script, a user-defined 'handler function' is called for each event in the trace. If there's no handler function defined for a given event type, the event is ignored (or passed to a 'trace_handled' function, see below) and the @@ -112,13 +112,13 @@ write a useful trace script. The sections below cover the rest. SCRIPT LAYOUT ------------- -Every perf trace Perl script should start by setting up a Perl module +Every perf script Perl script should start by setting up a Perl module search path and 'use'ing a few support modules (see module descriptions below): ---- - use lib "$ENV{'PERF_EXEC_PATH'}/scripts/perl/Perf-Trace-Util/lib"; - use lib "./Perf-Trace-Util/lib"; + use lib "$ENV{'PERF_EXEC_PATH'}/scripts/perl/perf-script-Util/lib"; + use lib "./perf-script-Util/lib"; use Perf::Trace::Core; use Perf::Trace::Context; use Perf::Trace::Util; @@ -162,7 +162,7 @@ sub trace_unhandled ---- The remaining sections provide descriptions of each of the available -built-in perf trace Perl modules and their associated functions. +built-in perf script Perl modules and their associated functions. AVAILABLE MODULES AND FUNCTIONS ------------------------------- @@ -170,7 +170,7 @@ AVAILABLE MODULES AND FUNCTIONS The following sections describe the functions and variables available via the various Perf::Trace::* Perl modules. To use the functions and variables from the given module, add the corresponding 'use -Perf::Trace::XXX' line to your perf trace script. +Perf::Trace::XXX' line to your perf script script. Perf::Trace::Core Module ~~~~~~~~~~~~~~~~~~~~~~~~ @@ -204,7 +204,7 @@ argument. Perf::Trace::Util Module ~~~~~~~~~~~~~~~~~~~~~~~~ -Various utility functions for use with perf trace: +Various utility functions for use with perf script: nsecs($secs, $nsecs) - returns total nsecs given secs/nsecs pair nsecs_secs($nsecs) - returns whole secs portion given nsecs @@ -214,4 +214,4 @@ Various utility functions for use with perf trace: SEE ALSO -------- -linkperf:perf-trace[1] +linkperf:perf-script[1] diff --git a/tools/perf/Documentation/perf-trace-python.txt b/tools/perf/Documentation/perf-script-python.txt index 693be804dd3..36b38277422 100644 --- a/tools/perf/Documentation/perf-trace-python.txt +++ b/tools/perf/Documentation/perf-script-python.txt @@ -1,19 +1,19 @@ -perf-trace-python(1) +perf-script-python(1) ==================== NAME ---- -perf-trace-python - Process trace data with a Python script +perf-script-python - Process trace data with a Python script SYNOPSIS -------- [verse] -'perf trace' [-s [Python]:script[.py] ] +'perf script' [-s [Python]:script[.py] ] DESCRIPTION ----------- -This perf trace option is used to process perf trace data using perf's +This perf script option is used to process perf script data using perf's built-in Python interpreter. It reads and processes the input file and displays the results of the trace analysis implemented in the given Python script, if any. @@ -23,15 +23,15 @@ A QUICK EXAMPLE This section shows the process, start to finish, of creating a working Python script that aggregates and extracts useful information from a -raw perf trace stream. You can avoid reading the rest of this +raw perf script stream. You can avoid reading the rest of this document if an example is enough for you; the rest of the document provides more details on each step and lists the library functions available to script writers. This example actually details the steps that were used to create the -'syscall-counts' script you see when you list the available perf trace -scripts via 'perf trace -l'. As such, this script also shows how to -integrate your script into the list of general-purpose 'perf trace' +'syscall-counts' script you see when you list the available perf script +scripts via 'perf script -l'. As such, this script also shows how to +integrate your script into the list of general-purpose 'perf script' scripts listed by that command. The syscall-counts script is a simple script, but demonstrates all the @@ -105,31 +105,31 @@ That single stream will be recorded in a file in the current directory called perf.data. Once we have a perf.data file containing our data, we can use the -g -'perf trace' option to generate a Python script that will contain a +'perf script' option to generate a Python script that will contain a callback handler for each event type found in the perf.data trace stream (for more details, see the STARTER SCRIPTS section). ---- -# perf trace -g python -generated Python script: perf-trace.py +# perf script -g python +generated Python script: perf-script.py The output file created also in the current directory is named -perf-trace.py. Here's the file in its entirety: +perf-script.py. Here's the file in its entirety: -# perf trace event handlers, generated by perf trace -g python +# perf script event handlers, generated by perf script -g python # Licensed under the terms of the GNU GPL License version 2 # The common_* event handler fields are the most useful fields common to # all events. They don't necessarily correspond to the 'common_*' fields # in the format files. Those fields not available as handler params can # be retrieved using Python functions of the form common_*(context). -# See the perf-trace-python Documentation for the list of available functions. +# See the perf-script-python Documentation for the list of available functions. import os import sys sys.path.append(os.environ['PERF_EXEC_PATH'] + \ - '/scripts/python/Perf-Trace-Util/lib/Perf/Trace') + '/scripts/python/perf-script-Util/lib/Perf/Trace') from perf_trace_context import * from Core import * @@ -160,7 +160,7 @@ def print_header(event_name, cpu, secs, nsecs, pid, comm): ---- At the top is a comment block followed by some import statements and a -path append which every perf trace script should include. +path append which every perf script script should include. Following that are a couple generated functions, trace_begin() and trace_end(), which are called at the beginning and the end of the @@ -189,8 +189,8 @@ simply a utility function used for that purpose. Let's rename the script and run it to see the default output: ---- -# mv perf-trace.py syscall-counts.py -# perf trace -s syscall-counts.py +# mv perf-script.py syscall-counts.py +# perf script -s syscall-counts.py raw_syscalls__sys_enter 1 00840.847582083 7506 perf id=1, args= raw_syscalls__sys_enter 1 00840.847595764 7506 perf id=1, args= @@ -216,7 +216,7 @@ import os import sys sys.path.append(os.environ['PERF_EXEC_PATH'] + \ - '/scripts/python/Perf-Trace-Util/lib/Perf/Trace') + '/scripts/python/perf-script-Util/lib/Perf/Trace') from perf_trace_context import * from Core import * @@ -279,7 +279,7 @@ import os import sys sys.path.append(os.environ['PERF_EXEC_PATH'] + \ - '/scripts/python/Perf-Trace-Util/lib/Perf/Trace') + '/scripts/python/perf-script-Util/lib/Perf/Trace') from perf_trace_context import * from Core import * @@ -315,7 +315,7 @@ def print_syscall_totals(): The script can be run just as before: - # perf trace -s syscall-counts.py + # perf script -s syscall-counts.py So those are the essential steps in writing and running a script. The process can be generalized to any tracepoint or set of tracepoints @@ -324,17 +324,17 @@ interested in by looking at the list of available events shown by 'perf list' and/or look in /sys/kernel/debug/tracing events for detailed event and field info, record the corresponding trace data using 'perf record', passing it the list of interesting events, -generate a skeleton script using 'perf trace -g python' and modify the +generate a skeleton script using 'perf script -g python' and modify the code to aggregate and display it for your particular needs. After you've done that you may end up with a general-purpose script that you want to keep around and have available for future use. By writing a couple of very simple shell scripts and putting them in the right place, you can have your script listed alongside the other -scripts listed by the 'perf trace -l' command e.g.: +scripts listed by the 'perf script -l' command e.g.: ---- -root@tropicana:~# perf trace -l +root@tropicana:~# perf script -l List of available trace scripts: workqueue-stats workqueue stats (ins/exe/create/destroy) wakeup-latency system-wide min/max/avg wakeup latency @@ -365,14 +365,14 @@ perf record -a -e raw_syscalls:sys_enter The 'report' script is also a shell script with the same base name as your script, but with -report appended. It should also be located in the perf/scripts/python/bin directory. In that script, you write the -'perf trace -s' command-line needed for running your script: +'perf script -s' command-line needed for running your script: ---- # cat kernel-source/tools/perf/scripts/python/bin/syscall-counts-report #!/bin/bash # description: system-wide syscall counts -perf trace -s ~/libexec/perf-core/scripts/python/syscall-counts.py +perf script -s ~/libexec/perf-core/scripts/python/syscall-counts.py ---- Note that the location of the Python script given in the shell script @@ -390,17 +390,17 @@ total 32 drwxr-xr-x 4 trz trz 4096 2010-01-26 22:30 . drwxr-xr-x 4 trz trz 4096 2010-01-26 22:29 .. drwxr-xr-x 2 trz trz 4096 2010-01-26 22:29 bin --rw-r--r-- 1 trz trz 2548 2010-01-26 22:29 check-perf-trace.py -drwxr-xr-x 3 trz trz 4096 2010-01-26 22:49 Perf-Trace-Util +-rw-r--r-- 1 trz trz 2548 2010-01-26 22:29 check-perf-script.py +drwxr-xr-x 3 trz trz 4096 2010-01-26 22:49 perf-script-Util -rw-r--r-- 1 trz trz 1462 2010-01-26 22:30 syscall-counts.py ---- Once you've done that (don't forget to do a new 'make install', -otherwise your script won't show up at run-time), 'perf trace -l' +otherwise your script won't show up at run-time), 'perf script -l' should show a new entry for your script: ---- -root@tropicana:~# perf trace -l +root@tropicana:~# perf script -l List of available trace scripts: workqueue-stats workqueue stats (ins/exe/create/destroy) wakeup-latency system-wide min/max/avg wakeup latency @@ -409,19 +409,19 @@ List of available trace scripts: syscall-counts system-wide syscall counts ---- -You can now perform the record step via 'perf trace record': +You can now perform the record step via 'perf script record': - # perf trace record syscall-counts + # perf script record syscall-counts -and display the output using 'perf trace report': +and display the output using 'perf script report': - # perf trace report syscall-counts + # perf script report syscall-counts STARTER SCRIPTS --------------- You can quickly get started writing a script for a particular set of -trace data by generating a skeleton script using 'perf trace -g +trace data by generating a skeleton script using 'perf script -g python' in the same directory as an existing perf.data trace file. That will generate a starter script containing a handler for each of the event types in the trace file; it simply prints every available @@ -430,13 +430,13 @@ field for each event in the trace file. You can also look at the existing scripts in ~/libexec/perf-core/scripts/python for typical examples showing how to do basic things like aggregate event data, print results, etc. Also, -the check-perf-trace.py script, while not interesting for its results, +the check-perf-script.py script, while not interesting for its results, attempts to exercise all of the main scripting features. EVENT HANDLERS -------------- -When perf trace is invoked using a trace script, a user-defined +When perf script is invoked using a trace script, a user-defined 'handler function' is called for each event in the trace. If there's no handler function defined for a given event type, the event is ignored (or passed to a 'trace_handled' function, see below) and the @@ -510,7 +510,7 @@ write a useful trace script. The sections below cover the rest. SCRIPT LAYOUT ------------- -Every perf trace Python script should start by setting up a Python +Every perf script Python script should start by setting up a Python module search path and 'import'ing a few support modules (see module descriptions below): @@ -519,7 +519,7 @@ descriptions below): import sys sys.path.append(os.environ['PERF_EXEC_PATH'] + \ - '/scripts/python/Perf-Trace-Util/lib/Perf/Trace') + '/scripts/python/perf-script-Util/lib/Perf/Trace') from perf_trace_context import * from Core import * @@ -559,15 +559,15 @@ def trace_unhandled(event_name, context, common_cpu, common_secs, ---- The remaining sections provide descriptions of each of the available -built-in perf trace Python modules and their associated functions. +built-in perf script Python modules and their associated functions. AVAILABLE MODULES AND FUNCTIONS ------------------------------- The following sections describe the functions and variables available -via the various perf trace Python modules. To use the functions and +via the various perf script Python modules. To use the functions and variables from the given module, add the corresponding 'from XXXX -import' line to your perf trace script. +import' line to your perf script script. Core.py Module ~~~~~~~~~~~~~~ @@ -610,7 +610,7 @@ argument. Util.py Module ~~~~~~~~~~~~~~ -Various utility functions for use with perf trace: +Various utility functions for use with perf script: nsecs(secs, nsecs) - returns total nsecs given secs/nsecs pair nsecs_secs(nsecs) - returns whole secs portion given nsecs @@ -620,4 +620,4 @@ Various utility functions for use with perf trace: SEE ALSO -------- -linkperf:perf-trace[1] +linkperf:perf-script[1] diff --git a/tools/perf/Documentation/perf-trace.txt b/tools/perf/Documentation/perf-script.txt index 26aff6bf9e5..29ad94293cd 100644 --- a/tools/perf/Documentation/perf-trace.txt +++ b/tools/perf/Documentation/perf-script.txt @@ -1,71 +1,71 @@ -perf-trace(1) +perf-script(1) ============= NAME ---- -perf-trace - Read perf.data (created by perf record) and display trace output +perf-script - Read perf.data (created by perf record) and display trace output SYNOPSIS -------- [verse] -'perf trace' [<options>] -'perf trace' [<options>] record <script> [<record-options>] <command> -'perf trace' [<options>] report <script> [script-args] -'perf trace' [<options>] <script> <required-script-args> [<record-options>] <command> -'perf trace' [<options>] <top-script> [script-args] +'perf script' [<options>] +'perf script' [<options>] record <script> [<record-options>] <command> +'perf script' [<options>] report <script> [script-args] +'perf script' [<options>] <script> <required-script-args> [<record-options>] <command> +'perf script' [<options>] <top-script> [script-args] DESCRIPTION ----------- This command reads the input file and displays the trace recorded. -There are several variants of perf trace: +There are several variants of perf script: - 'perf trace' to see a detailed trace of the workload that was + 'perf script' to see a detailed trace of the workload that was recorded. You can also run a set of pre-canned scripts that aggregate and summarize the raw trace data in various ways (the list of scripts is - available via 'perf trace -l'). The following variants allow you to + available via 'perf script -l'). The following variants allow you to record and run those scripts: - 'perf trace record <script> <command>' to record the events required - for 'perf trace report'. <script> is the name displayed in the - output of 'perf trace --list' i.e. the actual script name minus any + 'perf script record <script> <command>' to record the events required + for 'perf script report'. <script> is the name displayed in the + output of 'perf script --list' i.e. the actual script name minus any language extension. If <command> is not specified, the events are recorded using the -a (system-wide) 'perf record' option. - 'perf trace report <script> [args]' to run and display the results + 'perf script report <script> [args]' to run and display the results of <script>. <script> is the name displayed in the output of 'perf trace --list' i.e. the actual script name minus any language - extension. The perf.data output from a previous run of 'perf trace + extension. The perf.data output from a previous run of 'perf script record <script>' is used and should be present for this command to succeed. [args] refers to the (mainly optional) args expected by the script. - 'perf trace <script> <required-script-args> <command>' to both + 'perf script <script> <required-script-args> <command>' to both record the events required for <script> and to run the <script> using 'live-mode' i.e. without writing anything to disk. <script> - is the name displayed in the output of 'perf trace --list' i.e. the + is the name displayed in the output of 'perf script --list' i.e. the actual script name minus any language extension. If <command> is not specified, the events are recorded using the -a (system-wide) 'perf record' option. If <script> has any required args, they should be specified before <command>. This mode doesn't allow for optional script args to be specified; if optional script args are - desired, they can be specified using separate 'perf trace record' - and 'perf trace report' commands, with the stdout of the record step + desired, they can be specified using separate 'perf script record' + and 'perf script report' commands, with the stdout of the record step piped to the stdin of the report script, using the '-o -' and '-i -' options of the corresponding commands. - 'perf trace <top-script>' to both record the events required for + 'perf script <top-script>' to both record the events required for <top-script> and to run the <top-script> using 'live-mode' i.e. without writing anything to disk. <top-script> is the name - displayed in the output of 'perf trace --list' i.e. the actual + displayed in the output of 'perf script --list' i.e. the actual script name minus any language extension; a <top-script> is defined as any script name ending with the string 'top'. - [<record-options>] can be passed to the record steps of 'perf trace + [<record-options>] can be passed to the record steps of 'perf script record' and 'live-mode' variants; this isn't possible however for - <top-script> 'live-mode' or 'perf trace report' variants. + <top-script> 'live-mode' or 'perf script report' variants. See the 'SEE ALSO' section for links to language-specific information on how to write and run your own trace scripts. @@ -76,7 +76,7 @@ OPTIONS Any command you can specify in a shell. -D:: ---dump-raw-trace=:: +--dump-raw-script=:: Display verbose dump of the trace data. -L:: @@ -95,7 +95,7 @@ OPTIONS -g:: --gen-script=:: - Generate perf-trace.[ext] starter script for given language, + Generate perf-script.[ext] starter script for given language, using current perf.data. -a:: @@ -104,8 +104,15 @@ OPTIONS normally don't - this option allows the latter to be run in system-wide mode. +-i:: +--input=:: + Input file name. + +-d:: +--debug-mode:: + Do various checks like samples ordering and lost events. SEE ALSO -------- -linkperf:perf-record[1], linkperf:perf-trace-perl[1], -linkperf:perf-trace-python[1] +linkperf:perf-record[1], linkperf:perf-script-perl[1], +linkperf:perf-script-python[1] diff --git a/tools/perf/Documentation/perf-stat.txt b/tools/perf/Documentation/perf-stat.txt index 4b3a2d46b43..b6da7affbbe 100644 --- a/tools/perf/Documentation/perf-stat.txt +++ b/tools/perf/Documentation/perf-stat.txt @@ -8,8 +8,8 @@ perf-stat - Run a command and gather performance counter statistics SYNOPSIS -------- [verse] -'perf stat' [-e <EVENT> | --event=EVENT] [-S] [-a] <command> -'perf stat' [-e <EVENT> | --event=EVENT] [-S] [-a] -- <command> [<options>] +'perf stat' [-e <EVENT> | --event=EVENT] [-a] <command> +'perf stat' [-e <EVENT> | --event=EVENT] [-a] -- <command> [<options>] DESCRIPTION ----------- @@ -35,24 +35,54 @@ OPTIONS child tasks do not inherit counters -p:: --pid=<pid>:: - stat events on existing pid + stat events on existing process id + +-t:: +--tid=<tid>:: + stat events on existing thread id + -a:: - system-wide collection +--all-cpus:: + system-wide collection from all CPUs -c:: - scale counter values +--scale:: + scale/normalize counter values + +-r:: +--repeat=<n>:: + repeat command and print average + stddev (max: 100) -B:: +--big-num:: print large numbers with thousands' separators according to locale -C:: --cpu=:: -Count only on the list of cpus provided. Multiple CPUs can be provided as a -comma-sperated list with no space: 0,1. Ranges of CPUs are specified with -: 0-2. +Count only on the list of CPUs provided. Multiple CPUs can be provided as a +comma-separated list with no space: 0,1. Ranges of CPUs are specified with -: 0-2. In per-thread mode, this option is ignored. The -a option is still necessary to activate system-wide monitoring. Default is to count on all CPUs. +-A:: +--no-aggr:: +Do not aggregate counts across all monitored CPUs in system-wide mode (-a). +This option is only valid in system-wide mode. + +-n:: +--null:: + null run - don't start any counters + +-v:: +--verbose:: + be more verbose (show counter open errors, etc) + +-x SEP:: +--field-separator SEP:: +print counts using a CSV-style output to make it easy to import directly into +spreadsheets. Columns are separated by the string specified in SEP. + EXAMPLES -------- diff --git a/tools/perf/Documentation/perf-test.txt b/tools/perf/Documentation/perf-test.txt index 1c4b5f5b7f7..2c3b462f64b 100644 --- a/tools/perf/Documentation/perf-test.txt +++ b/tools/perf/Documentation/perf-test.txt @@ -12,7 +12,7 @@ SYNOPSIS DESCRIPTION ----------- -This command does assorted sanity tests, initially thru linked routines but +This command does assorted sanity tests, initially through linked routines but also will look for a directory with more tests in the form of scripts. OPTIONS diff --git a/tools/perf/Documentation/perf-timechart.txt b/tools/perf/Documentation/perf-timechart.txt index 4b1788355ec..d7b79e2ba2a 100644 --- a/tools/perf/Documentation/perf-timechart.txt +++ b/tools/perf/Documentation/perf-timechart.txt @@ -38,6 +38,8 @@ OPTIONS --process:: Select the processes to display, by name or PID +--symfs=<directory>:: + Look for files with symbols relative to this directory. SEE ALSO -------- diff --git a/tools/perf/Documentation/perf-top.txt b/tools/perf/Documentation/perf-top.txt index 1f9687663f2..f6eb1cdafb7 100644 --- a/tools/perf/Documentation/perf-top.txt +++ b/tools/perf/Documentation/perf-top.txt @@ -12,7 +12,7 @@ SYNOPSIS DESCRIPTION ----------- -This command generates and displays a performance counter profile in realtime. +This command generates and displays a performance counter profile in real time. OPTIONS @@ -27,8 +27,8 @@ OPTIONS -C <cpu-list>:: --cpu=<cpu>:: -Monitor only on the list of cpus provided. Multiple CPUs can be provided as a -comma-sperated list with no space: 0,1. Ranges of CPUs are specified with -: 0-2. +Monitor only on the list of CPUs provided. Multiple CPUs can be provided as a +comma-separated list with no space: 0,1. Ranges of CPUs are specified with -: 0-2. Default is to monitor all CPUS. -d <seconds>:: @@ -50,6 +50,10 @@ Default is to monitor all CPUS. --count-filter=<count>:: Only display functions with more events than this. +-g:: +--group:: + Put the counters into a counter group. + -F <freq>:: --freq=<freq>:: Profile at this frequency. @@ -68,7 +72,11 @@ Default is to monitor all CPUS. -p <pid>:: --pid=<pid>:: - Profile events on existing pid. + Profile events on existing Process ID. + +-t <tid>:: +--tid=<tid>:: + Profile events on existing thread ID. -r <priority>:: --realtime=<priority>:: @@ -78,6 +86,18 @@ Default is to monitor all CPUS. --sym-annotate=<symbol>:: Annotate this symbol. +-K:: +--hide_kernel_symbols:: + Hide kernel symbols. + +-U:: +--hide_user_symbols:: + Hide user symbols. + +-D:: +--dump-symtab:: + Dump the symbol table used for profiling. + -v:: --verbose:: Be more verbose (show counter open errors, etc). diff --git a/tools/perf/MANIFEST b/tools/perf/MANIFEST index 8c7fc0c8f0b..c12659d8cb2 100644 --- a/tools/perf/MANIFEST +++ b/tools/perf/MANIFEST @@ -7,6 +7,7 @@ include/linux/stringify.h lib/rbtree.c include/linux/swab.h arch/*/include/asm/unistd*.h +arch/*/lib/memcpy*.S include/linux/poison.h include/linux/magic.h include/linux/hw_breakpoint.h diff --git a/tools/perf/Makefile b/tools/perf/Makefile index d1db0f676a4..2b5387d53ba 100644 --- a/tools/perf/Makefile +++ b/tools/perf/Makefile @@ -185,7 +185,10 @@ ifeq ($(ARCH),i386) ARCH := x86 endif ifeq ($(ARCH),x86_64) + RAW_ARCH := x86_64 ARCH := x86 + ARCH_CFLAGS := -DARCH_X86_64 + ARCH_INCLUDE = ../../arch/x86/lib/memcpy_64.S endif # CFLAGS and LDFLAGS are for the users to override from the command line. @@ -224,7 +227,7 @@ ifndef PERF_DEBUG CFLAGS_OPTIMIZE = -O6 endif -CFLAGS = -ggdb3 -Wall -Wextra -std=gnu99 -Werror $(CFLAGS_OPTIMIZE) -D_FORTIFY_SOURCE=2 $(EXTRA_WARNINGS) $(EXTRA_CFLAGS) +CFLAGS = -fno-omit-frame-pointer -ggdb3 -Wall -Wextra -std=gnu99 -Werror $(CFLAGS_OPTIMIZE) -D_FORTIFY_SOURCE=2 $(EXTRA_WARNINGS) $(EXTRA_CFLAGS) EXTLIBS = -lpthread -lrt -lelf -lm ALL_CFLAGS = $(CFLAGS) -D_LARGEFILE64_SOURCE -D_FILE_OFFSET_BITS=64 ALL_LDFLAGS = $(LDFLAGS) @@ -375,6 +378,7 @@ LIB_H += util/include/linux/prefetch.h LIB_H += util/include/linux/rbtree.h LIB_H += util/include/linux/string.h LIB_H += util/include/linux/types.h +LIB_H += util/include/linux/linkage.h LIB_H += util/include/asm/asm-offsets.h LIB_H += util/include/asm/bug.h LIB_H += util/include/asm/byteorder.h @@ -383,6 +387,8 @@ LIB_H += util/include/asm/swab.h LIB_H += util/include/asm/system.h LIB_H += util/include/asm/uaccess.h LIB_H += util/include/dwarf-regs.h +LIB_H += util/include/asm/dwarf2.h +LIB_H += util/include/asm/cpufeature.h LIB_H += perf.h LIB_H += util/cache.h LIB_H += util/callchain.h @@ -390,6 +396,7 @@ LIB_H += util/build-id.h LIB_H += util/debug.h LIB_H += util/debugfs.h LIB_H += util/event.h +LIB_H += util/evsel.h LIB_H += util/exec_cmd.h LIB_H += util/types.h LIB_H += util/levenshtein.h @@ -398,6 +405,7 @@ LIB_H += util/parse-options.h LIB_H += util/parse-events.h LIB_H += util/quote.h LIB_H += util/util.h +LIB_H += util/xyarray.h LIB_H += util/header.h LIB_H += util/help.h LIB_H += util/session.h @@ -417,6 +425,7 @@ LIB_H += util/probe-finder.h LIB_H += util/probe-event.h LIB_H += util/pstack.h LIB_H += util/cpumap.h +LIB_H += $(ARCH_INCLUDE) LIB_OBJS += $(OUTPUT)util/abspath.o LIB_OBJS += $(OUTPUT)util/alias.o @@ -426,6 +435,7 @@ LIB_OBJS += $(OUTPUT)util/ctype.o LIB_OBJS += $(OUTPUT)util/debugfs.o LIB_OBJS += $(OUTPUT)util/environment.o LIB_OBJS += $(OUTPUT)util/event.o +LIB_OBJS += $(OUTPUT)util/evsel.o LIB_OBJS += $(OUTPUT)util/exec_cmd.o LIB_OBJS += $(OUTPUT)util/help.o LIB_OBJS += $(OUTPUT)util/levenshtein.o @@ -463,6 +473,7 @@ LIB_OBJS += $(OUTPUT)util/sort.o LIB_OBJS += $(OUTPUT)util/hist.o LIB_OBJS += $(OUTPUT)util/probe-event.o LIB_OBJS += $(OUTPUT)util/util.o +LIB_OBJS += $(OUTPUT)util/xyarray.o LIB_OBJS += $(OUTPUT)util/cpumap.o BUILTIN_OBJS += $(OUTPUT)builtin-annotate.o @@ -472,6 +483,9 @@ BUILTIN_OBJS += $(OUTPUT)builtin-bench.o # Benchmark modules BUILTIN_OBJS += $(OUTPUT)bench/sched-messaging.o BUILTIN_OBJS += $(OUTPUT)bench/sched-pipe.o +ifeq ($(RAW_ARCH),x86_64) +BUILTIN_OBJS += $(OUTPUT)bench/mem-memcpy-x86-64-asm.o +endif BUILTIN_OBJS += $(OUTPUT)bench/mem-memcpy.o BUILTIN_OBJS += $(OUTPUT)builtin-diff.o @@ -485,7 +499,7 @@ BUILTIN_OBJS += $(OUTPUT)builtin-report.o BUILTIN_OBJS += $(OUTPUT)builtin-stat.o BUILTIN_OBJS += $(OUTPUT)builtin-timechart.o BUILTIN_OBJS += $(OUTPUT)builtin-top.o -BUILTIN_OBJS += $(OUTPUT)builtin-trace.o +BUILTIN_OBJS += $(OUTPUT)builtin-script.o BUILTIN_OBJS += $(OUTPUT)builtin-probe.o BUILTIN_OBJS += $(OUTPUT)builtin-kmem.o BUILTIN_OBJS += $(OUTPUT)builtin-lock.o @@ -507,7 +521,7 @@ PERFLIBS = $(LIB_FILE) -include config.mak ifndef NO_DWARF -FLAGS_DWARF=$(ALL_CFLAGS) -I/usr/include/elfutils -ldw -lelf $(ALL_LDFLAGS) $(EXTLIBS) +FLAGS_DWARF=$(ALL_CFLAGS) -ldw -lelf $(ALL_LDFLAGS) $(EXTLIBS) ifneq ($(call try-cc,$(SOURCE_DWARF),$(FLAGS_DWARF)),y) msg := $(warning No libdw.h found or old libdw.h found or elfutils is older than 0.138, disables dwarf support. Please install new elfutils-devel/libdw-dev); NO_DWARF := 1 @@ -554,7 +568,7 @@ ifndef NO_DWARF ifeq ($(origin PERF_HAVE_DWARF_REGS), undefined) msg := $(warning DWARF register mappings have not been defined for architecture $(ARCH), DWARF support disabled); else - BASIC_CFLAGS += -I/usr/include/elfutils -DDWARF_SUPPORT + BASIC_CFLAGS += -DDWARF_SUPPORT EXTLIBS += -lelf -ldw LIB_OBJS += $(OUTPUT)util/probe-finder.o endif # PERF_HAVE_DWARF_REGS @@ -891,13 +905,14 @@ prefix_SQ = $(subst ','\'',$(prefix)) SHELL_PATH_SQ = $(subst ','\'',$(SHELL_PATH)) PERL_PATH_SQ = $(subst ','\'',$(PERL_PATH)) -LIBS = $(PERFLIBS) $(EXTLIBS) +LIBS = -Wl,--whole-archive $(PERFLIBS) -Wl,--no-whole-archive $(EXTLIBS) BASIC_CFLAGS += -DSHA1_HEADER='$(SHA1_HEADER_SQ)' \ $(COMPAT_CFLAGS) LIB_OBJS += $(COMPAT_OBJS) ALL_CFLAGS += $(BASIC_CFLAGS) +ALL_CFLAGS += $(ARCH_CFLAGS) ALL_LDFLAGS += $(BASIC_LDFLAGS) export TAR INSTALL DESTDIR SHELL_PATH diff --git a/tools/perf/arch/s390/Makefile b/tools/perf/arch/s390/Makefile new file mode 100644 index 00000000000..15130b50dfe --- /dev/null +++ b/tools/perf/arch/s390/Makefile @@ -0,0 +1,4 @@ +ifndef NO_DWARF +PERF_HAVE_DWARF_REGS := 1 +LIB_OBJS += $(OUTPUT)arch/$(ARCH)/util/dwarf-regs.o +endif diff --git a/tools/perf/arch/s390/util/dwarf-regs.c b/tools/perf/arch/s390/util/dwarf-regs.c new file mode 100644 index 00000000000..e19653e025f --- /dev/null +++ b/tools/perf/arch/s390/util/dwarf-regs.c @@ -0,0 +1,22 @@ +/* + * Mapping of DWARF debug register numbers into register names. + * + * Copyright IBM Corp. 2010 + * Author(s): Heiko Carstens <heiko.carstens@de.ibm.com>, + * + */ + +#include <libio.h> +#include <dwarf-regs.h> + +#define NUM_GPRS 16 + +static const char *gpr_names[NUM_GPRS] = { + "%r0", "%r1", "%r2", "%r3", "%r4", "%r5", "%r6", "%r7", + "%r8", "%r9", "%r10", "%r11", "%r12", "%r13", "%r14", "%r15", +}; + +const char *get_arch_regstr(unsigned int n) +{ + return (n >= NUM_GPRS) ? NULL : gpr_names[n]; +} diff --git a/tools/perf/bench/mem-memcpy-arch.h b/tools/perf/bench/mem-memcpy-arch.h new file mode 100644 index 00000000000..a72e36cb539 --- /dev/null +++ b/tools/perf/bench/mem-memcpy-arch.h @@ -0,0 +1,12 @@ + +#ifdef ARCH_X86_64 + +#define MEMCPY_FN(fn, name, desc) \ + extern void *fn(void *, const void *, size_t); + +#include "mem-memcpy-x86-64-asm-def.h" + +#undef MEMCPY_FN + +#endif + diff --git a/tools/perf/bench/mem-memcpy-x86-64-asm-def.h b/tools/perf/bench/mem-memcpy-x86-64-asm-def.h new file mode 100644 index 00000000000..d588b87696f --- /dev/null +++ b/tools/perf/bench/mem-memcpy-x86-64-asm-def.h @@ -0,0 +1,4 @@ + +MEMCPY_FN(__memcpy, + "x86-64-unrolled", + "unrolled memcpy() in arch/x86/lib/memcpy_64.S") diff --git a/tools/perf/bench/mem-memcpy-x86-64-asm.S b/tools/perf/bench/mem-memcpy-x86-64-asm.S new file mode 100644 index 00000000000..a57b66e853c --- /dev/null +++ b/tools/perf/bench/mem-memcpy-x86-64-asm.S @@ -0,0 +1,2 @@ + +#include "../../../arch/x86/lib/memcpy_64.S" diff --git a/tools/perf/bench/mem-memcpy.c b/tools/perf/bench/mem-memcpy.c index 38dae746514..db82021f4b9 100644 --- a/tools/perf/bench/mem-memcpy.c +++ b/tools/perf/bench/mem-memcpy.c @@ -12,6 +12,7 @@ #include "../util/parse-options.h" #include "../util/header.h" #include "bench.h" +#include "mem-memcpy-arch.h" #include <stdio.h> #include <stdlib.h> @@ -23,8 +24,10 @@ static const char *length_str = "1MB"; static const char *routine = "default"; -static bool use_clock = false; +static bool use_clock; static int clock_fd; +static bool only_prefault; +static bool no_prefault; static const struct option options[] = { OPT_STRING('l', "length", &length_str, "1MB", @@ -34,19 +37,33 @@ static const struct option options[] = { "Specify routine to copy"), OPT_BOOLEAN('c', "clock", &use_clock, "Use CPU clock for measuring"), + OPT_BOOLEAN('o', "only-prefault", &only_prefault, + "Show only the result with page faults before memcpy()"), + OPT_BOOLEAN('n', "no-prefault", &no_prefault, + "Show only the result without page faults before memcpy()"), OPT_END() }; +typedef void *(*memcpy_t)(void *, const void *, size_t); + struct routine { const char *name; const char *desc; - void * (*fn)(void *dst, const void *src, size_t len); + memcpy_t fn; }; struct routine routines[] = { { "default", "Default memcpy() provided by glibc", memcpy }, +#ifdef ARCH_X86_64 + +#define MEMCPY_FN(fn, name, desc) { name, desc, fn }, +#include "mem-memcpy-x86-64-asm-def.h" +#undef MEMCPY_FN + +#endif + { NULL, NULL, NULL } @@ -89,29 +106,98 @@ static double timeval2double(struct timeval *ts) (double)ts->tv_usec / (double)1000000; } +static void alloc_mem(void **dst, void **src, size_t length) +{ + *dst = zalloc(length); + if (!dst) + die("memory allocation failed - maybe length is too large?\n"); + + *src = zalloc(length); + if (!src) + die("memory allocation failed - maybe length is too large?\n"); +} + +static u64 do_memcpy_clock(memcpy_t fn, size_t len, bool prefault) +{ + u64 clock_start = 0ULL, clock_end = 0ULL; + void *src = NULL, *dst = NULL; + + alloc_mem(&src, &dst, len); + + if (prefault) + fn(dst, src, len); + + clock_start = get_clock(); + fn(dst, src, len); + clock_end = get_clock(); + + free(src); + free(dst); + return clock_end - clock_start; +} + +static double do_memcpy_gettimeofday(memcpy_t fn, size_t len, bool prefault) +{ + struct timeval tv_start, tv_end, tv_diff; + void *src = NULL, *dst = NULL; + + alloc_mem(&src, &dst, len); + + if (prefault) + fn(dst, src, len); + + BUG_ON(gettimeofday(&tv_start, NULL)); + fn(dst, src, len); + BUG_ON(gettimeofday(&tv_end, NULL)); + + timersub(&tv_end, &tv_start, &tv_diff); + + free(src); + free(dst); + return (double)((double)len / timeval2double(&tv_diff)); +} + +#define pf (no_prefault ? 0 : 1) + +#define print_bps(x) do { \ + if (x < K) \ + printf(" %14lf B/Sec", x); \ + else if (x < K * K) \ + printf(" %14lfd KB/Sec", x / K); \ + else if (x < K * K * K) \ + printf(" %14lf MB/Sec", x / K / K); \ + else \ + printf(" %14lf GB/Sec", x / K / K / K); \ + } while (0) + int bench_mem_memcpy(int argc, const char **argv, const char *prefix __used) { int i; - void *dst, *src; - size_t length; - double bps = 0.0; - struct timeval tv_start, tv_end, tv_diff; - u64 clock_start, clock_end, clock_diff; + size_t len; + double result_bps[2]; + u64 result_clock[2]; - clock_start = clock_end = clock_diff = 0ULL; argc = parse_options(argc, argv, options, bench_mem_memcpy_usage, 0); - tv_diff.tv_sec = 0; - tv_diff.tv_usec = 0; - length = (size_t)perf_atoll((char *)length_str); + if (use_clock) + init_clock(); + + len = (size_t)perf_atoll((char *)length_str); - if ((s64)length <= 0) { + result_clock[0] = result_clock[1] = 0ULL; + result_bps[0] = result_bps[1] = 0.0; + + if ((s64)len <= 0) { fprintf(stderr, "Invalid length:%s\n", length_str); return 1; } + /* same to without specifying either of prefault and no-prefault */ + if (only_prefault && no_prefault) + only_prefault = no_prefault = false; + for (i = 0; routines[i].name; i++) { if (!strcmp(routines[i].name, routine)) break; @@ -126,61 +212,80 @@ int bench_mem_memcpy(int argc, const char **argv, return 1; } - dst = zalloc(length); - if (!dst) - die("memory allocation failed - maybe length is too large?\n"); - - src = zalloc(length); - if (!src) - die("memory allocation failed - maybe length is too large?\n"); - - if (bench_format == BENCH_FORMAT_DEFAULT) { - printf("# Copying %s Bytes from %p to %p ...\n\n", - length_str, src, dst); - } - - if (use_clock) { - init_clock(); - clock_start = get_clock(); - } else { - BUG_ON(gettimeofday(&tv_start, NULL)); - } - - routines[i].fn(dst, src, length); + if (bench_format == BENCH_FORMAT_DEFAULT) + printf("# Copying %s Bytes ...\n\n", length_str); - if (use_clock) { - clock_end = get_clock(); - clock_diff = clock_end - clock_start; + if (!only_prefault && !no_prefault) { + /* show both of results */ + if (use_clock) { + result_clock[0] = + do_memcpy_clock(routines[i].fn, len, false); + result_clock[1] = + do_memcpy_clock(routines[i].fn, len, true); + } else { + result_bps[0] = + do_memcpy_gettimeofday(routines[i].fn, + len, false); + result_bps[1] = + do_memcpy_gettimeofday(routines[i].fn, + len, true); + } } else { - BUG_ON(gettimeofday(&tv_end, NULL)); - timersub(&tv_end, &tv_start, &tv_diff); - bps = (double)((double)length / timeval2double(&tv_diff)); + if (use_clock) { + result_clock[pf] = + do_memcpy_clock(routines[i].fn, + len, only_prefault); + } else { + result_bps[pf] = + do_memcpy_gettimeofday(routines[i].fn, + len, only_prefault); + } } switch (bench_format) { case BENCH_FORMAT_DEFAULT: - if (use_clock) { - printf(" %14lf Clock/Byte\n", - (double)clock_diff / (double)length); - } else { - if (bps < K) - printf(" %14lf B/Sec\n", bps); - else if (bps < K * K) - printf(" %14lfd KB/Sec\n", bps / 1024); - else if (bps < K * K * K) - printf(" %14lf MB/Sec\n", bps / 1024 / 1024); - else { - printf(" %14lf GB/Sec\n", - bps / 1024 / 1024 / 1024); + if (!only_prefault && !no_prefault) { + if (use_clock) { + printf(" %14lf Clock/Byte\n", + (double)result_clock[0] + / (double)len); + printf(" %14lf Clock/Byte (with prefault)\n", + (double)result_clock[1] + / (double)len); + } else { + print_bps(result_bps[0]); + printf("\n"); + print_bps(result_bps[1]); + printf(" (with prefault)\n"); } + } else { + if (use_clock) { + printf(" %14lf Clock/Byte", + (double)result_clock[pf] + / (double)len); + } else + print_bps(result_bps[pf]); + + printf("%s\n", only_prefault ? " (with prefault)" : ""); } break; case BENCH_FORMAT_SIMPLE: - if (use_clock) { - printf("%14lf\n", - (double)clock_diff / (double)length); - } else - printf("%lf\n", bps); + if (!only_prefault && !no_prefault) { + if (use_clock) { + printf("%lf %lf\n", + (double)result_clock[0] / (double)len, + (double)result_clock[1] / (double)len); + } else { + printf("%lf %lf\n", + result_bps[0], result_bps[1]); + } + } else { + if (use_clock) { + printf("%lf\n", (double)result_clock[pf] + / (double)len); + } else + printf("%lf\n", result_bps[pf]); + } break; default: /* reaching this means there's some disaster: */ diff --git a/tools/perf/builtin-annotate.c b/tools/perf/builtin-annotate.c index 6d5604d8df9..c056cdc0691 100644 --- a/tools/perf/builtin-annotate.c +++ b/tools/perf/builtin-annotate.c @@ -58,12 +58,12 @@ static int hists__add_entry(struct hists *self, struct addr_location *al) return hist_entry__inc_addr_samples(he, al->addr); } -static int process_sample_event(event_t *event, struct perf_session *session) +static int process_sample_event(event_t *event, struct sample_data *sample, + struct perf_session *session) { struct addr_location al; - struct sample_data data; - if (event__preprocess_sample(event, session, &al, &data, NULL) < 0) { + if (event__preprocess_sample(event, session, &al, sample, NULL) < 0) { pr_warning("problem processing %d event, skipping it.\n", event->header.type); return -1; @@ -375,6 +375,8 @@ static struct perf_event_ops event_ops = { .mmap = event__process_mmap, .comm = event__process_comm, .fork = event__process_task, + .ordered_samples = true, + .ordering_requires_timestamps = true, }; static int __cmd_annotate(void) @@ -382,7 +384,7 @@ static int __cmd_annotate(void) int ret; struct perf_session *session; - session = perf_session__new(input_name, O_RDONLY, force, false); + session = perf_session__new(input_name, O_RDONLY, force, false, &event_ops); if (session == NULL) return -ENOMEM; diff --git a/tools/perf/builtin-buildid-list.c b/tools/perf/builtin-buildid-list.c index c49837de7d3..5af32ae9031 100644 --- a/tools/perf/builtin-buildid-list.c +++ b/tools/perf/builtin-buildid-list.c @@ -38,7 +38,8 @@ static int __cmd_buildid_list(void) { struct perf_session *session; - session = perf_session__new(input_name, O_RDONLY, force, false); + session = perf_session__new(input_name, O_RDONLY, force, false, + &build_id__mark_dso_hit_ops); if (session == NULL) return -1; diff --git a/tools/perf/builtin-diff.c b/tools/perf/builtin-diff.c index fca1d440291..3153e492dbc 100644 --- a/tools/perf/builtin-diff.c +++ b/tools/perf/builtin-diff.c @@ -30,12 +30,13 @@ static int hists__add_entry(struct hists *self, return -ENOMEM; } -static int diff__process_sample_event(event_t *event, struct perf_session *session) +static int diff__process_sample_event(event_t *event, + struct sample_data *sample, + struct perf_session *session) { struct addr_location al; - struct sample_data data = { .period = 1, }; - if (event__preprocess_sample(event, session, &al, &data, NULL) < 0) { + if (event__preprocess_sample(event, session, &al, sample, NULL) < 0) { pr_warning("problem processing %d event, skipping it.\n", event->header.type); return -1; @@ -44,12 +45,12 @@ static int diff__process_sample_event(event_t *event, struct perf_session *sessi if (al.filtered || al.sym == NULL) return 0; - if (hists__add_entry(&session->hists, &al, data.period)) { + if (hists__add_entry(&session->hists, &al, sample->period)) { pr_warning("problem incrementing symbol period, skipping event\n"); return -1; } - session->hists.stats.total_period += data.period; + session->hists.stats.total_period += sample->period; return 0; } @@ -60,6 +61,8 @@ static struct perf_event_ops event_ops = { .exit = event__process_task, .fork = event__process_task, .lost = event__process_lost, + .ordered_samples = true, + .ordering_requires_timestamps = true, }; static void perf_session__insert_hist_entry_by_name(struct rb_root *root, @@ -141,8 +144,8 @@ static int __cmd_diff(void) int ret, i; struct perf_session *session[2]; - session[0] = perf_session__new(input_old, O_RDONLY, force, false); - session[1] = perf_session__new(input_new, O_RDONLY, force, false); + session[0] = perf_session__new(input_old, O_RDONLY, force, false, &event_ops); + session[1] = perf_session__new(input_new, O_RDONLY, force, false, &event_ops); if (session[0] == NULL || session[1] == NULL) return -ENOMEM; @@ -173,7 +176,7 @@ static const char * const diff_usage[] = { static const struct option options[] = { OPT_INCR('v', "verbose", &verbose, "be more verbose (show symbol address, etc)"), - OPT_BOOLEAN('m', "displacement", &show_displacement, + OPT_BOOLEAN('M', "displacement", &show_displacement, "Show position displacement relative to baseline"), OPT_BOOLEAN('D', "dump-raw-trace", &dump_trace, "dump raw trace in ASCII"), @@ -191,6 +194,8 @@ static const struct option options[] = { OPT_STRING('t', "field-separator", &symbol_conf.field_sep, "separator", "separator for columns, no spaces will be added between " "columns '.' is reserved."), + OPT_STRING(0, "symfs", &symbol_conf.symfs, "directory", + "Look for files with symbols relative to this directory"), OPT_END() }; diff --git a/tools/perf/builtin-inject.c b/tools/perf/builtin-inject.c index 8e3e47b064c..0c78ffa7bf6 100644 --- a/tools/perf/builtin-inject.c +++ b/tools/perf/builtin-inject.c @@ -16,8 +16,8 @@ static char const *input_name = "-"; static bool inject_build_ids; -static int event__repipe(event_t *event __used, - struct perf_session *session __used) +static int event__repipe_synth(event_t *event, + struct perf_session *session __used) { uint32_t size; void *buf = event; @@ -36,22 +36,30 @@ static int event__repipe(event_t *event __used, return 0; } -static int event__repipe_mmap(event_t *self, struct perf_session *session) +static int event__repipe(event_t *event, struct sample_data *sample __used, + struct perf_session *session) +{ + return event__repipe_synth(event, session); +} + +static int event__repipe_mmap(event_t *self, struct sample_data *sample, + struct perf_session *session) { int err; - err = event__process_mmap(self, session); - event__repipe(self, session); + err = event__process_mmap(self, sample, session); + event__repipe(self, sample, session); return err; } -static int event__repipe_task(event_t *self, struct perf_session *session) +static int event__repipe_task(event_t *self, struct sample_data *sample, + struct perf_session *session) { int err; - err = event__process_task(self, session); - event__repipe(self, session); + err = event__process_task(self, sample, session); + event__repipe(self, sample, session); return err; } @@ -61,7 +69,7 @@ static int event__repipe_tracing_data(event_t *self, { int err; - event__repipe(self, session); + event__repipe_synth(self, session); err = event__process_tracing_data(self, session); return err; @@ -111,7 +119,8 @@ static int dso__inject_build_id(struct dso *self, struct perf_session *session) return 0; } -static int event__inject_buildid(event_t *event, struct perf_session *session) +static int event__inject_buildid(event_t *event, struct sample_data *sample, + struct perf_session *session) { struct addr_location al; struct thread *thread; @@ -146,7 +155,7 @@ static int event__inject_buildid(event_t *event, struct perf_session *session) } repipe: - event__repipe(event, session); + event__repipe(event, sample, session); return 0; } @@ -160,10 +169,10 @@ struct perf_event_ops inject_ops = { .read = event__repipe, .throttle = event__repipe, .unthrottle = event__repipe, - .attr = event__repipe, - .event_type = event__repipe, - .tracing_data = event__repipe, - .build_id = event__repipe, + .attr = event__repipe_synth, + .event_type = event__repipe_synth, + .tracing_data = event__repipe_synth, + .build_id = event__repipe_synth, }; extern volatile int session_done; @@ -187,7 +196,7 @@ static int __cmd_inject(void) inject_ops.tracing_data = event__repipe_tracing_data; } - session = perf_session__new(input_name, O_RDONLY, false, true); + session = perf_session__new(input_name, O_RDONLY, false, true, &inject_ops); if (session == NULL) return -ENOMEM; diff --git a/tools/perf/builtin-kmem.c b/tools/perf/builtin-kmem.c index 31f60a2535e..def7ddc2fd4 100644 --- a/tools/perf/builtin-kmem.c +++ b/tools/perf/builtin-kmem.c @@ -304,22 +304,11 @@ process_raw_event(event_t *raw_event __used, void *data, } } -static int process_sample_event(event_t *event, struct perf_session *session) +static int process_sample_event(event_t *event, struct sample_data *sample, + struct perf_session *session) { - struct sample_data data; - struct thread *thread; + struct thread *thread = perf_session__findnew(session, event->ip.pid); - memset(&data, 0, sizeof(data)); - data.time = -1; - data.cpu = -1; - data.period = 1; - - event__parse_sample(event, session->sample_type, &data); - - dump_printf("(IP, %d): %d/%d: %#Lx period: %Ld\n", event->header.misc, - data.pid, data.tid, data.ip, data.period); - - thread = perf_session__findnew(session, event->ip.pid); if (thread == NULL) { pr_debug("problem processing %d event, skipping it.\n", event->header.type); @@ -328,8 +317,8 @@ static int process_sample_event(event_t *event, struct perf_session *session) dump_printf(" ... thread: %s:%d\n", thread->comm, thread->pid); - process_raw_event(event, data.raw_data, data.cpu, - data.time, thread); + process_raw_event(event, sample->raw_data, sample->cpu, + sample->time, thread); return 0; } @@ -492,7 +481,8 @@ static void sort_result(void) static int __cmd_kmem(void) { int err = -EINVAL; - struct perf_session *session = perf_session__new(input_name, O_RDONLY, 0, false); + struct perf_session *session = perf_session__new(input_name, O_RDONLY, + 0, false, &event_ops); if (session == NULL) return -ENOMEM; @@ -747,6 +737,9 @@ static int __cmd_record(int argc, const char **argv) rec_argc = ARRAY_SIZE(record_args) + argc - 1; rec_argv = calloc(rec_argc + 1, sizeof(char *)); + if (rec_argv == NULL) + return -ENOMEM; + for (i = 0; i < ARRAY_SIZE(record_args); i++) rec_argv[i] = strdup(record_args[i]); diff --git a/tools/perf/builtin-lock.c b/tools/perf/builtin-lock.c index 821c1586a22..b9c6e543297 100644 --- a/tools/perf/builtin-lock.c +++ b/tools/perf/builtin-lock.c @@ -834,22 +834,18 @@ static void dump_info(void) die("Unknown type of information\n"); } -static int process_sample_event(event_t *self, struct perf_session *s) +static int process_sample_event(event_t *self, struct sample_data *sample, + struct perf_session *s) { - struct sample_data data; - struct thread *thread; + struct thread *thread = perf_session__findnew(s, sample->tid); - bzero(&data, sizeof(data)); - event__parse_sample(self, s->sample_type, &data); - - thread = perf_session__findnew(s, data.tid); if (thread == NULL) { pr_debug("problem processing %d event, skipping it.\n", self->header.type); return -1; } - process_raw_event(data.raw_data, data.cpu, data.time, thread); + process_raw_event(sample->raw_data, sample->cpu, sample->time, thread); return 0; } @@ -862,7 +858,7 @@ static struct perf_event_ops eops = { static int read_events(void) { - session = perf_session__new(input_name, O_RDONLY, 0, false); + session = perf_session__new(input_name, O_RDONLY, 0, false, &eops); if (!session) die("Initializing perf session failed\n"); @@ -947,6 +943,9 @@ static int __cmd_record(int argc, const char **argv) rec_argc = ARRAY_SIZE(record_args) + argc - 1; rec_argv = calloc(rec_argc + 1, sizeof(char *)); + if (rec_argv == NULL) + return -ENOMEM; + for (i = 0; i < ARRAY_SIZE(record_args); i++) rec_argv[i] = strdup(record_args[i]); @@ -982,9 +981,9 @@ int cmd_lock(int argc, const char **argv, const char *prefix __used) usage_with_options(report_usage, report_options); } __cmd_report(); - } else if (!strcmp(argv[0], "trace")) { - /* Aliased to 'perf trace' */ - return cmd_trace(argc, argv, prefix); + } else if (!strcmp(argv[0], "script")) { + /* Aliased to 'perf script' */ + return cmd_script(argc, argv, prefix); } else if (!strcmp(argv[0], "info")) { if (argc) { argc = parse_options(argc, argv, diff --git a/tools/perf/builtin-record.c b/tools/perf/builtin-record.c index 564491fa18b..7069bd3e90b 100644 --- a/tools/perf/builtin-record.c +++ b/tools/perf/builtin-record.c @@ -18,6 +18,7 @@ #include "util/header.h" #include "util/event.h" +#include "util/evsel.h" #include "util/debug.h" #include "util/session.h" #include "util/symbol.h" @@ -27,17 +28,18 @@ #include <sched.h> #include <sys/mman.h> +#define FD(e, x, y) (*(int *)xyarray__entry(e->fd, x, y)) + enum write_mode_t { WRITE_FORCE, WRITE_APPEND }; -static int *fd[MAX_NR_CPUS][MAX_COUNTERS]; - static u64 user_interval = ULLONG_MAX; static u64 default_interval = 0; +static u64 sample_type; -static int nr_cpus = 0; +static struct cpu_map *cpus; static unsigned int page_size; static unsigned int mmap_pages = 128; static unsigned int user_freq = UINT_MAX; @@ -48,11 +50,11 @@ static const char *output_name = "perf.data"; static int group = 0; static int realtime_prio = 0; static bool raw_samples = false; +static bool sample_id_all_avail = true; static bool system_wide = false; static pid_t target_pid = -1; static pid_t target_tid = -1; -static pid_t *all_tids = NULL; -static int thread_num = 0; +static struct thread_map *threads; static pid_t child_pid = -1; static bool no_inherit = false; static enum write_mode_t write_mode = WRITE_FORCE; @@ -60,7 +62,9 @@ static bool call_graph = false; static bool inherit_stat = false; static bool no_samples = false; static bool sample_address = false; +static bool sample_time = false; static bool no_buildid = false; +static bool no_buildid_cache = false; static long samples = 0; static u64 bytes_written = 0; @@ -77,7 +81,6 @@ static struct perf_session *session; static const char *cpu_list; struct mmap_data { - int counter; void *base; unsigned int mask; unsigned int prev; @@ -128,6 +131,7 @@ static void write_output(void *buf, size_t size) } static int process_synthesized_event(event_t *event, + struct sample_data *sample __used, struct perf_session *self __used) { write_output(event, event->header.size); @@ -224,12 +228,12 @@ static struct perf_header_attr *get_header_attr(struct perf_event_attr *a, int n return h_attr; } -static void create_counter(int counter, int cpu) +static void create_counter(struct perf_evsel *evsel, int cpu) { - char *filter = filters[counter]; - struct perf_event_attr *attr = attrs + counter; + char *filter = evsel->filter; + struct perf_event_attr *attr = &evsel->attr; struct perf_header_attr *h_attr; - int track = !counter; /* only the first counter needs these */ + int track = !evsel->idx; /* only the first counter needs these */ int thread_index; int ret; struct { @@ -238,6 +242,19 @@ static void create_counter(int counter, int cpu) u64 time_running; u64 id; } read_data; + /* + * Check if parse_single_tracepoint_event has already asked for + * PERF_SAMPLE_TIME. + * + * XXX this is kludgy but short term fix for problems introduced by + * eac23d1c that broke 'perf script' by having different sample_types + * when using multiple tracepoint events when we use a perf binary + * that tries to use sample_id_all on an older kernel. + * + * We need to move counter creation to perf_session, support + * different sample_types, etc. + */ + bool time_needed = attr->sample_type & PERF_SAMPLE_TIME; attr->read_format = PERF_FORMAT_TOTAL_TIME_ENABLED | PERF_FORMAT_TOTAL_TIME_RUNNING | @@ -280,6 +297,10 @@ static void create_counter(int counter, int cpu) if (system_wide) attr->sample_type |= PERF_SAMPLE_CPU; + if (sample_id_all_avail && + (sample_time || system_wide || !no_inherit || cpu_list)) + attr->sample_type |= PERF_SAMPLE_TIME; + if (raw_samples) { attr->sample_type |= PERF_SAMPLE_TIME; attr->sample_type |= PERF_SAMPLE_RAW; @@ -293,13 +314,14 @@ static void create_counter(int counter, int cpu) attr->disabled = 1; attr->enable_on_exec = 1; } +retry_sample_id: + attr->sample_id_all = sample_id_all_avail ? 1 : 0; - for (thread_index = 0; thread_index < thread_num; thread_index++) { + for (thread_index = 0; thread_index < threads->nr; thread_index++) { try_again: - fd[nr_cpu][counter][thread_index] = sys_perf_event_open(attr, - all_tids[thread_index], cpu, group_fd, 0); + FD(evsel, nr_cpu, thread_index) = sys_perf_event_open(attr, threads->map[thread_index], cpu, group_fd, 0); - if (fd[nr_cpu][counter][thread_index] < 0) { + if (FD(evsel, nr_cpu, thread_index) < 0) { int err = errno; if (err == EPERM || err == EACCES) @@ -309,6 +331,18 @@ try_again: else if (err == ENODEV && cpu_list) { die("No such device - did you specify" " an out-of-range profile CPU?\n"); + } else if (err == ENOENT) { + die("%s event is not supported. ", + event_name(evsel)); + } else if (err == EINVAL && sample_id_all_avail) { + /* + * Old kernel, no attr->sample_id_type_all field + */ + sample_id_all_avail = false; + if (!sample_time && !raw_samples && !time_needed) + attr->sample_type &= ~PERF_SAMPLE_TIME; + + goto retry_sample_id; } /* @@ -326,8 +360,8 @@ try_again: goto try_again; } printf("\n"); - error("perfcounter syscall returned with %d (%s)\n", - fd[nr_cpu][counter][thread_index], strerror(err)); + error("sys_perf_event_open() syscall returned with %d (%s). /bin/dmesg may provide additional information.\n", + FD(evsel, nr_cpu, thread_index), strerror(err)); #if defined(__i386__) || defined(__x86_64__) if (attr->type == PERF_TYPE_HARDWARE && err == EOPNOTSUPP) @@ -341,7 +375,7 @@ try_again: exit(-1); } - h_attr = get_header_attr(attr, counter); + h_attr = get_header_attr(attr, evsel->idx); if (h_attr == NULL) die("nomem\n"); @@ -352,7 +386,7 @@ try_again: } } - if (read(fd[nr_cpu][counter][thread_index], &read_data, sizeof(read_data)) == -1) { + if (read(FD(evsel, nr_cpu, thread_index), &read_data, sizeof(read_data)) == -1) { perror("Unable to read perf file descriptor"); exit(-1); } @@ -362,43 +396,44 @@ try_again: exit(-1); } - assert(fd[nr_cpu][counter][thread_index] >= 0); - fcntl(fd[nr_cpu][counter][thread_index], F_SETFL, O_NONBLOCK); + assert(FD(evsel, nr_cpu, thread_index) >= 0); + fcntl(FD(evsel, nr_cpu, thread_index), F_SETFL, O_NONBLOCK); /* * First counter acts as the group leader: */ if (group && group_fd == -1) - group_fd = fd[nr_cpu][counter][thread_index]; - - if (counter || thread_index) { - ret = ioctl(fd[nr_cpu][counter][thread_index], - PERF_EVENT_IOC_SET_OUTPUT, - fd[nr_cpu][0][0]); + group_fd = FD(evsel, nr_cpu, thread_index); + + if (evsel->idx || thread_index) { + struct perf_evsel *first; + first = list_entry(evsel_list.next, struct perf_evsel, node); + ret = ioctl(FD(evsel, nr_cpu, thread_index), + PERF_EVENT_IOC_SET_OUTPUT, + FD(first, nr_cpu, 0)); if (ret) { error("failed to set output: %d (%s)\n", errno, strerror(errno)); exit(-1); } } else { - mmap_array[nr_cpu].counter = counter; mmap_array[nr_cpu].prev = 0; mmap_array[nr_cpu].mask = mmap_pages*page_size - 1; mmap_array[nr_cpu].base = mmap(NULL, (mmap_pages+1)*page_size, - PROT_READ|PROT_WRITE, MAP_SHARED, fd[nr_cpu][counter][thread_index], 0); + PROT_READ | PROT_WRITE, MAP_SHARED, FD(evsel, nr_cpu, thread_index), 0); if (mmap_array[nr_cpu].base == MAP_FAILED) { error("failed to mmap with %d (%s)\n", errno, strerror(errno)); exit(-1); } - event_array[nr_poll].fd = fd[nr_cpu][counter][thread_index]; + event_array[nr_poll].fd = FD(evsel, nr_cpu, thread_index); event_array[nr_poll].events = POLLIN; nr_poll++; } if (filter != NULL) { - ret = ioctl(fd[nr_cpu][counter][thread_index], - PERF_EVENT_IOC_SET_FILTER, filter); + ret = ioctl(FD(evsel, nr_cpu, thread_index), + PERF_EVENT_IOC_SET_FILTER, filter); if (ret) { error("failed to set filter with %d (%s)\n", errno, strerror(errno)); @@ -406,15 +441,19 @@ try_again: } } } + + if (!sample_type) + sample_type = attr->sample_type; } static void open_counters(int cpu) { - int counter; + struct perf_evsel *pos; group_fd = -1; - for (counter = 0; counter < nr_counters; counter++) - create_counter(counter, cpu); + + list_for_each_entry(pos, &evsel_list, node) + create_counter(pos, cpu); nr_cpu++; } @@ -437,7 +476,8 @@ static void atexit_header(void) if (!pipe_output) { session->header.data_size += bytes_written; - process_buildids(); + if (!no_buildid) + process_buildids(); perf_header__write(&session->header, output, true); perf_session__delete(session); symbol__exit(); @@ -500,7 +540,7 @@ static void mmap_read_all(void) static int __cmd_record(int argc, const char **argv) { - int i, counter; + int i; struct stat st; int flags; int err; @@ -552,19 +592,22 @@ static int __cmd_record(int argc, const char **argv) } session = perf_session__new(output_name, O_WRONLY, - write_mode == WRITE_FORCE, false); + write_mode == WRITE_FORCE, false, NULL); if (session == NULL) { pr_err("Not enough memory for reading perf file header\n"); return -1; } + if (!no_buildid) + perf_header__set_feat(&session->header, HEADER_BUILD_ID); + if (!file_new) { err = perf_header__read(session, output); if (err < 0) goto out_delete_session; } - if (have_tracepoints(attrs, nr_counters)) + if (have_tracepoints(&evsel_list)) perf_header__set_feat(&session->header, HEADER_TRACE_INFO); /* @@ -612,7 +655,7 @@ static int __cmd_record(int argc, const char **argv) } if (!system_wide && target_tid == -1 && target_pid == -1) - all_tids[0] = child_pid; + threads->map[0] = child_pid; close(child_ready_pipe[1]); close(go_pipe[0]); @@ -626,19 +669,15 @@ static int __cmd_record(int argc, const char **argv) close(child_ready_pipe[0]); } - nr_cpus = read_cpu_map(cpu_list); - if (nr_cpus < 1) { - perror("failed to collect number of CPUs"); - return -1; - } - if (!system_wide && no_inherit && !cpu_list) { open_counters(-1); } else { - for (i = 0; i < nr_cpus; i++) - open_counters(cpumap[i]); + for (i = 0; i < cpus->nr; i++) + open_counters(cpus->map[i]); } + perf_session__set_sample_type(session, sample_type); + if (pipe_output) { err = perf_header__write_pipe(output); if (err < 0) @@ -651,6 +690,8 @@ static int __cmd_record(int argc, const char **argv) post_processing_offset = lseek(output, 0, SEEK_CUR); + perf_session__set_sample_id_all(session, sample_id_all_avail); + if (pipe_output) { err = event__synthesize_attrs(&session->header, process_synthesized_event, @@ -667,7 +708,7 @@ static int __cmd_record(int argc, const char **argv) return err; } - if (have_tracepoints(attrs, nr_counters)) { + if (have_tracepoints(&evsel_list)) { /* * FIXME err <= 0 here actually means that * there were no tracepoints so its not really @@ -676,8 +717,7 @@ static int __cmd_record(int argc, const char **argv) * return this more properly and also * propagate errors that now are calling die() */ - err = event__synthesize_tracing_data(output, attrs, - nr_counters, + err = event__synthesize_tracing_data(output, &evsel_list, process_synthesized_event, session); if (err <= 0) { @@ -751,13 +791,13 @@ static int __cmd_record(int argc, const char **argv) if (done) { for (i = 0; i < nr_cpu; i++) { - for (counter = 0; - counter < nr_counters; - counter++) { + struct perf_evsel *pos; + + list_for_each_entry(pos, &evsel_list, node) { for (thread = 0; - thread < thread_num; + thread < threads->nr; thread++) - ioctl(fd[i][counter][thread], + ioctl(FD(pos, i, thread), PERF_EVENT_IOC_DISABLE); } } @@ -831,16 +871,20 @@ const struct option record_options[] = { "per thread counts"), OPT_BOOLEAN('d', "data", &sample_address, "Sample addresses"), + OPT_BOOLEAN('T', "timestamp", &sample_time, "Sample timestamps"), OPT_BOOLEAN('n', "no-samples", &no_samples, "don't sample"), - OPT_BOOLEAN('N', "no-buildid-cache", &no_buildid, + OPT_BOOLEAN('N', "no-buildid-cache", &no_buildid_cache, "do not update the buildid cache"), + OPT_BOOLEAN('B', "no-buildid", &no_buildid, + "do not collect buildids in perf.data"), OPT_END() }; int cmd_record(int argc, const char **argv, const char *prefix __used) { - int i, j, err = -ENOMEM; + int err = -ENOMEM; + struct perf_evsel *pos; argc = parse_options(argc, argv, record_options, record_usage, PARSE_OPT_STOP_AT_NON_OPTION); @@ -859,41 +903,36 @@ int cmd_record(int argc, const char **argv, const char *prefix __used) } symbol__init(); - if (no_buildid) + + if (no_buildid_cache || no_buildid) disable_buildid_cache(); - if (!nr_counters) { - nr_counters = 1; - attrs[0].type = PERF_TYPE_HARDWARE; - attrs[0].config = PERF_COUNT_HW_CPU_CYCLES; + if (list_empty(&evsel_list) && perf_evsel_list__create_default() < 0) { + pr_err("Not enough memory for event selector list\n"); + goto out_symbol_exit; } - if (target_pid != -1) { + if (target_pid != -1) target_tid = target_pid; - thread_num = find_all_tid(target_pid, &all_tids); - if (thread_num <= 0) { - fprintf(stderr, "Can't find all threads of pid %d\n", - target_pid); - usage_with_options(record_usage, record_options); - } - } else { - all_tids=malloc(sizeof(pid_t)); - if (!all_tids) - goto out_symbol_exit; - all_tids[0] = target_tid; - thread_num = 1; + threads = thread_map__new(target_pid, target_tid); + if (threads == NULL) { + pr_err("Problems finding threads of monitor\n"); + usage_with_options(record_usage, record_options); } - for (i = 0; i < MAX_NR_CPUS; i++) { - for (j = 0; j < MAX_COUNTERS; j++) { - fd[i][j] = malloc(sizeof(int)*thread_num); - if (!fd[i][j]) - goto out_free_fd; - } + cpus = cpu_map__new(cpu_list); + if (cpus == NULL) { + perror("failed to parse CPUs map"); + return -1; } - event_array = malloc( - sizeof(struct pollfd)*MAX_NR_CPUS*MAX_COUNTERS*thread_num); + + list_for_each_entry(pos, &evsel_list, node) { + if (perf_evsel__alloc_fd(pos, cpus->nr, threads->nr) < 0) + goto out_free_fd; + } + event_array = malloc((sizeof(struct pollfd) * MAX_NR_CPUS * + MAX_COUNTERS * threads->nr)); if (!event_array) goto out_free_fd; @@ -920,12 +959,8 @@ int cmd_record(int argc, const char **argv, const char *prefix __used) out_free_event_array: free(event_array); out_free_fd: - for (i = 0; i < MAX_NR_CPUS; i++) { - for (j = 0; j < MAX_COUNTERS; j++) - free(fd[i][j]); - } - free(all_tids); - all_tids = NULL; + thread_map__delete(threads); + threads = NULL; out_symbol_exit: symbol__exit(); return err; diff --git a/tools/perf/builtin-report.c b/tools/perf/builtin-report.c index 5de405d4523..75183a4518e 100644 --- a/tools/perf/builtin-report.c +++ b/tools/perf/builtin-report.c @@ -150,13 +150,13 @@ static int add_event_total(struct perf_session *session, return 0; } -static int process_sample_event(event_t *event, struct perf_session *session) +static int process_sample_event(event_t *event, struct sample_data *sample, + struct perf_session *session) { - struct sample_data data = { .period = 1, }; struct addr_location al; struct perf_event_attr *attr; - if (event__preprocess_sample(event, session, &al, &data, NULL) < 0) { + if (event__preprocess_sample(event, session, &al, sample, NULL) < 0) { fprintf(stderr, "problem processing %d event, skipping it.\n", event->header.type); return -1; @@ -165,14 +165,14 @@ static int process_sample_event(event_t *event, struct perf_session *session) if (al.filtered || (hide_unresolved && al.sym == NULL)) return 0; - if (perf_session__add_hist_entry(session, &al, &data)) { + if (perf_session__add_hist_entry(session, &al, sample)) { pr_debug("problem incrementing symbol period, skipping event\n"); return -1; } - attr = perf_header__find_attr(data.id, &session->header); + attr = perf_header__find_attr(sample->id, &session->header); - if (add_event_total(session, &data, attr)) { + if (add_event_total(session, sample, attr)) { pr_debug("problem adding event period\n"); return -1; } @@ -180,7 +180,8 @@ static int process_sample_event(event_t *event, struct perf_session *session) return 0; } -static int process_read_event(event_t *event, struct perf_session *session __used) +static int process_read_event(event_t *event, struct sample_data *sample __used, + struct perf_session *session __used) { struct perf_event_attr *attr; @@ -243,6 +244,8 @@ static struct perf_event_ops event_ops = { .event_type = event__process_event_type, .tracing_data = event__process_tracing_data, .build_id = event__process_build_id, + .ordered_samples = true, + .ordering_requires_timestamps = true, }; extern volatile int session_done; @@ -307,7 +310,7 @@ static int __cmd_report(void) signal(SIGINT, sig_handler); - session = perf_session__new(input_name, O_RDONLY, force, false); + session = perf_session__new(input_name, O_RDONLY, force, false, &event_ops); if (session == NULL) return -ENOMEM; @@ -442,6 +445,8 @@ static const struct option options[] = { "dump raw trace in ASCII"), OPT_STRING('k', "vmlinux", &symbol_conf.vmlinux_name, "file", "vmlinux pathname"), + OPT_STRING(0, "kallsyms", &symbol_conf.kallsyms_name, + "file", "kallsyms pathname"), OPT_BOOLEAN('f', "force", &force, "don't complain, do it"), OPT_BOOLEAN('m', "modules", &symbol_conf.use_modules, "load module symbols - WARNING: use only with -k and LIVE kernel"), @@ -478,6 +483,8 @@ static const struct option options[] = { "columns '.' is reserved."), OPT_BOOLEAN('U', "hide-unresolved", &hide_unresolved, "Only display entries resolved to a symbol"), + OPT_STRING(0, "symfs", &symbol_conf.symfs, "directory", + "Look for files with symbols relative to this directory"), OPT_END() }; diff --git a/tools/perf/builtin-sched.c b/tools/perf/builtin-sched.c index 55f3b5dcc73..abd4b8497bc 100644 --- a/tools/perf/builtin-sched.c +++ b/tools/perf/builtin-sched.c @@ -489,7 +489,8 @@ static void create_tasks(void) err = pthread_attr_init(&attr); BUG_ON(err); - err = pthread_attr_setstacksize(&attr, (size_t)(16*1024)); + err = pthread_attr_setstacksize(&attr, + (size_t) max(16 * 1024, PTHREAD_STACK_MIN)); BUG_ON(err); err = pthread_mutex_lock(&start_work_mutex); BUG_ON(err); @@ -1606,25 +1607,15 @@ process_raw_event(event_t *raw_event __used, struct perf_session *session, process_sched_migrate_task_event(data, session, event, cpu, timestamp, thread); } -static int process_sample_event(event_t *event, struct perf_session *session) +static int process_sample_event(event_t *event, struct sample_data *sample, + struct perf_session *session) { - struct sample_data data; struct thread *thread; if (!(session->sample_type & PERF_SAMPLE_RAW)) return 0; - memset(&data, 0, sizeof(data)); - data.time = -1; - data.cpu = -1; - data.period = -1; - - event__parse_sample(event, session->sample_type, &data); - - dump_printf("(IP, %d): %d/%d: %#Lx period: %Ld\n", event->header.misc, - data.pid, data.tid, data.ip, data.period); - - thread = perf_session__findnew(session, data.pid); + thread = perf_session__findnew(session, sample->pid); if (thread == NULL) { pr_debug("problem processing %d event, skipping it.\n", event->header.type); @@ -1633,10 +1624,11 @@ static int process_sample_event(event_t *event, struct perf_session *session) dump_printf(" ... thread: %s:%d\n", thread->comm, thread->pid); - if (profile_cpu != -1 && profile_cpu != (int)data.cpu) + if (profile_cpu != -1 && profile_cpu != (int)sample->cpu) return 0; - process_raw_event(event, session, data.raw_data, data.cpu, data.time, thread); + process_raw_event(event, session, sample->raw_data, sample->cpu, + sample->time, thread); return 0; } @@ -1652,7 +1644,8 @@ static struct perf_event_ops event_ops = { static int read_events(void) { int err = -EINVAL; - struct perf_session *session = perf_session__new(input_name, O_RDONLY, 0, false); + struct perf_session *session = perf_session__new(input_name, O_RDONLY, + 0, false, &event_ops); if (session == NULL) return -ENOMEM; @@ -1869,6 +1862,9 @@ static int __cmd_record(int argc, const char **argv) rec_argc = ARRAY_SIZE(record_args) + argc - 1; rec_argv = calloc(rec_argc + 1, sizeof(char *)); + if (rec_argv == NULL) + return -ENOMEM; + for (i = 0; i < ARRAY_SIZE(record_args); i++) rec_argv[i] = strdup(record_args[i]); @@ -1888,10 +1884,10 @@ int cmd_sched(int argc, const char **argv, const char *prefix __used) usage_with_options(sched_usage, sched_options); /* - * Aliased to 'perf trace' for now: + * Aliased to 'perf script' for now: */ - if (!strcmp(argv[0], "trace")) - return cmd_trace(argc, argv, prefix); + if (!strcmp(argv[0], "script")) + return cmd_script(argc, argv, prefix); symbol__init(); if (!strncmp(argv[0], "rec", 3)) { diff --git a/tools/perf/builtin-trace.c b/tools/perf/builtin-script.c index 86cfe3800e6..150a606002e 100644 --- a/tools/perf/builtin-trace.c +++ b/tools/perf/builtin-script.c @@ -56,29 +56,18 @@ static void setup_scripting(void) static int cleanup_scripting(void) { - pr_debug("\nperf trace script stopped\n"); + pr_debug("\nperf script stopped\n"); return scripting_ops->stop_script(); } static char const *input_name = "perf.data"; -static int process_sample_event(event_t *event, struct perf_session *session) +static int process_sample_event(event_t *event, struct sample_data *sample, + struct perf_session *session) { - struct sample_data data; - struct thread *thread; + struct thread *thread = perf_session__findnew(session, event->ip.pid); - memset(&data, 0, sizeof(data)); - data.time = -1; - data.cpu = -1; - data.period = 1; - - event__parse_sample(event, session->sample_type, &data); - - dump_printf("(IP, %d): %d/%d: %#Lx period: %Ld\n", event->header.misc, - data.pid, data.tid, data.ip, data.period); - - thread = perf_session__findnew(session, event->ip.pid); if (thread == NULL) { pr_debug("problem processing %d event, skipping it.\n", event->header.type); @@ -87,13 +76,13 @@ static int process_sample_event(event_t *event, struct perf_session *session) if (session->sample_type & PERF_SAMPLE_RAW) { if (debug_mode) { - if (data.time < last_timestamp) { + if (sample->time < last_timestamp) { pr_err("Samples misordered, previous: %llu " "this: %llu\n", last_timestamp, - data.time); + sample->time); nr_unordered++; } - last_timestamp = data.time; + last_timestamp = sample->time; return 0; } /* @@ -101,21 +90,12 @@ static int process_sample_event(event_t *event, struct perf_session *session) * field, although it should be the same than this perf * event pid */ - scripting_ops->process_event(data.cpu, data.raw_data, - data.raw_size, - data.time, thread->comm); + scripting_ops->process_event(sample->cpu, sample->raw_data, + sample->raw_size, + sample->time, thread->comm); } - session->hists.stats.total_period += data.period; - return 0; -} - -static u64 nr_lost; - -static int process_lost_event(event_t *event, struct perf_session *session __used) -{ - nr_lost += event->lost.lost; - + session->hists.stats.total_period += sample->period; return 0; } @@ -126,7 +106,7 @@ static struct perf_event_ops event_ops = { .event_type = event__process_event_type, .tracing_data = event__process_tracing_data, .build_id = event__process_build_id, - .lost = process_lost_event, + .ordering_requires_timestamps = true, .ordered_samples = true, }; @@ -137,7 +117,7 @@ static void sig_handler(int sig __unused) session_done = 1; } -static int __cmd_trace(struct perf_session *session) +static int __cmd_script(struct perf_session *session) { int ret; @@ -145,10 +125,8 @@ static int __cmd_trace(struct perf_session *session) ret = perf_session__process_events(session, &event_ops); - if (debug_mode) { + if (debug_mode) pr_err("Misordered timestamps: %llu\n", nr_unordered); - pr_err("Lost events: %llu\n", nr_lost); - } return ret; } @@ -159,7 +137,7 @@ struct script_spec { char spec[0]; }; -LIST_HEAD(script_specs); +static LIST_HEAD(script_specs); static struct script_spec *script_spec__new(const char *spec, struct scripting_ops *ops) @@ -247,7 +225,7 @@ static void list_available_languages(void) fprintf(stderr, "\n"); fprintf(stderr, "Scripting language extensions (used in " - "perf trace -s [spec:]script.[spec]):\n\n"); + "perf script -s [spec:]script.[spec]):\n\n"); list_for_each_entry(s, &script_specs, node) fprintf(stderr, " %-42s [%s]\n", s->spec, s->ops->name); @@ -301,17 +279,34 @@ static int parse_scriptname(const struct option *opt __used, return 0; } -#define for_each_lang(scripts_dir, lang_dirent, lang_next) \ +/* Helper function for filesystems that return a dent->d_type DT_UNKNOWN */ +static int is_directory(const char *base_path, const struct dirent *dent) +{ + char path[PATH_MAX]; + struct stat st; + + sprintf(path, "%s/%s", base_path, dent->d_name); + if (stat(path, &st)) + return 0; + + return S_ISDIR(st.st_mode); +} + +#define for_each_lang(scripts_path, scripts_dir, lang_dirent, lang_next)\ while (!readdir_r(scripts_dir, &lang_dirent, &lang_next) && \ lang_next) \ - if (lang_dirent.d_type == DT_DIR && \ + if ((lang_dirent.d_type == DT_DIR || \ + (lang_dirent.d_type == DT_UNKNOWN && \ + is_directory(scripts_path, &lang_dirent))) && \ (strcmp(lang_dirent.d_name, ".")) && \ (strcmp(lang_dirent.d_name, ".."))) -#define for_each_script(lang_dir, script_dirent, script_next) \ +#define for_each_script(lang_path, lang_dir, script_dirent, script_next)\ while (!readdir_r(lang_dir, &script_dirent, &script_next) && \ script_next) \ - if (script_dirent.d_type != DT_DIR) + if (script_dirent.d_type != DT_DIR && \ + (script_dirent.d_type != DT_UNKNOWN || \ + !is_directory(lang_path, &script_dirent))) #define RECORD_SUFFIX "-record" @@ -324,7 +319,7 @@ struct script_desc { char *args; }; -LIST_HEAD(script_descs); +static LIST_HEAD(script_descs); static struct script_desc *script_desc__new(const char *name) { @@ -380,10 +375,10 @@ out_delete_desc: return NULL; } -static char *ends_with(char *str, const char *suffix) +static const char *ends_with(const char *str, const char *suffix) { size_t suffix_len = strlen(suffix); - char *p = str; + const char *p = str; if (strlen(str) > suffix_len) { p = str + strlen(str) - suffix_len; @@ -466,16 +461,16 @@ static int list_available_scripts(const struct option *opt __used, if (!scripts_dir) return -1; - for_each_lang(scripts_dir, lang_dirent, lang_next) { + for_each_lang(scripts_path, scripts_dir, lang_dirent, lang_next) { snprintf(lang_path, MAXPATHLEN, "%s/%s/bin", scripts_path, lang_dirent.d_name); lang_dir = opendir(lang_path); if (!lang_dir) continue; - for_each_script(lang_dir, script_dirent, script_next) { + for_each_script(lang_path, lang_dir, script_dirent, script_next) { script_root = strdup(script_dirent.d_name); - str = ends_with(script_root, REPORT_SUFFIX); + str = (char *)ends_with(script_root, REPORT_SUFFIX); if (str) { *str = '\0'; desc = script_desc__findnew(script_root); @@ -514,16 +509,16 @@ static char *get_script_path(const char *script_root, const char *suffix) if (!scripts_dir) return NULL; - for_each_lang(scripts_dir, lang_dirent, lang_next) { + for_each_lang(scripts_path, scripts_dir, lang_dirent, lang_next) { snprintf(lang_path, MAXPATHLEN, "%s/%s/bin", scripts_path, lang_dirent.d_name); lang_dir = opendir(lang_path); if (!lang_dir) continue; - for_each_script(lang_dir, script_dirent, script_next) { + for_each_script(lang_path, lang_dir, script_dirent, script_next) { __script_root = strdup(script_dirent.d_name); - str = ends_with(__script_root, suffix); + str = (char *)ends_with(__script_root, suffix); if (str) { *str = '\0'; if (strcmp(__script_root, script_root)) @@ -543,7 +538,7 @@ static char *get_script_path(const char *script_root, const char *suffix) static bool is_top_script(const char *script_path) { - return ends_with((char *)script_path, "top") == NULL ? false : true; + return ends_with(script_path, "top") == NULL ? false : true; } static int has_required_arg(char *script_path) @@ -569,12 +564,12 @@ out: return n_args; } -static const char * const trace_usage[] = { - "perf trace [<options>]", - "perf trace [<options>] record <script> [<record-options>] <command>", - "perf trace [<options>] report <script> [script-args]", - "perf trace [<options>] <script> [<record-options>] <command>", - "perf trace [<options>] <top-script> [script-args]", +static const char * const script_usage[] = { + "perf script [<options>]", + "perf script [<options>] record <script> [<record-options>] <command>", + "perf script [<options>] report <script> [script-args]", + "perf script [<options>] <script> [<record-options>] <command>", + "perf script [<options>] <top-script> [script-args]", NULL }; @@ -591,7 +586,7 @@ static const struct option options[] = { "script file name (lang:script name, script name, or *)", parse_scriptname), OPT_STRING('g', "gen-script", &generate_script_lang, "lang", - "generate perf-trace.xx script in specified language"), + "generate perf-script.xx script in specified language"), OPT_STRING('i', "input", &input_name, "file", "input file name"), OPT_BOOLEAN('d', "debug-mode", &debug_mode, @@ -614,7 +609,7 @@ static bool have_cmd(int argc, const char **argv) return argc != 0; } -int cmd_trace(int argc, const char **argv, const char *prefix __used) +int cmd_script(int argc, const char **argv, const char *prefix __used) { char *rec_script_path = NULL; char *rep_script_path = NULL; @@ -626,7 +621,7 @@ int cmd_trace(int argc, const char **argv, const char *prefix __used) setup_scripting(); - argc = parse_options(argc, argv, options, trace_usage, + argc = parse_options(argc, argv, options, script_usage, PARSE_OPT_STOP_AT_NON_OPTION); if (argc > 1 && !strncmp(argv[0], "rec", strlen("rec"))) { @@ -640,7 +635,7 @@ int cmd_trace(int argc, const char **argv, const char *prefix __used) if (!rep_script_path) { fprintf(stderr, "Please specify a valid report script" - "(see 'perf trace -l' for listing)\n"); + "(see 'perf script -l' for listing)\n"); return -1; } } @@ -658,8 +653,8 @@ int cmd_trace(int argc, const char **argv, const char *prefix __used) if (!rec_script_path && !rep_script_path) { fprintf(stderr, " Couldn't find script %s\n\n See perf" - " trace -l for available scripts.\n", argv[0]); - usage_with_options(trace_usage, options); + " script -l for available scripts.\n", argv[0]); + usage_with_options(script_usage, options); } if (is_top_script(argv[0])) { @@ -671,9 +666,9 @@ int cmd_trace(int argc, const char **argv, const char *prefix __used) rec_args = (argc - 1) - rep_args; if (rec_args < 0) { fprintf(stderr, " %s script requires options." - "\n\n See perf trace -l for available " + "\n\n See perf script -l for available " "scripts and options.\n", argv[0]); - usage_with_options(trace_usage, options); + usage_with_options(script_usage, options); } } @@ -772,7 +767,7 @@ int cmd_trace(int argc, const char **argv, const char *prefix __used) if (!script_name) setup_pager(); - session = perf_session__new(input_name, O_RDONLY, 0, false); + session = perf_session__new(input_name, O_RDONLY, 0, false, &event_ops); if (session == NULL) return -ENOMEM; @@ -806,7 +801,7 @@ int cmd_trace(int argc, const char **argv, const char *prefix __used) return -1; } - err = scripting_ops->generate_script("perf-trace"); + err = scripting_ops->generate_script("perf-script"); goto out; } @@ -814,10 +809,10 @@ int cmd_trace(int argc, const char **argv, const char *prefix __used) err = scripting_ops->start_script(script_name, argc, argv); if (err) goto out; - pr_debug("perf trace started with script %s\n\n", script_name); + pr_debug("perf script started with script %s\n\n", script_name); } - err = __cmd_trace(session); + err = __cmd_script(session); perf_session__delete(session); cleanup_scripting(); diff --git a/tools/perf/builtin-stat.c b/tools/perf/builtin-stat.c index a6b4d44f950..c385a63ebfd 100644 --- a/tools/perf/builtin-stat.c +++ b/tools/perf/builtin-stat.c @@ -43,6 +43,7 @@ #include "util/parse-options.h" #include "util/parse-events.h" #include "util/event.h" +#include "util/evsel.h" #include "util/debug.h" #include "util/header.h" #include "util/cpumap.h" @@ -52,6 +53,8 @@ #include <math.h> #include <locale.h> +#define DEFAULT_SEPARATOR " " + static struct perf_event_attr default_attrs[] = { { .type = PERF_TYPE_SOFTWARE, .config = PERF_COUNT_SW_TASK_CLOCK }, @@ -69,25 +72,23 @@ static struct perf_event_attr default_attrs[] = { }; static bool system_wide = false; -static int nr_cpus = 0; +static struct cpu_map *cpus; static int run_idx = 0; static int run_count = 1; static bool no_inherit = false; static bool scale = true; +static bool no_aggr = false; static pid_t target_pid = -1; static pid_t target_tid = -1; -static pid_t *all_tids = NULL; -static int thread_num = 0; +static struct thread_map *threads; static pid_t child_pid = -1; static bool null_run = false; -static bool big_num = false; +static bool big_num = true; +static int big_num_opt = -1; static const char *cpu_list; - - -static int *fd[MAX_NR_CPUS][MAX_COUNTERS]; - -static int event_scaled[MAX_COUNTERS]; +static const char *csv_sep = NULL; +static bool csv_output = false; static volatile int done = 0; @@ -96,6 +97,22 @@ struct stats double n, mean, M2; }; +struct perf_stat { + struct stats res_stats[3]; +}; + +static int perf_evsel__alloc_stat_priv(struct perf_evsel *evsel) +{ + evsel->priv = zalloc(sizeof(struct perf_stat)); + return evsel->priv == NULL ? -ENOMEM : 0; +} + +static void perf_evsel__free_stat_priv(struct perf_evsel *evsel) +{ + free(evsel->priv); + evsel->priv = NULL; +} + static void update_stats(struct stats *stats, u64 val) { double delta; @@ -135,69 +152,38 @@ static double stddev_stats(struct stats *stats) return sqrt(variance_mean); } -struct stats event_res_stats[MAX_COUNTERS][3]; -struct stats runtime_nsecs_stats; +struct stats runtime_nsecs_stats[MAX_NR_CPUS]; +struct stats runtime_cycles_stats[MAX_NR_CPUS]; +struct stats runtime_branches_stats[MAX_NR_CPUS]; struct stats walltime_nsecs_stats; -struct stats runtime_cycles_stats; -struct stats runtime_branches_stats; -#define MATCH_EVENT(t, c, counter) \ - (attrs[counter].type == PERF_TYPE_##t && \ - attrs[counter].config == PERF_COUNT_##c) - -#define ERR_PERF_OPEN \ -"Error: counter %d, sys_perf_event_open() syscall returned with %d (%s)\n" - -static int create_perf_stat_counter(int counter) +static int create_perf_stat_counter(struct perf_evsel *evsel) { - struct perf_event_attr *attr = attrs + counter; - int thread; - int ncreated = 0; + struct perf_event_attr *attr = &evsel->attr; if (scale) attr->read_format = PERF_FORMAT_TOTAL_TIME_ENABLED | PERF_FORMAT_TOTAL_TIME_RUNNING; - if (system_wide) { - int cpu; - - for (cpu = 0; cpu < nr_cpus; cpu++) { - fd[cpu][counter][0] = sys_perf_event_open(attr, - -1, cpumap[cpu], -1, 0); - if (fd[cpu][counter][0] < 0) - pr_debug(ERR_PERF_OPEN, counter, - fd[cpu][counter][0], strerror(errno)); - else - ++ncreated; - } - } else { - attr->inherit = !no_inherit; - if (target_pid == -1 && target_tid == -1) { - attr->disabled = 1; - attr->enable_on_exec = 1; - } - for (thread = 0; thread < thread_num; thread++) { - fd[0][counter][thread] = sys_perf_event_open(attr, - all_tids[thread], -1, -1, 0); - if (fd[0][counter][thread] < 0) - pr_debug(ERR_PERF_OPEN, counter, - fd[0][counter][thread], - strerror(errno)); - else - ++ncreated; - } + if (system_wide) + return perf_evsel__open_per_cpu(evsel, cpus); + + attr->inherit = !no_inherit; + if (target_pid == -1 && target_tid == -1) { + attr->disabled = 1; + attr->enable_on_exec = 1; } - return ncreated; + return perf_evsel__open_per_thread(evsel, threads); } /* * Does the counter have nsecs as a unit? */ -static inline int nsec_counter(int counter) +static inline int nsec_counter(struct perf_evsel *evsel) { - if (MATCH_EVENT(SOFTWARE, SW_CPU_CLOCK, counter) || - MATCH_EVENT(SOFTWARE, SW_TASK_CLOCK, counter)) + if (perf_evsel__match(evsel, SOFTWARE, SW_CPU_CLOCK) || + perf_evsel__match(evsel, SOFTWARE, SW_TASK_CLOCK)) return 1; return 0; @@ -205,55 +191,19 @@ static inline int nsec_counter(int counter) /* * Read out the results of a single counter: + * aggregate counts across CPUs in system-wide mode */ -static void read_counter(int counter) +static int read_counter_aggr(struct perf_evsel *counter) { - u64 count[3], single_count[3]; - int cpu; - size_t res, nv; - int scaled; - int i, thread; - - count[0] = count[1] = count[2] = 0; - - nv = scale ? 3 : 1; - for (cpu = 0; cpu < nr_cpus; cpu++) { - for (thread = 0; thread < thread_num; thread++) { - if (fd[cpu][counter][thread] < 0) - continue; - - res = read(fd[cpu][counter][thread], - single_count, nv * sizeof(u64)); - assert(res == nv * sizeof(u64)); - - close(fd[cpu][counter][thread]); - fd[cpu][counter][thread] = -1; - - count[0] += single_count[0]; - if (scale) { - count[1] += single_count[1]; - count[2] += single_count[2]; - } - } - } - - scaled = 0; - if (scale) { - if (count[2] == 0) { - event_scaled[counter] = -1; - count[0] = 0; - return; - } + struct perf_stat *ps = counter->priv; + u64 *count = counter->counts->aggr.values; + int i; - if (count[2] < count[1]) { - event_scaled[counter] = 1; - count[0] = (unsigned long long) - ((double)count[0] * count[1] / count[2] + 0.5); - } - } + if (__perf_evsel__read(counter, cpus->nr, threads->nr, scale) < 0) + return -1; for (i = 0; i < 3; i++) - update_stats(&event_res_stats[counter][i], count[i]); + update_stats(&ps->res_stats[i], count[i]); if (verbose) { fprintf(stderr, "%s: %Ld %Ld %Ld\n", event_name(counter), @@ -263,26 +213,51 @@ static void read_counter(int counter) /* * Save the full runtime - to allow normalization during printout: */ - if (MATCH_EVENT(SOFTWARE, SW_TASK_CLOCK, counter)) - update_stats(&runtime_nsecs_stats, count[0]); - if (MATCH_EVENT(HARDWARE, HW_CPU_CYCLES, counter)) - update_stats(&runtime_cycles_stats, count[0]); - if (MATCH_EVENT(HARDWARE, HW_BRANCH_INSTRUCTIONS, counter)) - update_stats(&runtime_branches_stats, count[0]); + if (perf_evsel__match(counter, SOFTWARE, SW_TASK_CLOCK)) + update_stats(&runtime_nsecs_stats[0], count[0]); + if (perf_evsel__match(counter, HARDWARE, HW_CPU_CYCLES)) + update_stats(&runtime_cycles_stats[0], count[0]); + if (perf_evsel__match(counter, HARDWARE, HW_BRANCH_INSTRUCTIONS)) + update_stats(&runtime_branches_stats[0], count[0]); + + return 0; +} + +/* + * Read out the results of a single counter: + * do not aggregate counts across CPUs in system-wide mode + */ +static int read_counter(struct perf_evsel *counter) +{ + u64 *count; + int cpu; + + for (cpu = 0; cpu < cpus->nr; cpu++) { + if (__perf_evsel__read_on_cpu(counter, cpu, 0, scale) < 0) + return -1; + + count = counter->counts->cpu[cpu].values; + + if (perf_evsel__match(counter, SOFTWARE, SW_TASK_CLOCK)) + update_stats(&runtime_nsecs_stats[cpu], count[0]); + if (perf_evsel__match(counter, HARDWARE, HW_CPU_CYCLES)) + update_stats(&runtime_cycles_stats[cpu], count[0]); + if (perf_evsel__match(counter, HARDWARE, HW_BRANCH_INSTRUCTIONS)) + update_stats(&runtime_branches_stats[cpu], count[0]); + } + + return 0; } static int run_perf_stat(int argc __used, const char **argv) { unsigned long long t0, t1; + struct perf_evsel *counter; int status = 0; - int counter, ncreated = 0; int child_ready_pipe[2], go_pipe[2]; const bool forks = (argc > 0); char buf; - if (!system_wide) - nr_cpus = 1; - if (forks && (pipe(child_ready_pipe) < 0 || pipe(go_pipe) < 0)) { perror("failed to create pipes"); exit(1); @@ -322,7 +297,7 @@ static int run_perf_stat(int argc __used, const char **argv) } if (target_tid == -1 && target_pid == -1 && !system_wide) - all_tids[0] = child_pid; + threads->map[0] = child_pid; /* * Wait for the child to be ready to exec. @@ -334,16 +309,25 @@ static int run_perf_stat(int argc __used, const char **argv) close(child_ready_pipe[0]); } - for (counter = 0; counter < nr_counters; counter++) - ncreated += create_perf_stat_counter(counter); - - if (ncreated == 0) { - pr_err("No permission to collect %sstats.\n" - "Consider tweaking /proc/sys/kernel/perf_event_paranoid.\n", - system_wide ? "system-wide " : ""); - if (child_pid != -1) - kill(child_pid, SIGTERM); - return -1; + list_for_each_entry(counter, &evsel_list, node) { + if (create_perf_stat_counter(counter) < 0) { + if (errno == -EPERM || errno == -EACCES) { + error("You may not have permission to collect %sstats.\n" + "\t Consider tweaking" + " /proc/sys/kernel/perf_event_paranoid or running as root.", + system_wide ? "system-wide " : ""); + } else if (errno == ENOENT) { + error("%s event is not supported. ", event_name(counter)); + } else { + error("open_counter returned with %d (%s). " + "/bin/dmesg may provide additional information.\n", + errno, strerror(errno)); + } + if (child_pid != -1) + kill(child_pid, SIGTERM); + die("Not all events could be opened.\n"); + return -1; + } } /* @@ -362,60 +346,97 @@ static int run_perf_stat(int argc __used, const char **argv) update_stats(&walltime_nsecs_stats, t1 - t0); - for (counter = 0; counter < nr_counters; counter++) - read_counter(counter); + if (no_aggr) { + list_for_each_entry(counter, &evsel_list, node) { + read_counter(counter); + perf_evsel__close_fd(counter, cpus->nr, 1); + } + } else { + list_for_each_entry(counter, &evsel_list, node) { + read_counter_aggr(counter); + perf_evsel__close_fd(counter, cpus->nr, threads->nr); + } + } return WEXITSTATUS(status); } -static void print_noise(int counter, double avg) +static void print_noise(struct perf_evsel *evsel, double avg) { + struct perf_stat *ps; + if (run_count == 1) return; + ps = evsel->priv; fprintf(stderr, " ( +- %7.3f%% )", - 100 * stddev_stats(&event_res_stats[counter][0]) / avg); + 100 * stddev_stats(&ps->res_stats[0]) / avg); } -static void nsec_printout(int counter, double avg) +static void nsec_printout(int cpu, struct perf_evsel *evsel, double avg) { double msecs = avg / 1e6; + char cpustr[16] = { '\0', }; + const char *fmt = csv_output ? "%s%.6f%s%s" : "%s%18.6f%s%-24s"; - fprintf(stderr, " %18.6f %-24s", msecs, event_name(counter)); + if (no_aggr) + sprintf(cpustr, "CPU%*d%s", + csv_output ? 0 : -4, + cpus->map[cpu], csv_sep); + + fprintf(stderr, fmt, cpustr, msecs, csv_sep, event_name(evsel)); + + if (csv_output) + return; - if (MATCH_EVENT(SOFTWARE, SW_TASK_CLOCK, counter)) { + if (perf_evsel__match(evsel, SOFTWARE, SW_TASK_CLOCK)) fprintf(stderr, " # %10.3f CPUs ", avg / avg_stats(&walltime_nsecs_stats)); - } } -static void abs_printout(int counter, double avg) +static void abs_printout(int cpu, struct perf_evsel *evsel, double avg) { double total, ratio = 0.0; + char cpustr[16] = { '\0', }; + const char *fmt; + + if (csv_output) + fmt = "%s%.0f%s%s"; + else if (big_num) + fmt = "%s%'18.0f%s%-24s"; + else + fmt = "%s%18.0f%s%-24s"; - if (big_num) - fprintf(stderr, " %'18.0f %-24s", avg, event_name(counter)); + if (no_aggr) + sprintf(cpustr, "CPU%*d%s", + csv_output ? 0 : -4, + cpus->map[cpu], csv_sep); else - fprintf(stderr, " %18.0f %-24s", avg, event_name(counter)); + cpu = 0; + + fprintf(stderr, fmt, cpustr, avg, csv_sep, event_name(evsel)); - if (MATCH_EVENT(HARDWARE, HW_INSTRUCTIONS, counter)) { - total = avg_stats(&runtime_cycles_stats); + if (csv_output) + return; + + if (perf_evsel__match(evsel, HARDWARE, HW_INSTRUCTIONS)) { + total = avg_stats(&runtime_cycles_stats[cpu]); if (total) ratio = avg / total; fprintf(stderr, " # %10.3f IPC ", ratio); - } else if (MATCH_EVENT(HARDWARE, HW_BRANCH_MISSES, counter) && - runtime_branches_stats.n != 0) { - total = avg_stats(&runtime_branches_stats); + } else if (perf_evsel__match(evsel, HARDWARE, HW_BRANCH_MISSES) && + runtime_branches_stats[cpu].n != 0) { + total = avg_stats(&runtime_branches_stats[cpu]); if (total) ratio = avg * 100 / total; fprintf(stderr, " # %10.3f %% ", ratio); - } else if (runtime_nsecs_stats.n != 0) { - total = avg_stats(&runtime_nsecs_stats); + } else if (runtime_nsecs_stats[cpu].n != 0) { + total = avg_stats(&runtime_nsecs_stats[cpu]); if (total) ratio = 1000.0 * avg / total; @@ -426,30 +447,38 @@ static void abs_printout(int counter, double avg) /* * Print out the results of a single counter: + * aggregated counts in system-wide mode */ -static void print_counter(int counter) +static void print_counter_aggr(struct perf_evsel *counter) { - double avg = avg_stats(&event_res_stats[counter][0]); - int scaled = event_scaled[counter]; + struct perf_stat *ps = counter->priv; + double avg = avg_stats(&ps->res_stats[0]); + int scaled = counter->counts->scaled; if (scaled == -1) { - fprintf(stderr, " %18s %-24s\n", - "<not counted>", event_name(counter)); + fprintf(stderr, "%*s%s%-24s\n", + csv_output ? 0 : 18, + "<not counted>", csv_sep, event_name(counter)); return; } if (nsec_counter(counter)) - nsec_printout(counter, avg); + nsec_printout(-1, counter, avg); else - abs_printout(counter, avg); + abs_printout(-1, counter, avg); + + if (csv_output) { + fputc('\n', stderr); + return; + } print_noise(counter, avg); if (scaled) { double avg_enabled, avg_running; - avg_enabled = avg_stats(&event_res_stats[counter][1]); - avg_running = avg_stats(&event_res_stats[counter][2]); + avg_enabled = avg_stats(&ps->res_stats[1]); + avg_running = avg_stats(&ps->res_stats[2]); fprintf(stderr, " (scaled from %.2f%%)", 100 * avg_running / avg_enabled); @@ -458,40 +487,92 @@ static void print_counter(int counter) fprintf(stderr, "\n"); } +/* + * Print out the results of a single counter: + * does not use aggregated count in system-wide + */ +static void print_counter(struct perf_evsel *counter) +{ + u64 ena, run, val; + int cpu; + + for (cpu = 0; cpu < cpus->nr; cpu++) { + val = counter->counts->cpu[cpu].val; + ena = counter->counts->cpu[cpu].ena; + run = counter->counts->cpu[cpu].run; + if (run == 0 || ena == 0) { + fprintf(stderr, "CPU%*d%s%*s%s%-24s", + csv_output ? 0 : -4, + cpus->map[cpu], csv_sep, + csv_output ? 0 : 18, + "<not counted>", csv_sep, + event_name(counter)); + + fprintf(stderr, "\n"); + continue; + } + + if (nsec_counter(counter)) + nsec_printout(cpu, counter, val); + else + abs_printout(cpu, counter, val); + + if (!csv_output) { + print_noise(counter, 1.0); + + if (run != ena) { + fprintf(stderr, " (scaled from %.2f%%)", + 100.0 * run / ena); + } + } + fprintf(stderr, "\n"); + } +} + static void print_stat(int argc, const char **argv) { - int i, counter; + struct perf_evsel *counter; + int i; fflush(stdout); - fprintf(stderr, "\n"); - fprintf(stderr, " Performance counter stats for "); - if(target_pid == -1 && target_tid == -1) { - fprintf(stderr, "\'%s", argv[0]); - for (i = 1; i < argc; i++) - fprintf(stderr, " %s", argv[i]); - } else if (target_pid != -1) - fprintf(stderr, "process id \'%d", target_pid); - else - fprintf(stderr, "thread id \'%d", target_tid); - - fprintf(stderr, "\'"); - if (run_count > 1) - fprintf(stderr, " (%d runs)", run_count); - fprintf(stderr, ":\n\n"); + if (!csv_output) { + fprintf(stderr, "\n"); + fprintf(stderr, " Performance counter stats for "); + if(target_pid == -1 && target_tid == -1) { + fprintf(stderr, "\'%s", argv[0]); + for (i = 1; i < argc; i++) + fprintf(stderr, " %s", argv[i]); + } else if (target_pid != -1) + fprintf(stderr, "process id \'%d", target_pid); + else + fprintf(stderr, "thread id \'%d", target_tid); + + fprintf(stderr, "\'"); + if (run_count > 1) + fprintf(stderr, " (%d runs)", run_count); + fprintf(stderr, ":\n\n"); + } - for (counter = 0; counter < nr_counters; counter++) - print_counter(counter); + if (no_aggr) { + list_for_each_entry(counter, &evsel_list, node) + print_counter(counter); + } else { + list_for_each_entry(counter, &evsel_list, node) + print_counter_aggr(counter); + } - fprintf(stderr, "\n"); - fprintf(stderr, " %18.9f seconds time elapsed", - avg_stats(&walltime_nsecs_stats)/1e9); - if (run_count > 1) { - fprintf(stderr, " ( +- %7.3f%% )", + if (!csv_output) { + fprintf(stderr, "\n"); + fprintf(stderr, " %18.9f seconds time elapsed", + avg_stats(&walltime_nsecs_stats)/1e9); + if (run_count > 1) { + fprintf(stderr, " ( +- %7.3f%% )", 100*stddev_stats(&walltime_nsecs_stats) / avg_stats(&walltime_nsecs_stats)); + } + fprintf(stderr, "\n\n"); } - fprintf(stderr, "\n\n"); } static volatile int signr = -1; @@ -521,6 +602,13 @@ static const char * const stat_usage[] = { NULL }; +static int stat__set_big_num(const struct option *opt __used, + const char *s __used, int unset) +{ + big_num_opt = unset ? 0 : 1; + return 0; +} + static const struct option options[] = { OPT_CALLBACK('e', "event", NULL, "event", "event selector. use 'perf list' to list available events", @@ -541,64 +629,95 @@ static const struct option options[] = { "repeat command and print average + stddev (max: 100)"), OPT_BOOLEAN('n', "null", &null_run, "null run - dont start any counters"), - OPT_BOOLEAN('B', "big-num", &big_num, - "print large numbers with thousands\' separators"), + OPT_CALLBACK_NOOPT('B', "big-num", NULL, NULL, + "print large numbers with thousands\' separators", + stat__set_big_num), OPT_STRING('C', "cpu", &cpu_list, "cpu", "list of cpus to monitor in system-wide"), + OPT_BOOLEAN('A', "no-aggr", &no_aggr, + "disable CPU count aggregation"), + OPT_STRING('x', "field-separator", &csv_sep, "separator", + "print counts with custom separator"), OPT_END() }; int cmd_stat(int argc, const char **argv, const char *prefix __used) { - int status; - int i,j; + struct perf_evsel *pos; + int status = -ENOMEM; setlocale(LC_ALL, ""); argc = parse_options(argc, argv, options, stat_usage, PARSE_OPT_STOP_AT_NON_OPTION); + + if (csv_sep) + csv_output = true; + else + csv_sep = DEFAULT_SEPARATOR; + + /* + * let the spreadsheet do the pretty-printing + */ + if (csv_output) { + /* User explicitely passed -B? */ + if (big_num_opt == 1) { + fprintf(stderr, "-B option not supported with -x\n"); + usage_with_options(stat_usage, options); + } else /* Nope, so disable big number formatting */ + big_num = false; + } else if (big_num_opt == 0) /* User passed --no-big-num */ + big_num = false; + if (!argc && target_pid == -1 && target_tid == -1) usage_with_options(stat_usage, options); if (run_count <= 0) usage_with_options(stat_usage, options); + /* no_aggr is for system-wide only */ + if (no_aggr && !system_wide) + usage_with_options(stat_usage, options); + /* Set attrs and nr_counters if no event is selected and !null_run */ if (!null_run && !nr_counters) { - memcpy(attrs, default_attrs, sizeof(default_attrs)); + size_t c; + nr_counters = ARRAY_SIZE(default_attrs); + + for (c = 0; c < ARRAY_SIZE(default_attrs); ++c) { + pos = perf_evsel__new(&default_attrs[c], + nr_counters); + if (pos == NULL) + goto out; + list_add(&pos->node, &evsel_list); + } } - if (system_wide) - nr_cpus = read_cpu_map(cpu_list); - else - nr_cpus = 1; + if (target_pid != -1) + target_tid = target_pid; - if (nr_cpus < 1) + threads = thread_map__new(target_pid, target_tid); + if (threads == NULL) { + pr_err("Problems finding threads of monitor\n"); usage_with_options(stat_usage, options); + } - if (target_pid != -1) { - target_tid = target_pid; - thread_num = find_all_tid(target_pid, &all_tids); - if (thread_num <= 0) { - fprintf(stderr, "Can't find all threads of pid %d\n", - target_pid); - usage_with_options(stat_usage, options); - } - } else { - all_tids=malloc(sizeof(pid_t)); - if (!all_tids) - return -ENOMEM; + if (system_wide) + cpus = cpu_map__new(cpu_list); + else + cpus = cpu_map__dummy_new(); - all_tids[0] = target_tid; - thread_num = 1; + if (cpus == NULL) { + perror("failed to parse CPUs map"); + usage_with_options(stat_usage, options); + return -1; } - for (i = 0; i < MAX_NR_CPUS; i++) { - for (j = 0; j < MAX_COUNTERS; j++) { - fd[i][j] = malloc(sizeof(int)*thread_num); - if (!fd[i][j]) - return -ENOMEM; - } + list_for_each_entry(pos, &evsel_list, node) { + if (perf_evsel__alloc_stat_priv(pos) < 0 || + perf_evsel__alloc_counts(pos, cpus->nr) < 0 || + perf_evsel__alloc_fd(pos, cpus->nr, threads->nr) < 0) + goto out_free_fd; } /* @@ -621,6 +740,11 @@ int cmd_stat(int argc, const char **argv, const char *prefix __used) if (status != -1) print_stat(argc, argv); - +out_free_fd: + list_for_each_entry(pos, &evsel_list, node) + perf_evsel__free_stat_priv(pos); +out: + thread_map__delete(threads); + threads = NULL; return status; } diff --git a/tools/perf/builtin-test.c b/tools/perf/builtin-test.c index 035b9fa063a..ed5696198d3 100644 --- a/tools/perf/builtin-test.c +++ b/tools/perf/builtin-test.c @@ -119,10 +119,16 @@ static int test__vmlinux_matches_kallsyms(void) * end addresses too. */ for (nd = rb_first(&vmlinux_map->dso->symbols[type]); nd; nd = rb_next(nd)) { - struct symbol *pair; + struct symbol *pair, *first_pair; + bool backwards = true; sym = rb_entry(nd, struct symbol, rb_node); - pair = machine__find_kernel_symbol(&kallsyms, type, sym->start, NULL, NULL); + + if (sym->start == sym->end) + continue; + + first_pair = machine__find_kernel_symbol(&kallsyms, type, sym->start, NULL, NULL); + pair = first_pair; if (pair && pair->start == sym->start) { next_pair: @@ -143,8 +149,10 @@ next_pair: pr_debug("%#Lx: diff end addr for %s v: %#Lx k: %#Lx\n", sym->start, sym->name, sym->end, pair->end); } else { - struct rb_node *nnd = rb_prev(&pair->rb_node); - + struct rb_node *nnd; +detour: + nnd = backwards ? rb_prev(&pair->rb_node) : + rb_next(&pair->rb_node); if (nnd) { struct symbol *next = rb_entry(nnd, struct symbol, rb_node); @@ -153,6 +161,13 @@ next_pair: goto next_pair; } } + + if (backwards) { + backwards = false; + pair = first_pair; + goto detour; + } + pr_debug("%#Lx: diff name v: %s k: %s\n", sym->start, sym->name, pair->name); } @@ -219,6 +234,199 @@ out: return err; } +#include "util/cpumap.h" +#include "util/evsel.h" +#include <sys/types.h> + +static int trace_event__id(const char *event_name) +{ + char *filename; + int err = -1, fd; + + if (asprintf(&filename, + "/sys/kernel/debug/tracing/events/syscalls/%s/id", + event_name) < 0) + return -1; + + fd = open(filename, O_RDONLY); + if (fd >= 0) { + char id[16]; + if (read(fd, id, sizeof(id)) > 0) + err = atoi(id); + close(fd); + } + + free(filename); + return err; +} + +static int test__open_syscall_event(void) +{ + int err = -1, fd; + struct thread_map *threads; + struct perf_evsel *evsel; + struct perf_event_attr attr; + unsigned int nr_open_calls = 111, i; + int id = trace_event__id("sys_enter_open"); + + if (id < 0) { + pr_debug("is debugfs mounted on /sys/kernel/debug?\n"); + return -1; + } + + threads = thread_map__new(-1, getpid()); + if (threads == NULL) { + pr_debug("thread_map__new\n"); + return -1; + } + + memset(&attr, 0, sizeof(attr)); + attr.type = PERF_TYPE_TRACEPOINT; + attr.config = id; + evsel = perf_evsel__new(&attr, 0); + if (evsel == NULL) { + pr_debug("perf_evsel__new\n"); + goto out_thread_map_delete; + } + + if (perf_evsel__open_per_thread(evsel, threads) < 0) { + pr_debug("failed to open counter: %s, " + "tweak /proc/sys/kernel/perf_event_paranoid?\n", + strerror(errno)); + goto out_evsel_delete; + } + + for (i = 0; i < nr_open_calls; ++i) { + fd = open("/etc/passwd", O_RDONLY); + close(fd); + } + + if (perf_evsel__read_on_cpu(evsel, 0, 0) < 0) { + pr_debug("perf_evsel__open_read_on_cpu\n"); + goto out_close_fd; + } + + if (evsel->counts->cpu[0].val != nr_open_calls) { + pr_debug("perf_evsel__read_on_cpu: expected to intercept %d calls, got %Ld\n", + nr_open_calls, evsel->counts->cpu[0].val); + goto out_close_fd; + } + + err = 0; +out_close_fd: + perf_evsel__close_fd(evsel, 1, threads->nr); +out_evsel_delete: + perf_evsel__delete(evsel); +out_thread_map_delete: + thread_map__delete(threads); + return err; +} + +#include <sched.h> + +static int test__open_syscall_event_on_all_cpus(void) +{ + int err = -1, fd, cpu; + struct thread_map *threads; + struct cpu_map *cpus; + struct perf_evsel *evsel; + struct perf_event_attr attr; + unsigned int nr_open_calls = 111, i; + cpu_set_t *cpu_set; + size_t cpu_set_size; + int id = trace_event__id("sys_enter_open"); + + if (id < 0) { + pr_debug("is debugfs mounted on /sys/kernel/debug?\n"); + return -1; + } + + threads = thread_map__new(-1, getpid()); + if (threads == NULL) { + pr_debug("thread_map__new\n"); + return -1; + } + + cpus = cpu_map__new(NULL); + if (threads == NULL) { + pr_debug("thread_map__new\n"); + return -1; + } + + cpu_set = CPU_ALLOC(cpus->nr); + + if (cpu_set == NULL) + goto out_thread_map_delete; + + cpu_set_size = CPU_ALLOC_SIZE(cpus->nr); + CPU_ZERO_S(cpu_set_size, cpu_set); + + memset(&attr, 0, sizeof(attr)); + attr.type = PERF_TYPE_TRACEPOINT; + attr.config = id; + evsel = perf_evsel__new(&attr, 0); + if (evsel == NULL) { + pr_debug("perf_evsel__new\n"); + goto out_cpu_free; + } + + if (perf_evsel__open(evsel, cpus, threads) < 0) { + pr_debug("failed to open counter: %s, " + "tweak /proc/sys/kernel/perf_event_paranoid?\n", + strerror(errno)); + goto out_evsel_delete; + } + + for (cpu = 0; cpu < cpus->nr; ++cpu) { + unsigned int ncalls = nr_open_calls + cpu; + + CPU_SET(cpu, cpu_set); + sched_setaffinity(0, cpu_set_size, cpu_set); + for (i = 0; i < ncalls; ++i) { + fd = open("/etc/passwd", O_RDONLY); + close(fd); + } + CPU_CLR(cpu, cpu_set); + } + + /* + * Here we need to explicitely preallocate the counts, as if + * we use the auto allocation it will allocate just for 1 cpu, + * as we start by cpu 0. + */ + if (perf_evsel__alloc_counts(evsel, cpus->nr) < 0) { + pr_debug("perf_evsel__alloc_counts(ncpus=%d)\n", cpus->nr); + goto out_close_fd; + } + + for (cpu = 0; cpu < cpus->nr; ++cpu) { + unsigned int expected; + + if (perf_evsel__read_on_cpu(evsel, cpu, 0) < 0) { + pr_debug("perf_evsel__open_read_on_cpu\n"); + goto out_close_fd; + } + + expected = nr_open_calls + cpu; + if (evsel->counts->cpu[cpu].val != expected) { + pr_debug("perf_evsel__read_on_cpu: expected to intercept %d calls on cpu %d, got %Ld\n", + expected, cpu, evsel->counts->cpu[cpu].val); + goto out_close_fd; + } + } + + err = 0; +out_close_fd: + perf_evsel__close_fd(evsel, 1, threads->nr); +out_evsel_delete: + perf_evsel__delete(evsel); +out_cpu_free: + CPU_FREE(cpu_set); +out_thread_map_delete: + thread_map__delete(threads); + return err; +} + static struct test { const char *desc; int (*func)(void); @@ -228,6 +436,14 @@ static struct test { .func = test__vmlinux_matches_kallsyms, }, { + .desc = "detect open syscall event", + .func = test__open_syscall_event, + }, + { + .desc = "detect open syscall event on all cpus", + .func = test__open_syscall_event_on_all_cpus, + }, + { .func = NULL, }, }; diff --git a/tools/perf/builtin-timechart.c b/tools/perf/builtin-timechart.c index 9bcc38f0b70..746cf03cb05 100644 --- a/tools/perf/builtin-timechart.c +++ b/tools/perf/builtin-timechart.c @@ -32,6 +32,10 @@ #include "util/session.h" #include "util/svghelper.h" +#define SUPPORT_OLD_POWER_EVENTS 1 +#define PWR_EVENT_EXIT -1 + + static char const *input_name = "perf.data"; static char const *output_name = "output.svg"; @@ -272,19 +276,22 @@ static int cpus_cstate_state[MAX_CPUS]; static u64 cpus_pstate_start_times[MAX_CPUS]; static u64 cpus_pstate_state[MAX_CPUS]; -static int process_comm_event(event_t *event, struct perf_session *session __used) +static int process_comm_event(event_t *event, struct sample_data *sample __used, + struct perf_session *session __used) { pid_set_comm(event->comm.tid, event->comm.comm); return 0; } -static int process_fork_event(event_t *event, struct perf_session *session __used) +static int process_fork_event(event_t *event, struct sample_data *sample __used, + struct perf_session *session __used) { pid_fork(event->fork.pid, event->fork.ppid, event->fork.time); return 0; } -static int process_exit_event(event_t *event, struct perf_session *session __used) +static int process_exit_event(event_t *event, struct sample_data *sample __used, + struct perf_session *session __used) { pid_exit(event->fork.pid, event->fork.time); return 0; @@ -298,12 +305,21 @@ struct trace_entry { int lock_depth; }; -struct power_entry { +#ifdef SUPPORT_OLD_POWER_EVENTS +static int use_old_power_events; +struct power_entry_old { struct trace_entry te; u64 type; u64 value; u64 cpu_id; }; +#endif + +struct power_processor_entry { + struct trace_entry te; + u32 state; + u32 cpu_id; +}; #define TASK_COMM_LEN 16 struct wakeup_entry { @@ -470,48 +486,65 @@ static void sched_switch(int cpu, u64 timestamp, struct trace_entry *te) } -static int process_sample_event(event_t *event, struct perf_session *session) +static int process_sample_event(event_t *event __used, + struct sample_data *sample, + struct perf_session *session) { - struct sample_data data; struct trace_entry *te; - memset(&data, 0, sizeof(data)); - - event__parse_sample(event, session->sample_type, &data); - if (session->sample_type & PERF_SAMPLE_TIME) { - if (!first_time || first_time > data.time) - first_time = data.time; - if (last_time < data.time) - last_time = data.time; + if (!first_time || first_time > sample->time) + first_time = sample->time; + if (last_time < sample->time) + last_time = sample->time; } - te = (void *)data.raw_data; - if (session->sample_type & PERF_SAMPLE_RAW && data.raw_size > 0) { + te = (void *)sample->raw_data; + if (session->sample_type & PERF_SAMPLE_RAW && sample->raw_size > 0) { char *event_str; - struct power_entry *pe; - - pe = (void *)te; - +#ifdef SUPPORT_OLD_POWER_EVENTS + struct power_entry_old *peo; + peo = (void *)te; +#endif event_str = perf_header__find_event(te->type); if (!event_str) return 0; - if (strcmp(event_str, "power:power_start") == 0) - c_state_start(pe->cpu_id, data.time, pe->value); + if (strcmp(event_str, "power:cpu_idle") == 0) { + struct power_processor_entry *ppe = (void *)te; + if (ppe->state == (u32)PWR_EVENT_EXIT) + c_state_end(ppe->cpu_id, sample->time); + else + c_state_start(ppe->cpu_id, sample->time, + ppe->state); + } + else if (strcmp(event_str, "power:cpu_frequency") == 0) { + struct power_processor_entry *ppe = (void *)te; + p_state_change(ppe->cpu_id, sample->time, ppe->state); + } + + else if (strcmp(event_str, "sched:sched_wakeup") == 0) + sched_wakeup(sample->cpu, sample->time, sample->pid, te); - if (strcmp(event_str, "power:power_end") == 0) - c_state_end(pe->cpu_id, data.time); + else if (strcmp(event_str, "sched:sched_switch") == 0) + sched_switch(sample->cpu, sample->time, te); - if (strcmp(event_str, "power:power_frequency") == 0) - p_state_change(pe->cpu_id, data.time, pe->value); +#ifdef SUPPORT_OLD_POWER_EVENTS + if (use_old_power_events) { + if (strcmp(event_str, "power:power_start") == 0) + c_state_start(peo->cpu_id, sample->time, + peo->value); - if (strcmp(event_str, "sched:sched_wakeup") == 0) - sched_wakeup(data.cpu, data.time, data.pid, te); + else if (strcmp(event_str, "power:power_end") == 0) + c_state_end(sample->cpu, sample->time); - if (strcmp(event_str, "sched:sched_switch") == 0) - sched_switch(data.cpu, data.time, te); + else if (strcmp(event_str, + "power:power_frequency") == 0) + p_state_change(peo->cpu_id, sample->time, + peo->value); + } +#endif } return 0; } @@ -937,7 +970,8 @@ static struct perf_event_ops event_ops = { static int __cmd_timechart(void) { - struct perf_session *session = perf_session__new(input_name, O_RDONLY, 0, false); + struct perf_session *session = perf_session__new(input_name, O_RDONLY, + 0, false, &event_ops); int ret = -EINVAL; if (session == NULL) @@ -968,7 +1002,8 @@ static const char * const timechart_usage[] = { NULL }; -static const char *record_args[] = { +#ifdef SUPPORT_OLD_POWER_EVENTS +static const char * const record_old_args[] = { "record", "-a", "-R", @@ -980,16 +1015,43 @@ static const char *record_args[] = { "-e", "sched:sched_wakeup", "-e", "sched:sched_switch", }; +#endif + +static const char * const record_new_args[] = { + "record", + "-a", + "-R", + "-f", + "-c", "1", + "-e", "power:cpu_frequency", + "-e", "power:cpu_idle", + "-e", "sched:sched_wakeup", + "-e", "sched:sched_switch", +}; static int __cmd_record(int argc, const char **argv) { unsigned int rec_argc, i, j; const char **rec_argv; + const char * const *record_args = record_new_args; + unsigned int record_elems = ARRAY_SIZE(record_new_args); + +#ifdef SUPPORT_OLD_POWER_EVENTS + if (!is_valid_tracepoint("power:cpu_idle") && + is_valid_tracepoint("power:power_start")) { + use_old_power_events = 1; + record_args = record_old_args; + record_elems = ARRAY_SIZE(record_old_args); + } +#endif - rec_argc = ARRAY_SIZE(record_args) + argc - 1; + rec_argc = record_elems + argc - 1; rec_argv = calloc(rec_argc + 1, sizeof(char *)); - for (i = 0; i < ARRAY_SIZE(record_args); i++) + if (rec_argv == NULL) + return -ENOMEM; + + for (i = 0; i < record_elems; i++) rec_argv[i] = strdup(record_args[i]); for (j = 1; j < (unsigned int)argc; j++, i++) @@ -1018,6 +1080,8 @@ static const struct option options[] = { OPT_CALLBACK('p', "process", NULL, "process", "process selector. Pass a pid or process name.", parse_process), + OPT_STRING(0, "symfs", &symbol_conf.symfs, "directory", + "Look for files with symbols relative to this directory"), OPT_END() }; diff --git a/tools/perf/builtin-top.c b/tools/perf/builtin-top.c index dd625808c2a..6ce4042421b 100644 --- a/tools/perf/builtin-top.c +++ b/tools/perf/builtin-top.c @@ -21,6 +21,7 @@ #include "perf.h" #include "util/color.h" +#include "util/evsel.h" #include "util/session.h" #include "util/symbol.h" #include "util/thread.h" @@ -29,6 +30,7 @@ #include "util/parse-options.h" #include "util/parse-events.h" #include "util/cpumap.h" +#include "util/xyarray.h" #include "util/debug.h" @@ -55,7 +57,7 @@ #include <linux/unistd.h> #include <linux/types.h> -static int *fd[MAX_NR_CPUS][MAX_COUNTERS]; +#define FD(e, x, y) (*(int *)xyarray__entry(e->fd, x, y)) static bool system_wide = false; @@ -66,10 +68,9 @@ static int print_entries; static int target_pid = -1; static int target_tid = -1; -static pid_t *all_tids = NULL; -static int thread_num = 0; +static struct thread_map *threads; static bool inherit = false; -static int nr_cpus = 0; +static struct cpu_map *cpus; static int realtime_prio = 0; static bool group = false; static unsigned int page_size; @@ -100,6 +101,7 @@ struct sym_entry *sym_filter_entry = NULL; struct sym_entry *sym_filter_entry_sched = NULL; static int sym_pcnt_filter = 5; static int sym_counter = 0; +static struct perf_evsel *sym_evsel = NULL; static int display_weighted = -1; static const char *cpu_list; @@ -353,7 +355,7 @@ static void show_details(struct sym_entry *syme) return; symbol = sym_entry__symbol(syme); - printf("Showing %s for %s\n", event_name(sym_counter), symbol->name); + printf("Showing %s for %s\n", event_name(sym_evsel), symbol->name); printf(" Events Pcnt (>=%d%%)\n", sym_pcnt_filter); pthread_mutex_lock(&syme->src->lock); @@ -460,7 +462,8 @@ static void rb_insert_active_sym(struct rb_root *tree, struct sym_entry *se) static void print_sym_table(void) { int printed = 0, j; - int counter, snap = !display_weighted ? sym_counter : 0; + struct perf_evsel *counter; + int snap = !display_weighted ? sym_counter : 0; float samples_per_sec = samples/delay_secs; float ksamples_per_sec = kernel_samples/delay_secs; float us_samples_per_sec = (us_samples)/delay_secs; @@ -532,7 +535,9 @@ static void print_sym_table(void) } if (nr_counters == 1 || !display_weighted) { - printf("%Ld", (u64)attrs[0].sample_period); + struct perf_evsel *first; + first = list_entry(evsel_list.next, struct perf_evsel, node); + printf("%Ld", first->attr.sample_period); if (freq) printf("Hz "); else @@ -540,9 +545,9 @@ static void print_sym_table(void) } if (!display_weighted) - printf("%s", event_name(sym_counter)); - else for (counter = 0; counter < nr_counters; counter++) { - if (counter) + printf("%s", event_name(sym_evsel)); + else list_for_each_entry(counter, &evsel_list, node) { + if (counter->idx) printf("/"); printf("%s", event_name(counter)); @@ -558,12 +563,12 @@ static void print_sym_table(void) printf(" (all"); if (cpu_list) - printf(", CPU%s: %s)\n", nr_cpus > 1 ? "s" : "", cpu_list); + printf(", CPU%s: %s)\n", cpus->nr > 1 ? "s" : "", cpu_list); else { if (target_tid != -1) printf(")\n"); else - printf(", %d CPU%s)\n", nr_cpus, nr_cpus > 1 ? "s" : ""); + printf(", %d CPU%s)\n", cpus->nr, cpus->nr > 1 ? "s" : ""); } printf("%-*.*s\n", win_width, win_width, graph_dotted_line); @@ -739,7 +744,7 @@ static void print_mapped_keys(void) fprintf(stdout, "\t[e] display entries (lines). \t(%d)\n", print_entries); if (nr_counters > 1) - fprintf(stdout, "\t[E] active event counter. \t(%s)\n", event_name(sym_counter)); + fprintf(stdout, "\t[E] active event counter. \t(%s)\n", event_name(sym_evsel)); fprintf(stdout, "\t[f] profile display filter (count). \t(%d)\n", count_filter); @@ -826,19 +831,23 @@ static void handle_keypress(struct perf_session *session, int c) break; case 'E': if (nr_counters > 1) { - int i; - fprintf(stderr, "\nAvailable events:"); - for (i = 0; i < nr_counters; i++) - fprintf(stderr, "\n\t%d %s", i, event_name(i)); + + list_for_each_entry(sym_evsel, &evsel_list, node) + fprintf(stderr, "\n\t%d %s", sym_evsel->idx, event_name(sym_evsel)); prompt_integer(&sym_counter, "Enter details event counter"); if (sym_counter >= nr_counters) { - fprintf(stderr, "Sorry, no such event, using %s.\n", event_name(0)); + sym_evsel = list_entry(evsel_list.next, struct perf_evsel, node); sym_counter = 0; + fprintf(stderr, "Sorry, no such event, using %s.\n", event_name(sym_evsel)); sleep(1); + break; } + list_for_each_entry(sym_evsel, &evsel_list, node) + if (sym_evsel->idx == sym_counter) + break; } else sym_counter = 0; break; case 'f': @@ -977,12 +986,13 @@ static int symbol_filter(struct map *map, struct symbol *sym) } static void event__process_sample(const event_t *self, - struct perf_session *session, int counter) + struct sample_data *sample, + struct perf_session *session, + struct perf_evsel *evsel) { u64 ip = self->ip.ip; struct sym_entry *syme; struct addr_location al; - struct sample_data data; struct machine *machine; u8 origin = self->header.misc & PERF_RECORD_MISC_CPUMODE_MASK; @@ -1025,7 +1035,7 @@ static void event__process_sample(const event_t *self, if (self->header.misc & PERF_RECORD_MISC_EXACT_IP) exact_samples++; - if (event__preprocess_sample(self, session, &al, &data, + if (event__preprocess_sample(self, session, &al, sample, symbol_filter) < 0 || al.filtered) return; @@ -1071,9 +1081,9 @@ static void event__process_sample(const event_t *self, syme = symbol__priv(al.sym); if (!syme->skip) { - syme->count[counter]++; + syme->count[evsel->idx]++; syme->origin = origin; - record_precise_ip(syme, counter, ip); + record_precise_ip(syme, evsel->idx, ip); pthread_mutex_lock(&active_symbols_lock); if (list_empty(&syme->node) || !syme->node.next) __list_insert_active_sym(syme); @@ -1082,12 +1092,24 @@ static void event__process_sample(const event_t *self, } struct mmap_data { - int counter; void *base; int mask; unsigned int prev; }; +static int perf_evsel__alloc_mmap_per_thread(struct perf_evsel *evsel, + int ncpus, int nthreads) +{ + evsel->priv = xyarray__new(ncpus, nthreads, sizeof(struct mmap_data)); + return evsel->priv != NULL ? 0 : -ENOMEM; +} + +static void perf_evsel__free_mmap(struct perf_evsel *evsel) +{ + xyarray__delete(evsel->priv); + evsel->priv = NULL; +} + static unsigned int mmap_read_head(struct mmap_data *md) { struct perf_event_mmap_page *pc = md->base; @@ -1100,11 +1122,15 @@ static unsigned int mmap_read_head(struct mmap_data *md) } static void perf_session__mmap_read_counter(struct perf_session *self, - struct mmap_data *md) + struct perf_evsel *evsel, + int cpu, int thread_idx) { + struct xyarray *mmap_array = evsel->priv; + struct mmap_data *md = xyarray__entry(mmap_array, cpu, thread_idx); unsigned int head = mmap_read_head(md); unsigned int old = md->prev; unsigned char *data = md->base + page_size; + struct sample_data sample; int diff; /* @@ -1152,10 +1178,11 @@ static void perf_session__mmap_read_counter(struct perf_session *self, event = &event_copy; } + event__parse_sample(event, self, &sample); if (event->header.type == PERF_RECORD_SAMPLE) - event__process_sample(event, self, md->counter); + event__process_sample(event, &sample, self, evsel); else - event__process(event, self); + event__process(event, &sample, self); old += size; } @@ -1163,36 +1190,39 @@ static void perf_session__mmap_read_counter(struct perf_session *self, } static struct pollfd *event_array; -static struct mmap_data *mmap_array[MAX_NR_CPUS][MAX_COUNTERS]; static void perf_session__mmap_read(struct perf_session *self) { - int i, counter, thread_index; + struct perf_evsel *counter; + int i, thread_index; - for (i = 0; i < nr_cpus; i++) { - for (counter = 0; counter < nr_counters; counter++) + for (i = 0; i < cpus->nr; i++) { + list_for_each_entry(counter, &evsel_list, node) { for (thread_index = 0; - thread_index < thread_num; + thread_index < threads->nr; thread_index++) { perf_session__mmap_read_counter(self, - &mmap_array[i][counter][thread_index]); + counter, i, thread_index); } + } } } int nr_poll; int group_fd; -static void start_counter(int i, int counter) +static void start_counter(int i, struct perf_evsel *evsel) { + struct xyarray *mmap_array = evsel->priv; + struct mmap_data *mm; struct perf_event_attr *attr; int cpu = -1; int thread_index; if (target_tid == -1) - cpu = cpumap[i]; + cpu = cpus->map[i]; - attr = attrs + counter; + attr = &evsel->attr; attr->sample_type = PERF_SAMPLE_IP | PERF_SAMPLE_TID; @@ -1205,16 +1235,20 @@ static void start_counter(int i, int counter) attr->inherit = (cpu < 0) && inherit; attr->mmap = 1; - for (thread_index = 0; thread_index < thread_num; thread_index++) { + for (thread_index = 0; thread_index < threads->nr; thread_index++) { try_again: - fd[i][counter][thread_index] = sys_perf_event_open(attr, - all_tids[thread_index], cpu, group_fd, 0); + FD(evsel, i, thread_index) = sys_perf_event_open(attr, + threads->map[thread_index], cpu, group_fd, 0); - if (fd[i][counter][thread_index] < 0) { + if (FD(evsel, i, thread_index) < 0) { int err = errno; if (err == EPERM || err == EACCES) - die("No permission - are you root?\n"); + die("Permission error - are you root?\n" + "\t Consider tweaking" + " /proc/sys/kernel/perf_event_paranoid.\n"); + if (err == ENOENT) + die("%s event is not supported. ", event_name(evsel)); /* * If it's cycles then fall back to hrtimer * based cpu-clock-tick sw counter, which @@ -1231,30 +1265,30 @@ try_again: goto try_again; } printf("\n"); - error("perfcounter syscall returned with %d (%s)\n", - fd[i][counter][thread_index], strerror(err)); + error("sys_perf_event_open() syscall returned with %d (%s). /bin/dmesg may provide additional information.\n", + FD(evsel, i, thread_index), strerror(err)); die("No CONFIG_PERF_EVENTS=y kernel support configured?\n"); exit(-1); } - assert(fd[i][counter][thread_index] >= 0); - fcntl(fd[i][counter][thread_index], F_SETFL, O_NONBLOCK); + assert(FD(evsel, i, thread_index) >= 0); + fcntl(FD(evsel, i, thread_index), F_SETFL, O_NONBLOCK); /* * First counter acts as the group leader: */ if (group && group_fd == -1) - group_fd = fd[i][counter][thread_index]; + group_fd = FD(evsel, i, thread_index); - event_array[nr_poll].fd = fd[i][counter][thread_index]; + event_array[nr_poll].fd = FD(evsel, i, thread_index); event_array[nr_poll].events = POLLIN; nr_poll++; - mmap_array[i][counter][thread_index].counter = counter; - mmap_array[i][counter][thread_index].prev = 0; - mmap_array[i][counter][thread_index].mask = mmap_pages*page_size - 1; - mmap_array[i][counter][thread_index].base = mmap(NULL, (mmap_pages+1)*page_size, - PROT_READ, MAP_SHARED, fd[i][counter][thread_index], 0); - if (mmap_array[i][counter][thread_index].base == MAP_FAILED) + mm = xyarray__entry(mmap_array, i, thread_index); + mm->prev = 0; + mm->mask = mmap_pages*page_size - 1; + mm->base = mmap(NULL, (mmap_pages+1)*page_size, + PROT_READ, MAP_SHARED, FD(evsel, i, thread_index), 0); + if (mm->base == MAP_FAILED) die("failed to mmap with %d (%s)\n", errno, strerror(errno)); } } @@ -1262,13 +1296,13 @@ try_again: static int __cmd_top(void) { pthread_t thread; - int i, counter; - int ret; + struct perf_evsel *counter; + int i, ret; /* * FIXME: perf_session__new should allow passing a O_MMAP, so that all this * mmap reading, etc is encapsulated in it. Use O_WRONLY for now. */ - struct perf_session *session = perf_session__new(NULL, O_WRONLY, false, false); + struct perf_session *session = perf_session__new(NULL, O_WRONLY, false, false, NULL); if (session == NULL) return -ENOMEM; @@ -1277,9 +1311,9 @@ static int __cmd_top(void) else event__synthesize_threads(event__process, session); - for (i = 0; i < nr_cpus; i++) { + for (i = 0; i < cpus->nr; i++) { group_fd = -1; - for (counter = 0; counter < nr_counters; counter++) + list_for_each_entry(counter, &evsel_list, node) start_counter(i, counter); } @@ -1368,8 +1402,8 @@ static const struct option options[] = { int cmd_top(int argc, const char **argv, const char *prefix __used) { - int counter; - int i,j; + struct perf_evsel *pos; + int status = -ENOMEM; page_size = sysconf(_SC_PAGE_SIZE); @@ -1377,34 +1411,17 @@ int cmd_top(int argc, const char **argv, const char *prefix __used) if (argc) usage_with_options(top_usage, options); - if (target_pid != -1) { + if (target_pid != -1) target_tid = target_pid; - thread_num = find_all_tid(target_pid, &all_tids); - if (thread_num <= 0) { - fprintf(stderr, "Can't find all threads of pid %d\n", - target_pid); - usage_with_options(top_usage, options); - } - } else { - all_tids=malloc(sizeof(pid_t)); - if (!all_tids) - return -ENOMEM; - all_tids[0] = target_tid; - thread_num = 1; + threads = thread_map__new(target_pid, target_tid); + if (threads == NULL) { + pr_err("Problems finding threads of monitor\n"); + usage_with_options(top_usage, options); } - for (i = 0; i < MAX_NR_CPUS; i++) { - for (j = 0; j < MAX_COUNTERS; j++) { - fd[i][j] = malloc(sizeof(int)*thread_num); - mmap_array[i][j] = zalloc( - sizeof(struct mmap_data)*thread_num); - if (!fd[i][j] || !mmap_array[i][j]) - return -ENOMEM; - } - } - event_array = malloc( - sizeof(struct pollfd)*MAX_NR_CPUS*MAX_COUNTERS*thread_num); + event_array = malloc((sizeof(struct pollfd) * + MAX_NR_CPUS * MAX_COUNTERS * threads->nr)); if (!event_array) return -ENOMEM; @@ -1415,15 +1432,10 @@ int cmd_top(int argc, const char **argv, const char *prefix __used) cpu_list = NULL; } - if (!nr_counters) - nr_counters = 1; - - symbol_conf.priv_size = (sizeof(struct sym_entry) + - (nr_counters + 1) * sizeof(unsigned long)); - - symbol_conf.try_vmlinux_path = (symbol_conf.vmlinux_name == NULL); - if (symbol__init() < 0) - return -1; + if (!nr_counters && perf_evsel_list__create_default() < 0) { + pr_err("Not enough memory for event selector list\n"); + return -ENOMEM; + } if (delay_secs < 1) delay_secs = 1; @@ -1440,23 +1452,33 @@ int cmd_top(int argc, const char **argv, const char *prefix __used) exit(EXIT_FAILURE); } - /* - * Fill in the ones not specifically initialized via -c: - */ - for (counter = 0; counter < nr_counters; counter++) { - if (attrs[counter].sample_period) + if (target_tid != -1) + cpus = cpu_map__dummy_new(); + else + cpus = cpu_map__new(cpu_list); + + if (cpus == NULL) + usage_with_options(top_usage, options); + + list_for_each_entry(pos, &evsel_list, node) { + if (perf_evsel__alloc_mmap_per_thread(pos, cpus->nr, threads->nr) < 0 || + perf_evsel__alloc_fd(pos, cpus->nr, threads->nr) < 0) + goto out_free_fd; + /* + * Fill in the ones not specifically initialized via -c: + */ + if (pos->attr.sample_period) continue; - attrs[counter].sample_period = default_interval; + pos->attr.sample_period = default_interval; } - if (target_tid != -1) - nr_cpus = 1; - else - nr_cpus = read_cpu_map(cpu_list); + symbol_conf.priv_size = (sizeof(struct sym_entry) + + (nr_counters + 1) * sizeof(unsigned long)); - if (nr_cpus < 1) - usage_with_options(top_usage, options); + symbol_conf.try_vmlinux_path = (symbol_conf.vmlinux_name == NULL); + if (symbol__init() < 0) + return -1; get_term_dimensions(&winsize); if (print_entries == 0) { @@ -1464,5 +1486,10 @@ int cmd_top(int argc, const char **argv, const char *prefix __used) signal(SIGWINCH, sig_winch_handler); } - return __cmd_top(); + status = __cmd_top(); +out_free_fd: + list_for_each_entry(pos, &evsel_list, node) + perf_evsel__free_mmap(pos); + + return status; } diff --git a/tools/perf/builtin.h b/tools/perf/builtin.h index 921245b2858..c7798c7f24e 100644 --- a/tools/perf/builtin.h +++ b/tools/perf/builtin.h @@ -27,7 +27,7 @@ extern int cmd_report(int argc, const char **argv, const char *prefix); extern int cmd_stat(int argc, const char **argv, const char *prefix); extern int cmd_timechart(int argc, const char **argv, const char *prefix); extern int cmd_top(int argc, const char **argv, const char *prefix); -extern int cmd_trace(int argc, const char **argv, const char *prefix); +extern int cmd_script(int argc, const char **argv, const char *prefix); extern int cmd_version(int argc, const char **argv, const char *prefix); extern int cmd_probe(int argc, const char **argv, const char *prefix); extern int cmd_kmem(int argc, const char **argv, const char *prefix); diff --git a/tools/perf/command-list.txt b/tools/perf/command-list.txt index 949d77fc0b9..16b5088cf8f 100644 --- a/tools/perf/command-list.txt +++ b/tools/perf/command-list.txt @@ -16,7 +16,7 @@ perf-report mainporcelain common perf-stat mainporcelain common perf-timechart mainporcelain common perf-top mainporcelain common -perf-trace mainporcelain common +perf-script mainporcelain common perf-probe mainporcelain common perf-kmem mainporcelain common perf-lock mainporcelain common diff --git a/tools/perf/feature-tests.mak b/tools/perf/feature-tests.mak index b253db634f0..b041ca67a2c 100644 --- a/tools/perf/feature-tests.mak +++ b/tools/perf/feature-tests.mak @@ -9,8 +9,8 @@ endef ifndef NO_DWARF define SOURCE_DWARF #include <dwarf.h> -#include <libdw.h> -#include <version.h> +#include <elfutils/libdw.h> +#include <elfutils/version.h> #ifndef _ELFUTILS_PREREQ #error #endif diff --git a/tools/perf/perf.c b/tools/perf/perf.c index cdd6c03f1e1..5b1ecd66bb3 100644 --- a/tools/perf/perf.c +++ b/tools/perf/perf.c @@ -286,6 +286,8 @@ static int run_builtin(struct cmd_struct *p, int argc, const char **argv) status = p->fn(argc, argv, prefix); exit_browser(status); + perf_evsel_list__delete(); + if (status) return status & 0xff; @@ -323,7 +325,7 @@ static void handle_internal_command(int argc, const char **argv) { "top", cmd_top, 0 }, { "annotate", cmd_annotate, 0 }, { "version", cmd_version, 0 }, - { "trace", cmd_trace, 0 }, + { "script", cmd_script, 0 }, { "sched", cmd_sched, 0 }, { "probe", cmd_probe, 0 }, { "kmem", cmd_kmem, 0 }, diff --git a/tools/perf/scripts/perl/Perf-Trace-Util/Context.c b/tools/perf/scripts/perl/Perf-Trace-Util/Context.c index 01a64ad693f..790ceba6ad3 100644 --- a/tools/perf/scripts/perl/Perf-Trace-Util/Context.c +++ b/tools/perf/scripts/perl/Perf-Trace-Util/Context.c @@ -8,7 +8,7 @@ #line 1 "Context.xs" /* - * Context.xs. XS interfaces for perf trace. + * Context.xs. XS interfaces for perf script. * * Copyright (C) 2009 Tom Zanussi <tzanussi@gmail.com> * diff --git a/tools/perf/scripts/perl/Perf-Trace-Util/Context.xs b/tools/perf/scripts/perl/Perf-Trace-Util/Context.xs index 549cf0467d3..c1e2ed1ed34 100644 --- a/tools/perf/scripts/perl/Perf-Trace-Util/Context.xs +++ b/tools/perf/scripts/perl/Perf-Trace-Util/Context.xs @@ -1,5 +1,5 @@ /* - * Context.xs. XS interfaces for perf trace. + * Context.xs. XS interfaces for perf script. * * Copyright (C) 2009 Tom Zanussi <tzanussi@gmail.com> * @@ -23,7 +23,7 @@ #include "perl.h" #include "XSUB.h" #include "../../../perf.h" -#include "../../../util/trace-event.h" +#include "../../../util/script-event.h" MODULE = Perf::Trace::Context PACKAGE = Perf::Trace::Context PROTOTYPES: ENABLE diff --git a/tools/perf/scripts/perl/Perf-Trace-Util/README b/tools/perf/scripts/perl/Perf-Trace-Util/README index 9a970763079..2f0c7f3043e 100644 --- a/tools/perf/scripts/perl/Perf-Trace-Util/README +++ b/tools/perf/scripts/perl/Perf-Trace-Util/README @@ -1,7 +1,7 @@ Perf-Trace-Util version 0.01 ============================ -This module contains utility functions for use with perf trace. +This module contains utility functions for use with perf script. Core.pm and Util.pm are pure Perl modules; Core.pm contains routines that the core perf support for Perl calls on and should always be @@ -33,7 +33,7 @@ After you do that: INSTALLATION -Building perf with perf trace Perl scripting should install this +Building perf with perf script Perl scripting should install this module in the right place. You should make sure libperl and ExtUtils/Embed.pm are installed first diff --git a/tools/perf/scripts/perl/Perf-Trace-Util/lib/Perf/Trace/Context.pm b/tools/perf/scripts/perl/Perf-Trace-Util/lib/Perf/Trace/Context.pm index 6c7f3659cb1..4e2f6039ac9 100644 --- a/tools/perf/scripts/perl/Perf-Trace-Util/lib/Perf/Trace/Context.pm +++ b/tools/perf/scripts/perl/Perf-Trace-Util/lib/Perf/Trace/Context.pm @@ -34,7 +34,7 @@ Perf::Trace::Context - Perl extension for accessing functions in perf. =head1 SEE ALSO -Perf (trace) documentation +Perf (script) documentation =head1 AUTHOR diff --git a/tools/perf/scripts/perl/Perf-Trace-Util/lib/Perf/Trace/Core.pm b/tools/perf/scripts/perl/Perf-Trace-Util/lib/Perf/Trace/Core.pm index 9df376a9f62..9158458d3ee 100644 --- a/tools/perf/scripts/perl/Perf-Trace-Util/lib/Perf/Trace/Core.pm +++ b/tools/perf/scripts/perl/Perf-Trace-Util/lib/Perf/Trace/Core.pm @@ -163,7 +163,7 @@ sub dump_symbolic_fields __END__ =head1 NAME -Perf::Trace::Core - Perl extension for perf trace +Perf::Trace::Core - Perl extension for perf script =head1 SYNOPSIS @@ -171,7 +171,7 @@ Perf::Trace::Core - Perl extension for perf trace =head1 SEE ALSO -Perf (trace) documentation +Perf (script) documentation =head1 AUTHOR diff --git a/tools/perf/scripts/perl/Perf-Trace-Util/lib/Perf/Trace/Util.pm b/tools/perf/scripts/perl/Perf-Trace-Util/lib/Perf/Trace/Util.pm index d94b40c8ac8..05350011462 100644 --- a/tools/perf/scripts/perl/Perf-Trace-Util/lib/Perf/Trace/Util.pm +++ b/tools/perf/scripts/perl/Perf-Trace-Util/lib/Perf/Trace/Util.pm @@ -65,7 +65,7 @@ sub clear_term __END__ =head1 NAME -Perf::Trace::Util - Perl extension for perf trace +Perf::Trace::Util - Perl extension for perf script =head1 SYNOPSIS @@ -73,7 +73,7 @@ Perf::Trace::Util - Perl extension for perf trace =head1 SEE ALSO -Perf (trace) documentation +Perf (script) documentation =head1 AUTHOR diff --git a/tools/perf/scripts/perl/bin/failed-syscalls-report b/tools/perf/scripts/perl/bin/failed-syscalls-report index 4028d92dc4a..9f83cc1ad8b 100644 --- a/tools/perf/scripts/perl/bin/failed-syscalls-report +++ b/tools/perf/scripts/perl/bin/failed-syscalls-report @@ -7,4 +7,4 @@ if [ $# -gt 0 ] ; then shift fi fi -perf trace $@ -s "$PERF_EXEC_PATH"/scripts/perl/failed-syscalls.pl $comm +perf script $@ -s "$PERF_EXEC_PATH"/scripts/perl/failed-syscalls.pl $comm diff --git a/tools/perf/scripts/perl/bin/rw-by-file-report b/tools/perf/scripts/perl/bin/rw-by-file-report index ba25f4d41fb..77200b3f310 100644 --- a/tools/perf/scripts/perl/bin/rw-by-file-report +++ b/tools/perf/scripts/perl/bin/rw-by-file-report @@ -7,7 +7,4 @@ if [ $# -lt 1 ] ; then fi comm=$1 shift -perf trace $@ -s "$PERF_EXEC_PATH"/scripts/perl/rw-by-file.pl $comm - - - +perf script $@ -s "$PERF_EXEC_PATH"/scripts/perl/rw-by-file.pl $comm diff --git a/tools/perf/scripts/perl/bin/rw-by-pid-report b/tools/perf/scripts/perl/bin/rw-by-pid-report index 641a3f5d085..a27b9f311f9 100644 --- a/tools/perf/scripts/perl/bin/rw-by-pid-report +++ b/tools/perf/scripts/perl/bin/rw-by-pid-report @@ -1,6 +1,3 @@ #!/bin/bash # description: system-wide r/w activity -perf trace $@ -s "$PERF_EXEC_PATH"/scripts/perl/rw-by-pid.pl - - - +perf script $@ -s "$PERF_EXEC_PATH"/scripts/perl/rw-by-pid.pl diff --git a/tools/perf/scripts/perl/bin/rwtop-report b/tools/perf/scripts/perl/bin/rwtop-report index 4918dba7702..83e11ec2e19 100644 --- a/tools/perf/scripts/perl/bin/rwtop-report +++ b/tools/perf/scripts/perl/bin/rwtop-report @@ -17,7 +17,4 @@ if [ "$n_args" -gt 0 ] ; then interval=$1 shift fi -perf trace $@ -s "$PERF_EXEC_PATH"/scripts/perl/rwtop.pl $interval - - - +perf script $@ -s "$PERF_EXEC_PATH"/scripts/perl/rwtop.pl $interval diff --git a/tools/perf/scripts/perl/bin/wakeup-latency-report b/tools/perf/scripts/perl/bin/wakeup-latency-report index 49052ebcb63..889e8130cca 100644 --- a/tools/perf/scripts/perl/bin/wakeup-latency-report +++ b/tools/perf/scripts/perl/bin/wakeup-latency-report @@ -1,6 +1,3 @@ #!/bin/bash # description: system-wide min/max/avg wakeup latency -perf trace $@ -s "$PERF_EXEC_PATH"/scripts/perl/wakeup-latency.pl - - - +perf script $@ -s "$PERF_EXEC_PATH"/scripts/perl/wakeup-latency.pl diff --git a/tools/perf/scripts/perl/bin/workqueue-stats-report b/tools/perf/scripts/perl/bin/workqueue-stats-report index df0c65f4ca9..6d91411d248 100644 --- a/tools/perf/scripts/perl/bin/workqueue-stats-report +++ b/tools/perf/scripts/perl/bin/workqueue-stats-report @@ -1,7 +1,3 @@ #!/bin/bash # description: workqueue stats (ins/exe/create/destroy) -perf trace $@ -s "$PERF_EXEC_PATH"/scripts/perl/workqueue-stats.pl - - - - +perf script $@ -s "$PERF_EXEC_PATH"/scripts/perl/workqueue-stats.pl diff --git a/tools/perf/scripts/perl/check-perf-trace.pl b/tools/perf/scripts/perl/check-perf-trace.pl index 4e7dc0a407a..4e7076c2061 100644 --- a/tools/perf/scripts/perl/check-perf-trace.pl +++ b/tools/perf/scripts/perl/check-perf-trace.pl @@ -1,4 +1,4 @@ -# perf trace event handlers, generated by perf trace -g perl +# perf script event handlers, generated by perf script -g perl # (c) 2009, Tom Zanussi <tzanussi@gmail.com> # Licensed under the terms of the GNU GPL License version 2 diff --git a/tools/perf/scripts/perl/rw-by-file.pl b/tools/perf/scripts/perl/rw-by-file.pl index 2a39097687b..74844ee2be3 100644 --- a/tools/perf/scripts/perl/rw-by-file.pl +++ b/tools/perf/scripts/perl/rw-by-file.pl @@ -18,7 +18,7 @@ use lib "./Perf-Trace-Util/lib"; use Perf::Trace::Core; use Perf::Trace::Util; -my $usage = "perf trace -s rw-by-file.pl <comm>\n"; +my $usage = "perf script -s rw-by-file.pl <comm>\n"; my $for_comm = shift or die $usage; diff --git a/tools/perf/scripts/perl/workqueue-stats.pl b/tools/perf/scripts/perl/workqueue-stats.pl index b84b12699b7..a8eaff5119e 100644 --- a/tools/perf/scripts/perl/workqueue-stats.pl +++ b/tools/perf/scripts/perl/workqueue-stats.pl @@ -10,7 +10,7 @@ # workqueue:workqueue_destruction -e workqueue:workqueue_execution # -e workqueue:workqueue_insertion # -# perf trace -p -s tools/perf/scripts/perl/workqueue-stats.pl +# perf script -p -s tools/perf/scripts/perl/workqueue-stats.pl use 5.010000; use strict; diff --git a/tools/perf/scripts/python/Perf-Trace-Util/Context.c b/tools/perf/scripts/python/Perf-Trace-Util/Context.c index 957085dd5d8..315067b8f55 100644 --- a/tools/perf/scripts/python/Perf-Trace-Util/Context.c +++ b/tools/perf/scripts/python/Perf-Trace-Util/Context.c @@ -1,5 +1,5 @@ /* - * Context.c. Python interfaces for perf trace. + * Context.c. Python interfaces for perf script. * * Copyright (C) 2010 Tom Zanussi <tzanussi@gmail.com> * diff --git a/tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/Core.py b/tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/Core.py index aad7525bca1..de7211e4fa4 100644 --- a/tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/Core.py +++ b/tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/Core.py @@ -1,4 +1,4 @@ -# Core.py - Python extension for perf trace, core functions +# Core.py - Python extension for perf script, core functions # # Copyright (C) 2010 by Tom Zanussi <tzanussi@gmail.com> # diff --git a/tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/SchedGui.py b/tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/SchedGui.py index ae9a56e43e0..fdd92f69905 100644 --- a/tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/SchedGui.py +++ b/tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/SchedGui.py @@ -1,4 +1,4 @@ -# SchedGui.py - Python extension for perf trace, basic GUI code for +# SchedGui.py - Python extension for perf script, basic GUI code for # traces drawing and overview. # # Copyright (C) 2010 by Frederic Weisbecker <fweisbec@gmail.com> diff --git a/tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/Util.py b/tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/Util.py index 13cc02b5893..15c8400240f 100644 --- a/tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/Util.py +++ b/tools/perf/scripts/python/Perf-Trace-Util/lib/Perf/Trace/Util.py @@ -1,4 +1,4 @@ -# Util.py - Python extension for perf trace, miscellaneous utility code +# Util.py - Python extension for perf script, miscellaneous utility code # # Copyright (C) 2010 by Tom Zanussi <tzanussi@gmail.com> # diff --git a/tools/perf/scripts/python/bin/failed-syscalls-by-pid-report b/tools/perf/scripts/python/bin/failed-syscalls-by-pid-report index 03587021463..fda5096d0cb 100644 --- a/tools/perf/scripts/python/bin/failed-syscalls-by-pid-report +++ b/tools/perf/scripts/python/bin/failed-syscalls-by-pid-report @@ -7,4 +7,4 @@ if [ $# -gt 0 ] ; then shift fi fi -perf trace $@ -s "$PERF_EXEC_PATH"/scripts/python/failed-syscalls-by-pid.py $comm +perf script $@ -s "$PERF_EXEC_PATH"/scripts/python/failed-syscalls-by-pid.py $comm diff --git a/tools/perf/scripts/python/bin/futex-contention-report b/tools/perf/scripts/python/bin/futex-contention-report index c8268138fb7..6c44271091a 100644 --- a/tools/perf/scripts/python/bin/futex-contention-report +++ b/tools/perf/scripts/python/bin/futex-contention-report @@ -1,4 +1,4 @@ #!/bin/bash # description: futext contention measurement -perf trace $@ -s "$PERF_EXEC_PATH"/scripts/python/futex-contention.py +perf script $@ -s "$PERF_EXEC_PATH"/scripts/python/futex-contention.py diff --git a/tools/perf/scripts/python/bin/netdev-times-report b/tools/perf/scripts/python/bin/netdev-times-report index 4ad361b3124..8f759291da8 100644 --- a/tools/perf/scripts/python/bin/netdev-times-report +++ b/tools/perf/scripts/python/bin/netdev-times-report @@ -2,4 +2,4 @@ # description: display a process of packet and processing time # args: [tx] [rx] [dev=] [debug] -perf trace -s "$PERF_EXEC_PATH"/scripts/python/netdev-times.py $@ +perf script -s "$PERF_EXEC_PATH"/scripts/python/netdev-times.py $@ diff --git a/tools/perf/scripts/python/bin/sched-migration-report b/tools/perf/scripts/python/bin/sched-migration-report index df1791f07c2..68b037a1849 100644 --- a/tools/perf/scripts/python/bin/sched-migration-report +++ b/tools/perf/scripts/python/bin/sched-migration-report @@ -1,3 +1,3 @@ #!/bin/bash # description: sched migration overview -perf trace $@ -s "$PERF_EXEC_PATH"/scripts/python/sched-migration.py +perf script $@ -s "$PERF_EXEC_PATH"/scripts/python/sched-migration.py diff --git a/tools/perf/scripts/python/bin/sctop-report b/tools/perf/scripts/python/bin/sctop-report index 36b409c05e5..c32db294124 100644 --- a/tools/perf/scripts/python/bin/sctop-report +++ b/tools/perf/scripts/python/bin/sctop-report @@ -21,4 +21,4 @@ elif [ "$n_args" -gt 0 ] ; then interval=$1 shift fi -perf trace $@ -s "$PERF_EXEC_PATH"/scripts/python/sctop.py $comm $interval +perf script $@ -s "$PERF_EXEC_PATH"/scripts/python/sctop.py $comm $interval diff --git a/tools/perf/scripts/python/bin/syscall-counts-by-pid-report b/tools/perf/scripts/python/bin/syscall-counts-by-pid-report index 4eb88c9fc83..16eb8d65c54 100644 --- a/tools/perf/scripts/python/bin/syscall-counts-by-pid-report +++ b/tools/perf/scripts/python/bin/syscall-counts-by-pid-report @@ -7,4 +7,4 @@ if [ $# -gt 0 ] ; then shift fi fi -perf trace $@ -s "$PERF_EXEC_PATH"/scripts/python/syscall-counts-by-pid.py $comm +perf script $@ -s "$PERF_EXEC_PATH"/scripts/python/syscall-counts-by-pid.py $comm diff --git a/tools/perf/scripts/python/bin/syscall-counts-report b/tools/perf/scripts/python/bin/syscall-counts-report index cb2f9c5cf17..0f0e9d453bb 100644 --- a/tools/perf/scripts/python/bin/syscall-counts-report +++ b/tools/perf/scripts/python/bin/syscall-counts-report @@ -7,4 +7,4 @@ if [ $# -gt 0 ] ; then shift fi fi -perf trace $@ -s "$PERF_EXEC_PATH"/scripts/python/syscall-counts.py $comm +perf script $@ -s "$PERF_EXEC_PATH"/scripts/python/syscall-counts.py $comm diff --git a/tools/perf/scripts/python/check-perf-trace.py b/tools/perf/scripts/python/check-perf-trace.py index d9f7893e315..4647a7694cf 100644 --- a/tools/perf/scripts/python/check-perf-trace.py +++ b/tools/perf/scripts/python/check-perf-trace.py @@ -1,4 +1,4 @@ -# perf trace event handlers, generated by perf trace -g python +# perf script event handlers, generated by perf script -g python # (c) 2010, Tom Zanussi <tzanussi@gmail.com> # Licensed under the terms of the GNU GPL License version 2 # diff --git a/tools/perf/scripts/python/failed-syscalls-by-pid.py b/tools/perf/scripts/python/failed-syscalls-by-pid.py index acd7848717b..85805fac411 100644 --- a/tools/perf/scripts/python/failed-syscalls-by-pid.py +++ b/tools/perf/scripts/python/failed-syscalls-by-pid.py @@ -15,7 +15,7 @@ from perf_trace_context import * from Core import * from Util import * -usage = "perf trace -s syscall-counts-by-pid.py [comm|pid]\n"; +usage = "perf script -s syscall-counts-by-pid.py [comm|pid]\n"; for_comm = None for_pid = None diff --git a/tools/perf/scripts/python/sched-migration.py b/tools/perf/scripts/python/sched-migration.py index b934383c336..74d55ec08ae 100644 --- a/tools/perf/scripts/python/sched-migration.py +++ b/tools/perf/scripts/python/sched-migration.py @@ -4,7 +4,7 @@ # # Copyright (C) 2010 Frederic Weisbecker <fweisbec@gmail.com> # -# perf trace event handlers have been generated by perf trace -g python +# perf script event handlers have been generated by perf script -g python # # This software is distributed under the terms of the GNU General # Public License ("GPL") version 2 as published by the Free Software diff --git a/tools/perf/scripts/python/sctop.py b/tools/perf/scripts/python/sctop.py index 7a6ec2c7d8a..42c267e292f 100644 --- a/tools/perf/scripts/python/sctop.py +++ b/tools/perf/scripts/python/sctop.py @@ -17,7 +17,7 @@ from perf_trace_context import * from Core import * from Util import * -usage = "perf trace -s sctop.py [comm] [interval]\n"; +usage = "perf script -s sctop.py [comm] [interval]\n"; for_comm = None default_interval = 3 diff --git a/tools/perf/scripts/python/syscall-counts-by-pid.py b/tools/perf/scripts/python/syscall-counts-by-pid.py index d1ee3ec10cf..c64d1c55d74 100644 --- a/tools/perf/scripts/python/syscall-counts-by-pid.py +++ b/tools/perf/scripts/python/syscall-counts-by-pid.py @@ -14,7 +14,7 @@ from perf_trace_context import * from Core import * from Util import syscall_name -usage = "perf trace -s syscall-counts-by-pid.py [comm]\n"; +usage = "perf script -s syscall-counts-by-pid.py [comm]\n"; for_comm = None for_pid = None diff --git a/tools/perf/scripts/python/syscall-counts.py b/tools/perf/scripts/python/syscall-counts.py index ea183dc82d2..b435d3f188e 100644 --- a/tools/perf/scripts/python/syscall-counts.py +++ b/tools/perf/scripts/python/syscall-counts.py @@ -15,7 +15,7 @@ from perf_trace_context import * from Core import * from Util import syscall_name -usage = "perf trace -s syscall-counts.py [comm]\n"; +usage = "perf script -s syscall-counts.py [comm]\n"; for_comm = None diff --git a/tools/perf/util/build-id.c b/tools/perf/util/build-id.c index e437edb7241..deffb8c9607 100644 --- a/tools/perf/util/build-id.c +++ b/tools/perf/util/build-id.c @@ -14,7 +14,9 @@ #include <linux/kernel.h> #include "debug.h" -static int build_id__mark_dso_hit(event_t *event, struct perf_session *session) +static int build_id__mark_dso_hit(event_t *event, + struct sample_data *sample __used, + struct perf_session *session) { struct addr_location al; u8 cpumode = event->header.misc & PERF_RECORD_MISC_CPUMODE_MASK; @@ -35,7 +37,8 @@ static int build_id__mark_dso_hit(event_t *event, struct perf_session *session) return 0; } -static int event__exit_del_thread(event_t *self, struct perf_session *session) +static int event__exit_del_thread(event_t *self, struct sample_data *sample __used, + struct perf_session *session) { struct thread *thread = perf_session__findnew(session, self->fork.tid); diff --git a/tools/perf/util/cpumap.c b/tools/perf/util/cpumap.c index 0f9b8d7a7d7..3ccaa104338 100644 --- a/tools/perf/util/cpumap.c +++ b/tools/perf/util/cpumap.c @@ -4,32 +4,53 @@ #include <assert.h> #include <stdio.h> -int cpumap[MAX_NR_CPUS]; - -static int default_cpu_map(void) +static struct cpu_map *cpu_map__default_new(void) { - int nr_cpus, i; + struct cpu_map *cpus; + int nr_cpus; nr_cpus = sysconf(_SC_NPROCESSORS_ONLN); - assert(nr_cpus <= MAX_NR_CPUS); - assert((int)nr_cpus >= 0); + if (nr_cpus < 0) + return NULL; + + cpus = malloc(sizeof(*cpus) + nr_cpus * sizeof(int)); + if (cpus != NULL) { + int i; + for (i = 0; i < nr_cpus; ++i) + cpus->map[i] = i; - for (i = 0; i < nr_cpus; ++i) - cpumap[i] = i; + cpus->nr = nr_cpus; + } - return nr_cpus; + return cpus; } -static int read_all_cpu_map(void) +static struct cpu_map *cpu_map__trim_new(int nr_cpus, int *tmp_cpus) { + size_t payload_size = nr_cpus * sizeof(int); + struct cpu_map *cpus = malloc(sizeof(*cpus) + payload_size); + + if (cpus != NULL) { + cpus->nr = nr_cpus; + memcpy(cpus->map, tmp_cpus, payload_size); + } + + return cpus; +} + +static struct cpu_map *cpu_map__read_all_cpu_map(void) +{ + struct cpu_map *cpus = NULL; FILE *onlnf; int nr_cpus = 0; + int *tmp_cpus = NULL, *tmp; + int max_entries = 0; int n, cpu, prev; char sep; onlnf = fopen("/sys/devices/system/cpu/online", "r"); if (!onlnf) - return default_cpu_map(); + return cpu_map__default_new(); sep = 0; prev = -1; @@ -38,12 +59,28 @@ static int read_all_cpu_map(void) if (n <= 0) break; if (prev >= 0) { - assert(nr_cpus + cpu - prev - 1 < MAX_NR_CPUS); + int new_max = nr_cpus + cpu - prev - 1; + + if (new_max >= max_entries) { + max_entries = new_max + MAX_NR_CPUS / 2; + tmp = realloc(tmp_cpus, max_entries * sizeof(int)); + if (tmp == NULL) + goto out_free_tmp; + tmp_cpus = tmp; + } + while (++prev < cpu) - cpumap[nr_cpus++] = prev; + tmp_cpus[nr_cpus++] = prev; + } + if (nr_cpus == max_entries) { + max_entries += MAX_NR_CPUS; + tmp = realloc(tmp_cpus, max_entries * sizeof(int)); + if (tmp == NULL) + goto out_free_tmp; + tmp_cpus = tmp; } - assert (nr_cpus < MAX_NR_CPUS); - cpumap[nr_cpus++] = cpu; + + tmp_cpus[nr_cpus++] = cpu; if (n == 2 && sep == '-') prev = cpu; else @@ -51,24 +88,31 @@ static int read_all_cpu_map(void) if (n == 1 || sep == '\n') break; } - fclose(onlnf); - if (nr_cpus > 0) - return nr_cpus; - return default_cpu_map(); + if (nr_cpus > 0) + cpus = cpu_map__trim_new(nr_cpus, tmp_cpus); + else + cpus = cpu_map__default_new(); +out_free_tmp: + free(tmp_cpus); + fclose(onlnf); + return cpus; } -int read_cpu_map(const char *cpu_list) +struct cpu_map *cpu_map__new(const char *cpu_list) { + struct cpu_map *cpus = NULL; unsigned long start_cpu, end_cpu = 0; char *p = NULL; int i, nr_cpus = 0; + int *tmp_cpus = NULL, *tmp; + int max_entries = 0; if (!cpu_list) - return read_all_cpu_map(); + return cpu_map__read_all_cpu_map(); if (!isdigit(*cpu_list)) - goto invalid; + goto out; while (isdigit(*cpu_list)) { p = NULL; @@ -94,21 +138,42 @@ int read_cpu_map(const char *cpu_list) for (; start_cpu <= end_cpu; start_cpu++) { /* check for duplicates */ for (i = 0; i < nr_cpus; i++) - if (cpumap[i] == (int)start_cpu) + if (tmp_cpus[i] == (int)start_cpu) goto invalid; - assert(nr_cpus < MAX_NR_CPUS); - cpumap[nr_cpus++] = (int)start_cpu; + if (nr_cpus == max_entries) { + max_entries += MAX_NR_CPUS; + tmp = realloc(tmp_cpus, max_entries * sizeof(int)); + if (tmp == NULL) + goto invalid; + tmp_cpus = tmp; + } + tmp_cpus[nr_cpus++] = (int)start_cpu; } if (*p) ++p; cpu_list = p; } - if (nr_cpus > 0) - return nr_cpus; - return default_cpu_map(); + if (nr_cpus > 0) + cpus = cpu_map__trim_new(nr_cpus, tmp_cpus); + else + cpus = cpu_map__default_new(); invalid: - return -1; + free(tmp_cpus); +out: + return cpus; +} + +struct cpu_map *cpu_map__dummy_new(void) +{ + struct cpu_map *cpus = malloc(sizeof(*cpus) + sizeof(int)); + + if (cpus != NULL) { + cpus->nr = 1; + cpus->map[0] = -1; + } + + return cpus; } diff --git a/tools/perf/util/cpumap.h b/tools/perf/util/cpumap.h index 3e60f56e490..f7a4f42f630 100644 --- a/tools/perf/util/cpumap.h +++ b/tools/perf/util/cpumap.h @@ -1,7 +1,13 @@ #ifndef __PERF_CPUMAP_H #define __PERF_CPUMAP_H -extern int read_cpu_map(const char *cpu_list); -extern int cpumap[]; +struct cpu_map { + int nr; + int map[]; +}; + +struct cpu_map *cpu_map__new(const char *cpu_list); +struct cpu_map *cpu_map__dummy_new(void); +void *cpu_map__delete(struct cpu_map *map); #endif /* __PERF_CPUMAP_H */ diff --git a/tools/perf/util/debug.c b/tools/perf/util/debug.c index c8d81b00089..01bbe8ecec3 100644 --- a/tools/perf/util/debug.c +++ b/tools/perf/util/debug.c @@ -46,20 +46,16 @@ int dump_printf(const char *fmt, ...) return ret; } -static int dump_printf_color(const char *fmt, const char *color, ...) +#ifdef NO_NEWT_SUPPORT +void ui__warning(const char *format, ...) { va_list args; - int ret = 0; - if (dump_trace) { - va_start(args, color); - ret = color_vfprintf(stdout, color, fmt, args); - va_end(args); - } - - return ret; + va_start(args, format); + vfprintf(stderr, format, args); + va_end(args); } - +#endif void trace_event(event_t *event) { @@ -70,29 +66,29 @@ void trace_event(event_t *event) if (!dump_trace) return; - dump_printf("."); - dump_printf_color("\n. ... raw event: size %d bytes\n", color, - event->header.size); + printf("."); + color_fprintf(stdout, color, "\n. ... raw event: size %d bytes\n", + event->header.size); for (i = 0; i < event->header.size; i++) { if ((i & 15) == 0) { - dump_printf("."); - dump_printf_color(" %04x: ", color, i); + printf("."); + color_fprintf(stdout, color, " %04x: ", i); } - dump_printf_color(" %02x", color, raw_event[i]); + color_fprintf(stdout, color, " %02x", raw_event[i]); if (((i & 15) == 15) || i == event->header.size-1) { - dump_printf_color(" ", color); + color_fprintf(stdout, color, " "); for (j = 0; j < 15-(i & 15); j++) - dump_printf_color(" ", color); + color_fprintf(stdout, color, " "); for (j = i & ~15; j <= i; j++) { - dump_printf_color("%c", color, - isprint(raw_event[j]) ? - raw_event[j] : '.'); + color_fprintf(stdout, color, "%c", + isprint(raw_event[j]) ? + raw_event[j] : '.'); } - dump_printf_color("\n", color); + color_fprintf(stdout, color, "\n"); } } - dump_printf(".\n"); + printf(".\n"); } diff --git a/tools/perf/util/debug.h b/tools/perf/util/debug.h index 7b514082bba..ca35fd66b5d 100644 --- a/tools/perf/util/debug.h +++ b/tools/perf/util/debug.h @@ -35,4 +35,6 @@ int ui_helpline__show_help(const char *format, va_list ap); #include "ui/progress.h" #endif +void ui__warning(const char *format, ...) __attribute__((format(printf, 1, 2))); + #endif /* __PERF_DEBUG_H */ diff --git a/tools/perf/util/event.c b/tools/perf/util/event.c index dab9e754a28..2302ec051bb 100644 --- a/tools/perf/util/event.c +++ b/tools/perf/util/event.c @@ -7,7 +7,7 @@ #include "strlist.h" #include "thread.h" -const char *event__name[] = { +static const char *event__name[] = { [0] = "TOTAL", [PERF_RECORD_MMAP] = "MMAP", [PERF_RECORD_LOST] = "LOST", @@ -22,13 +22,31 @@ const char *event__name[] = { [PERF_RECORD_HEADER_EVENT_TYPE] = "EVENT_TYPE", [PERF_RECORD_HEADER_TRACING_DATA] = "TRACING_DATA", [PERF_RECORD_HEADER_BUILD_ID] = "BUILD_ID", + [PERF_RECORD_FINISHED_ROUND] = "FINISHED_ROUND", }; -static pid_t event__synthesize_comm(pid_t pid, int full, +const char *event__get_event_name(unsigned int id) +{ + if (id >= ARRAY_SIZE(event__name)) + return "INVALID"; + if (!event__name[id]) + return "UNKNOWN"; + return event__name[id]; +} + +static struct sample_data synth_sample = { + .pid = -1, + .tid = -1, + .time = -1, + .stream_id = -1, + .cpu = -1, + .period = 1, +}; + +static pid_t event__synthesize_comm(event_t *event, pid_t pid, int full, event__handler_t process, struct perf_session *session) { - event_t ev; char filename[PATH_MAX]; char bf[BUFSIZ]; FILE *fp; @@ -49,34 +67,39 @@ out_race: return 0; } - memset(&ev.comm, 0, sizeof(ev.comm)); - while (!ev.comm.comm[0] || !ev.comm.pid) { - if (fgets(bf, sizeof(bf), fp) == NULL) - goto out_failure; + memset(&event->comm, 0, sizeof(event->comm)); + + while (!event->comm.comm[0] || !event->comm.pid) { + if (fgets(bf, sizeof(bf), fp) == NULL) { + pr_warning("couldn't get COMM and pgid, malformed %s\n", filename); + goto out; + } if (memcmp(bf, "Name:", 5) == 0) { char *name = bf + 5; while (*name && isspace(*name)) ++name; size = strlen(name) - 1; - memcpy(ev.comm.comm, name, size++); + memcpy(event->comm.comm, name, size++); } else if (memcmp(bf, "Tgid:", 5) == 0) { char *tgids = bf + 5; while (*tgids && isspace(*tgids)) ++tgids; - tgid = ev.comm.pid = atoi(tgids); + tgid = event->comm.pid = atoi(tgids); } } - ev.comm.header.type = PERF_RECORD_COMM; + event->comm.header.type = PERF_RECORD_COMM; size = ALIGN(size, sizeof(u64)); - ev.comm.header.size = sizeof(ev.comm) - (sizeof(ev.comm.comm) - size); - + memset(event->comm.comm + size, 0, session->id_hdr_size); + event->comm.header.size = (sizeof(event->comm) - + (sizeof(event->comm.comm) - size) + + session->id_hdr_size); if (!full) { - ev.comm.tid = pid; + event->comm.tid = pid; - process(&ev, session); - goto out_fclose; + process(event, &synth_sample, session); + goto out; } snprintf(filename, sizeof(filename), "/proc/%d/task", pid); @@ -91,22 +114,19 @@ out_race: if (*end) continue; - ev.comm.tid = pid; + event->comm.tid = pid; - process(&ev, session); + process(event, &synth_sample, session); } - closedir(tasks); -out_fclose: + closedir(tasks); +out: fclose(fp); - return tgid; -out_failure: - pr_warning("couldn't get COMM and pgid, malformed %s\n", filename); - return -1; + return tgid; } -static int event__synthesize_mmap_events(pid_t pid, pid_t tgid, +static int event__synthesize_mmap_events(event_t *event, pid_t pid, pid_t tgid, event__handler_t process, struct perf_session *session) { @@ -124,29 +144,25 @@ static int event__synthesize_mmap_events(pid_t pid, pid_t tgid, return -1; } + event->header.type = PERF_RECORD_MMAP; + /* + * Just like the kernel, see __perf_event_mmap in kernel/perf_event.c + */ + event->header.misc = PERF_RECORD_MISC_USER; + while (1) { char bf[BUFSIZ], *pbf = bf; - event_t ev = { - .header = { - .type = PERF_RECORD_MMAP, - /* - * Just like the kernel, see __perf_event_mmap - * in kernel/perf_event.c - */ - .misc = PERF_RECORD_MISC_USER, - }, - }; int n; size_t size; if (fgets(bf, sizeof(bf), fp) == NULL) break; /* 00400000-0040c000 r-xp 00000000 fd:01 41038 /bin/cat */ - n = hex2u64(pbf, &ev.mmap.start); + n = hex2u64(pbf, &event->mmap.start); if (n < 0) continue; pbf += n + 1; - n = hex2u64(pbf, &ev.mmap.len); + n = hex2u64(pbf, &event->mmap.len); if (n < 0) continue; pbf += n + 3; @@ -161,19 +177,21 @@ static int event__synthesize_mmap_events(pid_t pid, pid_t tgid, continue; pbf += 3; - n = hex2u64(pbf, &ev.mmap.pgoff); + n = hex2u64(pbf, &event->mmap.pgoff); size = strlen(execname); execname[size - 1] = '\0'; /* Remove \n */ - memcpy(ev.mmap.filename, execname, size); + memcpy(event->mmap.filename, execname, size); size = ALIGN(size, sizeof(u64)); - ev.mmap.len -= ev.mmap.start; - ev.mmap.header.size = (sizeof(ev.mmap) - - (sizeof(ev.mmap.filename) - size)); - ev.mmap.pid = tgid; - ev.mmap.tid = pid; - - process(&ev, session); + event->mmap.len -= event->mmap.start; + event->mmap.header.size = (sizeof(event->mmap) - + (sizeof(event->mmap.filename) - size)); + memset(event->mmap.filename + size, 0, session->id_hdr_size); + event->mmap.header.size += session->id_hdr_size; + event->mmap.pid = tgid; + event->mmap.tid = pid; + + process(event, &synth_sample, session); } } @@ -187,20 +205,27 @@ int event__synthesize_modules(event__handler_t process, { struct rb_node *nd; struct map_groups *kmaps = &machine->kmaps; - u16 misc; + event_t *event = zalloc(sizeof(event->mmap) + session->id_hdr_size); + + if (event == NULL) { + pr_debug("Not enough memory synthesizing mmap event " + "for kernel modules\n"); + return -1; + } + + event->header.type = PERF_RECORD_MMAP; /* * kernel uses 0 for user space maps, see kernel/perf_event.c * __perf_event_mmap */ if (machine__is_host(machine)) - misc = PERF_RECORD_MISC_KERNEL; + event->header.misc = PERF_RECORD_MISC_KERNEL; else - misc = PERF_RECORD_MISC_GUEST_KERNEL; + event->header.misc = PERF_RECORD_MISC_GUEST_KERNEL; for (nd = rb_first(&kmaps->maps[MAP__FUNCTION]); nd; nd = rb_next(nd)) { - event_t ev; size_t size; struct map *pos = rb_entry(nd, struct map, rb_node); @@ -208,39 +233,78 @@ int event__synthesize_modules(event__handler_t process, continue; size = ALIGN(pos->dso->long_name_len + 1, sizeof(u64)); - memset(&ev, 0, sizeof(ev)); - ev.mmap.header.misc = misc; - ev.mmap.header.type = PERF_RECORD_MMAP; - ev.mmap.header.size = (sizeof(ev.mmap) - - (sizeof(ev.mmap.filename) - size)); - ev.mmap.start = pos->start; - ev.mmap.len = pos->end - pos->start; - ev.mmap.pid = machine->pid; - - memcpy(ev.mmap.filename, pos->dso->long_name, + event->mmap.header.type = PERF_RECORD_MMAP; + event->mmap.header.size = (sizeof(event->mmap) - + (sizeof(event->mmap.filename) - size)); + memset(event->mmap.filename + size, 0, session->id_hdr_size); + event->mmap.header.size += session->id_hdr_size; + event->mmap.start = pos->start; + event->mmap.len = pos->end - pos->start; + event->mmap.pid = machine->pid; + + memcpy(event->mmap.filename, pos->dso->long_name, pos->dso->long_name_len + 1); - process(&ev, session); + process(event, &synth_sample, session); } + free(event); return 0; } -int event__synthesize_thread(pid_t pid, event__handler_t process, - struct perf_session *session) +static int __event__synthesize_thread(event_t *comm_event, event_t *mmap_event, + pid_t pid, event__handler_t process, + struct perf_session *session) { - pid_t tgid = event__synthesize_comm(pid, 1, process, session); + pid_t tgid = event__synthesize_comm(comm_event, pid, 1, process, + session); if (tgid == -1) return -1; - return event__synthesize_mmap_events(pid, tgid, process, session); + return event__synthesize_mmap_events(mmap_event, pid, tgid, + process, session); +} + +int event__synthesize_thread(pid_t pid, event__handler_t process, + struct perf_session *session) +{ + event_t *comm_event, *mmap_event; + int err = -1; + + comm_event = malloc(sizeof(comm_event->comm) + session->id_hdr_size); + if (comm_event == NULL) + goto out; + + mmap_event = malloc(sizeof(mmap_event->mmap) + session->id_hdr_size); + if (mmap_event == NULL) + goto out_free_comm; + + err = __event__synthesize_thread(comm_event, mmap_event, pid, + process, session); + free(mmap_event); +out_free_comm: + free(comm_event); +out: + return err; } -void event__synthesize_threads(event__handler_t process, - struct perf_session *session) +int event__synthesize_threads(event__handler_t process, + struct perf_session *session) { DIR *proc; struct dirent dirent, *next; + event_t *comm_event, *mmap_event; + int err = -1; + + comm_event = malloc(sizeof(comm_event->comm) + session->id_hdr_size); + if (comm_event == NULL) + goto out; + + mmap_event = malloc(sizeof(mmap_event->mmap) + session->id_hdr_size); + if (mmap_event == NULL) + goto out_free_comm; proc = opendir("/proc"); + if (proc == NULL) + goto out_free_mmap; while (!readdir_r(proc, &dirent, &next) && next) { char *end; @@ -249,10 +313,18 @@ void event__synthesize_threads(event__handler_t process, if (*end) /* only interested in proper numerical dirents */ continue; - event__synthesize_thread(pid, process, session); + __event__synthesize_thread(comm_event, mmap_event, pid, + process, session); } closedir(proc); + err = 0; +out_free_mmap: + free(mmap_event); +out_free_comm: + free(comm_event); +out: + return err; } struct process_symbol_args { @@ -260,7 +332,8 @@ struct process_symbol_args { u64 start; }; -static int find_symbol_cb(void *arg, const char *name, char type, u64 start) +static int find_symbol_cb(void *arg, const char *name, char type, + u64 start, u64 end __used) { struct process_symbol_args *args = arg; @@ -286,18 +359,20 @@ int event__synthesize_kernel_mmap(event__handler_t process, char path[PATH_MAX]; char name_buff[PATH_MAX]; struct map *map; - - event_t ev = { - .header = { - .type = PERF_RECORD_MMAP, - }, - }; + int err; /* * We should get this from /sys/kernel/sections/.text, but till that is * available use this, and after it is use this as a fallback for older * kernels. */ struct process_symbol_args args = { .name = symbol_name, }; + event_t *event = zalloc(sizeof(event->mmap) + session->id_hdr_size); + + if (event == NULL) { + pr_debug("Not enough memory synthesizing mmap event " + "for kernel modules\n"); + return -1; + } mmap_name = machine__mmap_name(machine, name_buff, sizeof(name_buff)); if (machine__is_host(machine)) { @@ -305,10 +380,10 @@ int event__synthesize_kernel_mmap(event__handler_t process, * kernel uses PERF_RECORD_MISC_USER for user space maps, * see kernel/perf_event.c __perf_event_mmap */ - ev.header.misc = PERF_RECORD_MISC_KERNEL; + event->header.misc = PERF_RECORD_MISC_KERNEL; filename = "/proc/kallsyms"; } else { - ev.header.misc = PERF_RECORD_MISC_GUEST_KERNEL; + event->header.misc = PERF_RECORD_MISC_GUEST_KERNEL; if (machine__is_default_guest(machine)) filename = (char *) symbol_conf.default_guest_kallsyms; else { @@ -321,17 +396,21 @@ int event__synthesize_kernel_mmap(event__handler_t process, return -ENOENT; map = machine->vmlinux_maps[MAP__FUNCTION]; - size = snprintf(ev.mmap.filename, sizeof(ev.mmap.filename), + size = snprintf(event->mmap.filename, sizeof(event->mmap.filename), "%s%s", mmap_name, symbol_name) + 1; size = ALIGN(size, sizeof(u64)); - ev.mmap.header.size = (sizeof(ev.mmap) - - (sizeof(ev.mmap.filename) - size)); - ev.mmap.pgoff = args.start; - ev.mmap.start = map->start; - ev.mmap.len = map->end - ev.mmap.start; - ev.mmap.pid = machine->pid; - - return process(&ev, session); + event->mmap.header.type = PERF_RECORD_MMAP; + event->mmap.header.size = (sizeof(event->mmap) - + (sizeof(event->mmap.filename) - size) + session->id_hdr_size); + event->mmap.pgoff = args.start; + event->mmap.start = map->start; + event->mmap.len = map->end - event->mmap.start; + event->mmap.pid = machine->pid; + + err = process(event, &synth_sample, session); + free(event); + + return err; } static void thread__comm_adjust(struct thread *self, struct hists *hists) @@ -361,7 +440,8 @@ static int thread__set_comm_adjust(struct thread *self, const char *comm, return 0; } -int event__process_comm(event_t *self, struct perf_session *session) +int event__process_comm(event_t *self, struct sample_data *sample __used, + struct perf_session *session) { struct thread *thread = perf_session__findnew(session, self->comm.tid); @@ -376,7 +456,8 @@ int event__process_comm(event_t *self, struct perf_session *session) return 0; } -int event__process_lost(event_t *self, struct perf_session *session) +int event__process_lost(event_t *self, struct sample_data *sample __used, + struct perf_session *session) { dump_printf(": id:%Ld: lost:%Ld\n", self->lost.id, self->lost.lost); session->hists.stats.total_lost += self->lost.lost; @@ -392,7 +473,7 @@ static void event_set_kernel_mmap_len(struct map **maps, event_t *self) * a zero sized synthesized MMAP event for the kernel. */ if (maps[MAP__FUNCTION]->end == 0) - maps[MAP__FUNCTION]->end = ~0UL; + maps[MAP__FUNCTION]->end = ~0ULL; } static int event__process_kernel_mmap(event_t *self, @@ -485,7 +566,8 @@ out_problem: return -1; } -int event__process_mmap(event_t *self, struct perf_session *session) +int event__process_mmap(event_t *self, struct sample_data *sample __used, + struct perf_session *session) { struct machine *machine; struct thread *thread; @@ -526,7 +608,8 @@ out_problem: return 0; } -int event__process_task(event_t *self, struct perf_session *session) +int event__process_task(event_t *self, struct sample_data *sample __used, + struct perf_session *session) { struct thread *thread = perf_session__findnew(session, self->fork.tid); struct thread *parent = perf_session__findnew(session, self->fork.ptid); @@ -548,18 +631,19 @@ int event__process_task(event_t *self, struct perf_session *session) return 0; } -int event__process(event_t *event, struct perf_session *session) +int event__process(event_t *event, struct sample_data *sample, + struct perf_session *session) { switch (event->header.type) { case PERF_RECORD_COMM: - event__process_comm(event, session); + event__process_comm(event, sample, session); break; case PERF_RECORD_MMAP: - event__process_mmap(event, session); + event__process_mmap(event, sample, session); break; case PERF_RECORD_FORK: case PERF_RECORD_EXIT: - event__process_task(event, session); + event__process_task(event, sample, session); break; default: break; @@ -674,32 +758,8 @@ int event__preprocess_sample(const event_t *self, struct perf_session *session, symbol_filter_t filter) { u8 cpumode = self->header.misc & PERF_RECORD_MISC_CPUMODE_MASK; - struct thread *thread; - - event__parse_sample(self, session->sample_type, data); - - dump_printf("(IP, %d): %d/%d: %#Lx period: %Ld cpu:%d\n", - self->header.misc, data->pid, data->tid, data->ip, - data->period, data->cpu); - - if (session->sample_type & PERF_SAMPLE_CALLCHAIN) { - unsigned int i; - - dump_printf("... chain: nr:%Lu\n", data->callchain->nr); + struct thread *thread = perf_session__findnew(session, self->ip.pid); - if (!ip_callchain__valid(data->callchain, self)) { - pr_debug("call-chain problem with event, " - "skipping it.\n"); - goto out_filtered; - } - - if (dump_trace) { - for (i = 0; i < data->callchain->nr; i++) - dump_printf("..... %2d: %016Lx\n", - i, data->callchain->ips[i]); - } - } - thread = perf_session__findnew(session, self->ip.pid); if (thread == NULL) return -1; @@ -766,9 +826,65 @@ out_filtered: return 0; } -int event__parse_sample(const event_t *event, u64 type, struct sample_data *data) +static int event__parse_id_sample(const event_t *event, + struct perf_session *session, + struct sample_data *sample) { - const u64 *array = event->sample.array; + const u64 *array; + u64 type; + + sample->cpu = sample->pid = sample->tid = -1; + sample->stream_id = sample->id = sample->time = -1ULL; + + if (!session->sample_id_all) + return 0; + + array = event->sample.array; + array += ((event->header.size - + sizeof(event->header)) / sizeof(u64)) - 1; + type = session->sample_type; + + if (type & PERF_SAMPLE_CPU) { + u32 *p = (u32 *)array; + sample->cpu = *p; + array--; + } + + if (type & PERF_SAMPLE_STREAM_ID) { + sample->stream_id = *array; + array--; + } + + if (type & PERF_SAMPLE_ID) { + sample->id = *array; + array--; + } + + if (type & PERF_SAMPLE_TIME) { + sample->time = *array; + array--; + } + + if (type & PERF_SAMPLE_TID) { + u32 *p = (u32 *)array; + sample->pid = p[0]; + sample->tid = p[1]; + } + + return 0; +} + +int event__parse_sample(const event_t *event, struct perf_session *session, + struct sample_data *data) +{ + const u64 *array; + u64 type; + + if (event->header.type != PERF_RECORD_SAMPLE) + return event__parse_id_sample(event, session, data); + + array = event->sample.array; + type = session->sample_type; if (type & PERF_SAMPLE_IP) { data->ip = event->ip.ip; diff --git a/tools/perf/util/event.h b/tools/perf/util/event.h index 8e790dae702..2b7e91902f1 100644 --- a/tools/perf/util/event.h +++ b/tools/perf/util/event.h @@ -85,6 +85,7 @@ struct build_id_event { }; enum perf_user_event_type { /* above any possible kernel type */ + PERF_RECORD_USER_TYPE_START = 64, PERF_RECORD_HEADER_ATTR = 64, PERF_RECORD_HEADER_EVENT_TYPE = 65, PERF_RECORD_HEADER_TRACING_DATA = 66, @@ -135,12 +136,15 @@ void event__print_totals(void); struct perf_session; -typedef int (*event__handler_t)(event_t *event, struct perf_session *session); +typedef int (*event__handler_synth_t)(event_t *event, + struct perf_session *session); +typedef int (*event__handler_t)(event_t *event, struct sample_data *sample, + struct perf_session *session); int event__synthesize_thread(pid_t pid, event__handler_t process, struct perf_session *session); -void event__synthesize_threads(event__handler_t process, - struct perf_session *session); +int event__synthesize_threads(event__handler_t process, + struct perf_session *session); int event__synthesize_kernel_mmap(event__handler_t process, struct perf_session *session, struct machine *machine, @@ -150,18 +154,24 @@ int event__synthesize_modules(event__handler_t process, struct perf_session *session, struct machine *machine); -int event__process_comm(event_t *self, struct perf_session *session); -int event__process_lost(event_t *self, struct perf_session *session); -int event__process_mmap(event_t *self, struct perf_session *session); -int event__process_task(event_t *self, struct perf_session *session); -int event__process(event_t *event, struct perf_session *session); +int event__process_comm(event_t *self, struct sample_data *sample, + struct perf_session *session); +int event__process_lost(event_t *self, struct sample_data *sample, + struct perf_session *session); +int event__process_mmap(event_t *self, struct sample_data *sample, + struct perf_session *session); +int event__process_task(event_t *self, struct sample_data *sample, + struct perf_session *session); +int event__process(event_t *event, struct sample_data *sample, + struct perf_session *session); struct addr_location; int event__preprocess_sample(const event_t *self, struct perf_session *session, struct addr_location *al, struct sample_data *data, symbol_filter_t filter); -int event__parse_sample(const event_t *event, u64 type, struct sample_data *data); +int event__parse_sample(const event_t *event, struct perf_session *session, + struct sample_data *sample); -extern const char *event__name[]; +const char *event__get_event_name(unsigned int id); #endif /* __PERF_RECORD_H */ diff --git a/tools/perf/util/evsel.c b/tools/perf/util/evsel.c new file mode 100644 index 00000000000..f5cfed60af9 --- /dev/null +++ b/tools/perf/util/evsel.c @@ -0,0 +1,201 @@ +#include "evsel.h" +#include "../perf.h" +#include "util.h" +#include "cpumap.h" +#include "thread.h" + +#define FD(e, x, y) (*(int *)xyarray__entry(e->fd, x, y)) + +struct perf_evsel *perf_evsel__new(struct perf_event_attr *attr, int idx) +{ + struct perf_evsel *evsel = zalloc(sizeof(*evsel)); + + if (evsel != NULL) { + evsel->idx = idx; + evsel->attr = *attr; + INIT_LIST_HEAD(&evsel->node); + } + + return evsel; +} + +int perf_evsel__alloc_fd(struct perf_evsel *evsel, int ncpus, int nthreads) +{ + evsel->fd = xyarray__new(ncpus, nthreads, sizeof(int)); + return evsel->fd != NULL ? 0 : -ENOMEM; +} + +int perf_evsel__alloc_counts(struct perf_evsel *evsel, int ncpus) +{ + evsel->counts = zalloc((sizeof(*evsel->counts) + + (ncpus * sizeof(struct perf_counts_values)))); + return evsel->counts != NULL ? 0 : -ENOMEM; +} + +void perf_evsel__free_fd(struct perf_evsel *evsel) +{ + xyarray__delete(evsel->fd); + evsel->fd = NULL; +} + +void perf_evsel__close_fd(struct perf_evsel *evsel, int ncpus, int nthreads) +{ + int cpu, thread; + + for (cpu = 0; cpu < ncpus; cpu++) + for (thread = 0; thread < nthreads; ++thread) { + close(FD(evsel, cpu, thread)); + FD(evsel, cpu, thread) = -1; + } +} + +void perf_evsel__delete(struct perf_evsel *evsel) +{ + assert(list_empty(&evsel->node)); + xyarray__delete(evsel->fd); + free(evsel); +} + +int __perf_evsel__read_on_cpu(struct perf_evsel *evsel, + int cpu, int thread, bool scale) +{ + struct perf_counts_values count; + size_t nv = scale ? 3 : 1; + + if (FD(evsel, cpu, thread) < 0) + return -EINVAL; + + if (evsel->counts == NULL && perf_evsel__alloc_counts(evsel, cpu + 1) < 0) + return -ENOMEM; + + if (readn(FD(evsel, cpu, thread), &count, nv * sizeof(u64)) < 0) + return -errno; + + if (scale) { + if (count.run == 0) + count.val = 0; + else if (count.run < count.ena) + count.val = (u64)((double)count.val * count.ena / count.run + 0.5); + } else + count.ena = count.run = 0; + + evsel->counts->cpu[cpu] = count; + return 0; +} + +int __perf_evsel__read(struct perf_evsel *evsel, + int ncpus, int nthreads, bool scale) +{ + size_t nv = scale ? 3 : 1; + int cpu, thread; + struct perf_counts_values *aggr = &evsel->counts->aggr, count; + + aggr->val = 0; + + for (cpu = 0; cpu < ncpus; cpu++) { + for (thread = 0; thread < nthreads; thread++) { + if (FD(evsel, cpu, thread) < 0) + continue; + + if (readn(FD(evsel, cpu, thread), + &count, nv * sizeof(u64)) < 0) + return -errno; + + aggr->val += count.val; + if (scale) { + aggr->ena += count.ena; + aggr->run += count.run; + } + } + } + + evsel->counts->scaled = 0; + if (scale) { + if (aggr->run == 0) { + evsel->counts->scaled = -1; + aggr->val = 0; + return 0; + } + + if (aggr->run < aggr->ena) { + evsel->counts->scaled = 1; + aggr->val = (u64)((double)aggr->val * aggr->ena / aggr->run + 0.5); + } + } else + aggr->ena = aggr->run = 0; + + return 0; +} + +static int __perf_evsel__open(struct perf_evsel *evsel, struct cpu_map *cpus, + struct thread_map *threads) +{ + int cpu, thread; + + if (evsel->fd == NULL && + perf_evsel__alloc_fd(evsel, cpus->nr, threads->nr) < 0) + return -1; + + for (cpu = 0; cpu < cpus->nr; cpu++) { + for (thread = 0; thread < threads->nr; thread++) { + FD(evsel, cpu, thread) = sys_perf_event_open(&evsel->attr, + threads->map[thread], + cpus->map[cpu], -1, 0); + if (FD(evsel, cpu, thread) < 0) + goto out_close; + } + } + + return 0; + +out_close: + do { + while (--thread >= 0) { + close(FD(evsel, cpu, thread)); + FD(evsel, cpu, thread) = -1; + } + thread = threads->nr; + } while (--cpu >= 0); + return -1; +} + +static struct { + struct cpu_map map; + int cpus[1]; +} empty_cpu_map = { + .map.nr = 1, + .cpus = { -1, }, +}; + +static struct { + struct thread_map map; + int threads[1]; +} empty_thread_map = { + .map.nr = 1, + .threads = { -1, }, +}; + +int perf_evsel__open(struct perf_evsel *evsel, + struct cpu_map *cpus, struct thread_map *threads) +{ + + if (cpus == NULL) { + /* Work around old compiler warnings about strict aliasing */ + cpus = &empty_cpu_map.map; + } + + if (threads == NULL) + threads = &empty_thread_map.map; + + return __perf_evsel__open(evsel, cpus, threads); +} + +int perf_evsel__open_per_cpu(struct perf_evsel *evsel, struct cpu_map *cpus) +{ + return __perf_evsel__open(evsel, cpus, &empty_thread_map.map); +} + +int perf_evsel__open_per_thread(struct perf_evsel *evsel, struct thread_map *threads) +{ + return __perf_evsel__open(evsel, &empty_cpu_map.map, threads); +} diff --git a/tools/perf/util/evsel.h b/tools/perf/util/evsel.h new file mode 100644 index 00000000000..b2d755fe88a --- /dev/null +++ b/tools/perf/util/evsel.h @@ -0,0 +1,115 @@ +#ifndef __PERF_EVSEL_H +#define __PERF_EVSEL_H 1 + +#include <linux/list.h> +#include <stdbool.h> +#include "../../../include/linux/perf_event.h" +#include "types.h" +#include "xyarray.h" + +struct perf_counts_values { + union { + struct { + u64 val; + u64 ena; + u64 run; + }; + u64 values[3]; + }; +}; + +struct perf_counts { + s8 scaled; + struct perf_counts_values aggr; + struct perf_counts_values cpu[]; +}; + +struct perf_evsel { + struct list_head node; + struct perf_event_attr attr; + char *filter; + struct xyarray *fd; + struct perf_counts *counts; + int idx; + void *priv; +}; + +struct cpu_map; +struct thread_map; + +struct perf_evsel *perf_evsel__new(struct perf_event_attr *attr, int idx); +void perf_evsel__delete(struct perf_evsel *evsel); + +int perf_evsel__alloc_fd(struct perf_evsel *evsel, int ncpus, int nthreads); +int perf_evsel__alloc_counts(struct perf_evsel *evsel, int ncpus); +void perf_evsel__free_fd(struct perf_evsel *evsel); +void perf_evsel__close_fd(struct perf_evsel *evsel, int ncpus, int nthreads); + +int perf_evsel__open_per_cpu(struct perf_evsel *evsel, struct cpu_map *cpus); +int perf_evsel__open_per_thread(struct perf_evsel *evsel, struct thread_map *threads); +int perf_evsel__open(struct perf_evsel *evsel, + struct cpu_map *cpus, struct thread_map *threads); + +#define perf_evsel__match(evsel, t, c) \ + (evsel->attr.type == PERF_TYPE_##t && \ + evsel->attr.config == PERF_COUNT_##c) + +int __perf_evsel__read_on_cpu(struct perf_evsel *evsel, + int cpu, int thread, bool scale); + +/** + * perf_evsel__read_on_cpu - Read out the results on a CPU and thread + * + * @evsel - event selector to read value + * @cpu - CPU of interest + * @thread - thread of interest + */ +static inline int perf_evsel__read_on_cpu(struct perf_evsel *evsel, + int cpu, int thread) +{ + return __perf_evsel__read_on_cpu(evsel, cpu, thread, false); +} + +/** + * perf_evsel__read_on_cpu_scaled - Read out the results on a CPU and thread, scaled + * + * @evsel - event selector to read value + * @cpu - CPU of interest + * @thread - thread of interest + */ +static inline int perf_evsel__read_on_cpu_scaled(struct perf_evsel *evsel, + int cpu, int thread) +{ + return __perf_evsel__read_on_cpu(evsel, cpu, thread, true); +} + +int __perf_evsel__read(struct perf_evsel *evsel, int ncpus, int nthreads, + bool scale); + +/** + * perf_evsel__read - Read the aggregate results on all CPUs + * + * @evsel - event selector to read value + * @ncpus - Number of cpus affected, from zero + * @nthreads - Number of threads affected, from zero + */ +static inline int perf_evsel__read(struct perf_evsel *evsel, + int ncpus, int nthreads) +{ + return __perf_evsel__read(evsel, ncpus, nthreads, false); +} + +/** + * perf_evsel__read_scaled - Read the aggregate results on all CPUs, scaled + * + * @evsel - event selector to read value + * @ncpus - Number of cpus affected, from zero + * @nthreads - Number of threads affected, from zero + */ +static inline int perf_evsel__read_scaled(struct perf_evsel *evsel, + int ncpus, int nthreads) +{ + return __perf_evsel__read(evsel, ncpus, nthreads, true); +} + +#endif /* __PERF_EVSEL_H */ diff --git a/tools/perf/util/header.c b/tools/perf/util/header.c index 7cba0551a56..989fa2dee2f 100644 --- a/tools/perf/util/header.c +++ b/tools/perf/util/header.c @@ -152,6 +152,11 @@ void perf_header__set_feat(struct perf_header *self, int feat) set_bit(feat, self->adds_features); } +void perf_header__clear_feat(struct perf_header *self, int feat) +{ + clear_bit(feat, self->adds_features); +} + bool perf_header__has_feat(const struct perf_header *self, int feat) { return test_bit(feat, self->adds_features); @@ -433,8 +438,10 @@ static int perf_header__adds_write(struct perf_header *self, int fd) int idx = 0, err; session = container_of(self, struct perf_session, header); - if (perf_session__read_build_ids(session, true)) - perf_header__set_feat(self, HEADER_BUILD_ID); + + if (perf_header__has_feat(self, HEADER_BUILD_ID && + !perf_session__read_build_ids(session, true))) + perf_header__clear_feat(self, HEADER_BUILD_ID); nr_sections = bitmap_weight(self->adds_features, HEADER_FEAT_BITS); if (!nr_sections) @@ -456,7 +463,7 @@ static int perf_header__adds_write(struct perf_header *self, int fd) /* Write trace info */ trace_sec->offset = lseek(fd, 0, SEEK_CUR); - read_tracing_data(fd, attrs, nr_counters); + read_tracing_data(fd, &evsel_list); trace_sec->size = lseek(fd, 0, SEEK_CUR) - trace_sec->offset; } @@ -599,7 +606,7 @@ int perf_header__write(struct perf_header *self, int fd, bool at_exit) static int perf_header__getbuffer64(struct perf_header *self, int fd, void *buf, size_t size) { - if (do_read(fd, buf, size) <= 0) + if (readn(fd, buf, size) <= 0) return -1; if (self->needs_swap) @@ -655,7 +662,7 @@ int perf_file_header__read(struct perf_file_header *self, { lseek(fd, 0, SEEK_SET); - if (do_read(fd, self, sizeof(*self)) <= 0 || + if (readn(fd, self, sizeof(*self)) <= 0 || memcmp(&self->magic, __perf_magic, sizeof(self->magic))) return -1; @@ -816,7 +823,7 @@ static int perf_file_header__read_pipe(struct perf_pipe_file_header *self, struct perf_header *ph, int fd, bool repipe) { - if (do_read(fd, self, sizeof(*self)) <= 0 || + if (readn(fd, self, sizeof(*self)) <= 0 || memcmp(&self->magic, __perf_magic, sizeof(self->magic))) return -1; @@ -941,6 +948,24 @@ u64 perf_header__sample_type(struct perf_header *header) return type; } +bool perf_header__sample_id_all(const struct perf_header *header) +{ + bool value = false, first = true; + int i; + + for (i = 0; i < header->attrs; i++) { + struct perf_header_attr *attr = header->attr[i]; + + if (first) { + value = attr->attr.sample_id_all; + first = false; + } else if (value != attr->attr.sample_id_all) + die("non matching sample_id_all"); + } + + return value; +} + struct perf_event_attr * perf_header__find_attr(u64 id, struct perf_header *header) { @@ -987,21 +1012,23 @@ int event__synthesize_attr(struct perf_event_attr *attr, u16 ids, u64 *id, ev = malloc(size); + if (ev == NULL) + return -ENOMEM; + ev->attr.attr = *attr; memcpy(ev->attr.id, id, ids * sizeof(u64)); ev->attr.header.type = PERF_RECORD_HEADER_ATTR; ev->attr.header.size = size; - err = process(ev, session); + err = process(ev, NULL, session); free(ev); return err; } -int event__synthesize_attrs(struct perf_header *self, - event__handler_t process, +int event__synthesize_attrs(struct perf_header *self, event__handler_t process, struct perf_session *session) { struct perf_header_attr *attr; @@ -1071,7 +1098,7 @@ int event__synthesize_event_type(u64 event_id, char *name, ev.event_type.header.size = sizeof(ev.event_type) - (sizeof(ev.event_type.event_type.name) - size); - err = process(&ev, session); + err = process(&ev, NULL, session); return err; } @@ -1106,8 +1133,7 @@ int event__process_event_type(event_t *self, return 0; } -int event__synthesize_tracing_data(int fd, struct perf_event_attr *pattrs, - int nb_events, +int event__synthesize_tracing_data(int fd, struct list_head *pattrs, event__handler_t process, struct perf_session *session __unused) { @@ -1118,7 +1144,7 @@ int event__synthesize_tracing_data(int fd, struct perf_event_attr *pattrs, memset(&ev, 0, sizeof(ev)); ev.tracing_data.header.type = PERF_RECORD_HEADER_TRACING_DATA; - size = read_tracing_data_size(fd, pattrs, nb_events); + size = read_tracing_data_size(fd, pattrs); if (size <= 0) return size; aligned_size = ALIGN(size, sizeof(u64)); @@ -1126,9 +1152,9 @@ int event__synthesize_tracing_data(int fd, struct perf_event_attr *pattrs, ev.tracing_data.header.size = sizeof(ev.tracing_data); ev.tracing_data.size = aligned_size; - process(&ev, session); + process(&ev, NULL, session); - err = read_tracing_data(fd, pattrs, nb_events); + err = read_tracing_data(fd, pattrs); write_padded(fd, NULL, 0, padding); return aligned_size; @@ -1186,7 +1212,7 @@ int event__synthesize_build_id(struct dso *pos, u16 misc, ev.build_id.header.size = sizeof(ev.build_id) + len; memcpy(&ev.build_id.filename, pos->long_name, pos->long_name_len); - err = process(&ev, session); + err = process(&ev, NULL, session); return err; } diff --git a/tools/perf/util/header.h b/tools/perf/util/header.h index 402ac2454cf..33f16be7b72 100644 --- a/tools/perf/util/header.h +++ b/tools/perf/util/header.h @@ -81,9 +81,11 @@ void perf_header_attr__delete(struct perf_header_attr *self); int perf_header_attr__add_id(struct perf_header_attr *self, u64 id); u64 perf_header__sample_type(struct perf_header *header); +bool perf_header__sample_id_all(const struct perf_header *header); struct perf_event_attr * perf_header__find_attr(u64 id, struct perf_header *header); void perf_header__set_feat(struct perf_header *self, int feat); +void perf_header__clear_feat(struct perf_header *self, int feat); bool perf_header__has_feat(const struct perf_header *self, int feat); int perf_header__process_sections(struct perf_header *self, int fd, @@ -111,8 +113,7 @@ int event__synthesize_event_types(event__handler_t process, int event__process_event_type(event_t *self, struct perf_session *session); -int event__synthesize_tracing_data(int fd, struct perf_event_attr *pattrs, - int nb_events, +int event__synthesize_tracing_data(int fd, struct list_head *pattrs, event__handler_t process, struct perf_session *session); int event__process_tracing_data(event_t *self, diff --git a/tools/perf/util/hist.c b/tools/perf/util/hist.c index 76bcc35cf9b..c749ba6136a 100644 --- a/tools/perf/util/hist.c +++ b/tools/perf/util/hist.c @@ -1092,6 +1092,12 @@ int hist_entry__annotate(struct hist_entry *self, struct list_head *head, FILE *file; int err = 0; u64 len; + char symfs_filename[PATH_MAX]; + + if (filename) { + snprintf(symfs_filename, sizeof(symfs_filename), "%s%s", + symbol_conf.symfs, filename); + } if (filename == NULL) { if (dso->has_build_id) { @@ -1100,9 +1106,9 @@ int hist_entry__annotate(struct hist_entry *self, struct list_head *head, return -ENOMEM; } goto fallback; - } else if (readlink(filename, command, sizeof(command)) < 0 || + } else if (readlink(symfs_filename, command, sizeof(command)) < 0 || strstr(command, "[kernel.kallsyms]") || - access(filename, R_OK)) { + access(symfs_filename, R_OK)) { free(filename); fallback: /* @@ -1111,6 +1117,8 @@ fallback: * DSO is the same as when 'perf record' ran. */ filename = dso->long_name; + snprintf(symfs_filename, sizeof(symfs_filename), "%s%s", + symbol_conf.symfs, filename); free_filename = false; } @@ -1137,7 +1145,7 @@ fallback: "objdump --start-address=0x%016Lx --stop-address=0x%016Lx -dS -C %s|grep -v %s|expand", map__rip_2objdump(map, sym->start), map__rip_2objdump(map, sym->end), - filename, filename); + symfs_filename, filename); pr_debug("Executing: %s\n", command); @@ -1168,10 +1176,13 @@ size_t hists__fprintf_nr_events(struct hists *self, FILE *fp) size_t ret = 0; for (i = 0; i < PERF_RECORD_HEADER_MAX; ++i) { - if (!event__name[i]) + const char *name = event__get_event_name(i); + + if (!strcmp(name, "UNKNOWN")) continue; - ret += fprintf(fp, "%10s events: %10d\n", - event__name[i], self->stats.nr_events[i]); + + ret += fprintf(fp, "%16s events: %10d\n", name, + self->stats.nr_events[i]); } return ret; diff --git a/tools/perf/util/hist.h b/tools/perf/util/hist.h index 587d375d343..ee789856a8c 100644 --- a/tools/perf/util/hist.h +++ b/tools/perf/util/hist.h @@ -52,8 +52,10 @@ struct sym_priv { struct events_stats { u64 total_period; u64 total_lost; + u64 total_invalid_chains; u32 nr_events[PERF_RECORD_HEADER_MAX]; u32 nr_unknown_events; + u32 nr_invalid_chains; }; enum hist_column { diff --git a/tools/perf/util/include/asm/cpufeature.h b/tools/perf/util/include/asm/cpufeature.h new file mode 100644 index 00000000000..acffd5e4d1d --- /dev/null +++ b/tools/perf/util/include/asm/cpufeature.h @@ -0,0 +1,9 @@ + +#ifndef PERF_CPUFEATURE_H +#define PERF_CPUFEATURE_H + +/* cpufeature.h ... dummy header file for including arch/x86/lib/memcpy_64.S */ + +#define X86_FEATURE_REP_GOOD 0 + +#endif /* PERF_CPUFEATURE_H */ diff --git a/tools/perf/util/include/asm/dwarf2.h b/tools/perf/util/include/asm/dwarf2.h new file mode 100644 index 00000000000..bb4198e7837 --- /dev/null +++ b/tools/perf/util/include/asm/dwarf2.h @@ -0,0 +1,11 @@ + +#ifndef PERF_DWARF2_H +#define PERF_DWARF2_H + +/* dwarf2.h ... dummy header file for including arch/x86/lib/memcpy_64.S */ + +#define CFI_STARTPROC +#define CFI_ENDPROC + +#endif /* PERF_DWARF2_H */ + diff --git a/tools/perf/util/include/linux/bitops.h b/tools/perf/util/include/linux/bitops.h index bb4ac2e0538..8be0b968ca0 100644 --- a/tools/perf/util/include/linux/bitops.h +++ b/tools/perf/util/include/linux/bitops.h @@ -13,6 +13,11 @@ static inline void set_bit(int nr, unsigned long *addr) addr[nr / BITS_PER_LONG] |= 1UL << (nr % BITS_PER_LONG); } +static inline void clear_bit(int nr, unsigned long *addr) +{ + addr[nr / BITS_PER_LONG] &= ~(1UL << (nr % BITS_PER_LONG)); +} + static __always_inline int test_bit(unsigned int nr, const unsigned long *addr) { return ((1UL << (nr % BITS_PER_LONG)) & diff --git a/tools/perf/util/include/linux/linkage.h b/tools/perf/util/include/linux/linkage.h new file mode 100644 index 00000000000..06387cffe12 --- /dev/null +++ b/tools/perf/util/include/linux/linkage.h @@ -0,0 +1,13 @@ + +#ifndef PERF_LINUX_LINKAGE_H_ +#define PERF_LINUX_LINKAGE_H_ + +/* linkage.h ... for including arch/x86/lib/memcpy_64.S */ + +#define ENTRY(name) \ + .globl name; \ + name: + +#define ENDPROC(name) + +#endif /* PERF_LINUX_LINKAGE_H_ */ diff --git a/tools/perf/util/parse-events.c b/tools/perf/util/parse-events.c index 4af5bd59cfd..5cb6f4bde90 100644 --- a/tools/perf/util/parse-events.c +++ b/tools/perf/util/parse-events.c @@ -1,6 +1,7 @@ #include "../../../include/linux/hw_breakpoint.h" #include "util.h" #include "../perf.h" +#include "evsel.h" #include "parse-options.h" #include "parse-events.h" #include "exec_cmd.h" @@ -12,8 +13,7 @@ int nr_counters; -struct perf_event_attr attrs[MAX_COUNTERS]; -char *filters[MAX_COUNTERS]; +LIST_HEAD(evsel_list); struct event_symbol { u8 type; @@ -266,10 +266,10 @@ static char *event_cache_name(u8 cache_type, u8 cache_op, u8 cache_result) return name; } -const char *event_name(int counter) +const char *event_name(struct perf_evsel *evsel) { - u64 config = attrs[counter].config; - int type = attrs[counter].type; + u64 config = evsel->attr.config; + int type = evsel->attr.type; return __event_name(type, config); } @@ -434,7 +434,7 @@ parse_single_tracepoint_event(char *sys_name, id = atoll(id_buf); attr->config = id; attr->type = PERF_TYPE_TRACEPOINT; - *strp = evt_name + evt_length; + *strp += strlen(sys_name) + evt_length + 1; /* + 1 for the ':' */ attr->sample_type |= PERF_SAMPLE_RAW; attr->sample_type |= PERF_SAMPLE_TIME; @@ -490,12 +490,37 @@ parse_multiple_tracepoint_event(char *sys_name, const char *evt_exp, return EVT_HANDLED_ALL; } +static int store_event_type(const char *orgname) +{ + char filename[PATH_MAX], *c; + FILE *file; + int id, n; + + sprintf(filename, "%s/", debugfs_path); + strncat(filename, orgname, strlen(orgname)); + strcat(filename, "/id"); + + c = strchr(filename, ':'); + if (c) + *c = '/'; + + file = fopen(filename, "r"); + if (!file) + return 0; + n = fscanf(file, "%i", &id); + fclose(file); + if (n < 1) { + pr_err("cannot store event ID\n"); + return -EINVAL; + } + return perf_header__push_event(id, orgname); +} static enum event_result parse_tracepoint_event(const char **strp, struct perf_event_attr *attr) { const char *evt_name; - char *flags; + char *flags = NULL, *comma_loc; char sys_name[MAX_EVENT_LENGTH]; unsigned int sys_length, evt_length; @@ -514,6 +539,11 @@ static enum event_result parse_tracepoint_event(const char **strp, sys_name[sys_length] = '\0'; evt_name = evt_name + 1; + comma_loc = strchr(evt_name, ','); + if (comma_loc) { + /* take the event name up to the comma */ + evt_name = strndup(evt_name, comma_loc - evt_name); + } flags = strchr(evt_name, ':'); if (flags) { /* split it out: */ @@ -524,14 +554,17 @@ static enum event_result parse_tracepoint_event(const char **strp, evt_length = strlen(evt_name); if (evt_length >= MAX_EVENT_LENGTH) return EVT_FAILED; - if (strpbrk(evt_name, "*?")) { - *strp = evt_name + evt_length; + *strp += strlen(sys_name) + evt_length; return parse_multiple_tracepoint_event(sys_name, evt_name, flags); - } else + } else { + if (store_event_type(evt_name) < 0) + return EVT_FAILED; + return parse_single_tracepoint_event(sys_name, evt_name, evt_length, attr, strp); + } } static enum event_result @@ -774,45 +807,12 @@ modifier: return ret; } -static int store_event_type(const char *orgname) -{ - char filename[PATH_MAX], *c; - FILE *file; - int id, n; - - sprintf(filename, "%s/", debugfs_path); - strncat(filename, orgname, strlen(orgname)); - strcat(filename, "/id"); - - c = strchr(filename, ':'); - if (c) - *c = '/'; - - file = fopen(filename, "r"); - if (!file) - return 0; - n = fscanf(file, "%i", &id); - fclose(file); - if (n < 1) { - pr_err("cannot store event ID\n"); - return -EINVAL; - } - return perf_header__push_event(id, orgname); -} - int parse_events(const struct option *opt __used, const char *str, int unset __used) { struct perf_event_attr attr; enum event_result ret; - if (strchr(str, ':')) - if (store_event_type(str) < 0) - return -1; - for (;;) { - if (nr_counters == MAX_COUNTERS) - return -1; - memset(&attr, 0, sizeof(attr)); ret = parse_event_symbols(&str, &attr); if (ret == EVT_FAILED) @@ -822,8 +822,13 @@ int parse_events(const struct option *opt __used, const char *str, int unset __u return -1; if (ret != EVT_HANDLED_ALL) { - attrs[nr_counters] = attr; - nr_counters++; + struct perf_evsel *evsel; + evsel = perf_evsel__new(&attr, + nr_counters); + if (evsel == NULL) + return -1; + list_add_tail(&evsel->node, &evsel_list); + ++nr_counters; } if (*str == 0) @@ -840,21 +845,22 @@ int parse_events(const struct option *opt __used, const char *str, int unset __u int parse_filter(const struct option *opt __used, const char *str, int unset __used) { - int i = nr_counters - 1; - int len = strlen(str); + struct perf_evsel *last = NULL; + + if (!list_empty(&evsel_list)) + last = list_entry(evsel_list.prev, struct perf_evsel, node); - if (i < 0 || attrs[i].type != PERF_TYPE_TRACEPOINT) { + if (last == NULL || last->attr.type != PERF_TYPE_TRACEPOINT) { fprintf(stderr, "-F option should follow a -e tracepoint option\n"); return -1; } - filters[i] = malloc(len + 1); - if (!filters[i]) { + last->filter = strdup(str); + if (last->filter == NULL) { fprintf(stderr, "not enough memory to hold filter string\n"); return -1; } - strcpy(filters[i], str); return 0; } @@ -906,6 +912,47 @@ static void print_tracepoint_events(void) } /* + * Check whether event is in <debugfs_mount_point>/tracing/events + */ + +int is_valid_tracepoint(const char *event_string) +{ + DIR *sys_dir, *evt_dir; + struct dirent *sys_next, *evt_next, sys_dirent, evt_dirent; + char evt_path[MAXPATHLEN]; + char dir_path[MAXPATHLEN]; + + if (debugfs_valid_mountpoint(debugfs_path)) + return 0; + + sys_dir = opendir(debugfs_path); + if (!sys_dir) + return 0; + + for_each_subsystem(sys_dir, sys_dirent, sys_next) { + + snprintf(dir_path, MAXPATHLEN, "%s/%s", debugfs_path, + sys_dirent.d_name); + evt_dir = opendir(dir_path); + if (!evt_dir) + continue; + + for_each_event(sys_dirent, evt_dir, evt_dirent, evt_next) { + snprintf(evt_path, MAXPATHLEN, "%s:%s", + sys_dirent.d_name, evt_dirent.d_name); + if (!strcmp(evt_path, event_string)) { + closedir(evt_dir); + closedir(sys_dir); + return 1; + } + } + closedir(evt_dir); + } + closedir(sys_dir); + return 0; +} + +/* * Print the help text for the event symbols: */ void print_events(void) @@ -963,3 +1010,33 @@ void print_events(void) exit(129); } + +int perf_evsel_list__create_default(void) +{ + struct perf_evsel *evsel; + struct perf_event_attr attr; + + memset(&attr, 0, sizeof(attr)); + attr.type = PERF_TYPE_HARDWARE; + attr.config = PERF_COUNT_HW_CPU_CYCLES; + + evsel = perf_evsel__new(&attr, 0); + + if (evsel == NULL) + return -ENOMEM; + + list_add(&evsel->node, &evsel_list); + ++nr_counters; + return 0; +} + +void perf_evsel_list__delete(void) +{ + struct perf_evsel *pos, *n; + + list_for_each_entry_safe(pos, n, &evsel_list, node) { + list_del_init(&pos->node); + perf_evsel__delete(pos); + } + nr_counters = 0; +} diff --git a/tools/perf/util/parse-events.h b/tools/perf/util/parse-events.h index fc4ab3fe877..b82cafb8377 100644 --- a/tools/perf/util/parse-events.h +++ b/tools/perf/util/parse-events.h @@ -4,6 +4,16 @@ * Parse symbolic events/counts passed in as options: */ +#include "../../../include/linux/perf_event.h" + +struct list_head; +struct perf_evsel; + +extern struct list_head evsel_list; + +int perf_evsel_list__create_default(void); +void perf_evsel_list__delete(void); + struct option; struct tracepoint_path { @@ -13,14 +23,11 @@ struct tracepoint_path { }; extern struct tracepoint_path *tracepoint_id_to_path(u64 config); -extern bool have_tracepoints(struct perf_event_attr *pattrs, int nb_events); +extern bool have_tracepoints(struct list_head *evsel_list); extern int nr_counters; -extern struct perf_event_attr attrs[MAX_COUNTERS]; -extern char *filters[MAX_COUNTERS]; - -extern const char *event_name(int ctr); +const char *event_name(struct perf_evsel *event); extern const char *__event_name(int type, u64 config); extern int parse_events(const struct option *opt, const char *str, int unset); @@ -29,9 +36,9 @@ extern int parse_filter(const struct option *opt, const char *str, int unset); #define EVENTS_HELP_MAX (128*1024) extern void print_events(void); +extern int is_valid_tracepoint(const char *event_string); extern char debugfs_path[]; extern int valid_debugfs_mount(const char *debugfs); - #endif /* __PERF_PARSE_EVENTS_H */ diff --git a/tools/perf/util/parse-options.h b/tools/perf/util/parse-options.h index c7d72dce54b..abc31a1dac1 100644 --- a/tools/perf/util/parse-options.h +++ b/tools/perf/util/parse-options.h @@ -119,6 +119,10 @@ struct option { { .type = OPTION_CALLBACK, .short_name = (s), .long_name = (l), .value = (v), (a), .help = (h), .callback = (f), .flags = PARSE_OPT_NOARG } #define OPT_CALLBACK_DEFAULT(s, l, v, a, h, f, d) \ { .type = OPTION_CALLBACK, .short_name = (s), .long_name = (l), .value = (v), (a), .help = (h), .callback = (f), .defval = (intptr_t)d, .flags = PARSE_OPT_LASTARG_DEFAULT } +#define OPT_CALLBACK_DEFAULT_NOOPT(s, l, v, a, h, f, d) \ + { .type = OPTION_CALLBACK, .short_name = (s), .long_name = (l),\ + .value = (v), (a), .help = (h), .callback = (f), .defval = (intptr_t)d,\ + .flags = PARSE_OPT_LASTARG_DEFAULT | PARSE_OPT_NOARG} /* parse_options() will filter out the processed options and leave the * non-option argments in argv[]. diff --git a/tools/perf/util/probe-event.c b/tools/perf/util/probe-event.c index 61191c6cbe7..128aaab0aed 100644 --- a/tools/perf/util/probe-event.c +++ b/tools/perf/util/probe-event.c @@ -95,7 +95,7 @@ static int init_vmlinux(void) goto out; if (machine__create_kernel_maps(&machine) < 0) { - pr_debug("machine__create_kernel_maps "); + pr_debug("machine__create_kernel_maps() failed.\n"); goto out; } out: @@ -149,7 +149,8 @@ static int open_vmlinux(const char *module) { const char *path = kernel_get_module_path(module); if (!path) { - pr_err("Failed to find path of %s module", module ?: "kernel"); + pr_err("Failed to find path of %s module.\n", + module ?: "kernel"); return -ENOENT; } pr_debug("Try to open %s\n", path); @@ -226,7 +227,7 @@ static int try_to_find_probe_trace_events(struct perf_probe_event *pev, pr_warning("Warning: No dwarf info found in the vmlinux - " "please rebuild kernel with CONFIG_DEBUG_INFO=y.\n"); if (!need_dwarf) { - pr_debug("Trying to use symbols.\nn"); + pr_debug("Trying to use symbols.\n"); return 0; } } @@ -295,42 +296,49 @@ static int get_real_path(const char *raw_path, const char *comp_dir, #define LINEBUF_SIZE 256 #define NR_ADDITIONAL_LINES 2 -static int show_one_line(FILE *fp, int l, bool skip, bool show_num) +static int __show_one_line(FILE *fp, int l, bool skip, bool show_num) { char buf[LINEBUF_SIZE]; - const char *color = PERF_COLOR_BLUE; + const char *color = show_num ? "" : PERF_COLOR_BLUE; + const char *prefix = NULL; - if (fgets(buf, LINEBUF_SIZE, fp) == NULL) - goto error; - if (!skip) { - if (show_num) - fprintf(stdout, "%7d %s", l, buf); - else - color_fprintf(stdout, color, " %s", buf); - } - - while (strlen(buf) == LINEBUF_SIZE - 1 && - buf[LINEBUF_SIZE - 2] != '\n') { + do { if (fgets(buf, LINEBUF_SIZE, fp) == NULL) goto error; - if (!skip) { - if (show_num) - fprintf(stdout, "%s", buf); - else - color_fprintf(stdout, color, "%s", buf); + if (skip) + continue; + if (!prefix) { + prefix = show_num ? "%7d " : " "; + color_fprintf(stdout, color, prefix, l); } - } + color_fprintf(stdout, color, "%s", buf); - return 0; + } while (strchr(buf, '\n') == NULL); + + return 1; error: - if (feof(fp)) - pr_warning("Source file is shorter than expected.\n"); - else + if (ferror(fp)) { pr_warning("File read error: %s\n", strerror(errno)); + return -1; + } + return 0; +} - return -1; +static int _show_one_line(FILE *fp, int l, bool skip, bool show_num) +{ + int rv = __show_one_line(fp, l, skip, show_num); + if (rv == 0) { + pr_warning("Source file is shorter than expected.\n"); + rv = -1; + } + return rv; } +#define show_one_line_with_num(f,l) _show_one_line(f,l,false,true) +#define show_one_line(f,l) _show_one_line(f,l,false,false) +#define skip_one_line(f,l) _show_one_line(f,l,true,false) +#define show_one_line_or_eof(f,l) __show_one_line(f,l,false,false) + /* * Show line-range always requires debuginfo to find source file and * line number. @@ -379,7 +387,7 @@ int show_line_range(struct line_range *lr, const char *module) fprintf(stdout, "<%s:%d>\n", lr->function, lr->start - lr->offset); else - fprintf(stdout, "<%s:%d>\n", lr->file, lr->start); + fprintf(stdout, "<%s:%d>\n", lr->path, lr->start); fp = fopen(lr->path, "r"); if (fp == NULL) { @@ -388,26 +396,30 @@ int show_line_range(struct line_range *lr, const char *module) return -errno; } /* Skip to starting line number */ - while (l < lr->start && ret >= 0) - ret = show_one_line(fp, l++, true, false); - if (ret < 0) - goto end; + while (l < lr->start) { + ret = skip_one_line(fp, l++); + if (ret < 0) + goto end; + } list_for_each_entry(ln, &lr->line_list, list) { - while (ln->line > l && ret >= 0) - ret = show_one_line(fp, (l++) - lr->offset, - false, false); - if (ret >= 0) - ret = show_one_line(fp, (l++) - lr->offset, - false, true); + for (; ln->line > l; l++) { + ret = show_one_line(fp, l - lr->offset); + if (ret < 0) + goto end; + } + ret = show_one_line_with_num(fp, l++ - lr->offset); if (ret < 0) goto end; } if (lr->end == INT_MAX) lr->end = l + NR_ADDITIONAL_LINES; - while (l <= lr->end && !feof(fp) && ret >= 0) - ret = show_one_line(fp, (l++) - lr->offset, false, false); + while (l <= lr->end) { + ret = show_one_line_or_eof(fp, l++ - lr->offset); + if (ret <= 0) + break; + } end: fclose(fp); return ret; @@ -466,7 +478,7 @@ int show_available_vars(struct perf_probe_event *pevs, int npevs, fd = open_vmlinux(module); if (fd < 0) { - pr_warning("Failed to open debuginfo file.\n"); + pr_warning("Failed to open debug information file.\n"); return fd; } @@ -526,56 +538,87 @@ int show_available_vars(struct perf_probe_event *pevs __unused, } #endif +static int parse_line_num(char **ptr, int *val, const char *what) +{ + const char *start = *ptr; + + errno = 0; + *val = strtol(*ptr, ptr, 0); + if (errno || *ptr == start) { + semantic_error("'%s' is not a valid number.\n", what); + return -EINVAL; + } + return 0; +} + +/* + * Stuff 'lr' according to the line range described by 'arg'. + * The line range syntax is described by: + * + * SRC[:SLN[+NUM|-ELN]] + * FNC[:SLN[+NUM|-ELN]] + */ int parse_line_range_desc(const char *arg, struct line_range *lr) { - const char *ptr; - char *tmp; - /* - * <Syntax> - * SRC:SLN[+NUM|-ELN] - * FUNC[:SLN[+NUM|-ELN]] - */ - ptr = strchr(arg, ':'); - if (ptr) { - lr->start = (int)strtoul(ptr + 1, &tmp, 0); - if (*tmp == '+') { - lr->end = lr->start + (int)strtoul(tmp + 1, &tmp, 0); - lr->end--; /* - * Adjust the number of lines here. - * If the number of lines == 1, the - * the end of line should be equal to - * the start of line. - */ - } else if (*tmp == '-') - lr->end = (int)strtoul(tmp + 1, &tmp, 0); - else - lr->end = INT_MAX; + char *range, *name = strdup(arg); + int err; + + if (!name) + return -ENOMEM; + + lr->start = 0; + lr->end = INT_MAX; + + range = strchr(name, ':'); + if (range) { + *range++ = '\0'; + + err = parse_line_num(&range, &lr->start, "start line"); + if (err) + goto err; + + if (*range == '+' || *range == '-') { + const char c = *range++; + + err = parse_line_num(&range, &lr->end, "end line"); + if (err) + goto err; + + if (c == '+') { + lr->end += lr->start; + /* + * Adjust the number of lines here. + * If the number of lines == 1, the + * the end of line should be equal to + * the start of line. + */ + lr->end--; + } + } + pr_debug("Line range is %d to %d\n", lr->start, lr->end); + + err = -EINVAL; if (lr->start > lr->end) { semantic_error("Start line must be smaller" " than end line.\n"); - return -EINVAL; + goto err; } - if (*tmp != '\0') { - semantic_error("Tailing with invalid character '%d'.\n", - *tmp); - return -EINVAL; + if (*range != '\0') { + semantic_error("Tailing with invalid str '%s'.\n", range); + goto err; } - tmp = strndup(arg, (ptr - arg)); - } else { - tmp = strdup(arg); - lr->end = INT_MAX; } - if (tmp == NULL) - return -ENOMEM; - - if (strchr(tmp, '.')) - lr->file = tmp; + if (strchr(name, '.')) + lr->file = name; else - lr->function = tmp; + lr->function = name; return 0; +err: + free(name); + return err; } /* Check the name is good for event/group */ @@ -699,39 +742,40 @@ static int parse_perf_probe_point(char *arg, struct perf_probe_event *pev) /* Exclusion check */ if (pp->lazy_line && pp->line) { - semantic_error("Lazy pattern can't be used with line number."); + semantic_error("Lazy pattern can't be used with" + " line number.\n"); return -EINVAL; } if (pp->lazy_line && pp->offset) { - semantic_error("Lazy pattern can't be used with offset."); + semantic_error("Lazy pattern can't be used with offset.\n"); return -EINVAL; } if (pp->line && pp->offset) { - semantic_error("Offset can't be used with line number."); + semantic_error("Offset can't be used with line number.\n"); return -EINVAL; } if (!pp->line && !pp->lazy_line && pp->file && !pp->function) { semantic_error("File always requires line number or " - "lazy pattern."); + "lazy pattern.\n"); return -EINVAL; } if (pp->offset && !pp->function) { - semantic_error("Offset requires an entry function."); + semantic_error("Offset requires an entry function.\n"); return -EINVAL; } if (pp->retprobe && !pp->function) { - semantic_error("Return probe requires an entry function."); + semantic_error("Return probe requires an entry function.\n"); return -EINVAL; } if ((pp->offset || pp->line || pp->lazy_line) && pp->retprobe) { semantic_error("Offset/Line/Lazy pattern can't be used with " - "return probe."); + "return probe.\n"); return -EINVAL; } @@ -1005,7 +1049,7 @@ int synthesize_perf_probe_arg(struct perf_probe_arg *pa, char *buf, size_t len) return tmp - buf; error: - pr_debug("Failed to synthesize perf probe argument: %s", + pr_debug("Failed to synthesize perf probe argument: %s\n", strerror(-ret)); return ret; } @@ -1033,13 +1077,13 @@ static char *synthesize_perf_probe_point(struct perf_probe_point *pp) goto error; } if (pp->file) { - len = strlen(pp->file) - 31; - if (len < 0) - len = 0; - tmp = strchr(pp->file + len, '/'); - if (!tmp) - tmp = pp->file + len; - ret = e_snprintf(file, 32, "@%s", tmp + 1); + tmp = pp->file; + len = strlen(tmp); + if (len > 30) { + tmp = strchr(pp->file + len - 30, '/'); + tmp = tmp ? tmp + 1 : pp->file + len - 30; + } + ret = e_snprintf(file, 32, "@%s", tmp); if (ret <= 0) goto error; } @@ -1055,7 +1099,7 @@ static char *synthesize_perf_probe_point(struct perf_probe_point *pp) return buf; error: - pr_debug("Failed to synthesize perf probe point: %s", + pr_debug("Failed to synthesize perf probe point: %s\n", strerror(-ret)); if (buf) free(buf); @@ -1796,7 +1840,7 @@ static int del_trace_probe_event(int fd, const char *group, ret = e_snprintf(buf, 128, "%s:%s", group, event); if (ret < 0) { - pr_err("Failed to copy event."); + pr_err("Failed to copy event.\n"); return ret; } diff --git a/tools/perf/util/probe-finder.c b/tools/perf/util/probe-finder.c index ddf4d455632..ab83b6ac5d6 100644 --- a/tools/perf/util/probe-finder.c +++ b/tools/perf/util/probe-finder.c @@ -652,8 +652,8 @@ static_var: regs = get_arch_regstr(regn); if (!regs) { /* This should be a bug in DWARF or this tool */ - pr_warning("Mapping for DWARF register number %u " - "missing on this architecture.", regn); + pr_warning("Mapping for the register number %u " + "missing on this architecture.\n", regn); return -ERANGE; } @@ -699,13 +699,14 @@ static int convert_variable_type(Dwarf_Die *vr_die, if (ret != DW_TAG_pointer_type && ret != DW_TAG_array_type) { pr_warning("Failed to cast into string: " - "%s(%s) is not a pointer nor array.", + "%s(%s) is not a pointer nor array.\n", dwarf_diename(vr_die), dwarf_diename(&type)); return -EINVAL; } if (ret == DW_TAG_pointer_type) { if (die_get_real_type(&type, &type) == NULL) { - pr_warning("Failed to get a type information."); + pr_warning("Failed to get a type" + " information.\n"); return -ENOENT; } while (*ref_ptr) @@ -720,7 +721,7 @@ static int convert_variable_type(Dwarf_Die *vr_die, if (!die_compare_name(&type, "char") && !die_compare_name(&type, "unsigned char")) { pr_warning("Failed to cast into string: " - "%s is not (unsigned) char *.", + "%s is not (unsigned) char *.\n", dwarf_diename(vr_die)); return -EINVAL; } @@ -830,8 +831,8 @@ static int convert_variable_fields(Dwarf_Die *vr_die, const char *varname, return -EINVAL; } if (field->name[0] == '[') { - pr_err("Semantic error: %s is not a pointor nor array.", - varname); + pr_err("Semantic error: %s is not a pointor" + " nor array.\n", varname); return -EINVAL; } if (field->ref) { @@ -978,7 +979,7 @@ static int convert_to_trace_point(Dwarf_Die *sp_die, Dwarf_Addr paddr, name = dwarf_diename(sp_die); if (name) { if (dwarf_entrypc(sp_die, &eaddr) != 0) { - pr_warning("Failed to get entry pc of %s\n", + pr_warning("Failed to get entry address of %s\n", dwarf_diename(sp_die)); return -ENOENT; } @@ -994,7 +995,7 @@ static int convert_to_trace_point(Dwarf_Die *sp_die, Dwarf_Addr paddr, if (retprobe) { if (eaddr != paddr) { pr_warning("Return probe must be on the head of" - " a real function\n"); + " a real function.\n"); return -EINVAL; } tp->retprobe = true; @@ -1033,7 +1034,7 @@ static int call_probe_finder(Dwarf_Die *sp_die, struct probe_finder *pf) Dwarf_Frame *frame; if (dwarf_cfi_addrframe(pf->cfi, pf->addr, &frame) != 0 || dwarf_frame_cfa(frame, &pf->fb_ops, &nops) != 0) { - pr_warning("Failed to get CFA on 0x%jx\n", + pr_warning("Failed to get call frame on 0x%jx\n", (uintmax_t)pf->addr); return -ENOENT; } @@ -1060,7 +1061,7 @@ static int find_probe_point_by_line(struct probe_finder *pf) int ret = 0; if (dwarf_getsrclines(&pf->cu_die, &lines, &nlines) != 0) { - pr_warning("No source lines found in this CU.\n"); + pr_warning("No source lines found.\n"); return -ENOENT; } @@ -1162,7 +1163,7 @@ static int find_probe_point_lazy(Dwarf_Die *sp_die, struct probe_finder *pf) } if (dwarf_getsrclines(&pf->cu_die, &lines, &nlines) != 0) { - pr_warning("No source lines found in this CU.\n"); + pr_warning("No source lines found.\n"); return -ENOENT; } @@ -1220,7 +1221,7 @@ static int probe_point_inline_cb(Dwarf_Die *in_die, void *data) else { /* Get probe address */ if (dwarf_entrypc(in_die, &addr) != 0) { - pr_warning("Failed to get entry pc of %s.\n", + pr_warning("Failed to get entry address of %s.\n", dwarf_diename(in_die)); param->retval = -ENOENT; return DWARF_CB_ABORT; @@ -1261,8 +1262,8 @@ static int probe_point_search_cb(Dwarf_Die *sp_die, void *data) param->retval = find_probe_point_lazy(sp_die, pf); else { if (dwarf_entrypc(sp_die, &pf->addr) != 0) { - pr_warning("Failed to get entry pc of %s.\n", - dwarf_diename(sp_die)); + pr_warning("Failed to get entry address of " + "%s.\n", dwarf_diename(sp_die)); param->retval = -ENOENT; return DWARF_CB_ABORT; } @@ -1304,7 +1305,7 @@ static int find_probes(int fd, struct probe_finder *pf) dbg = dwfl_init_offline_dwarf(fd, &dwfl, &bias); if (!dbg) { - pr_warning("No dwarf info found in the vmlinux - " + pr_warning("No debug information found in the vmlinux - " "please rebuild with CONFIG_DEBUG_INFO=y.\n"); return -EBADF; } @@ -1549,7 +1550,7 @@ int find_perf_probe_point(unsigned long addr, struct perf_probe_point *ppt) /* Open the live linux kernel */ dbg = dwfl_init_live_kernel_dwarf(addr, &dwfl, &bias); if (!dbg) { - pr_warning("No dwarf info found in the vmlinux - " + pr_warning("No debug information found in the vmlinux - " "please rebuild with CONFIG_DEBUG_INFO=y.\n"); ret = -EINVAL; goto end; @@ -1559,7 +1560,8 @@ int find_perf_probe_point(unsigned long addr, struct perf_probe_point *ppt) addr += bias; /* Find cu die */ if (!dwarf_addrdie(dbg, (Dwarf_Addr)addr - bias, &cudie)) { - pr_warning("No CU DIE is found at %lx\n", addr); + pr_warning("Failed to find debug information for address %lx\n", + addr); ret = -EINVAL; goto end; } @@ -1684,7 +1686,7 @@ static int find_line_range_by_line(Dwarf_Die *sp_die, struct line_finder *lf) line_list__init(&lf->lr->line_list); if (dwarf_getsrclines(&lf->cu_die, &lines, &nlines) != 0) { - pr_warning("No source lines found in this CU.\n"); + pr_warning("No source lines found.\n"); return -ENOENT; } @@ -1809,7 +1811,7 @@ int find_line_range(int fd, struct line_range *lr) dbg = dwfl_init_offline_dwarf(fd, &dwfl, &bias); if (!dbg) { - pr_warning("No dwarf info found in the vmlinux - " + pr_warning("No debug information found in the vmlinux - " "please rebuild with CONFIG_DEBUG_INFO=y.\n"); return -EBADF; } diff --git a/tools/perf/util/probe-finder.h b/tools/perf/util/probe-finder.h index bba69d45569..beaefc3c122 100644 --- a/tools/perf/util/probe-finder.h +++ b/tools/perf/util/probe-finder.h @@ -34,9 +34,9 @@ extern int find_available_vars_at(int fd, struct perf_probe_event *pev, bool externs); #include <dwarf.h> -#include <libdw.h> -#include <libdwfl.h> -#include <version.h> +#include <elfutils/libdw.h> +#include <elfutils/libdwfl.h> +#include <elfutils/version.h> struct probe_finder { struct perf_probe_event *pev; /* Target probe event */ diff --git a/tools/perf/util/scripting-engines/trace-event-perl.c b/tools/perf/util/scripting-engines/trace-event-perl.c index b059dc50cc2..93680818e24 100644 --- a/tools/perf/util/scripting-engines/trace-event-perl.c +++ b/tools/perf/util/scripting-engines/trace-event-perl.c @@ -1,5 +1,5 @@ /* - * trace-event-perl. Feed perf trace events to an embedded Perl interpreter. + * trace-event-perl. Feed perf script events to an embedded Perl interpreter. * * Copyright (C) 2009 Tom Zanussi <tzanussi@gmail.com> * @@ -411,8 +411,8 @@ static int perl_generate_script(const char *outfile) return -1; } - fprintf(ofp, "# perf trace event handlers, " - "generated by perf trace -g perl\n"); + fprintf(ofp, "# perf script event handlers, " + "generated by perf script -g perl\n"); fprintf(ofp, "# Licensed under the terms of the GNU GPL" " License version 2\n\n"); diff --git a/tools/perf/util/scripting-engines/trace-event-python.c b/tools/perf/util/scripting-engines/trace-event-python.c index 33a63252374..c6d99334bdf 100644 --- a/tools/perf/util/scripting-engines/trace-event-python.c +++ b/tools/perf/util/scripting-engines/trace-event-python.c @@ -442,8 +442,8 @@ static int python_generate_script(const char *outfile) fprintf(stderr, "couldn't open %s\n", fname); return -1; } - fprintf(ofp, "# perf trace event handlers, " - "generated by perf trace -g python\n"); + fprintf(ofp, "# perf script event handlers, " + "generated by perf script -g python\n"); fprintf(ofp, "# Licensed under the terms of the GNU GPL" " License version 2\n\n"); diff --git a/tools/perf/util/session.c b/tools/perf/util/session.c index fa9d652c2dc..313dac2d94c 100644 --- a/tools/perf/util/session.c +++ b/tools/perf/util/session.c @@ -65,9 +65,49 @@ out_close: return -1; } +static void perf_session__id_header_size(struct perf_session *session) +{ + struct sample_data *data; + u64 sample_type = session->sample_type; + u16 size = 0; + + if (!session->sample_id_all) + goto out; + + if (sample_type & PERF_SAMPLE_TID) + size += sizeof(data->tid) * 2; + + if (sample_type & PERF_SAMPLE_TIME) + size += sizeof(data->time); + + if (sample_type & PERF_SAMPLE_ID) + size += sizeof(data->id); + + if (sample_type & PERF_SAMPLE_STREAM_ID) + size += sizeof(data->stream_id); + + if (sample_type & PERF_SAMPLE_CPU) + size += sizeof(data->cpu) * 2; +out: + session->id_hdr_size = size; +} + +void perf_session__set_sample_id_all(struct perf_session *session, bool value) +{ + session->sample_id_all = value; + perf_session__id_header_size(session); +} + +void perf_session__set_sample_type(struct perf_session *session, u64 type) +{ + session->sample_type = type; +} + void perf_session__update_sample_type(struct perf_session *self) { self->sample_type = perf_header__sample_type(&self->header); + self->sample_id_all = perf_header__sample_id_all(&self->header); + perf_session__id_header_size(self); } int perf_session__create_kernel_maps(struct perf_session *self) @@ -85,7 +125,9 @@ static void perf_session__destroy_kernel_maps(struct perf_session *self) machines__destroy_guest_kernel_maps(&self->machines); } -struct perf_session *perf_session__new(const char *filename, int mode, bool force, bool repipe) +struct perf_session *perf_session__new(const char *filename, int mode, + bool force, bool repipe, + struct perf_event_ops *ops) { size_t len = filename ? strlen(filename) + 1 : 0; struct perf_session *self = zalloc(sizeof(*self) + len); @@ -101,10 +143,20 @@ struct perf_session *perf_session__new(const char *filename, int mode, bool forc INIT_LIST_HEAD(&self->dead_threads); self->hists_tree = RB_ROOT; self->last_match = NULL; - self->mmap_window = 32; + /* + * On 64bit we can mmap the data file in one go. No need for tiny mmap + * slices. On 32bit we use 32MB. + */ +#if BITS_PER_LONG == 64 + self->mmap_window = ULLONG_MAX; +#else + self->mmap_window = 32 * 1024 * 1024ULL; +#endif self->machines = RB_ROOT; self->repipe = repipe; - INIT_LIST_HEAD(&self->ordered_samples.samples_head); + INIT_LIST_HEAD(&self->ordered_samples.samples); + INIT_LIST_HEAD(&self->ordered_samples.sample_cache); + INIT_LIST_HEAD(&self->ordered_samples.to_free); machine__init(&self->host_machine, "", HOST_KERNEL_ID); if (mode == O_RDONLY) { @@ -120,6 +172,13 @@ struct perf_session *perf_session__new(const char *filename, int mode, bool forc } perf_session__update_sample_type(self); + + if (ops && ops->ordering_requires_timestamps && + ops->ordered_samples && !self->sample_id_all) { + dump_printf("WARNING: No sample_id_all support, falling back to unordered processing\n"); + ops->ordered_samples = false; + } + out: return self; out_free: @@ -230,7 +289,15 @@ struct map_symbol *perf_session__resolve_callchain(struct perf_session *self, return syms; } +static int process_event_synth_stub(event_t *event __used, + struct perf_session *session __used) +{ + dump_printf(": unhandled!\n"); + return 0; +} + static int process_event_stub(event_t *event __used, + struct sample_data *sample __used, struct perf_session *session __used) { dump_printf(": unhandled!\n"); @@ -262,7 +329,7 @@ static void perf_event_ops__fill_defaults(struct perf_event_ops *handler) if (handler->exit == NULL) handler->exit = process_event_stub; if (handler->lost == NULL) - handler->lost = process_event_stub; + handler->lost = event__process_lost; if (handler->read == NULL) handler->read = process_event_stub; if (handler->throttle == NULL) @@ -270,13 +337,13 @@ static void perf_event_ops__fill_defaults(struct perf_event_ops *handler) if (handler->unthrottle == NULL) handler->unthrottle = process_event_stub; if (handler->attr == NULL) - handler->attr = process_event_stub; + handler->attr = process_event_synth_stub; if (handler->event_type == NULL) - handler->event_type = process_event_stub; + handler->event_type = process_event_synth_stub; if (handler->tracing_data == NULL) - handler->tracing_data = process_event_stub; + handler->tracing_data = process_event_synth_stub; if (handler->build_id == NULL) - handler->build_id = process_event_stub; + handler->build_id = process_event_synth_stub; if (handler->finished_round == NULL) { if (handler->ordered_samples) handler->finished_round = process_finished_round; @@ -386,33 +453,61 @@ static event__swap_op event__swap_ops[] = { struct sample_queue { u64 timestamp; - struct sample_event *event; + u64 file_offset; + event_t *event; struct list_head list; }; +static void perf_session_free_sample_buffers(struct perf_session *session) +{ + struct ordered_samples *os = &session->ordered_samples; + + while (!list_empty(&os->to_free)) { + struct sample_queue *sq; + + sq = list_entry(os->to_free.next, struct sample_queue, list); + list_del(&sq->list); + free(sq); + } +} + +static int perf_session_deliver_event(struct perf_session *session, + event_t *event, + struct sample_data *sample, + struct perf_event_ops *ops, + u64 file_offset); + static void flush_sample_queue(struct perf_session *s, struct perf_event_ops *ops) { - struct list_head *head = &s->ordered_samples.samples_head; - u64 limit = s->ordered_samples.next_flush; + struct ordered_samples *os = &s->ordered_samples; + struct list_head *head = &os->samples; struct sample_queue *tmp, *iter; + struct sample_data sample; + u64 limit = os->next_flush; + u64 last_ts = os->last_sample ? os->last_sample->timestamp : 0ULL; if (!ops->ordered_samples || !limit) return; list_for_each_entry_safe(iter, tmp, head, list) { if (iter->timestamp > limit) - return; + break; - if (iter == s->ordered_samples.last_inserted) - s->ordered_samples.last_inserted = NULL; + event__parse_sample(iter->event, s, &sample); + perf_session_deliver_event(s, iter->event, &sample, ops, + iter->file_offset); - ops->sample((event_t *)iter->event, s); - - s->ordered_samples.last_flush = iter->timestamp; + os->last_flush = iter->timestamp; list_del(&iter->list); - free(iter->event); - free(iter); + list_add(&iter->list, &os->sample_cache); + } + + if (list_empty(head)) { + os->last_sample = NULL; + } else if (last_ts <= limit) { + os->last_sample = + list_entry(head->prev, struct sample_queue, list); } } @@ -465,178 +560,265 @@ static int process_finished_round(event_t *event __used, return 0; } -static void __queue_sample_end(struct sample_queue *new, struct list_head *head) -{ - struct sample_queue *iter; - - list_for_each_entry_reverse(iter, head, list) { - if (iter->timestamp < new->timestamp) { - list_add(&new->list, &iter->list); - return; - } - } - - list_add(&new->list, head); -} - -static void __queue_sample_before(struct sample_queue *new, - struct sample_queue *iter, - struct list_head *head) -{ - list_for_each_entry_continue_reverse(iter, head, list) { - if (iter->timestamp < new->timestamp) { - list_add(&new->list, &iter->list); - return; - } - } - - list_add(&new->list, head); -} - -static void __queue_sample_after(struct sample_queue *new, - struct sample_queue *iter, - struct list_head *head) -{ - list_for_each_entry_continue(iter, head, list) { - if (iter->timestamp > new->timestamp) { - list_add_tail(&new->list, &iter->list); - return; - } - } - list_add_tail(&new->list, head); -} - /* The queue is ordered by time */ -static void __queue_sample_event(struct sample_queue *new, - struct perf_session *s) +static void __queue_event(struct sample_queue *new, struct perf_session *s) { - struct sample_queue *last_inserted = s->ordered_samples.last_inserted; - struct list_head *head = &s->ordered_samples.samples_head; + struct ordered_samples *os = &s->ordered_samples; + struct sample_queue *sample = os->last_sample; + u64 timestamp = new->timestamp; + struct list_head *p; + os->last_sample = new; - if (!last_inserted) { - __queue_sample_end(new, head); + if (!sample) { + list_add(&new->list, &os->samples); + os->max_timestamp = timestamp; return; } /* - * Most of the time the current event has a timestamp - * very close to the last event inserted, unless we just switched - * to another event buffer. Having a sorting based on a list and - * on the last inserted event that is close to the current one is - * probably more efficient than an rbtree based sorting. + * last_sample might point to some random place in the list as it's + * the last queued event. We expect that the new event is close to + * this. */ - if (last_inserted->timestamp >= new->timestamp) - __queue_sample_before(new, last_inserted, head); - else - __queue_sample_after(new, last_inserted, head); + if (sample->timestamp <= timestamp) { + while (sample->timestamp <= timestamp) { + p = sample->list.next; + if (p == &os->samples) { + list_add_tail(&new->list, &os->samples); + os->max_timestamp = timestamp; + return; + } + sample = list_entry(p, struct sample_queue, list); + } + list_add_tail(&new->list, &sample->list); + } else { + while (sample->timestamp > timestamp) { + p = sample->list.prev; + if (p == &os->samples) { + list_add(&new->list, &os->samples); + return; + } + sample = list_entry(p, struct sample_queue, list); + } + list_add(&new->list, &sample->list); + } } -static int queue_sample_event(event_t *event, struct sample_data *data, - struct perf_session *s) +#define MAX_SAMPLE_BUFFER (64 * 1024 / sizeof(struct sample_queue)) + +static int perf_session_queue_event(struct perf_session *s, event_t *event, + struct sample_data *data, u64 file_offset) { + struct ordered_samples *os = &s->ordered_samples; + struct list_head *sc = &os->sample_cache; u64 timestamp = data->time; struct sample_queue *new; + if (!timestamp || timestamp == ~0ULL) + return -ETIME; if (timestamp < s->ordered_samples.last_flush) { printf("Warning: Timestamp below last timeslice flush\n"); return -EINVAL; } - new = malloc(sizeof(*new)); - if (!new) - return -ENOMEM; + if (!list_empty(sc)) { + new = list_entry(sc->next, struct sample_queue, list); + list_del(&new->list); + } else if (os->sample_buffer) { + new = os->sample_buffer + os->sample_buffer_idx; + if (++os->sample_buffer_idx == MAX_SAMPLE_BUFFER) + os->sample_buffer = NULL; + } else { + os->sample_buffer = malloc(MAX_SAMPLE_BUFFER * sizeof(*new)); + if (!os->sample_buffer) + return -ENOMEM; + list_add(&os->sample_buffer->list, &os->to_free); + os->sample_buffer_idx = 2; + new = os->sample_buffer + 1; + } new->timestamp = timestamp; + new->file_offset = file_offset; + new->event = event; - new->event = malloc(event->header.size); - if (!new->event) { - free(new); - return -ENOMEM; - } + __queue_event(new, s); - memcpy(new->event, event, event->header.size); + return 0; +} - __queue_sample_event(new, s); - s->ordered_samples.last_inserted = new; +static void callchain__printf(struct sample_data *sample) +{ + unsigned int i; - if (new->timestamp > s->ordered_samples.max_timestamp) - s->ordered_samples.max_timestamp = new->timestamp; + printf("... chain: nr:%Lu\n", sample->callchain->nr); - return 0; + for (i = 0; i < sample->callchain->nr; i++) + printf("..... %2d: %016Lx\n", i, sample->callchain->ips[i]); } -static int perf_session__process_sample(event_t *event, struct perf_session *s, - struct perf_event_ops *ops) +static void perf_session__print_tstamp(struct perf_session *session, + event_t *event, + struct sample_data *sample) { - struct sample_data data; + if (event->header.type != PERF_RECORD_SAMPLE && + !session->sample_id_all) { + fputs("-1 -1 ", stdout); + return; + } - if (!ops->ordered_samples) - return ops->sample(event, s); + if ((session->sample_type & PERF_SAMPLE_CPU)) + printf("%u ", sample->cpu); - bzero(&data, sizeof(struct sample_data)); - event__parse_sample(event, s->sample_type, &data); + if (session->sample_type & PERF_SAMPLE_TIME) + printf("%Lu ", sample->time); +} - queue_sample_event(event, &data, s); +static void dump_event(struct perf_session *session, event_t *event, + u64 file_offset, struct sample_data *sample) +{ + if (!dump_trace) + return; - return 0; + printf("\n%#Lx [%#x]: event: %d\n", file_offset, event->header.size, + event->header.type); + + trace_event(event); + + if (sample) + perf_session__print_tstamp(session, event, sample); + + printf("%#Lx [%#x]: PERF_RECORD_%s", file_offset, event->header.size, + event__get_event_name(event->header.type)); } -static int perf_session__process_event(struct perf_session *self, - event_t *event, - struct perf_event_ops *ops, - u64 offset, u64 head) +static void dump_sample(struct perf_session *session, event_t *event, + struct sample_data *sample) { - trace_event(event); + if (!dump_trace) + return; - if (event->header.type < PERF_RECORD_HEADER_MAX) { - dump_printf("%#Lx [%#x]: PERF_RECORD_%s", - offset + head, event->header.size, - event__name[event->header.type]); - hists__inc_nr_events(&self->hists, event->header.type); - } + printf("(IP, %d): %d/%d: %#Lx period: %Ld\n", event->header.misc, + sample->pid, sample->tid, sample->ip, sample->period); - if (self->header.needs_swap && event__swap_ops[event->header.type]) - event__swap_ops[event->header.type](event); + if (session->sample_type & PERF_SAMPLE_CALLCHAIN) + callchain__printf(sample); +} + +static int perf_session_deliver_event(struct perf_session *session, + event_t *event, + struct sample_data *sample, + struct perf_event_ops *ops, + u64 file_offset) +{ + dump_event(session, event, file_offset, sample); switch (event->header.type) { case PERF_RECORD_SAMPLE: - return perf_session__process_sample(event, self, ops); + dump_sample(session, event, sample); + return ops->sample(event, sample, session); case PERF_RECORD_MMAP: - return ops->mmap(event, self); + return ops->mmap(event, sample, session); case PERF_RECORD_COMM: - return ops->comm(event, self); + return ops->comm(event, sample, session); case PERF_RECORD_FORK: - return ops->fork(event, self); + return ops->fork(event, sample, session); case PERF_RECORD_EXIT: - return ops->exit(event, self); + return ops->exit(event, sample, session); case PERF_RECORD_LOST: - return ops->lost(event, self); + return ops->lost(event, sample, session); case PERF_RECORD_READ: - return ops->read(event, self); + return ops->read(event, sample, session); case PERF_RECORD_THROTTLE: - return ops->throttle(event, self); + return ops->throttle(event, sample, session); case PERF_RECORD_UNTHROTTLE: - return ops->unthrottle(event, self); + return ops->unthrottle(event, sample, session); + default: + ++session->hists.stats.nr_unknown_events; + return -1; + } +} + +static int perf_session__preprocess_sample(struct perf_session *session, + event_t *event, struct sample_data *sample) +{ + if (event->header.type != PERF_RECORD_SAMPLE || + !(session->sample_type & PERF_SAMPLE_CALLCHAIN)) + return 0; + + if (!ip_callchain__valid(sample->callchain, event)) { + pr_debug("call-chain problem with event, skipping it.\n"); + ++session->hists.stats.nr_invalid_chains; + session->hists.stats.total_invalid_chains += sample->period; + return -EINVAL; + } + return 0; +} + +static int perf_session__process_user_event(struct perf_session *session, event_t *event, + struct perf_event_ops *ops, u64 file_offset) +{ + dump_event(session, event, file_offset, NULL); + + /* These events are processed right away */ + switch (event->header.type) { case PERF_RECORD_HEADER_ATTR: - return ops->attr(event, self); + return ops->attr(event, session); case PERF_RECORD_HEADER_EVENT_TYPE: - return ops->event_type(event, self); + return ops->event_type(event, session); case PERF_RECORD_HEADER_TRACING_DATA: /* setup for reading amidst mmap */ - lseek(self->fd, offset + head, SEEK_SET); - return ops->tracing_data(event, self); + lseek(session->fd, file_offset, SEEK_SET); + return ops->tracing_data(event, session); case PERF_RECORD_HEADER_BUILD_ID: - return ops->build_id(event, self); + return ops->build_id(event, session); case PERF_RECORD_FINISHED_ROUND: - return ops->finished_round(event, self, ops); + return ops->finished_round(event, session, ops); default: - ++self->hists.stats.nr_unknown_events; - return -1; + return -EINVAL; } } +static int perf_session__process_event(struct perf_session *session, + event_t *event, + struct perf_event_ops *ops, + u64 file_offset) +{ + struct sample_data sample; + int ret; + + if (session->header.needs_swap && event__swap_ops[event->header.type]) + event__swap_ops[event->header.type](event); + + if (event->header.type >= PERF_RECORD_HEADER_MAX) + return -EINVAL; + + hists__inc_nr_events(&session->hists, event->header.type); + + if (event->header.type >= PERF_RECORD_USER_TYPE_START) + return perf_session__process_user_event(session, event, ops, file_offset); + + /* + * For all kernel events we get the sample data + */ + event__parse_sample(event, session, &sample); + + /* Preprocess sample records - precheck callchains */ + if (perf_session__preprocess_sample(session, event, &sample)) + return 0; + + if (ops->ordered_samples) { + ret = perf_session_queue_event(session, event, &sample, + file_offset); + if (ret != -ETIME) + return ret; + } + + return perf_session_deliver_event(session, event, &sample, ops, + file_offset); +} + void perf_event_header__bswap(struct perf_event_header *self) { self->type = bswap_32(self->type); @@ -656,21 +838,33 @@ static struct thread *perf_session__register_idle_thread(struct perf_session *se return thread; } -int do_read(int fd, void *buf, size_t size) +static void perf_session__warn_about_errors(const struct perf_session *session, + const struct perf_event_ops *ops) { - void *buf_start = buf; - - while (size) { - int ret = read(fd, buf, size); - - if (ret <= 0) - return ret; + if (ops->lost == event__process_lost && + session->hists.stats.total_lost != 0) { + ui__warning("Processed %Lu events and LOST %Lu!\n\n" + "Check IO/CPU overload!\n\n", + session->hists.stats.total_period, + session->hists.stats.total_lost); + } - size -= ret; - buf += ret; + if (session->hists.stats.nr_unknown_events != 0) { + ui__warning("Found %u unknown events!\n\n" + "Is this an older tool processing a perf.data " + "file generated by a more recent tool?\n\n" + "If that is not the case, consider " + "reporting to linux-kernel@vger.kernel.org.\n\n", + session->hists.stats.nr_unknown_events); } - return buf - buf_start; + if (session->hists.stats.nr_invalid_chains != 0) { + ui__warning("Found invalid callchains!\n\n" + "%u out of %u events were discarded for this reason.\n\n" + "Consider reporting to linux-kernel@vger.kernel.org.\n\n", + session->hists.stats.nr_invalid_chains, + session->hists.stats.nr_events[PERF_RECORD_SAMPLE]); + } } #define session_done() (*(volatile int *)(&session_done)) @@ -690,7 +884,7 @@ static int __perf_session__process_pipe_events(struct perf_session *self, head = 0; more: - err = do_read(self->fd, &event, sizeof(struct perf_event_header)); + err = readn(self->fd, &event, sizeof(struct perf_event_header)); if (err <= 0) { if (err == 0) goto done; @@ -710,8 +904,7 @@ more: p += sizeof(struct perf_event_header); if (size - sizeof(struct perf_event_header)) { - err = do_read(self->fd, p, - size - sizeof(struct perf_event_header)); + err = readn(self->fd, p, size - sizeof(struct perf_event_header)); if (err <= 0) { if (err == 0) { pr_err("unexpected end of event stream\n"); @@ -724,8 +917,7 @@ more: } if (size == 0 || - (skip = perf_session__process_event(self, &event, ops, - 0, head)) < 0) { + (skip = perf_session__process_event(self, &event, ops, head)) < 0) { dump_printf("%#Lx [%#x]: skipping unknown header type: %d\n", head, event.header.size, event.header.type); /* @@ -740,9 +932,6 @@ more: head += size; - dump_printf("\n%#Lx [%#x]: event: %d\n", - head, event.header.size, event.header.type); - if (skip > 0) head += skip; @@ -751,82 +940,91 @@ more: done: err = 0; out_err: + perf_session__warn_about_errors(self, ops); + perf_session_free_sample_buffers(self); return err; } -int __perf_session__process_events(struct perf_session *self, +int __perf_session__process_events(struct perf_session *session, u64 data_offset, u64 data_size, u64 file_size, struct perf_event_ops *ops) { - int err, mmap_prot, mmap_flags; - u64 head, shift; - u64 offset = 0; - size_t page_size; + u64 head, page_offset, file_offset, file_pos, progress_next; + int err, mmap_prot, mmap_flags, map_idx = 0; + struct ui_progress *progress; + size_t page_size, mmap_size; + char *buf, *mmaps[8]; event_t *event; uint32_t size; - char *buf; - struct ui_progress *progress = ui_progress__new("Processing events...", - self->size); - if (progress == NULL) - return -1; perf_event_ops__fill_defaults(ops); page_size = sysconf(_SC_PAGESIZE); - head = data_offset; - shift = page_size * (head / page_size); - offset += shift; - head -= shift; + page_offset = page_size * (data_offset / page_size); + file_offset = page_offset; + head = data_offset - page_offset; + + if (data_offset + data_size < file_size) + file_size = data_offset + data_size; + + progress_next = file_size / 16; + progress = ui_progress__new("Processing events...", file_size); + if (progress == NULL) + return -1; + + mmap_size = session->mmap_window; + if (mmap_size > file_size) + mmap_size = file_size; + + memset(mmaps, 0, sizeof(mmaps)); mmap_prot = PROT_READ; mmap_flags = MAP_SHARED; - if (self->header.needs_swap) { + if (session->header.needs_swap) { mmap_prot |= PROT_WRITE; mmap_flags = MAP_PRIVATE; } remap: - buf = mmap(NULL, page_size * self->mmap_window, mmap_prot, - mmap_flags, self->fd, offset); + buf = mmap(NULL, mmap_size, mmap_prot, mmap_flags, session->fd, + file_offset); if (buf == MAP_FAILED) { pr_err("failed to mmap file\n"); err = -errno; goto out_err; } + mmaps[map_idx] = buf; + map_idx = (map_idx + 1) & (ARRAY_SIZE(mmaps) - 1); + file_pos = file_offset + head; more: event = (event_t *)(buf + head); - ui_progress__update(progress, offset); - if (self->header.needs_swap) + if (session->header.needs_swap) perf_event_header__bswap(&event->header); size = event->header.size; if (size == 0) size = 8; - if (head + event->header.size >= page_size * self->mmap_window) { - int munmap_ret; - - shift = page_size * (head / page_size); - - munmap_ret = munmap(buf, page_size * self->mmap_window); - assert(munmap_ret == 0); + if (head + event->header.size > mmap_size) { + if (mmaps[map_idx]) { + munmap(mmaps[map_idx], mmap_size); + mmaps[map_idx] = NULL; + } - offset += shift; - head -= shift; + page_offset = page_size * (head / page_size); + file_offset += page_offset; + head -= page_offset; goto remap; } size = event->header.size; - dump_printf("\n%#Lx [%#x]: event: %d\n", - offset + head, event->header.size, event->header.type); - if (size == 0 || - perf_session__process_event(self, event, ops, offset, head) < 0) { + perf_session__process_event(session, event, ops, file_pos) < 0) { dump_printf("%#Lx [%#x]: skipping unknown header type: %d\n", - offset + head, event->header.size, + file_offset + head, event->header.size, event->header.type); /* * assume we lost track of the stream, check alignment, and @@ -839,19 +1037,24 @@ more: } head += size; + file_pos += size; - if (offset + head >= data_offset + data_size) - goto done; + if (file_pos >= progress_next) { + progress_next += file_size / 16; + ui_progress__update(progress, file_pos); + } - if (offset + head < file_size) + if (file_pos < file_size) goto more; -done: + err = 0; /* do the final flush for ordered samples */ - self->ordered_samples.next_flush = ULLONG_MAX; - flush_sample_queue(self, ops); + session->ordered_samples.next_flush = ULLONG_MAX; + flush_sample_queue(session, ops); out_err: ui_progress__delete(progress); + perf_session__warn_about_errors(session, ops); + perf_session_free_sample_buffers(session); return err; } diff --git a/tools/perf/util/session.h b/tools/perf/util/session.h index 9fa0fc2a863..decd83f274f 100644 --- a/tools/perf/util/session.h +++ b/tools/perf/util/session.h @@ -17,8 +17,12 @@ struct ordered_samples { u64 last_flush; u64 next_flush; u64 max_timestamp; - struct list_head samples_head; - struct sample_queue *last_inserted; + struct list_head samples; + struct list_head sample_cache; + struct list_head to_free; + struct sample_queue *sample_buffer; + struct sample_queue *last_sample; + int sample_buffer_idx; }; struct perf_session { @@ -42,6 +46,8 @@ struct perf_session { int fd; bool fd_pipe; bool repipe; + bool sample_id_all; + u16 id_hdr_size; int cwdlen; char *cwd; struct ordered_samples ordered_samples; @@ -50,7 +56,9 @@ struct perf_session { struct perf_event_ops; -typedef int (*event_op)(event_t *self, struct perf_session *session); +typedef int (*event_op)(event_t *self, struct sample_data *sample, + struct perf_session *session); +typedef int (*event_synth_op)(event_t *self, struct perf_session *session); typedef int (*event_op2)(event_t *self, struct perf_session *session, struct perf_event_ops *ops); @@ -63,16 +71,19 @@ struct perf_event_ops { lost, read, throttle, - unthrottle, - attr, + unthrottle; + event_synth_op attr, event_type, tracing_data, build_id; event_op2 finished_round; bool ordered_samples; + bool ordering_requires_timestamps; }; -struct perf_session *perf_session__new(const char *filename, int mode, bool force, bool repipe); +struct perf_session *perf_session__new(const char *filename, int mode, + bool force, bool repipe, + struct perf_event_ops *ops); void perf_session__delete(struct perf_session *self); void perf_event_header__bswap(struct perf_event_header *self); @@ -98,8 +109,9 @@ void mem_bswap_64(void *src, int byte_size); int perf_session__create_kernel_maps(struct perf_session *self); -int do_read(int fd, void *buf, size_t size); void perf_session__update_sample_type(struct perf_session *self); +void perf_session__set_sample_id_all(struct perf_session *session, bool value); +void perf_session__set_sample_type(struct perf_session *session, u64 type); void perf_session__remove_thread(struct perf_session *self, struct thread *th); static inline diff --git a/tools/perf/util/sort.c b/tools/perf/util/sort.c index b62a553cc67..f44fa541d56 100644 --- a/tools/perf/util/sort.c +++ b/tools/perf/util/sort.c @@ -170,7 +170,7 @@ static int hist_entry__dso_snprintf(struct hist_entry *self, char *bf, return repsep_snprintf(bf, size, "%-*s", width, dso_name); } - return repsep_snprintf(bf, size, "%*Lx", width, self->ip); + return repsep_snprintf(bf, size, "%-*s", width, "[unknown]"); } /* --sort symbol */ @@ -196,7 +196,7 @@ static int hist_entry__sym_snprintf(struct hist_entry *self, char *bf, if (verbose) { char o = self->ms.map ? dso__symtab_origin(self->ms.map->dso) : '!'; - ret += repsep_snprintf(bf, size, "%*Lx %c ", + ret += repsep_snprintf(bf, size, "%-#*llx %c ", BITS_PER_LONG / 4, self->ip, o); } @@ -205,7 +205,7 @@ static int hist_entry__sym_snprintf(struct hist_entry *self, char *bf, ret += repsep_snprintf(bf + ret, size - ret, "%s", self->ms.sym->name); else - ret += repsep_snprintf(bf + ret, size - ret, "%*Lx", + ret += repsep_snprintf(bf + ret, size - ret, "%-#*llx", BITS_PER_LONG / 4, self->ip); return ret; diff --git a/tools/perf/util/symbol.c b/tools/perf/util/symbol.c index 439ab947daf..15ccfba8cdf 100644 --- a/tools/perf/util/symbol.c +++ b/tools/perf/util/symbol.c @@ -22,6 +22,10 @@ #include <limits.h> #include <sys/utsname.h> +#ifndef KSYM_NAME_LEN +#define KSYM_NAME_LEN 128 +#endif + #ifndef NT_GNU_BUILD_ID #define NT_GNU_BUILD_ID 3 #endif @@ -41,6 +45,7 @@ struct symbol_conf symbol_conf = { .exclude_other = true, .use_modules = true, .try_vmlinux_path = true, + .symfs = "", }; int dso__name_len(const struct dso *self) @@ -92,7 +97,7 @@ static void symbols__fixup_end(struct rb_root *self) prev = curr; curr = rb_entry(nd, struct symbol, rb_node); - if (prev->end == prev->start) + if (prev->end == prev->start && prev->end != curr->start) prev->end = curr->start - 1; } @@ -121,7 +126,7 @@ static void __map_groups__fixup_end(struct map_groups *self, enum map_type type) * We still haven't the actual symbols, so guess the * last map final address. */ - curr->end = ~0UL; + curr->end = ~0ULL; } static void map_groups__fixup_end(struct map_groups *self) @@ -425,16 +430,25 @@ size_t dso__fprintf(struct dso *self, enum map_type type, FILE *fp) int kallsyms__parse(const char *filename, void *arg, int (*process_symbol)(void *arg, const char *name, - char type, u64 start)) + char type, u64 start, u64 end)) { char *line = NULL; size_t n; - int err = 0; + int err = -1; + u64 prev_start = 0; + char prev_symbol_type = 0; + char *prev_symbol_name; FILE *file = fopen(filename, "r"); if (file == NULL) goto out_failure; + prev_symbol_name = malloc(KSYM_NAME_LEN); + if (prev_symbol_name == NULL) + goto out_close; + + err = 0; + while (!feof(file)) { u64 start; int line_len, len; @@ -454,14 +468,33 @@ int kallsyms__parse(const char *filename, void *arg, continue; symbol_type = toupper(line[len]); - symbol_name = line + len + 2; + len += 2; + symbol_name = line + len; + len = line_len - len; - err = process_symbol(arg, symbol_name, symbol_type, start); - if (err) + if (len >= KSYM_NAME_LEN) { + err = -1; break; + } + + if (prev_symbol_type) { + u64 end = start; + if (end != prev_start) + --end; + err = process_symbol(arg, prev_symbol_name, + prev_symbol_type, prev_start, end); + if (err) + break; + } + + memcpy(prev_symbol_name, symbol_name, len + 1); + prev_symbol_type = symbol_type; + prev_start = start; } + free(prev_symbol_name); free(line); +out_close: fclose(file); return err; @@ -483,7 +516,7 @@ static u8 kallsyms2elf_type(char type) } static int map__process_kallsym_symbol(void *arg, const char *name, - char type, u64 start) + char type, u64 start, u64 end) { struct symbol *sym; struct process_kallsyms_args *a = arg; @@ -492,11 +525,8 @@ static int map__process_kallsym_symbol(void *arg, const char *name, if (!symbol_type__is_a(type, a->map->type)) return 0; - /* - * Will fix up the end later, when we have all symbols sorted. - */ - sym = symbol__new(start, 0, kallsyms2elf_type(type), name); - + sym = symbol__new(start, end - start + 1, + kallsyms2elf_type(type), name); if (sym == NULL) return -ENOMEM; /* @@ -649,7 +679,6 @@ int dso__load_kallsyms(struct dso *self, const char *filename, if (dso__load_all_kallsyms(self, filename, map) < 0) return -1; - symbols__fixup_end(&self->symbols[map->type]); if (self->kernel == DSO_TYPE_GUEST_KERNEL) self->origin = DSO__ORIG_GUEST_KERNEL; else @@ -839,8 +868,11 @@ static int dso__synthesize_plt_symbols(struct dso *self, struct map *map, char sympltname[1024]; Elf *elf; int nr = 0, symidx, fd, err = 0; + char name[PATH_MAX]; - fd = open(self->long_name, O_RDONLY); + snprintf(name, sizeof(name), "%s%s", + symbol_conf.symfs, self->long_name); + fd = open(name, O_RDONLY); if (fd < 0) goto out; @@ -1452,16 +1484,19 @@ int dso__load(struct dso *self, struct map *map, symbol_filter_t filter) self->origin++) { switch (self->origin) { case DSO__ORIG_BUILD_ID_CACHE: - if (dso__build_id_filename(self, name, size) == NULL) + /* skip the locally configured cache if a symfs is given */ + if (symbol_conf.symfs[0] || + (dso__build_id_filename(self, name, size) == NULL)) { continue; + } break; case DSO__ORIG_FEDORA: - snprintf(name, size, "/usr/lib/debug%s.debug", - self->long_name); + snprintf(name, size, "%s/usr/lib/debug%s.debug", + symbol_conf.symfs, self->long_name); break; case DSO__ORIG_UBUNTU: - snprintf(name, size, "/usr/lib/debug%s", - self->long_name); + snprintf(name, size, "%s/usr/lib/debug%s", + symbol_conf.symfs, self->long_name); break; case DSO__ORIG_BUILDID: { char build_id_hex[BUILD_ID_SIZE * 2 + 1]; @@ -1473,19 +1508,26 @@ int dso__load(struct dso *self, struct map *map, symbol_filter_t filter) sizeof(self->build_id), build_id_hex); snprintf(name, size, - "/usr/lib/debug/.build-id/%.2s/%s.debug", - build_id_hex, build_id_hex + 2); + "%s/usr/lib/debug/.build-id/%.2s/%s.debug", + symbol_conf.symfs, build_id_hex, build_id_hex + 2); } break; case DSO__ORIG_DSO: - snprintf(name, size, "%s", self->long_name); + snprintf(name, size, "%s%s", + symbol_conf.symfs, self->long_name); break; case DSO__ORIG_GUEST_KMODULE: if (map->groups && map->groups->machine) root_dir = map->groups->machine->root_dir; else root_dir = ""; - snprintf(name, size, "%s%s", root_dir, self->long_name); + snprintf(name, size, "%s%s%s", symbol_conf.symfs, + root_dir, self->long_name); + break; + + case DSO__ORIG_KMODULE: + snprintf(name, size, "%s%s", symbol_conf.symfs, + self->long_name); break; default: @@ -1784,17 +1826,20 @@ int dso__load_vmlinux(struct dso *self, struct map *map, const char *vmlinux, symbol_filter_t filter) { int err = -1, fd; + char symfs_vmlinux[PATH_MAX]; - fd = open(vmlinux, O_RDONLY); + snprintf(symfs_vmlinux, sizeof(symfs_vmlinux), "%s/%s", + symbol_conf.symfs, vmlinux); + fd = open(symfs_vmlinux, O_RDONLY); if (fd < 0) return -1; dso__set_loaded(self, map->type); - err = dso__load_sym(self, map, vmlinux, fd, filter, 0, 0); + err = dso__load_sym(self, map, symfs_vmlinux, fd, filter, 0, 0); close(fd); if (err > 0) - pr_debug("Using %s for symbols\n", vmlinux); + pr_debug("Using %s for symbols\n", symfs_vmlinux); return err; } @@ -1836,8 +1881,8 @@ static int dso__load_kernel_sym(struct dso *self, struct map *map, const char *kallsyms_filename = NULL; char *kallsyms_allocated_filename = NULL; /* - * Step 1: if the user specified a vmlinux filename, use it and only - * it, reporting errors to the user if it cannot be used. + * Step 1: if the user specified a kallsyms or vmlinux filename, use + * it and only it, reporting errors to the user if it cannot be used. * * For instance, try to analyse an ARM perf.data file _without_ a * build-id, or if the user specifies the wrong path to the right @@ -1850,6 +1895,11 @@ static int dso__load_kernel_sym(struct dso *self, struct map *map, * validation in dso__load_vmlinux and will bail out if they don't * match. */ + if (symbol_conf.kallsyms_name != NULL) { + kallsyms_filename = symbol_conf.kallsyms_name; + goto do_kallsyms; + } + if (symbol_conf.vmlinux_name != NULL) { err = dso__load_vmlinux(self, map, symbol_conf.vmlinux_name, filter); @@ -1867,6 +1917,10 @@ static int dso__load_kernel_sym(struct dso *self, struct map *map, goto out_fixup; } + /* do not try local files if a symfs was given */ + if (symbol_conf.symfs[0] != 0) + return -1; + /* * Say the kernel DSO was created when processing the build-id header table, * we have a build-id, so check if it is the same as the running kernel, @@ -2136,7 +2190,7 @@ struct process_args { }; static int symbol__in_kernel(void *arg, const char *name, - char type __used, u64 start) + char type __used, u64 start, u64 end __used) { struct process_args *args = arg; @@ -2257,9 +2311,6 @@ static int vmlinux_path__init(void) struct utsname uts; char bf[PATH_MAX]; - if (uname(&uts) < 0) - return -1; - vmlinux_path = malloc(sizeof(char *) * 5); if (vmlinux_path == NULL) return -1; @@ -2272,6 +2323,14 @@ static int vmlinux_path__init(void) if (vmlinux_path[vmlinux_path__nr_entries] == NULL) goto out_fail; ++vmlinux_path__nr_entries; + + /* only try running kernel version if no symfs was given */ + if (symbol_conf.symfs[0] != 0) + return 0; + + if (uname(&uts) < 0) + return -1; + snprintf(bf, sizeof(bf), "/boot/vmlinux-%s", uts.release); vmlinux_path[vmlinux_path__nr_entries] = strdup(bf); if (vmlinux_path[vmlinux_path__nr_entries] == NULL) @@ -2331,6 +2390,8 @@ static int setup_list(struct strlist **list, const char *list_str, int symbol__init(void) { + const char *symfs; + if (symbol_conf.initialized) return 0; @@ -2359,6 +2420,18 @@ int symbol__init(void) symbol_conf.sym_list_str, "symbol") < 0) goto out_free_comm_list; + /* + * A path to symbols of "/" is identical to "" + * reset here for simplicity. + */ + symfs = realpath(symbol_conf.symfs, NULL); + if (symfs == NULL) + symfs = symbol_conf.symfs; + if (strcmp(symfs, "/") == 0) + symbol_conf.symfs = ""; + if (symfs != symbol_conf.symfs) + free((void *)symfs); + symbol_conf.initialized = true; return 0; diff --git a/tools/perf/util/symbol.h b/tools/perf/util/symbol.h index 6c6eafdb932..670cd1c88f5 100644 --- a/tools/perf/util/symbol.h +++ b/tools/perf/util/symbol.h @@ -72,6 +72,7 @@ struct symbol_conf { show_cpu_utilization, initialized; const char *vmlinux_name, + *kallsyms_name, *source_prefix, *field_sep; const char *default_guest_vmlinux_name, @@ -85,6 +86,7 @@ struct symbol_conf { struct strlist *dso_list, *comm_list, *sym_list; + const char *symfs; }; extern struct symbol_conf symbol_conf; @@ -215,7 +217,7 @@ bool __dsos__read_build_ids(struct list_head *head, bool with_hits); int build_id__sprintf(const u8 *self, int len, char *bf); int kallsyms__parse(const char *filename, void *arg, int (*process_symbol)(void *arg, const char *name, - char type, u64 start)); + char type, u64 start, u64 end)); void machine__destroy_kernel_maps(struct machine *self); int __machine__create_kernel_maps(struct machine *self, struct dso *kernel); diff --git a/tools/perf/util/thread.c b/tools/perf/util/thread.c index 8c72d888e44..00f4eade2e3 100644 --- a/tools/perf/util/thread.c +++ b/tools/perf/util/thread.c @@ -16,35 +16,50 @@ static int filter(const struct dirent *dir) return 1; } -int find_all_tid(int pid, pid_t ** all_tid) +struct thread_map *thread_map__new_by_pid(pid_t pid) { + struct thread_map *threads; char name[256]; int items; struct dirent **namelist = NULL; - int ret = 0; int i; sprintf(name, "/proc/%d/task", pid); items = scandir(name, &namelist, filter, NULL); if (items <= 0) - return -ENOENT; - *all_tid = malloc(sizeof(pid_t) * items); - if (!*all_tid) { - ret = -ENOMEM; - goto failure; - } - - for (i = 0; i < items; i++) - (*all_tid)[i] = atoi(namelist[i]->d_name); + return NULL; - ret = items; + threads = malloc(sizeof(*threads) + sizeof(pid_t) * items); + if (threads != NULL) { + for (i = 0; i < items; i++) + threads->map[i] = atoi(namelist[i]->d_name); + threads->nr = items; + } -failure: for (i=0; i<items; i++) free(namelist[i]); free(namelist); - return ret; + return threads; +} + +struct thread_map *thread_map__new_by_tid(pid_t tid) +{ + struct thread_map *threads = malloc(sizeof(*threads) + sizeof(pid_t)); + + if (threads != NULL) { + threads->map[0] = tid; + threads->nr = 1; + } + + return threads; +} + +struct thread_map *thread_map__new(pid_t pid, pid_t tid) +{ + if (pid != -1) + return thread_map__new_by_pid(pid); + return thread_map__new_by_tid(tid); } static struct thread *thread__new(pid_t pid) diff --git a/tools/perf/util/thread.h b/tools/perf/util/thread.h index 688500ff826..d7574101054 100644 --- a/tools/perf/util/thread.h +++ b/tools/perf/util/thread.h @@ -18,11 +18,24 @@ struct thread { int comm_len; }; +struct thread_map { + int nr; + int map[]; +}; + struct perf_session; void thread__delete(struct thread *self); -int find_all_tid(int pid, pid_t ** all_tid); +struct thread_map *thread_map__new_by_pid(pid_t pid); +struct thread_map *thread_map__new_by_tid(pid_t tid); +struct thread_map *thread_map__new(pid_t pid, pid_t tid); + +static inline void thread_map__delete(struct thread_map *threads) +{ + free(threads); +} + int thread__set_comm(struct thread *self, const char *comm); int thread__comm_len(struct thread *self); struct thread *perf_session__findnew(struct perf_session *self, pid_t pid); diff --git a/tools/perf/util/trace-event-info.c b/tools/perf/util/trace-event-info.c index b1572601286..35729f4c40c 100644 --- a/tools/perf/util/trace-event-info.c +++ b/tools/perf/util/trace-event-info.c @@ -34,11 +34,13 @@ #include <ctype.h> #include <errno.h> #include <stdbool.h> +#include <linux/list.h> #include <linux/kernel.h> #include "../perf.h" #include "trace-event.h" #include "debugfs.h" +#include "evsel.h" #define VERSION "0.5" @@ -469,16 +471,17 @@ out: } static struct tracepoint_path * -get_tracepoints_path(struct perf_event_attr *pattrs, int nb_events) +get_tracepoints_path(struct list_head *pattrs) { struct tracepoint_path path, *ppath = &path; - int i, nr_tracepoints = 0; + struct perf_evsel *pos; + int nr_tracepoints = 0; - for (i = 0; i < nb_events; i++) { - if (pattrs[i].type != PERF_TYPE_TRACEPOINT) + list_for_each_entry(pos, pattrs, node) { + if (pos->attr.type != PERF_TYPE_TRACEPOINT) continue; ++nr_tracepoints; - ppath->next = tracepoint_id_to_path(pattrs[i].config); + ppath->next = tracepoint_id_to_path(pos->attr.config); if (!ppath->next) die("%s\n", "No memory to alloc tracepoints list"); ppath = ppath->next; @@ -487,21 +490,21 @@ get_tracepoints_path(struct perf_event_attr *pattrs, int nb_events) return nr_tracepoints > 0 ? path.next : NULL; } -bool have_tracepoints(struct perf_event_attr *pattrs, int nb_events) +bool have_tracepoints(struct list_head *pattrs) { - int i; + struct perf_evsel *pos; - for (i = 0; i < nb_events; i++) - if (pattrs[i].type == PERF_TYPE_TRACEPOINT) + list_for_each_entry(pos, pattrs, node) + if (pos->attr.type == PERF_TYPE_TRACEPOINT) return true; return false; } -int read_tracing_data(int fd, struct perf_event_attr *pattrs, int nb_events) +int read_tracing_data(int fd, struct list_head *pattrs) { char buf[BUFSIZ]; - struct tracepoint_path *tps = get_tracepoints_path(pattrs, nb_events); + struct tracepoint_path *tps = get_tracepoints_path(pattrs); /* * What? No tracepoints? No sense writing anything here, bail out. @@ -545,14 +548,13 @@ int read_tracing_data(int fd, struct perf_event_attr *pattrs, int nb_events) return 0; } -ssize_t read_tracing_data_size(int fd, struct perf_event_attr *pattrs, - int nb_events) +ssize_t read_tracing_data_size(int fd, struct list_head *pattrs) { ssize_t size; int err = 0; calc_data_size = 1; - err = read_tracing_data(fd, pattrs, nb_events); + err = read_tracing_data(fd, pattrs); size = calc_data_size - 1; calc_data_size = 0; diff --git a/tools/perf/util/trace-event.h b/tools/perf/util/trace-event.h index b3e86b1e444..b5f12ca24d9 100644 --- a/tools/perf/util/trace-event.h +++ b/tools/perf/util/trace-event.h @@ -262,9 +262,8 @@ raw_field_value(struct event *event, const char *name, void *data); void *raw_field_ptr(struct event *event, const char *name, void *data); unsigned long long eval_flag(const char *flag); -int read_tracing_data(int fd, struct perf_event_attr *pattrs, int nb_events); -ssize_t read_tracing_data_size(int fd, struct perf_event_attr *pattrs, - int nb_events); +int read_tracing_data(int fd, struct list_head *pattrs); +ssize_t read_tracing_data_size(int fd, struct list_head *pattrs); /* taken from kernel/trace/trace.h */ enum trace_flag_type { diff --git a/tools/perf/util/ui/util.c b/tools/perf/util/ui/util.c index 056c69521a3..7b5a8926624 100644 --- a/tools/perf/util/ui/util.c +++ b/tools/perf/util/ui/util.c @@ -104,10 +104,24 @@ out_destroy_form: return rc; } -static const char yes[] = "Yes", no[] = "No"; +static const char yes[] = "Yes", no[] = "No", + warning_str[] = "Warning!", ok[] = "Ok"; bool ui__dialog_yesno(const char *msg) { /* newtWinChoice should really be accepting const char pointers... */ return newtWinChoice(NULL, (char *)yes, (char *)no, (char *)msg) == 1; } + +void ui__warning(const char *format, ...) +{ + va_list args; + + va_start(args, format); + if (use_browser > 0) + newtWinMessagev((char *)warning_str, (char *)ok, + (char *)format, args); + else + vfprintf(stderr, format, args); + va_end(args); +} diff --git a/tools/perf/util/util.c b/tools/perf/util/util.c index 214265674dd..5b3ea49aa63 100644 --- a/tools/perf/util/util.c +++ b/tools/perf/util/util.c @@ -114,3 +114,20 @@ unsigned long convert_unit(unsigned long value, char *unit) return value; } + +int readn(int fd, void *buf, size_t n) +{ + void *buf_start = buf; + + while (n) { + int ret = read(fd, buf, n); + + if (ret <= 0) + return ret; + + n -= ret; + buf += ret; + } + + return buf - buf_start; +} diff --git a/tools/perf/util/util.h b/tools/perf/util/util.h index 7562707ddd1..e833f26f3bf 100644 --- a/tools/perf/util/util.h +++ b/tools/perf/util/util.h @@ -265,6 +265,7 @@ void argv_free(char **argv); bool strglobmatch(const char *str, const char *pat); bool strlazymatch(const char *str, const char *pat); unsigned long convert_unit(unsigned long value, char *unit); +int readn(int fd, void *buf, size_t size); #define _STR(x) #x #define STR(x) _STR(x) diff --git a/tools/perf/util/xyarray.c b/tools/perf/util/xyarray.c new file mode 100644 index 00000000000..22afbf6c536 --- /dev/null +++ b/tools/perf/util/xyarray.c @@ -0,0 +1,20 @@ +#include "xyarray.h" +#include "util.h" + +struct xyarray *xyarray__new(int xlen, int ylen, size_t entry_size) +{ + size_t row_size = ylen * entry_size; + struct xyarray *xy = zalloc(sizeof(*xy) + xlen * row_size); + + if (xy != NULL) { + xy->entry_size = entry_size; + xy->row_size = row_size; + } + + return xy; +} + +void xyarray__delete(struct xyarray *xy) +{ + free(xy); +} diff --git a/tools/perf/util/xyarray.h b/tools/perf/util/xyarray.h new file mode 100644 index 00000000000..c488a07275d --- /dev/null +++ b/tools/perf/util/xyarray.h @@ -0,0 +1,20 @@ +#ifndef _PERF_XYARRAY_H_ +#define _PERF_XYARRAY_H_ 1 + +#include <sys/types.h> + +struct xyarray { + size_t row_size; + size_t entry_size; + char contents[]; +}; + +struct xyarray *xyarray__new(int xlen, int ylen, size_t entry_size); +void xyarray__delete(struct xyarray *xy); + +static inline void *xyarray__entry(struct xyarray *xy, int x, int y) +{ + return &xy->contents[x * xy->row_size + y * xy->entry_size]; +} + +#endif /* _PERF_XYARRAY_H_ */ diff --git a/tools/power/x86/turbostat/Makefile b/tools/power/x86/turbostat/Makefile new file mode 100644 index 00000000000..fd8e1f1297a --- /dev/null +++ b/tools/power/x86/turbostat/Makefile @@ -0,0 +1,8 @@ +turbostat : turbostat.c + +clean : + rm -f turbostat + +install : + install turbostat /usr/bin/turbostat + install turbostat.8 /usr/share/man/man8 diff --git a/tools/power/x86/turbostat/turbostat.8 b/tools/power/x86/turbostat/turbostat.8 new file mode 100644 index 00000000000..ff75125deed --- /dev/null +++ b/tools/power/x86/turbostat/turbostat.8 @@ -0,0 +1,172 @@ +.TH TURBOSTAT 8 +.SH NAME +turbostat \- Report processor frequency and idle statistics +.SH SYNOPSIS +.ft B +.B turbostat +.RB [ "\-v" ] +.RB [ "\-M MSR#" ] +.RB command +.br +.B turbostat +.RB [ "\-v" ] +.RB [ "\-M MSR#" ] +.RB [ "\-i interval_sec" ] +.SH DESCRIPTION +\fBturbostat \fP reports processor topology, frequency +and idle power state statistics on modern X86 processors. +Either \fBcommand\fP is forked and statistics are printed +upon its completion, or statistics are printed periodically. + +\fBturbostat \fP +requires that the processor +supports an "invariant" TSC, plus the APERF and MPERF MSRs. +\fBturbostat \fP will report idle cpu power state residency +on processors that additionally support C-state residency counters. + +.SS Options +The \fB-v\fP option increases verbosity. +.PP +The \fB-M MSR#\fP option dumps the specified MSR, +in addition to the usual frequency and idle statistics. +.PP +The \fB-i interval_sec\fP option prints statistics every \fiinterval_sec\fP seconds. +The default is 5 seconds. +.PP +The \fBcommand\fP parameter forks \fBcommand\fP and upon its exit, +displays the statistics gathered since it was forked. +.PP +.SH FIELD DESCRIPTIONS +.nf +\fBpkg\fP processor package number. +\fBcore\fP processor core number. +\fBCPU\fP Linux CPU (logical processor) number. +\fB%c0\fP percent of the interval that the CPU retired instructions. +\fBGHz\fP average clock rate while the CPU was in c0 state. +\fBTSC\fP average GHz that the TSC ran during the entire interval. +\fB%c1, %c3, %c6\fP show the percentage residency in hardware core idle states. +\fB%pc3, %pc6\fP percentage residency in hardware package idle states. +.fi +.PP +.SH EXAMPLE +Without any parameters, turbostat prints out counters ever 5 seconds. +(override interval with "-i sec" option, or specify a command +for turbostat to fork). + +The first row of statistics reflect the average for the entire system. +Subsequent rows show per-CPU statistics. + +.nf +[root@x980]# ./turbostat +core CPU %c0 GHz TSC %c1 %c3 %c6 %pc3 %pc6 + 0.04 1.62 3.38 0.11 0.00 99.85 0.00 95.07 + 0 0 0.04 1.62 3.38 0.06 0.00 99.90 0.00 95.07 + 0 6 0.02 1.62 3.38 0.08 0.00 99.90 0.00 95.07 + 1 2 0.10 1.62 3.38 0.29 0.00 99.61 0.00 95.07 + 1 8 0.11 1.62 3.38 0.28 0.00 99.61 0.00 95.07 + 2 4 0.01 1.62 3.38 0.01 0.00 99.98 0.00 95.07 + 2 10 0.01 1.61 3.38 0.02 0.00 99.98 0.00 95.07 + 8 1 0.07 1.62 3.38 0.15 0.00 99.78 0.00 95.07 + 8 7 0.03 1.62 3.38 0.19 0.00 99.78 0.00 95.07 + 9 3 0.01 1.62 3.38 0.02 0.00 99.98 0.00 95.07 + 9 9 0.01 1.62 3.38 0.02 0.00 99.98 0.00 95.07 + 10 5 0.01 1.62 3.38 0.13 0.00 99.86 0.00 95.07 + 10 11 0.08 1.62 3.38 0.05 0.00 99.86 0.00 95.07 +.fi +.SH VERBOSE EXAMPLE +The "-v" option adds verbosity to the output: + +.nf +GenuineIntel 11 CPUID levels; family:model:stepping 0x6:2c:2 (6:44:2) +12 * 133 = 1600 MHz max efficiency +25 * 133 = 3333 MHz TSC frequency +26 * 133 = 3467 MHz max turbo 4 active cores +26 * 133 = 3467 MHz max turbo 3 active cores +27 * 133 = 3600 MHz max turbo 2 active cores +27 * 133 = 3600 MHz max turbo 1 active cores + +.fi +The \fBmax efficiency\fP frequency, a.k.a. Low Frequency Mode, is the frequency +available at the minimum package voltage. The \fBTSC frequency\fP is the nominal +maximum frequency of the processor if turbo-mode were not available. This frequency +should be sustainable on all CPUs indefinitely, given nominal power and cooling. +The remaining rows show what maximum turbo frequency is possible +depending on the number of idle cores. Note that this information is +not available on all processors. +.SH FORK EXAMPLE +If turbostat is invoked with a command, it will fork that command +and output the statistics gathered when the command exits. +eg. Here a cycle soaker is run on 1 CPU (see %c0) for a few seconds +until ^C while the other CPUs are mostly idle: + +.nf +[root@x980 lenb]# ./turbostat cat /dev/zero > /dev/null + +^Ccore CPU %c0 GHz TSC %c1 %c3 %c6 %pc3 %pc6 + 8.49 3.63 3.38 16.23 0.66 74.63 0.00 0.00 + 0 0 1.22 3.62 3.38 32.18 0.00 66.60 0.00 0.00 + 0 6 0.40 3.61 3.38 33.00 0.00 66.60 0.00 0.00 + 1 2 0.11 3.14 3.38 0.19 3.95 95.75 0.00 0.00 + 1 8 0.05 2.88 3.38 0.25 3.95 95.75 0.00 0.00 + 2 4 0.00 3.13 3.38 0.02 0.00 99.98 0.00 0.00 + 2 10 0.00 3.09 3.38 0.02 0.00 99.98 0.00 0.00 + 8 1 0.04 3.50 3.38 14.43 0.00 85.54 0.00 0.00 + 8 7 0.03 2.98 3.38 14.43 0.00 85.54 0.00 0.00 + 9 3 0.00 3.16 3.38 100.00 0.00 0.00 0.00 0.00 + 9 9 99.93 3.63 3.38 0.06 0.00 0.00 0.00 0.00 + 10 5 0.01 2.82 3.38 0.08 0.00 99.91 0.00 0.00 + 10 11 0.02 3.36 3.38 0.06 0.00 99.91 0.00 0.00 +6.950866 sec + +.fi +Above the cycle soaker drives cpu9 up 3.6 Ghz turbo limit +while the other processors are generally in various states of idle. + +Note that cpu3 is an HT sibling sharing core9 +with cpu9, and thus it is unable to get to an idle state +deeper than c1 while cpu9 is busy. + +Note that turbostat reports average GHz of 3.61, while +the arithmetic average of the GHz column above is 3.24. +This is a weighted average, where the weight is %c0. ie. it is the total number of +un-halted cycles elapsed per time divided by the number of CPUs. +.SH NOTES + +.B "turbostat " +must be run as root. + +.B "turbostat " +reads hardware counters, but doesn't write them. +So it will not interfere with the OS or other programs, including +multiple invocations of itself. + +\fBturbostat \fP +may work poorly on Linux-2.6.20 through 2.6.29, +as \fBacpi-cpufreq \fPperiodically cleared the APERF and MPERF +in those kernels. + +The APERF, MPERF MSRs are defined to count non-halted cycles. +Although it is not guaranteed by the architecture, turbostat assumes +that they count at TSC rate, which is true on all processors tested to date. + +.SH REFERENCES +"IntelÂŽ Turbo Boost Technology +in IntelÂŽ Core⢠Microarchitecture (Nehalem) Based Processors" +http://download.intel.com/design/processor/applnots/320354.pdf + +"IntelÂŽ 64 and IA-32 Architectures Software Developer's Manual +Volume 3B: System Programming Guide" +http://www.intel.com/products/processor/manuals/ + +.SH FILES +.ta +.nf +/dev/cpu/*/msr +.fi + +.SH "SEE ALSO" +msr(4), vmstat(8) +.PP +.SH AUTHORS +.nf +Written by Len Brown <len.brown@intel.com> diff --git a/tools/power/x86/turbostat/turbostat.c b/tools/power/x86/turbostat/turbostat.c new file mode 100644 index 00000000000..4c6983de6fd --- /dev/null +++ b/tools/power/x86/turbostat/turbostat.c @@ -0,0 +1,1048 @@ +/* + * turbostat -- show CPU frequency and C-state residency + * on modern Intel turbo-capable processors. + * + * Copyright (c) 2010, Intel Corporation. + * Len Brown <len.brown@intel.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/wait.h> +#include <sys/stat.h> +#include <sys/resource.h> +#include <fcntl.h> +#include <signal.h> +#include <sys/time.h> +#include <stdlib.h> +#include <dirent.h> +#include <string.h> +#include <ctype.h> + +#define MSR_TSC 0x10 +#define MSR_NEHALEM_PLATFORM_INFO 0xCE +#define MSR_NEHALEM_TURBO_RATIO_LIMIT 0x1AD +#define MSR_APERF 0xE8 +#define MSR_MPERF 0xE7 +#define MSR_PKG_C2_RESIDENCY 0x60D /* SNB only */ +#define MSR_PKG_C3_RESIDENCY 0x3F8 +#define MSR_PKG_C6_RESIDENCY 0x3F9 +#define MSR_PKG_C7_RESIDENCY 0x3FA /* SNB only */ +#define MSR_CORE_C3_RESIDENCY 0x3FC +#define MSR_CORE_C6_RESIDENCY 0x3FD +#define MSR_CORE_C7_RESIDENCY 0x3FE /* SNB only */ + +char *proc_stat = "/proc/stat"; +unsigned int interval_sec = 5; /* set with -i interval_sec */ +unsigned int verbose; /* set with -v */ +unsigned int skip_c0; +unsigned int skip_c1; +unsigned int do_nhm_cstates; +unsigned int do_snb_cstates; +unsigned int has_aperf; +unsigned int units = 1000000000; /* Ghz etc */ +unsigned int genuine_intel; +unsigned int has_invariant_tsc; +unsigned int do_nehalem_platform_info; +unsigned int do_nehalem_turbo_ratio_limit; +unsigned int extra_msr_offset; +double bclk; +unsigned int show_pkg; +unsigned int show_core; +unsigned int show_cpu; + +int aperf_mperf_unstable; +int backwards_count; +char *progname; +int need_reinitialize; + +int num_cpus; + +typedef struct per_cpu_counters { + unsigned long long tsc; /* per thread */ + unsigned long long aperf; /* per thread */ + unsigned long long mperf; /* per thread */ + unsigned long long c1; /* per thread (calculated) */ + unsigned long long c3; /* per core */ + unsigned long long c6; /* per core */ + unsigned long long c7; /* per core */ + unsigned long long pc2; /* per package */ + unsigned long long pc3; /* per package */ + unsigned long long pc6; /* per package */ + unsigned long long pc7; /* per package */ + unsigned long long extra_msr; /* per thread */ + int pkg; + int core; + int cpu; + struct per_cpu_counters *next; +} PCC; + +PCC *pcc_even; +PCC *pcc_odd; +PCC *pcc_delta; +PCC *pcc_average; +struct timeval tv_even; +struct timeval tv_odd; +struct timeval tv_delta; + +unsigned long long get_msr(int cpu, off_t offset) +{ + ssize_t retval; + unsigned long long msr; + char pathname[32]; + int fd; + + sprintf(pathname, "/dev/cpu/%d/msr", cpu); + fd = open(pathname, O_RDONLY); + if (fd < 0) { + perror(pathname); + need_reinitialize = 1; + return 0; + } + + retval = pread(fd, &msr, sizeof msr, offset); + if (retval != sizeof msr) { + fprintf(stderr, "cpu%d pread(..., 0x%zx) = %jd\n", + cpu, offset, retval); + exit(-2); + } + + close(fd); + return msr; +} + +void print_header() +{ + if (show_pkg) + fprintf(stderr, "pkg "); + if (show_core) + fprintf(stderr, "core"); + if (show_cpu) + fprintf(stderr, " CPU"); + if (do_nhm_cstates) + fprintf(stderr, " %%c0 "); + if (has_aperf) + fprintf(stderr, " GHz"); + fprintf(stderr, " TSC"); + if (do_nhm_cstates) + fprintf(stderr, " %%c1 "); + if (do_nhm_cstates) + fprintf(stderr, " %%c3 "); + if (do_nhm_cstates) + fprintf(stderr, " %%c6 "); + if (do_snb_cstates) + fprintf(stderr, " %%c7 "); + if (do_snb_cstates) + fprintf(stderr, " %%pc2 "); + if (do_nhm_cstates) + fprintf(stderr, " %%pc3 "); + if (do_nhm_cstates) + fprintf(stderr, " %%pc6 "); + if (do_snb_cstates) + fprintf(stderr, " %%pc7 "); + if (extra_msr_offset) + fprintf(stderr, " MSR 0x%x ", extra_msr_offset); + + putc('\n', stderr); +} + +void dump_pcc(PCC *pcc) +{ + fprintf(stderr, "package: %d ", pcc->pkg); + fprintf(stderr, "core:: %d ", pcc->core); + fprintf(stderr, "CPU: %d ", pcc->cpu); + fprintf(stderr, "TSC: %016llX\n", pcc->tsc); + fprintf(stderr, "c3: %016llX\n", pcc->c3); + fprintf(stderr, "c6: %016llX\n", pcc->c6); + fprintf(stderr, "c7: %016llX\n", pcc->c7); + fprintf(stderr, "aperf: %016llX\n", pcc->aperf); + fprintf(stderr, "pc2: %016llX\n", pcc->pc2); + fprintf(stderr, "pc3: %016llX\n", pcc->pc3); + fprintf(stderr, "pc6: %016llX\n", pcc->pc6); + fprintf(stderr, "pc7: %016llX\n", pcc->pc7); + fprintf(stderr, "msr0x%x: %016llX\n", extra_msr_offset, pcc->extra_msr); +} + +void dump_list(PCC *pcc) +{ + printf("dump_list 0x%p\n", pcc); + + for (; pcc; pcc = pcc->next) + dump_pcc(pcc); +} + +void print_pcc(PCC *p) +{ + double interval_float; + + interval_float = tv_delta.tv_sec + tv_delta.tv_usec/1000000.0; + + /* topology columns, print blanks on 1st (average) line */ + if (p == pcc_average) { + if (show_pkg) + fprintf(stderr, " "); + if (show_core) + fprintf(stderr, " "); + if (show_cpu) + fprintf(stderr, " "); + } else { + if (show_pkg) + fprintf(stderr, "%4d", p->pkg); + if (show_core) + fprintf(stderr, "%4d", p->core); + if (show_cpu) + fprintf(stderr, "%4d", p->cpu); + } + + /* %c0 */ + if (do_nhm_cstates) { + if (!skip_c0) + fprintf(stderr, "%7.2f", 100.0 * p->mperf/p->tsc); + else + fprintf(stderr, " ****"); + } + + /* GHz */ + if (has_aperf) { + if (!aperf_mperf_unstable) { + fprintf(stderr, "%5.2f", + 1.0 * p->tsc / units * p->aperf / + p->mperf / interval_float); + } else { + if (p->aperf > p->tsc || p->mperf > p->tsc) { + fprintf(stderr, " ****"); + } else { + fprintf(stderr, "%4.1f*", + 1.0 * p->tsc / + units * p->aperf / + p->mperf / interval_float); + } + } + } + + /* TSC */ + fprintf(stderr, "%5.2f", 1.0 * p->tsc/units/interval_float); + + if (do_nhm_cstates) { + if (!skip_c1) + fprintf(stderr, "%7.2f", 100.0 * p->c1/p->tsc); + else + fprintf(stderr, " ****"); + } + if (do_nhm_cstates) + fprintf(stderr, "%7.2f", 100.0 * p->c3/p->tsc); + if (do_nhm_cstates) + fprintf(stderr, "%7.2f", 100.0 * p->c6/p->tsc); + if (do_snb_cstates) + fprintf(stderr, "%7.2f", 100.0 * p->c7/p->tsc); + if (do_snb_cstates) + fprintf(stderr, "%7.2f", 100.0 * p->pc2/p->tsc); + if (do_nhm_cstates) + fprintf(stderr, "%7.2f", 100.0 * p->pc3/p->tsc); + if (do_nhm_cstates) + fprintf(stderr, "%7.2f", 100.0 * p->pc6/p->tsc); + if (do_snb_cstates) + fprintf(stderr, "%7.2f", 100.0 * p->pc7/p->tsc); + if (extra_msr_offset) + fprintf(stderr, " 0x%016llx", p->extra_msr); + putc('\n', stderr); +} + +void print_counters(PCC *cnt) +{ + PCC *pcc; + + print_header(); + + if (num_cpus > 1) + print_pcc(pcc_average); + + for (pcc = cnt; pcc != NULL; pcc = pcc->next) + print_pcc(pcc); + +} + +#define SUBTRACT_COUNTER(after, before, delta) (delta = (after - before), (before > after)) + + +int compute_delta(PCC *after, PCC *before, PCC *delta) +{ + int errors = 0; + int perf_err = 0; + + skip_c0 = skip_c1 = 0; + + for ( ; after && before && delta; + after = after->next, before = before->next, delta = delta->next) { + if (before->cpu != after->cpu) { + printf("cpu configuration changed: %d != %d\n", + before->cpu, after->cpu); + return -1; + } + + if (SUBTRACT_COUNTER(after->tsc, before->tsc, delta->tsc)) { + fprintf(stderr, "cpu%d TSC went backwards %llX to %llX\n", + before->cpu, before->tsc, after->tsc); + errors++; + } + /* check for TSC < 1 Mcycles over interval */ + if (delta->tsc < (1000 * 1000)) { + fprintf(stderr, "Insanely slow TSC rate," + " TSC stops in idle?\n"); + fprintf(stderr, "You can disable all c-states" + " by booting with \"idle=poll\"\n"); + fprintf(stderr, "or just the deep ones with" + " \"processor.max_cstate=1\"\n"); + exit(-3); + } + if (SUBTRACT_COUNTER(after->c3, before->c3, delta->c3)) { + fprintf(stderr, "cpu%d c3 counter went backwards %llX to %llX\n", + before->cpu, before->c3, after->c3); + errors++; + } + if (SUBTRACT_COUNTER(after->c6, before->c6, delta->c6)) { + fprintf(stderr, "cpu%d c6 counter went backwards %llX to %llX\n", + before->cpu, before->c6, after->c6); + errors++; + } + if (SUBTRACT_COUNTER(after->c7, before->c7, delta->c7)) { + fprintf(stderr, "cpu%d c7 counter went backwards %llX to %llX\n", + before->cpu, before->c7, after->c7); + errors++; + } + if (SUBTRACT_COUNTER(after->pc2, before->pc2, delta->pc2)) { + fprintf(stderr, "cpu%d pc2 counter went backwards %llX to %llX\n", + before->cpu, before->pc2, after->pc2); + errors++; + } + if (SUBTRACT_COUNTER(after->pc3, before->pc3, delta->pc3)) { + fprintf(stderr, "cpu%d pc3 counter went backwards %llX to %llX\n", + before->cpu, before->pc3, after->pc3); + errors++; + } + if (SUBTRACT_COUNTER(after->pc6, before->pc6, delta->pc6)) { + fprintf(stderr, "cpu%d pc6 counter went backwards %llX to %llX\n", + before->cpu, before->pc6, after->pc6); + errors++; + } + if (SUBTRACT_COUNTER(after->pc7, before->pc7, delta->pc7)) { + fprintf(stderr, "cpu%d pc7 counter went backwards %llX to %llX\n", + before->cpu, before->pc7, after->pc7); + errors++; + } + + perf_err = SUBTRACT_COUNTER(after->aperf, before->aperf, delta->aperf); + if (perf_err) { + fprintf(stderr, "cpu%d aperf counter went backwards %llX to %llX\n", + before->cpu, before->aperf, after->aperf); + } + perf_err |= SUBTRACT_COUNTER(after->mperf, before->mperf, delta->mperf); + if (perf_err) { + fprintf(stderr, "cpu%d mperf counter went backwards %llX to %llX\n", + before->cpu, before->mperf, after->mperf); + } + if (perf_err) { + if (!aperf_mperf_unstable) { + fprintf(stderr, "%s: APERF or MPERF went backwards *\n", progname); + fprintf(stderr, "* Frequency results do not cover entire interval *\n"); + fprintf(stderr, "* fix this by running Linux-2.6.30 or later *\n"); + + aperf_mperf_unstable = 1; + } + /* + * mperf delta is likely a huge "positive" number + * can not use it for calculating c0 time + */ + skip_c0 = 1; + skip_c1 = 1; + } + + /* + * As mperf and tsc collection are not atomic, + * it is possible for mperf's non-halted cycles + * to exceed TSC's all cycles: show c1 = 0% in that case. + */ + if (delta->mperf > delta->tsc) + delta->c1 = 0; + else /* normal case, derive c1 */ + delta->c1 = delta->tsc - delta->mperf + - delta->c3 - delta->c6 - delta->c7; + + if (delta->mperf == 0) + delta->mperf = 1; /* divide by 0 protection */ + + /* + * for "extra msr", just copy the latest w/o subtracting + */ + delta->extra_msr = after->extra_msr; + if (errors) { + fprintf(stderr, "ERROR cpu%d before:\n", before->cpu); + dump_pcc(before); + fprintf(stderr, "ERROR cpu%d after:\n", before->cpu); + dump_pcc(after); + errors = 0; + } + } + return 0; +} + +void compute_average(PCC *delta, PCC *avg) +{ + PCC *sum; + + sum = calloc(1, sizeof(PCC)); + if (sum == NULL) { + perror("calloc sum"); + exit(1); + } + + for (; delta; delta = delta->next) { + sum->tsc += delta->tsc; + sum->c1 += delta->c1; + sum->c3 += delta->c3; + sum->c6 += delta->c6; + sum->c7 += delta->c7; + sum->aperf += delta->aperf; + sum->mperf += delta->mperf; + sum->pc2 += delta->pc2; + sum->pc3 += delta->pc3; + sum->pc6 += delta->pc6; + sum->pc7 += delta->pc7; + } + avg->tsc = sum->tsc/num_cpus; + avg->c1 = sum->c1/num_cpus; + avg->c3 = sum->c3/num_cpus; + avg->c6 = sum->c6/num_cpus; + avg->c7 = sum->c7/num_cpus; + avg->aperf = sum->aperf/num_cpus; + avg->mperf = sum->mperf/num_cpus; + avg->pc2 = sum->pc2/num_cpus; + avg->pc3 = sum->pc3/num_cpus; + avg->pc6 = sum->pc6/num_cpus; + avg->pc7 = sum->pc7/num_cpus; + + free(sum); +} + +void get_counters(PCC *pcc) +{ + for ( ; pcc; pcc = pcc->next) { + pcc->tsc = get_msr(pcc->cpu, MSR_TSC); + if (do_nhm_cstates) + pcc->c3 = get_msr(pcc->cpu, MSR_CORE_C3_RESIDENCY); + if (do_nhm_cstates) + pcc->c6 = get_msr(pcc->cpu, MSR_CORE_C6_RESIDENCY); + if (do_snb_cstates) + pcc->c7 = get_msr(pcc->cpu, MSR_CORE_C7_RESIDENCY); + if (has_aperf) + pcc->aperf = get_msr(pcc->cpu, MSR_APERF); + if (has_aperf) + pcc->mperf = get_msr(pcc->cpu, MSR_MPERF); + if (do_snb_cstates) + pcc->pc2 = get_msr(pcc->cpu, MSR_PKG_C2_RESIDENCY); + if (do_nhm_cstates) + pcc->pc3 = get_msr(pcc->cpu, MSR_PKG_C3_RESIDENCY); + if (do_nhm_cstates) + pcc->pc6 = get_msr(pcc->cpu, MSR_PKG_C6_RESIDENCY); + if (do_snb_cstates) + pcc->pc7 = get_msr(pcc->cpu, MSR_PKG_C7_RESIDENCY); + if (extra_msr_offset) + pcc->extra_msr = get_msr(pcc->cpu, extra_msr_offset); + } +} + + +void print_nehalem_info() +{ + unsigned long long msr; + unsigned int ratio; + + if (!do_nehalem_platform_info) + return; + + msr = get_msr(0, MSR_NEHALEM_PLATFORM_INFO); + + ratio = (msr >> 40) & 0xFF; + fprintf(stderr, "%d * %.0f = %.0f MHz max efficiency\n", + ratio, bclk, ratio * bclk); + + ratio = (msr >> 8) & 0xFF; + fprintf(stderr, "%d * %.0f = %.0f MHz TSC frequency\n", + ratio, bclk, ratio * bclk); + + if (verbose > 1) + fprintf(stderr, "MSR_NEHALEM_PLATFORM_INFO: 0x%llx\n", msr); + + if (!do_nehalem_turbo_ratio_limit) + return; + + msr = get_msr(0, MSR_NEHALEM_TURBO_RATIO_LIMIT); + + ratio = (msr >> 24) & 0xFF; + if (ratio) + fprintf(stderr, "%d * %.0f = %.0f MHz max turbo 4 active cores\n", + ratio, bclk, ratio * bclk); + + ratio = (msr >> 16) & 0xFF; + if (ratio) + fprintf(stderr, "%d * %.0f = %.0f MHz max turbo 3 active cores\n", + ratio, bclk, ratio * bclk); + + ratio = (msr >> 8) & 0xFF; + if (ratio) + fprintf(stderr, "%d * %.0f = %.0f MHz max turbo 2 active cores\n", + ratio, bclk, ratio * bclk); + + ratio = (msr >> 0) & 0xFF; + if (ratio) + fprintf(stderr, "%d * %.0f = %.0f MHz max turbo 1 active cores\n", + ratio, bclk, ratio * bclk); + +} + +void free_counter_list(PCC *list) +{ + PCC *p; + + for (p = list; p; ) { + PCC *free_me; + + free_me = p; + p = p->next; + free(free_me); + } + return; +} + +void free_all_counters(void) +{ + free_counter_list(pcc_even); + pcc_even = NULL; + + free_counter_list(pcc_odd); + pcc_odd = NULL; + + free_counter_list(pcc_delta); + pcc_delta = NULL; + + free_counter_list(pcc_average); + pcc_average = NULL; +} + +void insert_cpu_counters(PCC **list, PCC *new) +{ + PCC *prev; + + /* + * list was empty + */ + if (*list == NULL) { + new->next = *list; + *list = new; + return; + } + + show_cpu = 1; /* there is more than one CPU */ + + /* + * insert on front of list. + * It is sorted by ascending package#, core#, cpu# + */ + if (((*list)->pkg > new->pkg) || + (((*list)->pkg == new->pkg) && ((*list)->core > new->core)) || + (((*list)->pkg == new->pkg) && ((*list)->core == new->core) && ((*list)->cpu > new->cpu))) { + new->next = *list; + *list = new; + return; + } + + prev = *list; + + while (prev->next && (prev->next->pkg < new->pkg)) { + prev = prev->next; + show_pkg = 1; /* there is more than 1 package */ + } + + while (prev->next && (prev->next->pkg == new->pkg) + && (prev->next->core < new->core)) { + prev = prev->next; + show_core = 1; /* there is more than 1 core */ + } + + while (prev->next && (prev->next->pkg == new->pkg) + && (prev->next->core == new->core) + && (prev->next->cpu < new->cpu)) { + prev = prev->next; + } + + /* + * insert after "prev" + */ + new->next = prev->next; + prev->next = new; + + return; +} + +void alloc_new_cpu_counters(int pkg, int core, int cpu) +{ + PCC *new; + + if (verbose > 1) + printf("pkg%d core%d, cpu%d\n", pkg, core, cpu); + + new = (PCC *)calloc(1, sizeof(PCC)); + if (new == NULL) { + perror("calloc"); + exit(1); + } + new->pkg = pkg; + new->core = core; + new->cpu = cpu; + insert_cpu_counters(&pcc_odd, new); + + new = (PCC *)calloc(1, sizeof(PCC)); + if (new == NULL) { + perror("calloc"); + exit(1); + } + new->pkg = pkg; + new->core = core; + new->cpu = cpu; + insert_cpu_counters(&pcc_even, new); + + new = (PCC *)calloc(1, sizeof(PCC)); + if (new == NULL) { + perror("calloc"); + exit(1); + } + new->pkg = pkg; + new->core = core; + new->cpu = cpu; + insert_cpu_counters(&pcc_delta, new); + + new = (PCC *)calloc(1, sizeof(PCC)); + if (new == NULL) { + perror("calloc"); + exit(1); + } + new->pkg = pkg; + new->core = core; + new->cpu = cpu; + pcc_average = new; +} + +int get_physical_package_id(int cpu) +{ + char path[64]; + FILE *filep; + int pkg; + + sprintf(path, "/sys/devices/system/cpu/cpu%d/topology/physical_package_id", cpu); + filep = fopen(path, "r"); + if (filep == NULL) { + perror(path); + exit(1); + } + fscanf(filep, "%d", &pkg); + fclose(filep); + return pkg; +} + +int get_core_id(int cpu) +{ + char path[64]; + FILE *filep; + int core; + + sprintf(path, "/sys/devices/system/cpu/cpu%d/topology/core_id", cpu); + filep = fopen(path, "r"); + if (filep == NULL) { + perror(path); + exit(1); + } + fscanf(filep, "%d", &core); + fclose(filep); + return core; +} + +/* + * run func(index, cpu) on every cpu in /proc/stat + */ + +int for_all_cpus(void (func)(int, int, int)) +{ + FILE *fp; + int cpu_count; + int retval; + + fp = fopen(proc_stat, "r"); + if (fp == NULL) { + perror(proc_stat); + exit(1); + } + + retval = fscanf(fp, "cpu %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d\n"); + if (retval != 0) { + perror("/proc/stat format"); + exit(1); + } + + for (cpu_count = 0; ; cpu_count++) { + int cpu; + + retval = fscanf(fp, "cpu%u %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d\n", &cpu); + if (retval != 1) + break; + + func(get_physical_package_id(cpu), get_core_id(cpu), cpu); + } + fclose(fp); + return cpu_count; +} + +void re_initialize(void) +{ + printf("turbostat: topology changed, re-initializing.\n"); + free_all_counters(); + num_cpus = for_all_cpus(alloc_new_cpu_counters); + need_reinitialize = 0; + printf("num_cpus is now %d\n", num_cpus); +} + +void dummy(int pkg, int core, int cpu) { return; } +/* + * check to see if a cpu came on-line + */ +void verify_num_cpus() +{ + int new_num_cpus; + + new_num_cpus = for_all_cpus(dummy); + + if (new_num_cpus != num_cpus) { + if (verbose) + printf("num_cpus was %d, is now %d\n", + num_cpus, new_num_cpus); + need_reinitialize = 1; + } + + return; +} + +void turbostat_loop() +{ +restart: + get_counters(pcc_even); + gettimeofday(&tv_even, (struct timezone *)NULL); + + while (1) { + verify_num_cpus(); + if (need_reinitialize) { + re_initialize(); + goto restart; + } + sleep(interval_sec); + get_counters(pcc_odd); + gettimeofday(&tv_odd, (struct timezone *)NULL); + + compute_delta(pcc_odd, pcc_even, pcc_delta); + timersub(&tv_odd, &tv_even, &tv_delta); + compute_average(pcc_delta, pcc_average); + print_counters(pcc_delta); + if (need_reinitialize) { + re_initialize(); + goto restart; + } + sleep(interval_sec); + get_counters(pcc_even); + gettimeofday(&tv_even, (struct timezone *)NULL); + compute_delta(pcc_even, pcc_odd, pcc_delta); + timersub(&tv_even, &tv_odd, &tv_delta); + compute_average(pcc_delta, pcc_average); + print_counters(pcc_delta); + } +} + +void check_dev_msr() +{ + struct stat sb; + + if (stat("/dev/cpu/0/msr", &sb)) { + fprintf(stderr, "no /dev/cpu/0/msr\n"); + fprintf(stderr, "Try \"# modprobe msr\"\n"); + exit(-5); + } +} + +void check_super_user() +{ + if (getuid() != 0) { + fprintf(stderr, "must be root\n"); + exit(-6); + } +} + +int has_nehalem_turbo_ratio_limit(unsigned int family, unsigned int model) +{ + if (!genuine_intel) + return 0; + + if (family != 6) + return 0; + + switch (model) { + case 0x1A: /* Core i7, Xeon 5500 series - Bloomfield, Gainstown NHM-EP */ + case 0x1E: /* Core i7 and i5 Processor - Clarksfield, Lynnfield, Jasper Forest */ + case 0x1F: /* Core i7 and i5 Processor - Nehalem */ + case 0x25: /* Westmere Client - Clarkdale, Arrandale */ + case 0x2C: /* Westmere EP - Gulftown */ + case 0x2A: /* SNB */ + case 0x2D: /* SNB Xeon */ + return 1; + case 0x2E: /* Nehalem-EX Xeon - Beckton */ + case 0x2F: /* Westmere-EX Xeon - Eagleton */ + default: + return 0; + } +} + +int is_snb(unsigned int family, unsigned int model) +{ + if (!genuine_intel) + return 0; + + switch (model) { + case 0x2A: + case 0x2D: + return 1; + } + return 0; +} + +double discover_bclk(unsigned int family, unsigned int model) +{ + if (is_snb(family, model)) + return 100.00; + else + return 133.33; +} + +void check_cpuid() +{ + unsigned int eax, ebx, ecx, edx, max_level; + unsigned int fms, family, model, stepping; + + eax = ebx = ecx = edx = 0; + + asm("cpuid" : "=a" (max_level), "=b" (ebx), "=c" (ecx), "=d" (edx) : "a" (0)); + + if (ebx == 0x756e6547 && edx == 0x49656e69 && ecx == 0x6c65746e) + genuine_intel = 1; + + if (verbose) + fprintf(stderr, "%.4s%.4s%.4s ", + (char *)&ebx, (char *)&edx, (char *)&ecx); + + asm("cpuid" : "=a" (fms), "=c" (ecx), "=d" (edx) : "a" (1) : "ebx"); + family = (fms >> 8) & 0xf; + model = (fms >> 4) & 0xf; + stepping = fms & 0xf; + if (family == 6 || family == 0xf) + model += ((fms >> 16) & 0xf) << 4; + + if (verbose) + fprintf(stderr, "%d CPUID levels; family:model:stepping 0x%x:%x:%x (%d:%d:%d)\n", + max_level, family, model, stepping, family, model, stepping); + + if (!(edx & (1 << 5))) { + fprintf(stderr, "CPUID: no MSR\n"); + exit(1); + } + + /* + * check max extended function levels of CPUID. + * This is needed to check for invariant TSC. + * This check is valid for both Intel and AMD. + */ + ebx = ecx = edx = 0; + asm("cpuid" : "=a" (max_level), "=b" (ebx), "=c" (ecx), "=d" (edx) : "a" (0x80000000)); + + if (max_level < 0x80000007) { + fprintf(stderr, "CPUID: no invariant TSC (max_level 0x%x)\n", max_level); + exit(1); + } + + /* + * Non-Stop TSC is advertised by CPUID.EAX=0x80000007: EDX.bit8 + * this check is valid for both Intel and AMD + */ + asm("cpuid" : "=a" (eax), "=b" (ebx), "=c" (ecx), "=d" (edx) : "a" (0x80000007)); + has_invariant_tsc = edx && (1 << 8); + + if (!has_invariant_tsc) { + fprintf(stderr, "No invariant TSC\n"); + exit(1); + } + + /* + * APERF/MPERF is advertised by CPUID.EAX=0x6: ECX.bit0 + * this check is valid for both Intel and AMD + */ + + asm("cpuid" : "=a" (eax), "=b" (ebx), "=c" (ecx), "=d" (edx) : "a" (0x6)); + has_aperf = ecx && (1 << 0); + if (!has_aperf) { + fprintf(stderr, "No APERF MSR\n"); + exit(1); + } + + do_nehalem_platform_info = genuine_intel && has_invariant_tsc; + do_nhm_cstates = genuine_intel; /* all Intel w/ non-stop TSC have NHM counters */ + do_snb_cstates = is_snb(family, model); + bclk = discover_bclk(family, model); + + do_nehalem_turbo_ratio_limit = has_nehalem_turbo_ratio_limit(family, model); +} + + +void usage() +{ + fprintf(stderr, "%s: [-v] [-M MSR#] [-i interval_sec | command ...]\n", + progname); + exit(1); +} + + +/* + * in /dev/cpu/ return success for names that are numbers + * ie. filter out ".", "..", "microcode". + */ +int dir_filter(const struct dirent *dirp) +{ + if (isdigit(dirp->d_name[0])) + return 1; + else + return 0; +} + +int open_dev_cpu_msr(int dummy1) +{ + return 0; +} + +void turbostat_init() +{ + check_cpuid(); + + check_dev_msr(); + check_super_user(); + + num_cpus = for_all_cpus(alloc_new_cpu_counters); + + if (verbose) + print_nehalem_info(); +} + +int fork_it(char **argv) +{ + int retval; + pid_t child_pid; + get_counters(pcc_even); + gettimeofday(&tv_even, (struct timezone *)NULL); + + child_pid = fork(); + if (!child_pid) { + /* child */ + execvp(argv[0], argv); + } else { + int status; + + /* parent */ + if (child_pid == -1) { + perror("fork"); + exit(1); + } + + signal(SIGINT, SIG_IGN); + signal(SIGQUIT, SIG_IGN); + if (waitpid(child_pid, &status, 0) == -1) { + perror("wait"); + exit(1); + } + } + get_counters(pcc_odd); + gettimeofday(&tv_odd, (struct timezone *)NULL); + retval = compute_delta(pcc_odd, pcc_even, pcc_delta); + + timersub(&tv_odd, &tv_even, &tv_delta); + compute_average(pcc_delta, pcc_average); + if (!retval) + print_counters(pcc_delta); + + fprintf(stderr, "%.6f sec\n", tv_delta.tv_sec + tv_delta.tv_usec/1000000.0);; + + return 0; +} + +void cmdline(int argc, char **argv) +{ + int opt; + + progname = argv[0]; + + while ((opt = getopt(argc, argv, "+vi:M:")) != -1) { + switch (opt) { + case 'v': + verbose++; + break; + case 'i': + interval_sec = atoi(optarg); + break; + case 'M': + sscanf(optarg, "%x", &extra_msr_offset); + if (verbose > 1) + fprintf(stderr, "MSR 0x%X\n", extra_msr_offset); + break; + default: + usage(); + } + } +} + +int main(int argc, char **argv) +{ + cmdline(argc, argv); + + if (verbose > 1) + fprintf(stderr, "turbostat Dec 6, 2010" + " - Len Brown <lenb@kernel.org>\n"); + if (verbose > 1) + fprintf(stderr, "http://userweb.kernel.org/~lenb/acpi/utils/pmtools/turbostat/\n"); + + turbostat_init(); + + /* + * if any params left, it must be a command to fork + */ + if (argc - optind) + return fork_it(argv + optind); + else + turbostat_loop(); + + return 0; +} diff --git a/tools/power/x86/x86_energy_perf_policy/Makefile b/tools/power/x86/x86_energy_perf_policy/Makefile new file mode 100644 index 00000000000..f458237fdd7 --- /dev/null +++ b/tools/power/x86/x86_energy_perf_policy/Makefile @@ -0,0 +1,8 @@ +x86_energy_perf_policy : x86_energy_perf_policy.c + +clean : + rm -f x86_energy_perf_policy + +install : + install x86_energy_perf_policy /usr/bin/ + install x86_energy_perf_policy.8 /usr/share/man/man8/ diff --git a/tools/power/x86/x86_energy_perf_policy/x86_energy_perf_policy.8 b/tools/power/x86/x86_energy_perf_policy/x86_energy_perf_policy.8 new file mode 100644 index 00000000000..8eaaad648cd --- /dev/null +++ b/tools/power/x86/x86_energy_perf_policy/x86_energy_perf_policy.8 @@ -0,0 +1,104 @@ +.\" This page Copyright (C) 2010 Len Brown <len.brown@intel.com> +.\" Distributed under the GPL, Copyleft 1994. +.TH X86_ENERGY_PERF_POLICY 8 +.SH NAME +x86_energy_perf_policy \- read or write MSR_IA32_ENERGY_PERF_BIAS +.SH SYNOPSIS +.ft B +.B x86_energy_perf_policy +.RB [ "\-c cpu" ] +.RB [ "\-v" ] +.RB "\-r" +.br +.B x86_energy_perf_policy +.RB [ "\-c cpu" ] +.RB [ "\-v" ] +.RB 'performance' +.br +.B x86_energy_perf_policy +.RB [ "\-c cpu" ] +.RB [ "\-v" ] +.RB 'normal' +.br +.B x86_energy_perf_policy +.RB [ "\-c cpu" ] +.RB [ "\-v" ] +.RB 'powersave' +.br +.B x86_energy_perf_policy +.RB [ "\-c cpu" ] +.RB [ "\-v" ] +.RB n +.br +.SH DESCRIPTION +\fBx86_energy_perf_policy\fP +allows software to convey +its policy for the relative importance of performance +versus energy savings to the processor. + +The processor uses this information in model-specific ways +when it must select trade-offs between performance and +energy efficiency. + +This policy hint does not supersede Processor Performance states +(P-states) or CPU Idle power states (C-states), but allows +software to have influence where it would otherwise be unable +to express a preference. + +For example, this setting may tell the hardware how +aggressively or conservatively to control frequency +in the "turbo range" above the explicitly OS-controlled +P-state frequency range. It may also tell the hardware +how aggressively is should enter the OS requested C-states. + +Support for this feature is indicated by CPUID.06H.ECX.bit3 +per the Intel Architectures Software Developer's Manual. + +.SS Options +\fB-c\fP limits operation to a single CPU. +The default is to operate on all CPUs. +Note that MSR_IA32_ENERGY_PERF_BIAS is defined per +logical processor, but that the initial implementations +of the MSR were shared among all processors in each package. +.PP +\fB-v\fP increases verbosity. By default +x86_energy_perf_policy is silent. +.PP +\fB-r\fP is for "read-only" mode - the unchanged state +is read and displayed. +.PP +.I performance +Set a policy where performance is paramount. +The processor will be unwilling to sacrifice any performance +for the sake of energy saving. This is the hardware default. +.PP +.I normal +Set a policy with a normal balance between performance and energy efficiency. +The processor will tolerate minor performance compromise +for potentially significant energy savings. +This reasonable default for most desktops and servers. +.PP +.I powersave +Set a policy where the processor can accept +a measurable performance hit to maximize energy efficiency. +.PP +.I n +Set MSR_IA32_ENERGY_PERF_BIAS to the specified number. +The range of valid numbers is 0-15, where 0 is maximum +performance and 15 is maximum energy efficiency. + +.SH NOTES +.B "x86_energy_perf_policy " +runs only as root. +.SH FILES +.ta +.nf +/dev/cpu/*/msr +.fi + +.SH "SEE ALSO" +msr(4) +.PP +.SH AUTHORS +.nf +Written by Len Brown <len.brown@intel.com> diff --git a/tools/power/x86/x86_energy_perf_policy/x86_energy_perf_policy.c b/tools/power/x86/x86_energy_perf_policy/x86_energy_perf_policy.c new file mode 100644 index 00000000000..d9678a34dd7 --- /dev/null +++ b/tools/power/x86/x86_energy_perf_policy/x86_energy_perf_policy.c @@ -0,0 +1,325 @@ +/* + * x86_energy_perf_policy -- set the energy versus performance + * policy preference bias on recent X86 processors. + */ +/* + * Copyright (c) 2010, Intel Corporation. + * Len Brown <len.brown@intel.com> + * + * This program is free software; you can redistribute it and/or modify it + * under the terms and conditions of the GNU General Public License, + * version 2, as published by the Free Software Foundation. + * + * This program is distributed in the hope it will be useful, but WITHOUT + * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or + * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for + * more details. + * + * You should have received a copy of the GNU General Public License along with + * this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin St - Fifth Floor, Boston, MA 02110-1301 USA. + */ + +#include <stdio.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/resource.h> +#include <fcntl.h> +#include <signal.h> +#include <sys/time.h> +#include <stdlib.h> +#include <string.h> + +unsigned int verbose; /* set with -v */ +unsigned int read_only; /* set with -r */ +char *progname; +unsigned long long new_bias; +int cpu = -1; + +/* + * Usage: + * + * -c cpu: limit action to a single CPU (default is all CPUs) + * -v: verbose output (can invoke more than once) + * -r: read-only, don't change any settings + * + * performance + * Performance is paramount. + * Unwilling to sacrafice any performance + * for the sake of energy saving. (hardware default) + * + * normal + * Can tolerate minor performance compromise + * for potentially significant energy savings. + * (reasonable default for most desktops and servers) + * + * powersave + * Can tolerate significant performance hit + * to maximize energy savings. + * + * n + * a numerical value to write to the underlying MSR. + */ +void usage(void) +{ + printf("%s: [-c cpu] [-v] " + "(-r | 'performance' | 'normal' | 'powersave' | n)\n", + progname); + exit(1); +} + +#define MSR_IA32_ENERGY_PERF_BIAS 0x000001b0 + +#define BIAS_PERFORMANCE 0 +#define BIAS_BALANCE 6 +#define BIAS_POWERSAVE 15 + +void cmdline(int argc, char **argv) +{ + int opt; + + progname = argv[0]; + + while ((opt = getopt(argc, argv, "+rvc:")) != -1) { + switch (opt) { + case 'c': + cpu = atoi(optarg); + break; + case 'r': + read_only = 1; + break; + case 'v': + verbose++; + break; + default: + usage(); + } + } + /* if -r, then should be no additional optind */ + if (read_only && (argc > optind)) + usage(); + + /* + * if no -r , then must be one additional optind + */ + if (!read_only) { + + if (argc != optind + 1) { + printf("must supply -r or policy param\n"); + usage(); + } + + if (!strcmp("performance", argv[optind])) { + new_bias = BIAS_PERFORMANCE; + } else if (!strcmp("normal", argv[optind])) { + new_bias = BIAS_BALANCE; + } else if (!strcmp("powersave", argv[optind])) { + new_bias = BIAS_POWERSAVE; + } else { + char *endptr; + + new_bias = strtoull(argv[optind], &endptr, 0); + if (endptr == argv[optind] || + new_bias > BIAS_POWERSAVE) { + fprintf(stderr, "invalid value: %s\n", + argv[optind]); + usage(); + } + } + } +} + +/* + * validate_cpuid() + * returns on success, quietly exits on failure (make verbose with -v) + */ +void validate_cpuid(void) +{ + unsigned int eax, ebx, ecx, edx, max_level; + char brand[16]; + unsigned int fms, family, model, stepping; + + eax = ebx = ecx = edx = 0; + + asm("cpuid" : "=a" (max_level), "=b" (ebx), "=c" (ecx), + "=d" (edx) : "a" (0)); + + if (ebx != 0x756e6547 || edx != 0x49656e69 || ecx != 0x6c65746e) { + if (verbose) + fprintf(stderr, "%.4s%.4s%.4s != GenuineIntel", + (char *)&ebx, (char *)&edx, (char *)&ecx); + exit(1); + } + + asm("cpuid" : "=a" (fms), "=c" (ecx), "=d" (edx) : "a" (1) : "ebx"); + family = (fms >> 8) & 0xf; + model = (fms >> 4) & 0xf; + stepping = fms & 0xf; + if (family == 6 || family == 0xf) + model += ((fms >> 16) & 0xf) << 4; + + if (verbose > 1) + printf("CPUID %s %d levels family:model:stepping " + "0x%x:%x:%x (%d:%d:%d)\n", brand, max_level, + family, model, stepping, family, model, stepping); + + if (!(edx & (1 << 5))) { + if (verbose) + printf("CPUID: no MSR\n"); + exit(1); + } + + /* + * Support for MSR_IA32_ENERGY_PERF_BIAS + * is indicated by CPUID.06H.ECX.bit3 + */ + asm("cpuid" : "=a" (eax), "=b" (ebx), "=c" (ecx), "=d" (edx) : "a" (6)); + if (verbose) + printf("CPUID.06H.ECX: 0x%x\n", ecx); + if (!(ecx & (1 << 3))) { + if (verbose) + printf("CPUID: No MSR_IA32_ENERGY_PERF_BIAS\n"); + exit(1); + } + return; /* success */ +} + +unsigned long long get_msr(int cpu, int offset) +{ + unsigned long long msr; + char msr_path[32]; + int retval; + int fd; + + sprintf(msr_path, "/dev/cpu/%d/msr", cpu); + fd = open(msr_path, O_RDONLY); + if (fd < 0) { + printf("Try \"# modprobe msr\"\n"); + perror(msr_path); + exit(1); + } + + retval = pread(fd, &msr, sizeof msr, offset); + + if (retval != sizeof msr) { + printf("pread cpu%d 0x%x = %d\n", cpu, offset, retval); + exit(-2); + } + close(fd); + return msr; +} + +unsigned long long put_msr(int cpu, unsigned long long new_msr, int offset) +{ + unsigned long long old_msr; + char msr_path[32]; + int retval; + int fd; + + sprintf(msr_path, "/dev/cpu/%d/msr", cpu); + fd = open(msr_path, O_RDWR); + if (fd < 0) { + perror(msr_path); + exit(1); + } + + retval = pread(fd, &old_msr, sizeof old_msr, offset); + if (retval != sizeof old_msr) { + perror("pwrite"); + printf("pread cpu%d 0x%x = %d\n", cpu, offset, retval); + exit(-2); + } + + retval = pwrite(fd, &new_msr, sizeof new_msr, offset); + if (retval != sizeof new_msr) { + perror("pwrite"); + printf("pwrite cpu%d 0x%x = %d\n", cpu, offset, retval); + exit(-2); + } + + close(fd); + + return old_msr; +} + +void print_msr(int cpu) +{ + printf("cpu%d: 0x%016llx\n", + cpu, get_msr(cpu, MSR_IA32_ENERGY_PERF_BIAS)); +} + +void update_msr(int cpu) +{ + unsigned long long previous_msr; + + previous_msr = put_msr(cpu, new_bias, MSR_IA32_ENERGY_PERF_BIAS); + + if (verbose) + printf("cpu%d msr0x%x 0x%016llx -> 0x%016llx\n", + cpu, MSR_IA32_ENERGY_PERF_BIAS, previous_msr, new_bias); + + return; +} + +char *proc_stat = "/proc/stat"; +/* + * run func() on every cpu in /dev/cpu + */ +void for_every_cpu(void (func)(int)) +{ + FILE *fp; + int retval; + + fp = fopen(proc_stat, "r"); + if (fp == NULL) { + perror(proc_stat); + exit(1); + } + + retval = fscanf(fp, "cpu %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d\n"); + if (retval != 0) { + perror("/proc/stat format"); + exit(1); + } + + while (1) { + int cpu; + + retval = fscanf(fp, + "cpu%u %*d %*d %*d %*d %*d %*d %*d %*d %*d %*d\n", + &cpu); + if (retval != 1) + return; + + func(cpu); + } + fclose(fp); +} + +int main(int argc, char **argv) +{ + cmdline(argc, argv); + + if (verbose > 1) + printf("x86_energy_perf_policy Nov 24, 2010" + " - Len Brown <lenb@kernel.org>\n"); + if (verbose > 1 && !read_only) + printf("new_bias %lld\n", new_bias); + + validate_cpuid(); + + if (cpu != -1) { + if (read_only) + print_msr(cpu); + else + update_msr(cpu); + } else { + if (read_only) + for_every_cpu(print_msr); + else + for_every_cpu(update_msr); + } + + return 0; +} diff --git a/tools/slub/slabinfo.c b/tools/slub/slabinfo.c new file mode 100644 index 00000000000..516551c9f17 --- /dev/null +++ b/tools/slub/slabinfo.c @@ -0,0 +1,1364 @@ +/* + * Slabinfo: Tool to get reports about slabs + * + * (C) 2007 sgi, Christoph Lameter + * + * Compile by: + * + * gcc -o slabinfo slabinfo.c + */ +#include <stdio.h> +#include <stdlib.h> +#include <sys/types.h> +#include <dirent.h> +#include <strings.h> +#include <string.h> +#include <unistd.h> +#include <stdarg.h> +#include <getopt.h> +#include <regex.h> +#include <errno.h> + +#define MAX_SLABS 500 +#define MAX_ALIASES 500 +#define MAX_NODES 1024 + +struct slabinfo { + char *name; + int alias; + int refs; + int aliases, align, cache_dma, cpu_slabs, destroy_by_rcu; + int hwcache_align, object_size, objs_per_slab; + int sanity_checks, slab_size, store_user, trace; + int order, poison, reclaim_account, red_zone; + unsigned long partial, objects, slabs, objects_partial, objects_total; + unsigned long alloc_fastpath, alloc_slowpath; + unsigned long free_fastpath, free_slowpath; + unsigned long free_frozen, free_add_partial, free_remove_partial; + unsigned long alloc_from_partial, alloc_slab, free_slab, alloc_refill; + unsigned long cpuslab_flush, deactivate_full, deactivate_empty; + unsigned long deactivate_to_head, deactivate_to_tail; + unsigned long deactivate_remote_frees, order_fallback; + int numa[MAX_NODES]; + int numa_partial[MAX_NODES]; +} slabinfo[MAX_SLABS]; + +struct aliasinfo { + char *name; + char *ref; + struct slabinfo *slab; +} aliasinfo[MAX_ALIASES]; + +int slabs = 0; +int actual_slabs = 0; +int aliases = 0; +int alias_targets = 0; +int highest_node = 0; + +char buffer[4096]; + +int show_empty = 0; +int show_report = 0; +int show_alias = 0; +int show_slab = 0; +int skip_zero = 1; +int show_numa = 0; +int show_track = 0; +int show_first_alias = 0; +int validate = 0; +int shrink = 0; +int show_inverted = 0; +int show_single_ref = 0; +int show_totals = 0; +int sort_size = 0; +int sort_active = 0; +int set_debug = 0; +int show_ops = 0; +int show_activity = 0; + +/* Debug options */ +int sanity = 0; +int redzone = 0; +int poison = 0; +int tracking = 0; +int tracing = 0; + +int page_size; + +regex_t pattern; + +static void fatal(const char *x, ...) +{ + va_list ap; + + va_start(ap, x); + vfprintf(stderr, x, ap); + va_end(ap); + exit(EXIT_FAILURE); +} + +static void usage(void) +{ + printf("slabinfo 5/7/2007. (c) 2007 sgi.\n\n" + "slabinfo [-ahnpvtsz] [-d debugopts] [slab-regexp]\n" + "-a|--aliases Show aliases\n" + "-A|--activity Most active slabs first\n" + "-d<options>|--debug=<options> Set/Clear Debug options\n" + "-D|--display-active Switch line format to activity\n" + "-e|--empty Show empty slabs\n" + "-f|--first-alias Show first alias\n" + "-h|--help Show usage information\n" + "-i|--inverted Inverted list\n" + "-l|--slabs Show slabs\n" + "-n|--numa Show NUMA information\n" + "-o|--ops Show kmem_cache_ops\n" + "-s|--shrink Shrink slabs\n" + "-r|--report Detailed report on single slabs\n" + "-S|--Size Sort by size\n" + "-t|--tracking Show alloc/free information\n" + "-T|--Totals Show summary information\n" + "-v|--validate Validate slabs\n" + "-z|--zero Include empty slabs\n" + "-1|--1ref Single reference\n" + "\nValid debug options (FZPUT may be combined)\n" + "a / A Switch on all debug options (=FZUP)\n" + "- Switch off all debug options\n" + "f / F Sanity Checks (SLAB_DEBUG_FREE)\n" + "z / Z Redzoning\n" + "p / P Poisoning\n" + "u / U Tracking\n" + "t / T Tracing\n" + ); +} + +static unsigned long read_obj(const char *name) +{ + FILE *f = fopen(name, "r"); + + if (!f) + buffer[0] = 0; + else { + if (!fgets(buffer, sizeof(buffer), f)) + buffer[0] = 0; + fclose(f); + if (buffer[strlen(buffer)] == '\n') + buffer[strlen(buffer)] = 0; + } + return strlen(buffer); +} + + +/* + * Get the contents of an attribute + */ +static unsigned long get_obj(const char *name) +{ + if (!read_obj(name)) + return 0; + + return atol(buffer); +} + +static unsigned long get_obj_and_str(const char *name, char **x) +{ + unsigned long result = 0; + char *p; + + *x = NULL; + + if (!read_obj(name)) { + x = NULL; + return 0; + } + result = strtoul(buffer, &p, 10); + while (*p == ' ') + p++; + if (*p) + *x = strdup(p); + return result; +} + +static void set_obj(struct slabinfo *s, const char *name, int n) +{ + char x[100]; + FILE *f; + + snprintf(x, 100, "%s/%s", s->name, name); + f = fopen(x, "w"); + if (!f) + fatal("Cannot write to %s\n", x); + + fprintf(f, "%d\n", n); + fclose(f); +} + +static unsigned long read_slab_obj(struct slabinfo *s, const char *name) +{ + char x[100]; + FILE *f; + size_t l; + + snprintf(x, 100, "%s/%s", s->name, name); + f = fopen(x, "r"); + if (!f) { + buffer[0] = 0; + l = 0; + } else { + l = fread(buffer, 1, sizeof(buffer), f); + buffer[l] = 0; + fclose(f); + } + return l; +} + + +/* + * Put a size string together + */ +static int store_size(char *buffer, unsigned long value) +{ + unsigned long divisor = 1; + char trailer = 0; + int n; + + if (value > 1000000000UL) { + divisor = 100000000UL; + trailer = 'G'; + } else if (value > 1000000UL) { + divisor = 100000UL; + trailer = 'M'; + } else if (value > 1000UL) { + divisor = 100; + trailer = 'K'; + } + + value /= divisor; + n = sprintf(buffer, "%ld",value); + if (trailer) { + buffer[n] = trailer; + n++; + buffer[n] = 0; + } + if (divisor != 1) { + memmove(buffer + n - 2, buffer + n - 3, 4); + buffer[n-2] = '.'; + n++; + } + return n; +} + +static void decode_numa_list(int *numa, char *t) +{ + int node; + int nr; + + memset(numa, 0, MAX_NODES * sizeof(int)); + + if (!t) + return; + + while (*t == 'N') { + t++; + node = strtoul(t, &t, 10); + if (*t == '=') { + t++; + nr = strtoul(t, &t, 10); + numa[node] = nr; + if (node > highest_node) + highest_node = node; + } + while (*t == ' ') + t++; + } +} + +static void slab_validate(struct slabinfo *s) +{ + if (strcmp(s->name, "*") == 0) + return; + + set_obj(s, "validate", 1); +} + +static void slab_shrink(struct slabinfo *s) +{ + if (strcmp(s->name, "*") == 0) + return; + + set_obj(s, "shrink", 1); +} + +int line = 0; + +static void first_line(void) +{ + if (show_activity) + printf("Name Objects Alloc Free %%Fast Fallb O\n"); + else + printf("Name Objects Objsize Space " + "Slabs/Part/Cpu O/S O %%Fr %%Ef Flg\n"); +} + +/* + * Find the shortest alias of a slab + */ +static struct aliasinfo *find_one_alias(struct slabinfo *find) +{ + struct aliasinfo *a; + struct aliasinfo *best = NULL; + + for(a = aliasinfo;a < aliasinfo + aliases; a++) { + if (a->slab == find && + (!best || strlen(best->name) < strlen(a->name))) { + best = a; + if (strncmp(a->name,"kmall", 5) == 0) + return best; + } + } + return best; +} + +static unsigned long slab_size(struct slabinfo *s) +{ + return s->slabs * (page_size << s->order); +} + +static unsigned long slab_activity(struct slabinfo *s) +{ + return s->alloc_fastpath + s->free_fastpath + + s->alloc_slowpath + s->free_slowpath; +} + +static void slab_numa(struct slabinfo *s, int mode) +{ + int node; + + if (strcmp(s->name, "*") == 0) + return; + + if (!highest_node) { + printf("\n%s: No NUMA information available.\n", s->name); + return; + } + + if (skip_zero && !s->slabs) + return; + + if (!line) { + printf("\n%-21s:", mode ? "NUMA nodes" : "Slab"); + for(node = 0; node <= highest_node; node++) + printf(" %4d", node); + printf("\n----------------------"); + for(node = 0; node <= highest_node; node++) + printf("-----"); + printf("\n"); + } + printf("%-21s ", mode ? "All slabs" : s->name); + for(node = 0; node <= highest_node; node++) { + char b[20]; + + store_size(b, s->numa[node]); + printf(" %4s", b); + } + printf("\n"); + if (mode) { + printf("%-21s ", "Partial slabs"); + for(node = 0; node <= highest_node; node++) { + char b[20]; + + store_size(b, s->numa_partial[node]); + printf(" %4s", b); + } + printf("\n"); + } + line++; +} + +static void show_tracking(struct slabinfo *s) +{ + printf("\n%s: Kernel object allocation\n", s->name); + printf("-----------------------------------------------------------------------\n"); + if (read_slab_obj(s, "alloc_calls")) + printf(buffer); + else + printf("No Data\n"); + + printf("\n%s: Kernel object freeing\n", s->name); + printf("------------------------------------------------------------------------\n"); + if (read_slab_obj(s, "free_calls")) + printf(buffer); + else + printf("No Data\n"); + +} + +static void ops(struct slabinfo *s) +{ + if (strcmp(s->name, "*") == 0) + return; + + if (read_slab_obj(s, "ops")) { + printf("\n%s: kmem_cache operations\n", s->name); + printf("--------------------------------------------\n"); + printf(buffer); + } else + printf("\n%s has no kmem_cache operations\n", s->name); +} + +static const char *onoff(int x) +{ + if (x) + return "On "; + return "Off"; +} + +static void slab_stats(struct slabinfo *s) +{ + unsigned long total_alloc; + unsigned long total_free; + unsigned long total; + + if (!s->alloc_slab) + return; + + total_alloc = s->alloc_fastpath + s->alloc_slowpath; + total_free = s->free_fastpath + s->free_slowpath; + + if (!total_alloc) + return; + + printf("\n"); + printf("Slab Perf Counter Alloc Free %%Al %%Fr\n"); + printf("--------------------------------------------------\n"); + printf("Fastpath %8lu %8lu %3lu %3lu\n", + s->alloc_fastpath, s->free_fastpath, + s->alloc_fastpath * 100 / total_alloc, + s->free_fastpath * 100 / total_free); + printf("Slowpath %8lu %8lu %3lu %3lu\n", + total_alloc - s->alloc_fastpath, s->free_slowpath, + (total_alloc - s->alloc_fastpath) * 100 / total_alloc, + s->free_slowpath * 100 / total_free); + printf("Page Alloc %8lu %8lu %3lu %3lu\n", + s->alloc_slab, s->free_slab, + s->alloc_slab * 100 / total_alloc, + s->free_slab * 100 / total_free); + printf("Add partial %8lu %8lu %3lu %3lu\n", + s->deactivate_to_head + s->deactivate_to_tail, + s->free_add_partial, + (s->deactivate_to_head + s->deactivate_to_tail) * 100 / total_alloc, + s->free_add_partial * 100 / total_free); + printf("Remove partial %8lu %8lu %3lu %3lu\n", + s->alloc_from_partial, s->free_remove_partial, + s->alloc_from_partial * 100 / total_alloc, + s->free_remove_partial * 100 / total_free); + + printf("RemoteObj/SlabFrozen %8lu %8lu %3lu %3lu\n", + s->deactivate_remote_frees, s->free_frozen, + s->deactivate_remote_frees * 100 / total_alloc, + s->free_frozen * 100 / total_free); + + printf("Total %8lu %8lu\n\n", total_alloc, total_free); + + if (s->cpuslab_flush) + printf("Flushes %8lu\n", s->cpuslab_flush); + + if (s->alloc_refill) + printf("Refill %8lu\n", s->alloc_refill); + + total = s->deactivate_full + s->deactivate_empty + + s->deactivate_to_head + s->deactivate_to_tail; + + if (total) + printf("Deactivate Full=%lu(%lu%%) Empty=%lu(%lu%%) " + "ToHead=%lu(%lu%%) ToTail=%lu(%lu%%)\n", + s->deactivate_full, (s->deactivate_full * 100) / total, + s->deactivate_empty, (s->deactivate_empty * 100) / total, + s->deactivate_to_head, (s->deactivate_to_head * 100) / total, + s->deactivate_to_tail, (s->deactivate_to_tail * 100) / total); +} + +static void report(struct slabinfo *s) +{ + if (strcmp(s->name, "*") == 0) + return; + + printf("\nSlabcache: %-20s Aliases: %2d Order : %2d Objects: %lu\n", + s->name, s->aliases, s->order, s->objects); + if (s->hwcache_align) + printf("** Hardware cacheline aligned\n"); + if (s->cache_dma) + printf("** Memory is allocated in a special DMA zone\n"); + if (s->destroy_by_rcu) + printf("** Slabs are destroyed via RCU\n"); + if (s->reclaim_account) + printf("** Reclaim accounting active\n"); + + printf("\nSizes (bytes) Slabs Debug Memory\n"); + printf("------------------------------------------------------------------------\n"); + printf("Object : %7d Total : %7ld Sanity Checks : %s Total: %7ld\n", + s->object_size, s->slabs, onoff(s->sanity_checks), + s->slabs * (page_size << s->order)); + printf("SlabObj: %7d Full : %7ld Redzoning : %s Used : %7ld\n", + s->slab_size, s->slabs - s->partial - s->cpu_slabs, + onoff(s->red_zone), s->objects * s->object_size); + printf("SlabSiz: %7d Partial: %7ld Poisoning : %s Loss : %7ld\n", + page_size << s->order, s->partial, onoff(s->poison), + s->slabs * (page_size << s->order) - s->objects * s->object_size); + printf("Loss : %7d CpuSlab: %7d Tracking : %s Lalig: %7ld\n", + s->slab_size - s->object_size, s->cpu_slabs, onoff(s->store_user), + (s->slab_size - s->object_size) * s->objects); + printf("Align : %7d Objects: %7d Tracing : %s Lpadd: %7ld\n", + s->align, s->objs_per_slab, onoff(s->trace), + ((page_size << s->order) - s->objs_per_slab * s->slab_size) * + s->slabs); + + ops(s); + show_tracking(s); + slab_numa(s, 1); + slab_stats(s); +} + +static void slabcache(struct slabinfo *s) +{ + char size_str[20]; + char dist_str[40]; + char flags[20]; + char *p = flags; + + if (strcmp(s->name, "*") == 0) + return; + + if (actual_slabs == 1) { + report(s); + return; + } + + if (skip_zero && !show_empty && !s->slabs) + return; + + if (show_empty && s->slabs) + return; + + store_size(size_str, slab_size(s)); + snprintf(dist_str, 40, "%lu/%lu/%d", s->slabs - s->cpu_slabs, + s->partial, s->cpu_slabs); + + if (!line++) + first_line(); + + if (s->aliases) + *p++ = '*'; + if (s->cache_dma) + *p++ = 'd'; + if (s->hwcache_align) + *p++ = 'A'; + if (s->poison) + *p++ = 'P'; + if (s->reclaim_account) + *p++ = 'a'; + if (s->red_zone) + *p++ = 'Z'; + if (s->sanity_checks) + *p++ = 'F'; + if (s->store_user) + *p++ = 'U'; + if (s->trace) + *p++ = 'T'; + + *p = 0; + if (show_activity) { + unsigned long total_alloc; + unsigned long total_free; + + total_alloc = s->alloc_fastpath + s->alloc_slowpath; + total_free = s->free_fastpath + s->free_slowpath; + + printf("%-21s %8ld %10ld %10ld %3ld %3ld %5ld %1d\n", + s->name, s->objects, + total_alloc, total_free, + total_alloc ? (s->alloc_fastpath * 100 / total_alloc) : 0, + total_free ? (s->free_fastpath * 100 / total_free) : 0, + s->order_fallback, s->order); + } + else + printf("%-21s %8ld %7d %8s %14s %4d %1d %3ld %3ld %s\n", + s->name, s->objects, s->object_size, size_str, dist_str, + s->objs_per_slab, s->order, + s->slabs ? (s->partial * 100) / s->slabs : 100, + s->slabs ? (s->objects * s->object_size * 100) / + (s->slabs * (page_size << s->order)) : 100, + flags); +} + +/* + * Analyze debug options. Return false if something is amiss. + */ +static int debug_opt_scan(char *opt) +{ + if (!opt || !opt[0] || strcmp(opt, "-") == 0) + return 1; + + if (strcasecmp(opt, "a") == 0) { + sanity = 1; + poison = 1; + redzone = 1; + tracking = 1; + return 1; + } + + for ( ; *opt; opt++) + switch (*opt) { + case 'F' : case 'f': + if (sanity) + return 0; + sanity = 1; + break; + case 'P' : case 'p': + if (poison) + return 0; + poison = 1; + break; + + case 'Z' : case 'z': + if (redzone) + return 0; + redzone = 1; + break; + + case 'U' : case 'u': + if (tracking) + return 0; + tracking = 1; + break; + + case 'T' : case 't': + if (tracing) + return 0; + tracing = 1; + break; + default: + return 0; + } + return 1; +} + +static int slab_empty(struct slabinfo *s) +{ + if (s->objects > 0) + return 0; + + /* + * We may still have slabs even if there are no objects. Shrinking will + * remove them. + */ + if (s->slabs != 0) + set_obj(s, "shrink", 1); + + return 1; +} + +static void slab_debug(struct slabinfo *s) +{ + if (strcmp(s->name, "*") == 0) + return; + + if (sanity && !s->sanity_checks) { + set_obj(s, "sanity", 1); + } + if (!sanity && s->sanity_checks) { + if (slab_empty(s)) + set_obj(s, "sanity", 0); + else + fprintf(stderr, "%s not empty cannot disable sanity checks\n", s->name); + } + if (redzone && !s->red_zone) { + if (slab_empty(s)) + set_obj(s, "red_zone", 1); + else + fprintf(stderr, "%s not empty cannot enable redzoning\n", s->name); + } + if (!redzone && s->red_zone) { + if (slab_empty(s)) + set_obj(s, "red_zone", 0); + else + fprintf(stderr, "%s not empty cannot disable redzoning\n", s->name); + } + if (poison && !s->poison) { + if (slab_empty(s)) + set_obj(s, "poison", 1); + else + fprintf(stderr, "%s not empty cannot enable poisoning\n", s->name); + } + if (!poison && s->poison) { + if (slab_empty(s)) + set_obj(s, "poison", 0); + else + fprintf(stderr, "%s not empty cannot disable poisoning\n", s->name); + } + if (tracking && !s->store_user) { + if (slab_empty(s)) + set_obj(s, "store_user", 1); + else + fprintf(stderr, "%s not empty cannot enable tracking\n", s->name); + } + if (!tracking && s->store_user) { + if (slab_empty(s)) + set_obj(s, "store_user", 0); + else + fprintf(stderr, "%s not empty cannot disable tracking\n", s->name); + } + if (tracing && !s->trace) { + if (slabs == 1) + set_obj(s, "trace", 1); + else + fprintf(stderr, "%s can only enable trace for one slab at a time\n", s->name); + } + if (!tracing && s->trace) + set_obj(s, "trace", 1); +} + +static void totals(void) +{ + struct slabinfo *s; + + int used_slabs = 0; + char b1[20], b2[20], b3[20], b4[20]; + unsigned long long max = 1ULL << 63; + + /* Object size */ + unsigned long long min_objsize = max, max_objsize = 0, avg_objsize; + + /* Number of partial slabs in a slabcache */ + unsigned long long min_partial = max, max_partial = 0, + avg_partial, total_partial = 0; + + /* Number of slabs in a slab cache */ + unsigned long long min_slabs = max, max_slabs = 0, + avg_slabs, total_slabs = 0; + + /* Size of the whole slab */ + unsigned long long min_size = max, max_size = 0, + avg_size, total_size = 0; + + /* Bytes used for object storage in a slab */ + unsigned long long min_used = max, max_used = 0, + avg_used, total_used = 0; + + /* Waste: Bytes used for alignment and padding */ + unsigned long long min_waste = max, max_waste = 0, + avg_waste, total_waste = 0; + /* Number of objects in a slab */ + unsigned long long min_objects = max, max_objects = 0, + avg_objects, total_objects = 0; + /* Waste per object */ + unsigned long long min_objwaste = max, + max_objwaste = 0, avg_objwaste, + total_objwaste = 0; + + /* Memory per object */ + unsigned long long min_memobj = max, + max_memobj = 0, avg_memobj, + total_objsize = 0; + + /* Percentage of partial slabs per slab */ + unsigned long min_ppart = 100, max_ppart = 0, + avg_ppart, total_ppart = 0; + + /* Number of objects in partial slabs */ + unsigned long min_partobj = max, max_partobj = 0, + avg_partobj, total_partobj = 0; + + /* Percentage of partial objects of all objects in a slab */ + unsigned long min_ppartobj = 100, max_ppartobj = 0, + avg_ppartobj, total_ppartobj = 0; + + + for (s = slabinfo; s < slabinfo + slabs; s++) { + unsigned long long size; + unsigned long used; + unsigned long long wasted; + unsigned long long objwaste; + unsigned long percentage_partial_slabs; + unsigned long percentage_partial_objs; + + if (!s->slabs || !s->objects) + continue; + + used_slabs++; + + size = slab_size(s); + used = s->objects * s->object_size; + wasted = size - used; + objwaste = s->slab_size - s->object_size; + + percentage_partial_slabs = s->partial * 100 / s->slabs; + if (percentage_partial_slabs > 100) + percentage_partial_slabs = 100; + + percentage_partial_objs = s->objects_partial * 100 + / s->objects; + + if (percentage_partial_objs > 100) + percentage_partial_objs = 100; + + if (s->object_size < min_objsize) + min_objsize = s->object_size; + if (s->partial < min_partial) + min_partial = s->partial; + if (s->slabs < min_slabs) + min_slabs = s->slabs; + if (size < min_size) + min_size = size; + if (wasted < min_waste) + min_waste = wasted; + if (objwaste < min_objwaste) + min_objwaste = objwaste; + if (s->objects < min_objects) + min_objects = s->objects; + if (used < min_used) + min_used = used; + if (s->objects_partial < min_partobj) + min_partobj = s->objects_partial; + if (percentage_partial_slabs < min_ppart) + min_ppart = percentage_partial_slabs; + if (percentage_partial_objs < min_ppartobj) + min_ppartobj = percentage_partial_objs; + if (s->slab_size < min_memobj) + min_memobj = s->slab_size; + + if (s->object_size > max_objsize) + max_objsize = s->object_size; + if (s->partial > max_partial) + max_partial = s->partial; + if (s->slabs > max_slabs) + max_slabs = s->slabs; + if (size > max_size) + max_size = size; + if (wasted > max_waste) + max_waste = wasted; + if (objwaste > max_objwaste) + max_objwaste = objwaste; + if (s->objects > max_objects) + max_objects = s->objects; + if (used > max_used) + max_used = used; + if (s->objects_partial > max_partobj) + max_partobj = s->objects_partial; + if (percentage_partial_slabs > max_ppart) + max_ppart = percentage_partial_slabs; + if (percentage_partial_objs > max_ppartobj) + max_ppartobj = percentage_partial_objs; + if (s->slab_size > max_memobj) + max_memobj = s->slab_size; + + total_partial += s->partial; + total_slabs += s->slabs; + total_size += size; + total_waste += wasted; + + total_objects += s->objects; + total_used += used; + total_partobj += s->objects_partial; + total_ppart += percentage_partial_slabs; + total_ppartobj += percentage_partial_objs; + + total_objwaste += s->objects * objwaste; + total_objsize += s->objects * s->slab_size; + } + + if (!total_objects) { + printf("No objects\n"); + return; + } + if (!used_slabs) { + printf("No slabs\n"); + return; + } + + /* Per slab averages */ + avg_partial = total_partial / used_slabs; + avg_slabs = total_slabs / used_slabs; + avg_size = total_size / used_slabs; + avg_waste = total_waste / used_slabs; + + avg_objects = total_objects / used_slabs; + avg_used = total_used / used_slabs; + avg_partobj = total_partobj / used_slabs; + avg_ppart = total_ppart / used_slabs; + avg_ppartobj = total_ppartobj / used_slabs; + + /* Per object object sizes */ + avg_objsize = total_used / total_objects; + avg_objwaste = total_objwaste / total_objects; + avg_partobj = total_partobj * 100 / total_objects; + avg_memobj = total_objsize / total_objects; + + printf("Slabcache Totals\n"); + printf("----------------\n"); + printf("Slabcaches : %3d Aliases : %3d->%-3d Active: %3d\n", + slabs, aliases, alias_targets, used_slabs); + + store_size(b1, total_size);store_size(b2, total_waste); + store_size(b3, total_waste * 100 / total_used); + printf("Memory used: %6s # Loss : %6s MRatio:%6s%%\n", b1, b2, b3); + + store_size(b1, total_objects);store_size(b2, total_partobj); + store_size(b3, total_partobj * 100 / total_objects); + printf("# Objects : %6s # PartObj: %6s ORatio:%6s%%\n", b1, b2, b3); + + printf("\n"); + printf("Per Cache Average Min Max Total\n"); + printf("---------------------------------------------------------\n"); + + store_size(b1, avg_objects);store_size(b2, min_objects); + store_size(b3, max_objects);store_size(b4, total_objects); + printf("#Objects %10s %10s %10s %10s\n", + b1, b2, b3, b4); + + store_size(b1, avg_slabs);store_size(b2, min_slabs); + store_size(b3, max_slabs);store_size(b4, total_slabs); + printf("#Slabs %10s %10s %10s %10s\n", + b1, b2, b3, b4); + + store_size(b1, avg_partial);store_size(b2, min_partial); + store_size(b3, max_partial);store_size(b4, total_partial); + printf("#PartSlab %10s %10s %10s %10s\n", + b1, b2, b3, b4); + store_size(b1, avg_ppart);store_size(b2, min_ppart); + store_size(b3, max_ppart); + store_size(b4, total_partial * 100 / total_slabs); + printf("%%PartSlab%10s%% %10s%% %10s%% %10s%%\n", + b1, b2, b3, b4); + + store_size(b1, avg_partobj);store_size(b2, min_partobj); + store_size(b3, max_partobj); + store_size(b4, total_partobj); + printf("PartObjs %10s %10s %10s %10s\n", + b1, b2, b3, b4); + + store_size(b1, avg_ppartobj);store_size(b2, min_ppartobj); + store_size(b3, max_ppartobj); + store_size(b4, total_partobj * 100 / total_objects); + printf("%% PartObj%10s%% %10s%% %10s%% %10s%%\n", + b1, b2, b3, b4); + + store_size(b1, avg_size);store_size(b2, min_size); + store_size(b3, max_size);store_size(b4, total_size); + printf("Memory %10s %10s %10s %10s\n", + b1, b2, b3, b4); + + store_size(b1, avg_used);store_size(b2, min_used); + store_size(b3, max_used);store_size(b4, total_used); + printf("Used %10s %10s %10s %10s\n", + b1, b2, b3, b4); + + store_size(b1, avg_waste);store_size(b2, min_waste); + store_size(b3, max_waste);store_size(b4, total_waste); + printf("Loss %10s %10s %10s %10s\n", + b1, b2, b3, b4); + + printf("\n"); + printf("Per Object Average Min Max\n"); + printf("---------------------------------------------\n"); + + store_size(b1, avg_memobj);store_size(b2, min_memobj); + store_size(b3, max_memobj); + printf("Memory %10s %10s %10s\n", + b1, b2, b3); + store_size(b1, avg_objsize);store_size(b2, min_objsize); + store_size(b3, max_objsize); + printf("User %10s %10s %10s\n", + b1, b2, b3); + + store_size(b1, avg_objwaste);store_size(b2, min_objwaste); + store_size(b3, max_objwaste); + printf("Loss %10s %10s %10s\n", + b1, b2, b3); +} + +static void sort_slabs(void) +{ + struct slabinfo *s1,*s2; + + for (s1 = slabinfo; s1 < slabinfo + slabs; s1++) { + for (s2 = s1 + 1; s2 < slabinfo + slabs; s2++) { + int result; + + if (sort_size) + result = slab_size(s1) < slab_size(s2); + else if (sort_active) + result = slab_activity(s1) < slab_activity(s2); + else + result = strcasecmp(s1->name, s2->name); + + if (show_inverted) + result = -result; + + if (result > 0) { + struct slabinfo t; + + memcpy(&t, s1, sizeof(struct slabinfo)); + memcpy(s1, s2, sizeof(struct slabinfo)); + memcpy(s2, &t, sizeof(struct slabinfo)); + } + } + } +} + +static void sort_aliases(void) +{ + struct aliasinfo *a1,*a2; + + for (a1 = aliasinfo; a1 < aliasinfo + aliases; a1++) { + for (a2 = a1 + 1; a2 < aliasinfo + aliases; a2++) { + char *n1, *n2; + + n1 = a1->name; + n2 = a2->name; + if (show_alias && !show_inverted) { + n1 = a1->ref; + n2 = a2->ref; + } + if (strcasecmp(n1, n2) > 0) { + struct aliasinfo t; + + memcpy(&t, a1, sizeof(struct aliasinfo)); + memcpy(a1, a2, sizeof(struct aliasinfo)); + memcpy(a2, &t, sizeof(struct aliasinfo)); + } + } + } +} + +static void link_slabs(void) +{ + struct aliasinfo *a; + struct slabinfo *s; + + for (a = aliasinfo; a < aliasinfo + aliases; a++) { + + for (s = slabinfo; s < slabinfo + slabs; s++) + if (strcmp(a->ref, s->name) == 0) { + a->slab = s; + s->refs++; + break; + } + if (s == slabinfo + slabs) + fatal("Unresolved alias %s\n", a->ref); + } +} + +static void alias(void) +{ + struct aliasinfo *a; + char *active = NULL; + + sort_aliases(); + link_slabs(); + + for(a = aliasinfo; a < aliasinfo + aliases; a++) { + + if (!show_single_ref && a->slab->refs == 1) + continue; + + if (!show_inverted) { + if (active) { + if (strcmp(a->slab->name, active) == 0) { + printf(" %s", a->name); + continue; + } + } + printf("\n%-12s <- %s", a->slab->name, a->name); + active = a->slab->name; + } + else + printf("%-20s -> %s\n", a->name, a->slab->name); + } + if (active) + printf("\n"); +} + + +static void rename_slabs(void) +{ + struct slabinfo *s; + struct aliasinfo *a; + + for (s = slabinfo; s < slabinfo + slabs; s++) { + if (*s->name != ':') + continue; + + if (s->refs > 1 && !show_first_alias) + continue; + + a = find_one_alias(s); + + if (a) + s->name = a->name; + else { + s->name = "*"; + actual_slabs--; + } + } +} + +static int slab_mismatch(char *slab) +{ + return regexec(&pattern, slab, 0, NULL, 0); +} + +static void read_slab_dir(void) +{ + DIR *dir; + struct dirent *de; + struct slabinfo *slab = slabinfo; + struct aliasinfo *alias = aliasinfo; + char *p; + char *t; + int count; + + if (chdir("/sys/kernel/slab") && chdir("/sys/slab")) + fatal("SYSFS support for SLUB not active\n"); + + dir = opendir("."); + while ((de = readdir(dir))) { + if (de->d_name[0] == '.' || + (de->d_name[0] != ':' && slab_mismatch(de->d_name))) + continue; + switch (de->d_type) { + case DT_LNK: + alias->name = strdup(de->d_name); + count = readlink(de->d_name, buffer, sizeof(buffer)); + + if (count < 0) + fatal("Cannot read symlink %s\n", de->d_name); + + buffer[count] = 0; + p = buffer + count; + while (p > buffer && p[-1] != '/') + p--; + alias->ref = strdup(p); + alias++; + break; + case DT_DIR: + if (chdir(de->d_name)) + fatal("Unable to access slab %s\n", slab->name); + slab->name = strdup(de->d_name); + slab->alias = 0; + slab->refs = 0; + slab->aliases = get_obj("aliases"); + slab->align = get_obj("align"); + slab->cache_dma = get_obj("cache_dma"); + slab->cpu_slabs = get_obj("cpu_slabs"); + slab->destroy_by_rcu = get_obj("destroy_by_rcu"); + slab->hwcache_align = get_obj("hwcache_align"); + slab->object_size = get_obj("object_size"); + slab->objects = get_obj("objects"); + slab->objects_partial = get_obj("objects_partial"); + slab->objects_total = get_obj("objects_total"); + slab->objs_per_slab = get_obj("objs_per_slab"); + slab->order = get_obj("order"); + slab->partial = get_obj("partial"); + slab->partial = get_obj_and_str("partial", &t); + decode_numa_list(slab->numa_partial, t); + free(t); + slab->poison = get_obj("poison"); + slab->reclaim_account = get_obj("reclaim_account"); + slab->red_zone = get_obj("red_zone"); + slab->sanity_checks = get_obj("sanity_checks"); + slab->slab_size = get_obj("slab_size"); + slab->slabs = get_obj_and_str("slabs", &t); + decode_numa_list(slab->numa, t); + free(t); + slab->store_user = get_obj("store_user"); + slab->trace = get_obj("trace"); + slab->alloc_fastpath = get_obj("alloc_fastpath"); + slab->alloc_slowpath = get_obj("alloc_slowpath"); + slab->free_fastpath = get_obj("free_fastpath"); + slab->free_slowpath = get_obj("free_slowpath"); + slab->free_frozen= get_obj("free_frozen"); + slab->free_add_partial = get_obj("free_add_partial"); + slab->free_remove_partial = get_obj("free_remove_partial"); + slab->alloc_from_partial = get_obj("alloc_from_partial"); + slab->alloc_slab = get_obj("alloc_slab"); + slab->alloc_refill = get_obj("alloc_refill"); + slab->free_slab = get_obj("free_slab"); + slab->cpuslab_flush = get_obj("cpuslab_flush"); + slab->deactivate_full = get_obj("deactivate_full"); + slab->deactivate_empty = get_obj("deactivate_empty"); + slab->deactivate_to_head = get_obj("deactivate_to_head"); + slab->deactivate_to_tail = get_obj("deactivate_to_tail"); + slab->deactivate_remote_frees = get_obj("deactivate_remote_frees"); + slab->order_fallback = get_obj("order_fallback"); + chdir(".."); + if (slab->name[0] == ':') + alias_targets++; + slab++; + break; + default : + fatal("Unknown file type %lx\n", de->d_type); + } + } + closedir(dir); + slabs = slab - slabinfo; + actual_slabs = slabs; + aliases = alias - aliasinfo; + if (slabs > MAX_SLABS) + fatal("Too many slabs\n"); + if (aliases > MAX_ALIASES) + fatal("Too many aliases\n"); +} + +static void output_slabs(void) +{ + struct slabinfo *slab; + + for (slab = slabinfo; slab < slabinfo + slabs; slab++) { + + if (slab->alias) + continue; + + + if (show_numa) + slab_numa(slab, 0); + else if (show_track) + show_tracking(slab); + else if (validate) + slab_validate(slab); + else if (shrink) + slab_shrink(slab); + else if (set_debug) + slab_debug(slab); + else if (show_ops) + ops(slab); + else if (show_slab) + slabcache(slab); + else if (show_report) + report(slab); + } +} + +struct option opts[] = { + { "aliases", 0, NULL, 'a' }, + { "activity", 0, NULL, 'A' }, + { "debug", 2, NULL, 'd' }, + { "display-activity", 0, NULL, 'D' }, + { "empty", 0, NULL, 'e' }, + { "first-alias", 0, NULL, 'f' }, + { "help", 0, NULL, 'h' }, + { "inverted", 0, NULL, 'i'}, + { "numa", 0, NULL, 'n' }, + { "ops", 0, NULL, 'o' }, + { "report", 0, NULL, 'r' }, + { "shrink", 0, NULL, 's' }, + { "slabs", 0, NULL, 'l' }, + { "track", 0, NULL, 't'}, + { "validate", 0, NULL, 'v' }, + { "zero", 0, NULL, 'z' }, + { "1ref", 0, NULL, '1'}, + { NULL, 0, NULL, 0 } +}; + +int main(int argc, char *argv[]) +{ + int c; + int err; + char *pattern_source; + + page_size = getpagesize(); + + while ((c = getopt_long(argc, argv, "aAd::Defhil1noprstvzTS", + opts, NULL)) != -1) + switch (c) { + case '1': + show_single_ref = 1; + break; + case 'a': + show_alias = 1; + break; + case 'A': + sort_active = 1; + break; + case 'd': + set_debug = 1; + if (!debug_opt_scan(optarg)) + fatal("Invalid debug option '%s'\n", optarg); + break; + case 'D': + show_activity = 1; + break; + case 'e': + show_empty = 1; + break; + case 'f': + show_first_alias = 1; + break; + case 'h': + usage(); + return 0; + case 'i': + show_inverted = 1; + break; + case 'n': + show_numa = 1; + break; + case 'o': + show_ops = 1; + break; + case 'r': + show_report = 1; + break; + case 's': + shrink = 1; + break; + case 'l': + show_slab = 1; + break; + case 't': + show_track = 1; + break; + case 'v': + validate = 1; + break; + case 'z': + skip_zero = 0; + break; + case 'T': + show_totals = 1; + break; + case 'S': + sort_size = 1; + break; + + default: + fatal("%s: Invalid option '%c'\n", argv[0], optopt); + + } + + if (!show_slab && !show_alias && !show_track && !show_report + && !validate && !shrink && !set_debug && !show_ops) + show_slab = 1; + + if (argc > optind) + pattern_source = argv[optind]; + else + pattern_source = ".*"; + + err = regcomp(&pattern, pattern_source, REG_ICASE|REG_NOSUB); + if (err) + fatal("%s: Invalid pattern '%s' code %d\n", + argv[0], pattern_source, err); + read_slab_dir(); + if (show_alias) + alias(); + else + if (show_totals) + totals(); + else { + link_slabs(); + rename_slabs(); + sort_slabs(); + output_slabs(); + } + return 0; +} diff --git a/tools/testing/ktest/compare-ktest-sample.pl b/tools/testing/ktest/compare-ktest-sample.pl new file mode 100755 index 00000000000..9a571e71683 --- /dev/null +++ b/tools/testing/ktest/compare-ktest-sample.pl @@ -0,0 +1,30 @@ +#!/usr/bin/perl + +open (IN,"ktest.pl"); +while (<IN>) { + if (/\$opt\{"?([A-Z].*?)(\[.*\])?"?\}/ || + /set_test_option\("(.*?)"/) { + $opt{$1} = 1; + } +} +close IN; + +open (IN, "sample.conf"); +while (<IN>) { + if (/^\s*#?\s*(\S+)\s*=/) { + $samp{$1} = 1; + } +} +close IN; + +foreach $opt (keys %opt) { + if (!defined($samp{$opt})) { + print "opt = $opt\n"; + } +} + +foreach $samp (keys %samp) { + if (!defined($opt{$samp})) { + print "samp = $samp\n"; + } +} diff --git a/tools/testing/ktest/ktest.pl b/tools/testing/ktest/ktest.pl new file mode 100755 index 00000000000..e1c62eeb88f --- /dev/null +++ b/tools/testing/ktest/ktest.pl @@ -0,0 +1,2023 @@ +#!/usr/bin/perl -w +# +# Copywrite 2010 - Steven Rostedt <srostedt@redhat.com>, Red Hat Inc. +# Licensed under the terms of the GNU GPL License version 2 +# + +use strict; +use IPC::Open2; +use Fcntl qw(F_GETFL F_SETFL O_NONBLOCK); +use File::Path qw(mkpath); +use File::Copy qw(cp); +use FileHandle; + +my $VERSION = "0.2"; + +$| = 1; + +my %opt; +my %repeat_tests; +my %repeats; +my %default; + +#default opts +$default{"NUM_TESTS"} = 1; +$default{"REBOOT_TYPE"} = "grub"; +$default{"TEST_TYPE"} = "test"; +$default{"BUILD_TYPE"} = "randconfig"; +$default{"MAKE_CMD"} = "make"; +$default{"TIMEOUT"} = 120; +$default{"TMP_DIR"} = "/tmp/ktest"; +$default{"SLEEP_TIME"} = 60; # sleep time between tests +$default{"BUILD_NOCLEAN"} = 0; +$default{"REBOOT_ON_ERROR"} = 0; +$default{"POWEROFF_ON_ERROR"} = 0; +$default{"REBOOT_ON_SUCCESS"} = 1; +$default{"POWEROFF_ON_SUCCESS"} = 0; +$default{"BUILD_OPTIONS"} = ""; +$default{"BISECT_SLEEP_TIME"} = 60; # sleep time between bisects +$default{"CLEAR_LOG"} = 0; +$default{"SUCCESS_LINE"} = "login:"; +$default{"BOOTED_TIMEOUT"} = 1; +$default{"DIE_ON_FAILURE"} = 1; +$default{"SSH_EXEC"} = "ssh \$SSH_USER\@\$MACHINE \$SSH_COMMAND"; +$default{"SCP_TO_TARGET"} = "scp \$SRC_FILE \$SSH_USER\@\$MACHINE:\$DST_FILE"; +$default{"REBOOT"} = "ssh \$SSH_USER\@\$MACHINE reboot"; +$default{"STOP_AFTER_SUCCESS"} = 10; +$default{"STOP_AFTER_FAILURE"} = 60; +$default{"LOCALVERSION"} = "-test"; + +my $ktest_config; +my $version; +my $machine; +my $ssh_user; +my $tmpdir; +my $builddir; +my $outputdir; +my $output_config; +my $test_type; +my $build_type; +my $build_options; +my $reboot_type; +my $reboot_script; +my $power_cycle; +my $reboot; +my $reboot_on_error; +my $poweroff_on_error; +my $die_on_failure; +my $powercycle_after_reboot; +my $poweroff_after_halt; +my $ssh_exec; +my $scp_to_target; +my $power_off; +my $grub_menu; +my $grub_number; +my $target; +my $make; +my $post_install; +my $noclean; +my $minconfig; +my $addconfig; +my $in_bisect = 0; +my $bisect_bad = ""; +my $reverse_bisect; +my $in_patchcheck = 0; +my $run_test; +my $redirect; +my $buildlog; +my $dmesg; +my $monitor_fp; +my $monitor_pid; +my $monitor_cnt = 0; +my $sleep_time; +my $bisect_sleep_time; +my $store_failures; +my $timeout; +my $booted_timeout; +my $console; +my $success_line; +my $stop_after_success; +my $stop_after_failure; +my $build_target; +my $target_image; +my $localversion; +my $iteration = 0; +my $successes = 0; + +my %entered_configs; +my %config_help; + +$config_help{"MACHINE"} = << "EOF" + The machine hostname that you will test. +EOF + ; +$config_help{"SSH_USER"} = << "EOF" + The box is expected to have ssh on normal bootup, provide the user + (most likely root, since you need privileged operations) +EOF + ; +$config_help{"BUILD_DIR"} = << "EOF" + The directory that contains the Linux source code (full path). +EOF + ; +$config_help{"OUTPUT_DIR"} = << "EOF" + The directory that the objects will be built (full path). + (can not be same as BUILD_DIR) +EOF + ; +$config_help{"BUILD_TARGET"} = << "EOF" + The location of the compiled file to copy to the target. + (relative to OUTPUT_DIR) +EOF + ; +$config_help{"TARGET_IMAGE"} = << "EOF" + The place to put your image on the test machine. +EOF + ; +$config_help{"POWER_CYCLE"} = << "EOF" + A script or command to reboot the box. + + Here is a digital loggers power switch example + POWER_CYCLE = wget --no-proxy -O /dev/null -q --auth-no-challenge 'http://admin:admin\@power/outlet?5=CCL' + + Here is an example to reboot a virtual box on the current host + with the name "Guest". + POWER_CYCLE = virsh destroy Guest; sleep 5; virsh start Guest +EOF + ; +$config_help{"CONSOLE"} = << "EOF" + The script or command that reads the console + + If you use ttywatch server, something like the following would work. +CONSOLE = nc -d localhost 3001 + + For a virtual machine with guest name "Guest". +CONSOLE = virsh console Guest +EOF + ; +$config_help{"LOCALVERSION"} = << "EOF" + Required version ending to differentiate the test + from other linux builds on the system. +EOF + ; +$config_help{"REBOOT_TYPE"} = << "EOF" + Way to reboot the box to the test kernel. + Only valid options so far are "grub" and "script". + + If you specify grub, it will assume grub version 1 + and will search in /boot/grub/menu.lst for the title \$GRUB_MENU + and select that target to reboot to the kernel. If this is not + your setup, then specify "script" and have a command or script + specified in REBOOT_SCRIPT to boot to the target. + + The entry in /boot/grub/menu.lst must be entered in manually. + The test will not modify that file. +EOF + ; +$config_help{"GRUB_MENU"} = << "EOF" + The grub title name for the test kernel to boot + (Only mandatory if REBOOT_TYPE = grub) + + Note, ktest.pl will not update the grub menu.lst, you need to + manually add an option for the test. ktest.pl will search + the grub menu.lst for this option to find what kernel to + reboot into. + + For example, if in the /boot/grub/menu.lst the test kernel title has: + title Test Kernel + kernel vmlinuz-test + GRUB_MENU = Test Kernel +EOF + ; +$config_help{"REBOOT_SCRIPT"} = << "EOF" + A script to reboot the target into the test kernel + (Only mandatory if REBOOT_TYPE = script) +EOF + ; + + +sub get_ktest_config { + my ($config) = @_; + + return if (defined($opt{$config})); + + if (defined($config_help{$config})) { + print "\n"; + print $config_help{$config}; + } + + for (;;) { + print "$config = "; + if (defined($default{$config})) { + print "\[$default{$config}\] "; + } + $entered_configs{$config} = <STDIN>; + $entered_configs{$config} =~ s/^\s*(.*\S)\s*$/$1/; + if ($entered_configs{$config} =~ /^\s*$/) { + if ($default{$config}) { + $entered_configs{$config} = $default{$config}; + } else { + print "Your answer can not be blank\n"; + next; + } + } + last; + } +} + +sub get_ktest_configs { + get_ktest_config("MACHINE"); + get_ktest_config("SSH_USER"); + get_ktest_config("BUILD_DIR"); + get_ktest_config("OUTPUT_DIR"); + get_ktest_config("BUILD_TARGET"); + get_ktest_config("TARGET_IMAGE"); + get_ktest_config("POWER_CYCLE"); + get_ktest_config("CONSOLE"); + get_ktest_config("LOCALVERSION"); + + my $rtype = $opt{"REBOOT_TYPE"}; + + if (!defined($rtype)) { + if (!defined($opt{"GRUB_MENU"})) { + get_ktest_config("REBOOT_TYPE"); + $rtype = $entered_configs{"REBOOT_TYPE"}; + } else { + $rtype = "grub"; + } + } + + if ($rtype eq "grub") { + get_ktest_config("GRUB_MENU"); + } else { + get_ktest_config("REBOOT_SCRIPT"); + } +} + +sub set_value { + my ($lvalue, $rvalue) = @_; + + if (defined($opt{$lvalue})) { + die "Error: Option $lvalue defined more than once!\n"; + } + if ($rvalue =~ /^\s*$/) { + delete $opt{$lvalue}; + } else { + $opt{$lvalue} = $rvalue; + } +} + +sub read_config { + my ($config) = @_; + + open(IN, $config) || die "can't read file $config"; + + my $name = $config; + $name =~ s,.*/(.*),$1,; + + my $test_num = 0; + my $default = 1; + my $repeat = 1; + my $num_tests_set = 0; + my $skip = 0; + my $rest; + + while (<IN>) { + + # ignore blank lines and comments + next if (/^\s*$/ || /\s*\#/); + + if (/^\s*TEST_START(.*)/) { + + $rest = $1; + + if ($num_tests_set) { + die "$name: $.: Can not specify both NUM_TESTS and TEST_START\n"; + } + + my $old_test_num = $test_num; + my $old_repeat = $repeat; + + $test_num += $repeat; + $default = 0; + $repeat = 1; + + if ($rest =~ /\s+SKIP(.*)/) { + $rest = $1; + $skip = 1; + } else { + $skip = 0; + } + + if ($rest =~ /\s+ITERATE\s+(\d+)(.*)$/) { + $repeat = $1; + $rest = $2; + $repeat_tests{"$test_num"} = $repeat; + } + + if ($rest =~ /\s+SKIP(.*)/) { + $rest = $1; + $skip = 1; + } + + if ($rest !~ /^\s*$/) { + die "$name: $.: Gargbage found after TEST_START\n$_"; + } + + if ($skip) { + $test_num = $old_test_num; + $repeat = $old_repeat; + } + + } elsif (/^\s*DEFAULTS(.*)$/) { + $default = 1; + + $rest = $1; + + if ($rest =~ /\s+SKIP(.*)/) { + $rest = $1; + $skip = 1; + } else { + $skip = 0; + } + + if ($rest !~ /^\s*$/) { + die "$name: $.: Gargbage found after DEFAULTS\n$_"; + } + + } elsif (/^\s*([A-Z_\[\]\d]+)\s*=\s*(.*?)\s*$/) { + + next if ($skip); + + my $lvalue = $1; + my $rvalue = $2; + + if (!$default && + ($lvalue eq "NUM_TESTS" || + $lvalue eq "LOG_FILE" || + $lvalue eq "CLEAR_LOG")) { + die "$name: $.: $lvalue must be set in DEFAULTS section\n"; + } + + if ($lvalue eq "NUM_TESTS") { + if ($test_num) { + die "$name: $.: Can not specify both NUM_TESTS and TEST_START\n"; + } + if (!$default) { + die "$name: $.: NUM_TESTS must be set in default section\n"; + } + $num_tests_set = 1; + } + + if ($default || $lvalue =~ /\[\d+\]$/) { + set_value($lvalue, $rvalue); + } else { + my $val = "$lvalue\[$test_num\]"; + set_value($val, $rvalue); + + if ($repeat > 1) { + $repeats{$val} = $repeat; + } + } + } else { + die "$name: $.: Garbage found in config\n$_"; + } + } + + close(IN); + + if ($test_num) { + $test_num += $repeat - 1; + $opt{"NUM_TESTS"} = $test_num; + } + + # make sure we have all mandatory configs + get_ktest_configs; + + # set any defaults + + foreach my $default (keys %default) { + if (!defined($opt{$default})) { + $opt{$default} = $default{$default}; + } + } +} + +sub _logit { + if (defined($opt{"LOG_FILE"})) { + open(OUT, ">> $opt{LOG_FILE}") or die "Can't write to $opt{LOG_FILE}"; + print OUT @_; + close(OUT); + } +} + +sub logit { + if (defined($opt{"LOG_FILE"})) { + _logit @_; + } else { + print @_; + } +} + +sub doprint { + print @_; + _logit @_; +} + +sub run_command; + +sub reboot { + # try to reboot normally + if (run_command $reboot) { + if (defined($powercycle_after_reboot)) { + sleep $powercycle_after_reboot; + run_command "$power_cycle"; + } + } else { + # nope? power cycle it. + run_command "$power_cycle"; + } +} + +sub do_not_reboot { + my $i = $iteration; + + return $test_type eq "build" || + ($test_type eq "patchcheck" && $opt{"PATCHCHECK_TYPE[$i]"} eq "build") || + ($test_type eq "bisect" && $opt{"BISECT_TYPE[$i]"} eq "build"); +} + +sub dodie { + doprint "CRITICAL FAILURE... ", @_, "\n"; + + my $i = $iteration; + + if ($reboot_on_error && !do_not_reboot) { + + doprint "REBOOTING\n"; + reboot; + + } elsif ($poweroff_on_error && defined($power_off)) { + doprint "POWERING OFF\n"; + `$power_off`; + } + + die @_, "\n"; +} + +sub open_console { + my ($fp) = @_; + + my $flags; + + my $pid = open($fp, "$console|") or + dodie "Can't open console $console"; + + $flags = fcntl($fp, F_GETFL, 0) or + dodie "Can't get flags for the socket: $!"; + $flags = fcntl($fp, F_SETFL, $flags | O_NONBLOCK) or + dodie "Can't set flags for the socket: $!"; + + return $pid; +} + +sub close_console { + my ($fp, $pid) = @_; + + doprint "kill child process $pid\n"; + kill 2, $pid; + + print "closing!\n"; + close($fp); +} + +sub start_monitor { + if ($monitor_cnt++) { + return; + } + $monitor_fp = \*MONFD; + $monitor_pid = open_console $monitor_fp; + + return; + + open(MONFD, "Stop perl from warning about single use of MONFD"); +} + +sub end_monitor { + if (--$monitor_cnt) { + return; + } + close_console($monitor_fp, $monitor_pid); +} + +sub wait_for_monitor { + my ($time) = @_; + my $line; + + doprint "** Wait for monitor to settle down **\n"; + + # read the monitor and wait for the system to calm down + do { + $line = wait_for_input($monitor_fp, $time); + print "$line" if (defined($line)); + } while (defined($line)); + print "** Monitor flushed **\n"; +} + +sub fail { + + if ($die_on_failure) { + dodie @_; + } + + doprint "FAILED\n"; + + my $i = $iteration; + + # no need to reboot for just building. + if (!do_not_reboot) { + doprint "REBOOTING\n"; + reboot; + start_monitor; + wait_for_monitor $sleep_time; + end_monitor; + } + + doprint "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; + doprint "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; + doprint "KTEST RESULT: TEST $i Failed: ", @_, "\n"; + doprint "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; + doprint "%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%\n"; + + return 1 if (!defined($store_failures)); + + my @t = localtime; + my $date = sprintf "%04d%02d%02d%02d%02d%02d", + 1900+$t[5],$t[4],$t[3],$t[2],$t[1],$t[0]; + + my $type = $build_type; + if ($type =~ /useconfig/) { + $type = "useconfig"; + } + + my $dir = "$machine-$test_type-$type-fail-$date"; + my $faildir = "$store_failures/$dir"; + + if (!-d $faildir) { + mkpath($faildir) or + die "can't create $faildir"; + } + if (-f "$output_config") { + cp "$output_config", "$faildir/config" or + die "failed to copy .config"; + } + if (-f $buildlog) { + cp $buildlog, "$faildir/buildlog" or + die "failed to move $buildlog"; + } + if (-f $dmesg) { + cp $dmesg, "$faildir/dmesg" or + die "failed to move $dmesg"; + } + + doprint "*** Saved info to $faildir ***\n"; + + return 1; +} + +sub run_command { + my ($command) = @_; + my $dolog = 0; + my $dord = 0; + my $pid; + + $command =~ s/\$SSH_USER/$ssh_user/g; + $command =~ s/\$MACHINE/$machine/g; + + doprint("$command ... "); + + $pid = open(CMD, "$command 2>&1 |") or + (fail "unable to exec $command" and return 0); + + if (defined($opt{"LOG_FILE"})) { + open(LOG, ">>$opt{LOG_FILE}") or + dodie "failed to write to log"; + $dolog = 1; + } + + if (defined($redirect)) { + open (RD, ">$redirect") or + dodie "failed to write to redirect $redirect"; + $dord = 1; + } + + while (<CMD>) { + print LOG if ($dolog); + print RD if ($dord); + } + + waitpid($pid, 0); + my $failed = $?; + + close(CMD); + close(LOG) if ($dolog); + close(RD) if ($dord); + + if ($failed) { + doprint "FAILED!\n"; + } else { + doprint "SUCCESS\n"; + } + + return !$failed; +} + +sub run_ssh { + my ($cmd) = @_; + my $cp_exec = $ssh_exec; + + $cp_exec =~ s/\$SSH_COMMAND/$cmd/g; + return run_command "$cp_exec"; +} + +sub run_scp { + my ($src, $dst) = @_; + my $cp_scp = $scp_to_target; + + $cp_scp =~ s/\$SRC_FILE/$src/g; + $cp_scp =~ s/\$DST_FILE/$dst/g; + + return run_command "$cp_scp"; +} + +sub get_grub_index { + + if ($reboot_type ne "grub") { + return; + } + return if (defined($grub_number)); + + doprint "Find grub menu ... "; + $grub_number = -1; + + my $ssh_grub = $ssh_exec; + $ssh_grub =~ s,\$SSH_COMMAND,cat /boot/grub/menu.lst,g; + + open(IN, "$ssh_grub |") + or die "unable to get menu.lst"; + + while (<IN>) { + if (/^\s*title\s+$grub_menu\s*$/) { + $grub_number++; + last; + } elsif (/^\s*title\s/) { + $grub_number++; + } + } + close(IN); + + die "Could not find '$grub_menu' in /boot/grub/menu on $machine" + if ($grub_number < 0); + doprint "$grub_number\n"; +} + +sub wait_for_input +{ + my ($fp, $time) = @_; + my $rin; + my $ready; + my $line; + my $ch; + + if (!defined($time)) { + $time = $timeout; + } + + $rin = ''; + vec($rin, fileno($fp), 1) = 1; + $ready = select($rin, undef, undef, $time); + + $line = ""; + + # try to read one char at a time + while (sysread $fp, $ch, 1) { + $line .= $ch; + last if ($ch eq "\n"); + } + + if (!length($line)) { + return undef; + } + + return $line; +} + +sub reboot_to { + if ($reboot_type eq "grub") { + run_ssh "'(echo \"savedefault --default=$grub_number --once\" | grub --batch; reboot)'"; + return; + } + + run_command "$reboot_script"; +} + +sub get_sha1 { + my ($commit) = @_; + + doprint "git rev-list --max-count=1 $commit ... "; + my $sha1 = `git rev-list --max-count=1 $commit`; + my $ret = $?; + + logit $sha1; + + if ($ret) { + doprint "FAILED\n"; + dodie "Failed to get git $commit"; + } + + print "SUCCESS\n"; + + chomp $sha1; + + return $sha1; +} + +sub monitor { + my $booted = 0; + my $bug = 0; + my $skip_call_trace = 0; + my $loops; + + wait_for_monitor 5; + + my $line; + my $full_line = ""; + + open(DMESG, "> $dmesg") or + die "unable to write to $dmesg"; + + reboot_to; + + my $success_start; + my $failure_start; + + for (;;) { + + if ($booted) { + $line = wait_for_input($monitor_fp, $booted_timeout); + } else { + $line = wait_for_input($monitor_fp); + } + + last if (!defined($line)); + + doprint $line; + print DMESG $line; + + # we are not guaranteed to get a full line + $full_line .= $line; + + if ($full_line =~ /$success_line/) { + $booted = 1; + $success_start = time; + } + + if ($booted && defined($stop_after_success) && + $stop_after_success >= 0) { + my $now = time; + if ($now - $success_start >= $stop_after_success) { + doprint "Test forced to stop after $stop_after_success seconds after success\n"; + last; + } + } + + if ($full_line =~ /\[ backtrace testing \]/) { + $skip_call_trace = 1; + } + + if ($full_line =~ /call trace:/i) { + if (!$skip_call_trace) { + $bug = 1; + $failure_start = time; + } + } + + if ($bug && defined($stop_after_failure) && + $stop_after_failure >= 0) { + my $now = time; + if ($now - $failure_start >= $stop_after_failure) { + doprint "Test forced to stop after $stop_after_failure seconds after failure\n"; + last; + } + } + + if ($full_line =~ /\[ end of backtrace testing \]/) { + $skip_call_trace = 0; + } + + if ($full_line =~ /Kernel panic -/) { + $bug = 1; + } + + if ($line =~ /\n/) { + $full_line = ""; + } + } + + close(DMESG); + + if ($bug) { + return 0 if ($in_bisect); + fail "failed - got a bug report" and return 0; + } + + if (!$booted) { + return 0 if ($in_bisect); + fail "failed - never got a boot prompt." and return 0; + } + + return 1; +} + +sub install { + + run_scp "$outputdir/$build_target", "$target_image" or + dodie "failed to copy image"; + + my $install_mods = 0; + + # should we process modules? + $install_mods = 0; + open(IN, "$output_config") or dodie("Can't read config file"); + while (<IN>) { + if (/CONFIG_MODULES(=y)?/) { + $install_mods = 1 if (defined($1)); + last; + } + } + close(IN); + + if (!$install_mods) { + doprint "No modules needed\n"; + return; + } + + run_command "$make INSTALL_MOD_PATH=$tmpdir modules_install" or + dodie "Failed to install modules"; + + my $modlib = "/lib/modules/$version"; + my $modtar = "ktest-mods.tar.bz2"; + + run_ssh "rm -rf $modlib" or + dodie "failed to remove old mods: $modlib"; + + # would be nice if scp -r did not follow symbolic links + run_command "cd $tmpdir && tar -cjf $modtar lib/modules/$version" or + dodie "making tarball"; + + run_scp "$tmpdir/$modtar", "/tmp" or + dodie "failed to copy modules"; + + unlink "$tmpdir/$modtar"; + + run_ssh "'(cd / && tar xf /tmp/$modtar)'" or + dodie "failed to tar modules"; + + run_ssh "rm -f /tmp/$modtar"; + + return if (!defined($post_install)); + + my $cp_post_install = $post_install; + $cp_post_install = s/\$KERNEL_VERSION/$version/g; + run_command "$cp_post_install" or + dodie "Failed to run post install"; +} + +sub check_buildlog { + my ($patch) = @_; + + my @files = `git show $patch | diffstat -l`; + + open(IN, "git show $patch |") or + dodie "failed to show $patch"; + while (<IN>) { + if (m,^--- a/(.*),) { + chomp $1; + $files[$#files] = $1; + } + } + close(IN); + + open(IN, $buildlog) or dodie "Can't open $buildlog"; + while (<IN>) { + if (/^\s*(.*?):.*(warning|error)/) { + my $err = $1; + foreach my $file (@files) { + my $fullpath = "$builddir/$file"; + if ($file eq $err || $fullpath eq $err) { + fail "$file built with warnings" and return 0; + } + } + } + } + close(IN); + + return 1; +} + +sub build { + my ($type) = @_; + my $defconfig = ""; + + unlink $buildlog; + + if ($type =~ /^useconfig:(.*)/) { + run_command "cp $1 $output_config" or + dodie "could not copy $1 to .config"; + + $type = "oldconfig"; + } + + # old config can ask questions + if ($type eq "oldconfig") { + $type = "oldnoconfig"; + + # allow for empty configs + run_command "touch $output_config"; + + run_command "mv $output_config $outputdir/config_temp" or + dodie "moving .config"; + + if (!$noclean && !run_command "$make mrproper") { + dodie "make mrproper"; + } + + run_command "mv $outputdir/config_temp $output_config" or + dodie "moving config_temp"; + + } elsif (!$noclean) { + unlink "$output_config"; + run_command "$make mrproper" or + dodie "make mrproper"; + } + + # add something to distinguish this build + open(OUT, "> $outputdir/localversion") or dodie("Can't make localversion file"); + print OUT "$localversion\n"; + close(OUT); + + if (defined($minconfig)) { + $defconfig = "KCONFIG_ALLCONFIG=$minconfig"; + } + + run_command "$defconfig $make $type" or + dodie "failed make config"; + + $redirect = "$buildlog"; + if (!run_command "$make $build_options") { + undef $redirect; + # bisect may need this to pass + return 0 if ($in_bisect); + fail "failed build" and return 0; + } + undef $redirect; + + return 1; +} + +sub halt { + if (!run_ssh "halt" or defined($power_off)) { + if (defined($poweroff_after_halt)) { + sleep $poweroff_after_halt; + run_command "$power_off"; + } + } else { + # nope? the zap it! + run_command "$power_off"; + } +} + +sub success { + my ($i) = @_; + + $successes++; + + doprint "\n\n*******************************************\n"; + doprint "*******************************************\n"; + doprint "KTEST RESULT: TEST $i SUCCESS!!!! **\n"; + doprint "*******************************************\n"; + doprint "*******************************************\n"; + + if ($i != $opt{"NUM_TESTS"} && !do_not_reboot) { + doprint "Reboot and wait $sleep_time seconds\n"; + reboot; + start_monitor; + wait_for_monitor $sleep_time; + end_monitor; + } +} + +sub get_version { + # get the release name + doprint "$make kernelrelease ... "; + $version = `$make kernelrelease | tail -1`; + chomp($version); + doprint "$version\n"; +} + +sub child_run_test { + my $failed = 0; + + # child should have no power + $reboot_on_error = 0; + $poweroff_on_error = 0; + $die_on_failure = 1; + + run_command $run_test or $failed = 1; + exit $failed; +} + +my $child_done; + +sub child_finished { + $child_done = 1; +} + +sub do_run_test { + my $child_pid; + my $child_exit; + my $line; + my $full_line; + my $bug = 0; + + wait_for_monitor 1; + + doprint "run test $run_test\n"; + + $child_done = 0; + + $SIG{CHLD} = qw(child_finished); + + $child_pid = fork; + + child_run_test if (!$child_pid); + + $full_line = ""; + + do { + $line = wait_for_input($monitor_fp, 1); + if (defined($line)) { + + # we are not guaranteed to get a full line + $full_line .= $line; + + if ($full_line =~ /call trace:/i) { + $bug = 1; + } + + if ($full_line =~ /Kernel panic -/) { + $bug = 1; + } + + if ($line =~ /\n/) { + $full_line = ""; + } + } + } while (!$child_done && !$bug); + + if ($bug) { + doprint "Detected kernel crash!\n"; + # kill the child with extreme prejudice + kill 9, $child_pid; + } + + waitpid $child_pid, 0; + $child_exit = $?; + + if ($bug || $child_exit) { + return 0 if $in_bisect; + fail "test failed" and return 0; + } + return 1; +} + +sub run_git_bisect { + my ($command) = @_; + + doprint "$command ... "; + + my $output = `$command 2>&1`; + my $ret = $?; + + logit $output; + + if ($ret) { + doprint "FAILED\n"; + dodie "Failed to git bisect"; + } + + doprint "SUCCESS\n"; + if ($output =~ m/^(Bisecting: .*\(roughly \d+ steps?\))\s+\[([[:xdigit:]]+)\]/) { + doprint "$1 [$2]\n"; + } elsif ($output =~ m/^([[:xdigit:]]+) is the first bad commit/) { + $bisect_bad = $1; + doprint "Found bad commit... $1\n"; + return 0; + } else { + # we already logged it, just print it now. + print $output; + } + + return 1; +} + +# returns 1 on success, 0 on failure +sub run_bisect_test { + my ($type, $buildtype) = @_; + + my $failed = 0; + my $result; + my $output; + my $ret; + + $in_bisect = 1; + + build $buildtype or $failed = 1; + + if ($type ne "build") { + dodie "Failed on build" if $failed; + + # Now boot the box + get_grub_index; + get_version; + install; + + start_monitor; + monitor or $failed = 1; + + if ($type ne "boot") { + dodie "Failed on boot" if $failed; + + do_run_test or $failed = 1; + } + end_monitor; + } + + if ($failed) { + $result = 0; + + # reboot the box to a good kernel + if ($type ne "build") { + doprint "Reboot and sleep $bisect_sleep_time seconds\n"; + reboot; + start_monitor; + wait_for_monitor $bisect_sleep_time; + end_monitor; + } + } else { + $result = 1; + } + $in_bisect = 0; + + return $result; +} + +sub run_bisect { + my ($type) = @_; + my $buildtype = "oldconfig"; + + # We should have a minconfig to use? + if (defined($minconfig)) { + $buildtype = "useconfig:$minconfig"; + } + + my $ret = run_bisect_test $type, $buildtype; + + + # Are we looking for where it worked, not failed? + if ($reverse_bisect) { + $ret = !$ret; + } + + if ($ret) { + return "good"; + } else { + return "bad"; + } +} + +sub bisect { + my ($i) = @_; + + my $result; + + die "BISECT_GOOD[$i] not defined\n" if (!defined($opt{"BISECT_GOOD[$i]"})); + die "BISECT_BAD[$i] not defined\n" if (!defined($opt{"BISECT_BAD[$i]"})); + die "BISECT_TYPE[$i] not defined\n" if (!defined($opt{"BISECT_TYPE[$i]"})); + + my $good = $opt{"BISECT_GOOD[$i]"}; + my $bad = $opt{"BISECT_BAD[$i]"}; + my $type = $opt{"BISECT_TYPE[$i]"}; + my $start = $opt{"BISECT_START[$i]"}; + my $replay = $opt{"BISECT_REPLAY[$i]"}; + + # convert to true sha1's + $good = get_sha1($good); + $bad = get_sha1($bad); + + if (defined($opt{"BISECT_REVERSE[$i]"}) && + $opt{"BISECT_REVERSE[$i]"} == 1) { + doprint "Performing a reverse bisect (bad is good, good is bad!)\n"; + $reverse_bisect = 1; + } else { + $reverse_bisect = 0; + } + + # Can't have a test without having a test to run + if ($type eq "test" && !defined($run_test)) { + $type = "boot"; + } + + my $check = $opt{"BISECT_CHECK[$i]"}; + if (defined($check) && $check ne "0") { + + # get current HEAD + my $head = get_sha1("HEAD"); + + if ($check ne "good") { + doprint "TESTING BISECT BAD [$bad]\n"; + run_command "git checkout $bad" or + die "Failed to checkout $bad"; + + $result = run_bisect $type; + + if ($result ne "bad") { + fail "Tested BISECT_BAD [$bad] and it succeeded" and return 0; + } + } + + if ($check ne "bad") { + doprint "TESTING BISECT GOOD [$good]\n"; + run_command "git checkout $good" or + die "Failed to checkout $good"; + + $result = run_bisect $type; + + if ($result ne "good") { + fail "Tested BISECT_GOOD [$good] and it failed" and return 0; + } + } + + # checkout where we started + run_command "git checkout $head" or + die "Failed to checkout $head"; + } + + run_command "git bisect start" or + dodie "could not start bisect"; + + run_command "git bisect good $good" or + dodie "could not set bisect good to $good"; + + run_git_bisect "git bisect bad $bad" or + dodie "could not set bisect bad to $bad"; + + if (defined($replay)) { + run_command "git bisect replay $replay" or + dodie "failed to run replay"; + } + + if (defined($start)) { + run_command "git checkout $start" or + dodie "failed to checkout $start"; + } + + my $test; + do { + $result = run_bisect $type; + $test = run_git_bisect "git bisect $result"; + } while ($test); + + run_command "git bisect log" or + dodie "could not capture git bisect log"; + + run_command "git bisect reset" or + dodie "could not reset git bisect"; + + doprint "Bad commit was [$bisect_bad]\n"; + + success $i; +} + +my %config_ignore; +my %config_set; + +my %config_list; +my %null_config; + +my %dependency; + +sub process_config_ignore { + my ($config) = @_; + + open (IN, $config) + or dodie "Failed to read $config"; + + while (<IN>) { + if (/^(.*?(CONFIG\S*)(=.*| is not set))/) { + $config_ignore{$2} = $1; + } + } + + close(IN); +} + +sub read_current_config { + my ($config_ref) = @_; + + %{$config_ref} = (); + undef %{$config_ref}; + + my @key = keys %{$config_ref}; + if ($#key >= 0) { + print "did not delete!\n"; + exit; + } + open (IN, "$output_config"); + + while (<IN>) { + if (/^(CONFIG\S+)=(.*)/) { + ${$config_ref}{$1} = $2; + } + } + close(IN); +} + +sub get_dependencies { + my ($config) = @_; + + my $arr = $dependency{$config}; + if (!defined($arr)) { + return (); + } + + my @deps = @{$arr}; + + foreach my $dep (@{$arr}) { + print "ADD DEP $dep\n"; + @deps = (@deps, get_dependencies $dep); + } + + return @deps; +} + +sub create_config { + my @configs = @_; + + open(OUT, ">$output_config") or dodie "Can not write to $output_config"; + + foreach my $config (@configs) { + print OUT "$config_set{$config}\n"; + my @deps = get_dependencies $config; + foreach my $dep (@deps) { + print OUT "$config_set{$dep}\n"; + } + } + + foreach my $config (keys %config_ignore) { + print OUT "$config_ignore{$config}\n"; + } + close(OUT); + +# exit; + run_command "$make oldnoconfig" or + dodie "failed make config oldconfig"; + +} + +sub compare_configs { + my (%a, %b) = @_; + + foreach my $item (keys %a) { + if (!defined($b{$item})) { + print "diff $item\n"; + return 1; + } + delete $b{$item}; + } + + my @keys = keys %b; + if ($#keys) { + print "diff2 $keys[0]\n"; + } + return -1 if ($#keys >= 0); + + return 0; +} + +sub run_config_bisect_test { + my ($type) = @_; + + return run_bisect_test $type, "oldconfig"; +} + +sub process_passed { + my (%configs) = @_; + + doprint "These configs had no failure: (Enabling them for further compiles)\n"; + # Passed! All these configs are part of a good compile. + # Add them to the min options. + foreach my $config (keys %configs) { + if (defined($config_list{$config})) { + doprint " removing $config\n"; + $config_ignore{$config} = $config_list{$config}; + delete $config_list{$config}; + } + } + doprint "config copied to $outputdir/config_good\n"; + run_command "cp -f $output_config $outputdir/config_good"; +} + +sub process_failed { + my ($config) = @_; + + doprint "\n\n***************************************\n"; + doprint "Found bad config: $config\n"; + doprint "***************************************\n\n"; +} + +sub run_config_bisect { + + my @start_list = keys %config_list; + + if ($#start_list < 0) { + doprint "No more configs to test!!!\n"; + return -1; + } + + doprint "***** RUN TEST ***\n"; + my $type = $opt{"CONFIG_BISECT_TYPE[$iteration]"}; + my $ret; + my %current_config; + + my $count = $#start_list + 1; + doprint " $count configs to test\n"; + + my $half = int($#start_list / 2); + + do { + my @tophalf = @start_list[0 .. $half]; + + create_config @tophalf; + read_current_config \%current_config; + + $count = $#tophalf + 1; + doprint "Testing $count configs\n"; + my $found = 0; + # make sure we test something + foreach my $config (@tophalf) { + if (defined($current_config{$config})) { + logit " $config\n"; + $found = 1; + } + } + if (!$found) { + # try the other half + doprint "Top half produced no set configs, trying bottom half\n"; + @tophalf = @start_list[$half .. $#start_list]; + create_config @tophalf; + read_current_config \%current_config; + foreach my $config (@tophalf) { + if (defined($current_config{$config})) { + logit " $config\n"; + $found = 1; + } + } + if (!$found) { + doprint "Failed: Can't make new config with current configs\n"; + foreach my $config (@start_list) { + doprint " CONFIG: $config\n"; + } + return -1; + } + $count = $#tophalf + 1; + doprint "Testing $count configs\n"; + } + + $ret = run_config_bisect_test $type; + + if ($ret) { + process_passed %current_config; + return 0; + } + + doprint "This config had a failure.\n"; + doprint "Removing these configs that were not set in this config:\n"; + doprint "config copied to $outputdir/config_bad\n"; + run_command "cp -f $output_config $outputdir/config_bad"; + + # A config exists in this group that was bad. + foreach my $config (keys %config_list) { + if (!defined($current_config{$config})) { + doprint " removing $config\n"; + delete $config_list{$config}; + } + } + + @start_list = @tophalf; + + if ($#start_list == 0) { + process_failed $start_list[0]; + return 1; + } + + # remove half the configs we are looking at and see if + # they are good. + $half = int($#start_list / 2); + } while ($half > 0); + + # we found a single config, try it again + my @tophalf = @start_list[0 .. 0]; + + $ret = run_config_bisect_test $type; + if ($ret) { + process_passed %current_config; + return 0; + } + + process_failed $start_list[0]; + return 1; +} + +sub config_bisect { + my ($i) = @_; + + my $start_config = $opt{"CONFIG_BISECT[$i]"}; + + my $tmpconfig = "$tmpdir/use_config"; + + # Make the file with the bad config and the min config + if (defined($minconfig)) { + # read the min config for things to ignore + run_command "cp $minconfig $tmpconfig" or + dodie "failed to copy $minconfig to $tmpconfig"; + } else { + unlink $tmpconfig; + } + + # Add other configs + if (defined($addconfig)) { + run_command "cat $addconfig >> $tmpconfig" or + dodie "failed to append $addconfig"; + } + + my $defconfig = ""; + if (-f $tmpconfig) { + $defconfig = "KCONFIG_ALLCONFIG=$tmpconfig"; + process_config_ignore $tmpconfig; + } + + # now process the start config + run_command "cp $start_config $output_config" or + dodie "failed to copy $start_config to $output_config"; + + # read directly what we want to check + my %config_check; + open (IN, $output_config) + or dodie "faied to open $output_config"; + + while (<IN>) { + if (/^((CONFIG\S*)=.*)/) { + $config_check{$2} = $1; + } + } + close(IN); + + # Now run oldconfig with the minconfig (and addconfigs) + run_command "$defconfig $make oldnoconfig" or + dodie "failed make config oldconfig"; + + # check to see what we lost (or gained) + open (IN, $output_config) + or dodie "Failed to read $start_config"; + + my %removed_configs; + my %added_configs; + + while (<IN>) { + if (/^((CONFIG\S*)=.*)/) { + # save off all options + $config_set{$2} = $1; + if (defined($config_check{$2})) { + if (defined($config_ignore{$2})) { + $removed_configs{$2} = $1; + } else { + $config_list{$2} = $1; + } + } elsif (!defined($config_ignore{$2})) { + $added_configs{$2} = $1; + $config_list{$2} = $1; + } + } + } + close(IN); + + my @confs = keys %removed_configs; + if ($#confs >= 0) { + doprint "Configs overridden by default configs and removed from check:\n"; + foreach my $config (@confs) { + doprint " $config\n"; + } + } + @confs = keys %added_configs; + if ($#confs >= 0) { + doprint "Configs appearing in make oldconfig and added:\n"; + foreach my $config (@confs) { + doprint " $config\n"; + } + } + + my %config_test; + my $once = 0; + + # Sometimes kconfig does weird things. We must make sure + # that the config we autocreate has everything we need + # to test, otherwise we may miss testing configs, or + # may not be able to create a new config. + # Here we create a config with everything set. + create_config (keys %config_list); + read_current_config \%config_test; + foreach my $config (keys %config_list) { + if (!defined($config_test{$config})) { + if (!$once) { + $once = 1; + doprint "Configs not produced by kconfig (will not be checked):\n"; + } + doprint " $config\n"; + delete $config_list{$config}; + } + } + my $ret; + do { + $ret = run_config_bisect; + } while (!$ret); + + return $ret if ($ret < 0); + + success $i; +} + +sub patchcheck { + my ($i) = @_; + + die "PATCHCHECK_START[$i] not defined\n" + if (!defined($opt{"PATCHCHECK_START[$i]"})); + die "PATCHCHECK_TYPE[$i] not defined\n" + if (!defined($opt{"PATCHCHECK_TYPE[$i]"})); + + my $start = $opt{"PATCHCHECK_START[$i]"}; + + my $end = "HEAD"; + if (defined($opt{"PATCHCHECK_END[$i]"})) { + $end = $opt{"PATCHCHECK_END[$i]"}; + } + + # Get the true sha1's since we can use things like HEAD~3 + $start = get_sha1($start); + $end = get_sha1($end); + + my $type = $opt{"PATCHCHECK_TYPE[$i]"}; + + # Can't have a test without having a test to run + if ($type eq "test" && !defined($run_test)) { + $type = "boot"; + } + + open (IN, "git log --pretty=oneline $end|") or + dodie "could not get git list"; + + my @list; + + while (<IN>) { + chomp; + $list[$#list+1] = $_; + last if (/^$start/); + } + close(IN); + + if ($list[$#list] !~ /^$start/) { + fail "SHA1 $start not found"; + } + + # go backwards in the list + @list = reverse @list; + + my $save_clean = $noclean; + + $in_patchcheck = 1; + foreach my $item (@list) { + my $sha1 = $item; + $sha1 =~ s/^([[:xdigit:]]+).*/$1/; + + doprint "\nProcessing commit $item\n\n"; + + run_command "git checkout $sha1" or + die "Failed to checkout $sha1"; + + # only clean on the first and last patch + if ($item eq $list[0] || + $item eq $list[$#list]) { + $noclean = $save_clean; + } else { + $noclean = 1; + } + + if (defined($minconfig)) { + build "useconfig:$minconfig" or return 0; + } else { + # ?? no config to use? + build "oldconfig" or return 0; + } + + check_buildlog $sha1 or return 0; + + next if ($type eq "build"); + + get_grub_index; + get_version; + install; + + my $failed = 0; + + start_monitor; + monitor or $failed = 1; + + if (!$failed && $type ne "boot"){ + do_run_test or $failed = 1; + } + end_monitor; + return 0 if ($failed); + + } + $in_patchcheck = 0; + success $i; + + return 1; +} + +$#ARGV < 1 or die "ktest.pl version: $VERSION\n usage: ktest.pl config-file\n"; + +if ($#ARGV == 0) { + $ktest_config = $ARGV[0]; + if (! -f $ktest_config) { + print "$ktest_config does not exist.\n"; + my $ans; + for (;;) { + print "Create it? [Y/n] "; + $ans = <STDIN>; + chomp $ans; + if ($ans =~ /^\s*$/) { + $ans = "y"; + } + last if ($ans =~ /^y$/i || $ans =~ /^n$/i); + print "Please answer either 'y' or 'n'.\n"; + } + if ($ans !~ /^y$/i) { + exit 0; + } + } +} else { + $ktest_config = "ktest.conf"; +} + +if (! -f $ktest_config) { + open(OUT, ">$ktest_config") or die "Can not create $ktest_config"; + print OUT << "EOF" +# Generated by ktest.pl +# +# Define each test with TEST_START +# The config options below it will override the defaults +TEST_START + +DEFAULTS +EOF +; + close(OUT); +} +read_config $ktest_config; + +# Append any configs entered in manually to the config file. +my @new_configs = keys %entered_configs; +if ($#new_configs >= 0) { + print "\nAppending entered in configs to $ktest_config\n"; + open(OUT, ">>$ktest_config") or die "Can not append to $ktest_config"; + foreach my $config (@new_configs) { + print OUT "$config = $entered_configs{$config}\n"; + $opt{$config} = $entered_configs{$config}; + } +} + +if ($opt{"CLEAR_LOG"} && defined($opt{"LOG_FILE"})) { + unlink $opt{"LOG_FILE"}; +} + +doprint "\n\nSTARTING AUTOMATED TESTS\n\n"; + +for (my $i = 0, my $repeat = 1; $i <= $opt{"NUM_TESTS"}; $i += $repeat) { + + if (!$i) { + doprint "DEFAULT OPTIONS:\n"; + } else { + doprint "\nTEST $i OPTIONS"; + if (defined($repeat_tests{$i})) { + $repeat = $repeat_tests{$i}; + doprint " ITERATE $repeat"; + } + doprint "\n"; + } + + foreach my $option (sort keys %opt) { + + if ($option =~ /\[(\d+)\]$/) { + next if ($i != $1); + } else { + next if ($i); + } + + doprint "$option = $opt{$option}\n"; + } +} + +sub set_test_option { + my ($name, $i) = @_; + + my $option = "$name\[$i\]"; + + if (defined($opt{$option})) { + return $opt{$option}; + } + + foreach my $test (keys %repeat_tests) { + if ($i >= $test && + $i < $test + $repeat_tests{$test}) { + $option = "$name\[$test\]"; + if (defined($opt{$option})) { + return $opt{$option}; + } + } + } + + if (defined($opt{$name})) { + return $opt{$name}; + } + + return undef; +} + +# First we need to do is the builds +for (my $i = 1; $i <= $opt{"NUM_TESTS"}; $i++) { + + $iteration = $i; + + my $makecmd = set_test_option("MAKE_CMD", $i); + + $machine = set_test_option("MACHINE", $i); + $ssh_user = set_test_option("SSH_USER", $i); + $tmpdir = set_test_option("TMP_DIR", $i); + $outputdir = set_test_option("OUTPUT_DIR", $i); + $builddir = set_test_option("BUILD_DIR", $i); + $test_type = set_test_option("TEST_TYPE", $i); + $build_type = set_test_option("BUILD_TYPE", $i); + $build_options = set_test_option("BUILD_OPTIONS", $i); + $power_cycle = set_test_option("POWER_CYCLE", $i); + $reboot = set_test_option("REBOOT", $i); + $noclean = set_test_option("BUILD_NOCLEAN", $i); + $minconfig = set_test_option("MIN_CONFIG", $i); + $run_test = set_test_option("TEST", $i); + $addconfig = set_test_option("ADD_CONFIG", $i); + $reboot_type = set_test_option("REBOOT_TYPE", $i); + $grub_menu = set_test_option("GRUB_MENU", $i); + $post_install = set_test_option("POST_INSTALL", $i); + $reboot_script = set_test_option("REBOOT_SCRIPT", $i); + $reboot_on_error = set_test_option("REBOOT_ON_ERROR", $i); + $poweroff_on_error = set_test_option("POWEROFF_ON_ERROR", $i); + $die_on_failure = set_test_option("DIE_ON_FAILURE", $i); + $power_off = set_test_option("POWER_OFF", $i); + $powercycle_after_reboot = set_test_option("POWERCYCLE_AFTER_REBOOT", $i); + $poweroff_after_halt = set_test_option("POWEROFF_AFTER_HALT", $i); + $sleep_time = set_test_option("SLEEP_TIME", $i); + $bisect_sleep_time = set_test_option("BISECT_SLEEP_TIME", $i); + $store_failures = set_test_option("STORE_FAILURES", $i); + $timeout = set_test_option("TIMEOUT", $i); + $booted_timeout = set_test_option("BOOTED_TIMEOUT", $i); + $console = set_test_option("CONSOLE", $i); + $success_line = set_test_option("SUCCESS_LINE", $i); + $stop_after_success = set_test_option("STOP_AFTER_SUCCESS", $i); + $stop_after_failure = set_test_option("STOP_AFTER_FAILURE", $i); + $build_target = set_test_option("BUILD_TARGET", $i); + $ssh_exec = set_test_option("SSH_EXEC", $i); + $scp_to_target = set_test_option("SCP_TO_TARGET", $i); + $target_image = set_test_option("TARGET_IMAGE", $i); + $localversion = set_test_option("LOCALVERSION", $i); + + chdir $builddir || die "can't change directory to $builddir"; + + if (!-d $tmpdir) { + mkpath($tmpdir) or + die "can't create $tmpdir"; + } + + $ENV{"SSH_USER"} = $ssh_user; + $ENV{"MACHINE"} = $machine; + + $target = "$ssh_user\@$machine"; + + $buildlog = "$tmpdir/buildlog-$machine"; + $dmesg = "$tmpdir/dmesg-$machine"; + $make = "$makecmd O=$outputdir"; + $output_config = "$outputdir/.config"; + + if ($reboot_type eq "grub") { + dodie "GRUB_MENU not defined" if (!defined($grub_menu)); + } elsif (!defined($reboot_script)) { + dodie "REBOOT_SCRIPT not defined" + } + + my $run_type = $build_type; + if ($test_type eq "patchcheck") { + $run_type = $opt{"PATCHCHECK_TYPE[$i]"}; + } elsif ($test_type eq "bisect") { + $run_type = $opt{"BISECT_TYPE[$i]"}; + } elsif ($test_type eq "config_bisect") { + $run_type = $opt{"CONFIG_BISECT_TYPE[$i]"}; + } + + # mistake in config file? + if (!defined($run_type)) { + $run_type = "ERROR"; + } + + doprint "\n\n"; + doprint "RUNNING TEST $i of $opt{NUM_TESTS} with option $test_type $run_type\n\n"; + + unlink $dmesg; + unlink $buildlog; + + if (!defined($minconfig)) { + $minconfig = $addconfig; + + } elsif (defined($addconfig)) { + run_command "cat $addconfig $minconfig > $tmpdir/add_config" or + dodie "Failed to create temp config"; + $minconfig = "$tmpdir/add_config"; + } + + my $checkout = $opt{"CHECKOUT[$i]"}; + if (defined($checkout)) { + run_command "git checkout $checkout" or + die "failed to checkout $checkout"; + } + + if ($test_type eq "bisect") { + bisect $i; + next; + } elsif ($test_type eq "config_bisect") { + config_bisect $i; + next; + } elsif ($test_type eq "patchcheck") { + patchcheck $i; + next; + } + + if ($build_type ne "nobuild") { + build $build_type or next; + } + + if ($test_type ne "build") { + get_grub_index; + get_version; + install; + + my $failed = 0; + start_monitor; + monitor or $failed = 1;; + + if (!$failed && $test_type ne "boot" && defined($run_test)) { + do_run_test or $failed = 1; + } + end_monitor; + next if ($failed); + } + + success $i; +} + +if ($opt{"POWEROFF_ON_SUCCESS"}) { + halt; +} elsif ($opt{"REBOOT_ON_SUCCESS"} && !do_not_reboot) { + reboot; +} + +doprint "\n $successes of $opt{NUM_TESTS} tests were successful\n\n"; + +exit 0; diff --git a/tools/testing/ktest/sample.conf b/tools/testing/ktest/sample.conf new file mode 100644 index 00000000000..3408c594b2d --- /dev/null +++ b/tools/testing/ktest/sample.conf @@ -0,0 +1,622 @@ +# +# Config file for ktest.pl +# +# Note, all paths must be absolute +# + +# Options set in the beginning of the file are considered to be +# default options. These options can be overriden by test specific +# options, with the following exceptions: +# +# LOG_FILE +# CLEAR_LOG +# POWEROFF_ON_SUCCESS +# REBOOT_ON_SUCCESS +# +# Test specific options are set after the label: +# +# TEST_START +# +# The options after a TEST_START label are specific to that test. +# Each TEST_START label will set up a new test. If you want to +# perform a test more than once, you can add the ITERATE label +# to it followed by the number of times you want that test +# to iterate. If the ITERATE is left off, the test will only +# be performed once. +# +# TEST_START ITERATE 10 +# +# You can skip a test by adding SKIP (before or after the ITERATE +# and number) +# +# TEST_START SKIP +# +# TEST_START SKIP ITERATE 10 +# +# TEST_START ITERATE 10 SKIP +# +# The SKIP label causes the options and the test itself to be ignored. +# This is useful to set up several different tests in one config file, and +# only enabling the ones you want to use for a current test run. +# +# You can add default options anywhere in the file as well +# with the DEFAULTS tag. This allows you to have default options +# after the test options to keep the test options at the top +# of the file. You can even place the DEFAULTS tag between +# test cases (but not in the middle of a single test case) +# +# TEST_START +# MIN_CONFIG = /home/test/config-test1 +# +# DEFAULTS +# MIN_CONFIG = /home/test/config-default +# +# TEST_START ITERATE 10 +# +# The above will run the first test with MIN_CONFIG set to +# /home/test/config-test-1. Then 10 tests will be executed +# with MIN_CONFIG with /home/test/config-default. +# +# You can also disable defaults with the SKIP option +# +# DEFAULTS SKIP +# MIN_CONFIG = /home/test/config-use-sometimes +# +# DEFAULTS +# MIN_CONFIG = /home/test/config-most-times +# +# The above will ignore the first MIN_CONFIG. If you want to +# use the first MIN_CONFIG, remove the SKIP from the first +# DEFAULTS tag and add it to the second. Be careful, options +# may only be declared once per test or default. If you have +# the same option name under the same test or as default +# ktest will fail to execute, and no tests will run. +# + + +#### Mandatory Default Options #### + +# These options must be in the default section, although most +# may be overridden by test options. + +# The machine hostname that you will test +#MACHINE = target + +# The box is expected to have ssh on normal bootup, provide the user +# (most likely root, since you need privileged operations) +#SSH_USER = root + +# The directory that contains the Linux source code +#BUILD_DIR = /home/test/linux.git + +# The directory that the objects will be built +# (can not be same as BUILD_DIR) +#OUTPUT_DIR = /home/test/build/target + +# The location of the compiled file to copy to the target +# (relative to OUTPUT_DIR) +#BUILD_TARGET = arch/x86/boot/bzImage + +# The place to put your image on the test machine +#TARGET_IMAGE = /boot/vmlinuz-test + +# A script or command to reboot the box +# +# Here is a digital loggers power switch example +#POWER_CYCLE = wget --no-proxy -O /dev/null -q --auth-no-challenge 'http://admin:admin@power/outlet?5=CCL' +# +# Here is an example to reboot a virtual box on the current host +# with the name "Guest". +#POWER_CYCLE = virsh destroy Guest; sleep 5; virsh start Guest + +# The script or command that reads the console +# +# If you use ttywatch server, something like the following would work. +#CONSOLE = nc -d localhost 3001 +# +# For a virtual machine with guest name "Guest". +#CONSOLE = virsh console Guest + +# Required version ending to differentiate the test +# from other linux builds on the system. +#LOCALVERSION = -test + +# The grub title name for the test kernel to boot +# (Only mandatory if REBOOT_TYPE = grub) +# +# Note, ktest.pl will not update the grub menu.lst, you need to +# manually add an option for the test. ktest.pl will search +# the grub menu.lst for this option to find what kernel to +# reboot into. +# +# For example, if in the /boot/grub/menu.lst the test kernel title has: +# title Test Kernel +# kernel vmlinuz-test +#GRUB_MENU = Test Kernel + +# A script to reboot the target into the test kernel +# (Only mandatory if REBOOT_TYPE = script) +#REBOOT_SCRIPT = + +#### Optional Config Options (all have defaults) #### + +# Start a test setup. If you leave this off, all options +# will be default and the test will run once. +# This is a label and not really an option (it takes no value). +# You can append ITERATE and a number after it to iterate the +# test a number of times, or SKIP to ignore this test. +# +#TEST_START +#TEST_START ITERATE 5 +#TEST_START SKIP + +# Have the following options as default again. Used after tests +# have already been defined by TEST_START. Optionally, you can +# just define all default options before the first TEST_START +# and you do not need this option. +# +# This is a label and not really an option (it takes no value). +# You can append SKIP to this label and the options within this +# section will be ignored. +# +# DEFAULTS +# DEFAULTS SKIP + +# The default test type (default test) +# The test types may be: +# build - only build the kernel, do nothing else +# boot - build and boot the kernel +# test - build, boot and if TEST is set, run the test script +# (If TEST is not set, it defaults back to boot) +# bisect - Perform a bisect on the kernel (see BISECT_TYPE below) +# patchcheck - Do a test on a series of commits in git (see PATCHCHECK below) +#TEST_TYPE = test + +# Test to run if there is a successful boot and TEST_TYPE is test. +# Must exit with 0 on success and non zero on error +# default (undefined) +#TEST = ssh user@machine /root/run_test + +# The build type is any make config type or special command +# (default randconfig) +# nobuild - skip the clean and build step +# useconfig:/path/to/config - use the given config and run +# oldconfig on it. +# This option is ignored if TEST_TYPE is patchcheck or bisect +#BUILD_TYPE = randconfig + +# The make command (default make) +# If you are building a 32bit x86 on a 64 bit host +#MAKE_CMD = CC=i386-gcc AS=i386-as make ARCH=i386 + +# Any build options for the make of the kernel (not for other makes, like configs) +# (default "") +#BUILD_OPTIONS = -j20 + +# If you need an initrd, you can add a script or code here to install +# it. The environment variable KERNEL_VERSION will be set to the +# kernel version that is used. Remember to add the initrd line +# to your grub menu.lst file. +# +# Here's a couple of examples to use: +#POST_INSTALL = ssh user@target /sbin/mkinitrd --allow-missing -f /boot/initramfs-test.img $KERNEL_VERSION +# +# or on some systems: +#POST_INSTALL = ssh user@target /sbin/dracut -f /boot/initramfs-test.img $KERNEL_VERSION + +# Way to reboot the box to the test kernel. +# Only valid options so far are "grub" and "script" +# (default grub) +# If you specify grub, it will assume grub version 1 +# and will search in /boot/grub/menu.lst for the title $GRUB_MENU +# and select that target to reboot to the kernel. If this is not +# your setup, then specify "script" and have a command or script +# specified in REBOOT_SCRIPT to boot to the target. +# +# The entry in /boot/grub/menu.lst must be entered in manually. +# The test will not modify that file. +#REBOOT_TYPE = grub + +# The min config that is needed to build for the machine +# A nice way to create this is with the following: +# +# $ ssh target +# $ lsmod > mymods +# $ scp mymods host:/tmp +# $ exit +# $ cd linux.git +# $ rm .config +# $ make LSMOD=mymods localyesconfig +# $ grep '^CONFIG' .config > /home/test/config-min +# +# If you want even less configs: +# +# log in directly to target (do not ssh) +# +# $ su +# # lsmod | cut -d' ' -f1 | xargs rmmod +# +# repeat the above several times +# +# # lsmod > mymods +# # reboot +# +# May need to reboot to get your network back to copy the mymods +# to the host, and then remove the previous .config and run the +# localyesconfig again. The CONFIG_MIN generated like this will +# not guarantee network activity to the box so the TEST_TYPE of +# test may fail. +# +# You might also want to set: +# CONFIG_CMDLINE="<your options here>" +# randconfig may set the above and override your real command +# line options. +# (default undefined) +#MIN_CONFIG = /home/test/config-min + +# Sometimes there's options that just break the boot and +# you do not care about. Here are a few: +# # CONFIG_STAGING is not set +# Staging drivers are horrible, and can break the build. +# # CONFIG_SCSI_DEBUG is not set +# SCSI_DEBUG may change your root partition +# # CONFIG_KGDB_SERIAL_CONSOLE is not set +# KGDB may cause oops waiting for a connection that's not there. +# This option points to the file containing config options that will be prepended +# to the MIN_CONFIG (or be the MIN_CONFIG if it is not set) +# +# Note, config options in MIN_CONFIG will override these options. +# +# (default undefined) +#ADD_CONFIG = /home/test/config-broken + +# The location on the host where to write temp files +# (default /tmp/ktest) +#TMP_DIR = /tmp/ktest + +# Optional log file to write the status (recommended) +# Note, this is a DEFAULT section only option. +# (default undefined) +#LOG_FILE = /home/test/logfiles/target.log + +# Remove old logfile if it exists before starting all tests. +# Note, this is a DEFAULT section only option. +# (default 0) +#CLEAR_LOG = 0 + +# Line to define a successful boot up in console output. +# This is what the line contains, not the entire line. If you need +# the entire line to match, then use regural expression syntax like: +# (do not add any quotes around it) +# +# SUCCESS_LINE = ^MyBox Login:$ +# +# (default "login:") +#SUCCESS_LINE = login: + +# In case the console constantly fills the screen, having +# a specified time to stop the test after success is recommended. +# (in seconds) +# (default 10) +#STOP_AFTER_SUCCESS = 10 + +# In case the console constantly fills the screen, having +# a specified time to stop the test after failure is recommended. +# (in seconds) +# (default 60) +#STOP_AFTER_FAILURE = 60 + +# Stop testing if a build fails. If set, the script will end if +# a failure is detected, otherwise it will save off the .config, +# dmesg and bootlog in a directory called +# MACHINE-TEST_TYPE_BUILD_TYPE-fail-yyyymmddhhmmss +# if the STORE_FAILURES directory is set. +# (default 1) +# Note, even if this is set to zero, there are some errors that still +# stop the tests. +#DIE_ON_FAILURE = 1 + +# Directory to store failure directories on failure. If this is not +# set, DIE_ON_FAILURE=0 will not save off the .config, dmesg and +# bootlog. This option is ignored if DIE_ON_FAILURE is not set. +# (default undefined) +#STORE_FAILURES = /home/test/failures + +# Build without doing a make mrproper, or removing .config +# (default 0) +#BUILD_NOCLEAN = 0 + +# As the test reads the console, after it hits the SUCCESS_LINE +# the time it waits for the monitor to settle down between reads +# can usually be lowered. +# (in seconds) (default 1) +#BOOTED_TIMEOUT = 1 + +# The timeout in seconds when we consider the box hung after +# the console stop producing output. Be sure to leave enough +# time here to get pass a reboot. Some machines may not produce +# any console output for a long time during a reboot. You do +# not want the test to fail just because the system was in +# the process of rebooting to the test kernel. +# (default 120) +#TIMEOUT = 120 + +# In between tests, a reboot of the box may occur, and this +# is the time to wait for the console after it stops producing +# output. Some machines may not produce a large lag on reboot +# so this should accommodate it. +# The difference between this and TIMEOUT, is that TIMEOUT happens +# when rebooting to the test kernel. This sleep time happens +# after a test has completed and we are about to start running +# another test. If a reboot to the reliable kernel happens, +# we wait SLEEP_TIME for the console to stop producing output +# before starting the next test. +# (default 60) +#SLEEP_TIME = 60 + +# The time in between bisects to sleep (in seconds) +# (default 60) +#BISECT_SLEEP_TIME = 60 + +# Reboot the target box on error (default 0) +#REBOOT_ON_ERROR = 0 + +# Power off the target on error (ignored if REBOOT_ON_ERROR is set) +# Note, this is a DEFAULT section only option. +# (default 0) +#POWEROFF_ON_ERROR = 0 + +# Power off the target after all tests have completed successfully +# Note, this is a DEFAULT section only option. +# (default 0) +#POWEROFF_ON_SUCCESS = 0 + +# Reboot the target after all test completed successfully (default 1) +# (ignored if POWEROFF_ON_SUCCESS is set) +#REBOOT_ON_SUCCESS = 1 + +# In case there are isses with rebooting, you can specify this +# to always powercycle after this amount of time after calling +# reboot. +# Note, POWERCYCLE_AFTER_REBOOT = 0 does NOT disable it. It just +# makes it powercycle immediately after rebooting. Do not define +# it if you do not want it. +# (default undefined) +#POWERCYCLE_AFTER_REBOOT = 5 + +# In case there's isses with halting, you can specify this +# to always poweroff after this amount of time after calling +# halt. +# Note, POWEROFF_AFTER_HALT = 0 does NOT disable it. It just +# makes it poweroff immediately after halting. Do not define +# it if you do not want it. +# (default undefined) +#POWEROFF_AFTER_HALT = 20 + +# A script or command to power off the box (default undefined) +# Needed for POWEROFF_ON_ERROR and SUCCESS +# +# Example for digital loggers power switch: +#POWER_OFF = wget --no-proxy -O /dev/null -q --auth-no-challenge 'http://admin:admin@power/outlet?5=OFF' +# +# Example for a virtual guest call "Guest". +#POWER_OFF = virsh destroy Guest + +# The way to execute a command on the target +# (default ssh $SSH_USER@$MACHINE $SSH_COMMAND";) +# The variables SSH_USER, MACHINE and SSH_COMMAND are defined +#SSH_EXEC = ssh $SSH_USER@$MACHINE $SSH_COMMAND"; + +# The way to copy a file to the target +# (default scp $SRC_FILE $SSH_USER@$MACHINE:$DST_FILE) +# The variables SSH_USER, MACHINE, SRC_FILE and DST_FILE are defined. +#SCP_TO_TARGET = scp $SRC_FILE $SSH_USER@$MACHINE:$DST_FILE + +# The nice way to reboot the target +# (default ssh $SSH_USER@$MACHINE reboot) +# The variables SSH_USER and MACHINE are defined. +#REBOOT = ssh $SSH_USER@$MACHINE reboot + +#### Per test run options #### +# The following options are only allowed in TEST_START sections. +# They are ignored in the DEFAULTS sections. +# +# All of these are optional and undefined by default, although +# some of these options are required for TEST_TYPE of patchcheck +# and bisect. +# +# +# CHECKOUT = branch +# +# If the BUILD_DIR is a git repository, then you can set this option +# to checkout the given branch before running the TEST. If you +# specify this for the first run, that branch will be used for +# all preceding tests until a new CHECKOUT is set. +# +# +# +# For TEST_TYPE = patchcheck +# +# This expects the BUILD_DIR to be a git repository, and +# will checkout the PATCHCHECK_START commit. +# +# The option BUILD_TYPE will be ignored. +# +# The MIN_CONFIG will be used for all builds of the patchcheck. The build type +# used for patchcheck is oldconfig. +# +# PATCHCHECK_START is required and is the first patch to +# test (the SHA1 of the commit). You may also specify anything +# that git checkout allows (branch name, tage, HEAD~3). +# +# PATCHCHECK_END is the last patch to check (default HEAD) +# +# PATCHCHECK_TYPE is required and is the type of test to run: +# build, boot, test. +# +# Note, the build test will look for warnings, if a warning occurred +# in a file that a commit touches, the build will fail. +# +# If BUILD_NOCLEAN is set, then make mrproper will not be run on +# any of the builds, just like all other TEST_TYPE tests. But +# what makes patchcheck different from the other tests, is if +# BUILD_NOCLEAN is not set, only the first and last patch run +# make mrproper. This helps speed up the test. +# +# Example: +# TEST_START +# TEST_TYPE = patchcheck +# CHECKOUT = mybranch +# PATCHCHECK_TYPE = boot +# PATCHCHECK_START = 747e94ae3d1b4c9bf5380e569f614eb9040b79e7 +# PATCHCHECK_END = HEAD~2 +# +# +# +# For TEST_TYPE = bisect +# +# You can specify a git bisect if the BUILD_DIR is a git repository. +# The MIN_CONFIG will be used for all builds of the bisect. The build type +# used for bisecting is oldconfig. +# +# The option BUILD_TYPE will be ignored. +# +# BISECT_TYPE is the type of test to perform: +# build - bad fails to build +# boot - bad builds but fails to boot +# test - bad boots but fails a test +# +# BISECT_GOOD is the commit (SHA1) to label as good (accepts all git good commit types) +# BISECT_BAD is the commit to label as bad (accepts all git bad commit types) +# +# The above three options are required for a bisect operation. +# +# BISECT_REPLAY = /path/to/replay/file (optional, default undefined) +# +# If an operation failed in the bisect that was not expected to +# fail. Then the test ends. The state of the BUILD_DIR will be +# left off at where the failure occurred. You can examine the +# reason for the failure, and perhaps even find a git commit +# that would work to continue with. You can run: +# +# git bisect log > /path/to/replay/file +# +# The adding: +# +# BISECT_REPLAY= /path/to/replay/file +# +# And running the test again. The test will perform the initial +# git bisect start, git bisect good, and git bisect bad, and +# then it will run git bisect replay on this file, before +# continuing with the bisect. +# +# BISECT_START = commit (optional, default undefined) +# +# As with BISECT_REPLAY, if the test failed on a commit that +# just happen to have a bad commit in the middle of the bisect, +# and you need to skip it. If BISECT_START is defined, it +# will checkout that commit after doing the initial git bisect start, +# git bisect good, git bisect bad, and running the git bisect replay +# if the BISECT_REPLAY is set. +# +# BISECT_REVERSE = 1 (optional, default 0) +# +# In those strange instances where it was broken forever +# and you are trying to find where it started to work! +# Set BISECT_GOOD to the commit that was last known to fail +# Set BISECT_BAD to the commit that is known to start working. +# With BISECT_REVERSE = 1, The test will consider failures as +# good, and success as bad. +# +# BISECT_CHECK = 1 (optional, default 0) +# +# Just to be sure the good is good and bad is bad, setting +# BISECT_CHECK to 1 will start the bisect by first checking +# out BISECT_BAD and makes sure it fails, then it will check +# out BISECT_GOOD and makes sure it succeeds before starting +# the bisect (it works for BISECT_REVERSE too). +# +# You can limit the test to just check BISECT_GOOD or +# BISECT_BAD with BISECT_CHECK = good or +# BISECT_CHECK = bad, respectively. +# +# Example: +# TEST_START +# TEST_TYPE = bisect +# BISECT_GOOD = v2.6.36 +# BISECT_BAD = b5153163ed580e00c67bdfecb02b2e3843817b3e +# BISECT_TYPE = build +# MIN_CONFIG = /home/test/config-bisect +# +# +# +# For TEST_TYPE = config_bisect +# +# In those cases that you have two different configs. One of them +# work, the other does not, and you do not know what config causes +# the problem. +# The TEST_TYPE config_bisect will bisect the bad config looking for +# what config causes the failure. +# +# The way it works is this: +# +# First it finds a config to work with. Since a different version, or +# MIN_CONFIG may cause different dependecies, it must run through this +# preparation. +# +# Overwrites any config set in the bad config with a config set in +# either the MIN_CONFIG or ADD_CONFIG. Thus, make sure these configs +# are minimal and do not disable configs you want to test: +# (ie. # CONFIG_FOO is not set). +# +# An oldconfig is run on the bad config and any new config that +# appears will be added to the configs to test. +# +# Finally, it generates a config with the above result and runs it +# again through make oldconfig to produce a config that should be +# satisfied by kconfig. +# +# Then it starts the bisect. +# +# The configs to test are cut in half. If all the configs in this +# half depend on a config in the other half, then the other half +# is tested instead. If no configs are enabled by either half, then +# this means a circular dependency exists and the test fails. +# +# A config is created with the test half, and the bisect test is run. +# +# If the bisect succeeds, then all configs in the generated config +# are removed from the configs to test and added to the configs that +# will be enabled for all builds (they will be enabled, but not be part +# of the configs to examine). +# +# If the bisect fails, then all test configs that were not enabled by +# the config file are removed from the test. These configs will not +# be enabled in future tests. Since current config failed, we consider +# this to be a subset of the config that we started with. +# +# When we are down to one config, it is considered the bad config. +# +# Note, the config chosen may not be the true bad config. Due to +# dependencies and selections of the kbuild system, mulitple +# configs may be needed to cause a failure. If you disable the +# config that was found and restart the test, if the test fails +# again, it is recommended to rerun the config_bisect with a new +# bad config without the found config enabled. +# +# The option BUILD_TYPE will be ignored. +# +# CONFIG_BISECT_TYPE is the type of test to perform: +# build - bad fails to build +# boot - bad builds but fails to boot +# test - bad boots but fails a test +# +# CONFIG_BISECT is the config that failed to boot +# +# Example: +# TEST_START +# TEST_TYPE = config_bisect +# CONFIG_BISECT_TYPE = build +# CONFIG_BISECT = /home/test/˘onfig-bad +# MIN_CONFIG = /home/test/config-min +# diff --git a/tools/virtio/Makefile b/tools/virtio/Makefile new file mode 100644 index 00000000000..d1d442ed106 --- /dev/null +++ b/tools/virtio/Makefile @@ -0,0 +1,12 @@ +all: test mod +test: virtio_test +virtio_test: virtio_ring.o virtio_test.o +CFLAGS += -g -O2 -Wall -I. -I ../../usr/include/ -Wno-pointer-sign -fno-strict-overflow -MMD +vpath %.c ../../drivers/virtio +mod: + ${MAKE} -C `pwd`/../.. M=`pwd`/vhost_test +.PHONY: all test mod clean +clean: + ${RM} *.o vhost_test/*.o vhost_test/.*.cmd \ + vhost_test/Module.symvers vhost_test/modules.order *.d +-include *.d diff --git a/tools/virtio/linux/device.h b/tools/virtio/linux/device.h new file mode 100644 index 00000000000..4ad7e1df0db --- /dev/null +++ b/tools/virtio/linux/device.h @@ -0,0 +1,2 @@ +#ifndef LINUX_DEVICE_H +#endif diff --git a/tools/virtio/linux/slab.h b/tools/virtio/linux/slab.h new file mode 100644 index 00000000000..81baeac8ae4 --- /dev/null +++ b/tools/virtio/linux/slab.h @@ -0,0 +1,2 @@ +#ifndef LINUX_SLAB_H +#endif diff --git a/tools/virtio/linux/virtio.h b/tools/virtio/linux/virtio.h new file mode 100644 index 00000000000..669bcdd4580 --- /dev/null +++ b/tools/virtio/linux/virtio.h @@ -0,0 +1,223 @@ +#ifndef LINUX_VIRTIO_H +#define LINUX_VIRTIO_H + +#include <stdbool.h> +#include <stdlib.h> +#include <stddef.h> +#include <stdio.h> +#include <string.h> +#include <assert.h> + +#include <linux/types.h> +#include <errno.h> + +typedef unsigned long long dma_addr_t; + +struct scatterlist { + unsigned long page_link; + unsigned int offset; + unsigned int length; + dma_addr_t dma_address; +}; + +struct page { + unsigned long long dummy; +}; + +#define BUG_ON(__BUG_ON_cond) assert(!(__BUG_ON_cond)) + +/* Physical == Virtual */ +#define virt_to_phys(p) ((unsigned long)p) +#define phys_to_virt(a) ((void *)(unsigned long)(a)) +/* Page address: Virtual / 4K */ +#define virt_to_page(p) ((struct page*)((virt_to_phys(p) / 4096) * \ + sizeof(struct page))) +#define offset_in_page(p) (((unsigned long)p) % 4096) +#define sg_phys(sg) ((sg->page_link & ~0x3) / sizeof(struct page) * 4096 + \ + sg->offset) +static inline void sg_mark_end(struct scatterlist *sg) +{ + /* + * Set termination bit, clear potential chain bit + */ + sg->page_link |= 0x02; + sg->page_link &= ~0x01; +} +static inline void sg_init_table(struct scatterlist *sgl, unsigned int nents) +{ + memset(sgl, 0, sizeof(*sgl) * nents); + sg_mark_end(&sgl[nents - 1]); +} +static inline void sg_assign_page(struct scatterlist *sg, struct page *page) +{ + unsigned long page_link = sg->page_link & 0x3; + + /* + * In order for the low bit stealing approach to work, pages + * must be aligned at a 32-bit boundary as a minimum. + */ + BUG_ON((unsigned long) page & 0x03); + sg->page_link = page_link | (unsigned long) page; +} + +static inline void sg_set_page(struct scatterlist *sg, struct page *page, + unsigned int len, unsigned int offset) +{ + sg_assign_page(sg, page); + sg->offset = offset; + sg->length = len; +} + +static inline void sg_set_buf(struct scatterlist *sg, const void *buf, + unsigned int buflen) +{ + sg_set_page(sg, virt_to_page(buf), buflen, offset_in_page(buf)); +} + +static inline void sg_init_one(struct scatterlist *sg, const void *buf, unsigned int buflen) +{ + sg_init_table(sg, 1); + sg_set_buf(sg, buf, buflen); +} + +typedef __u16 u16; + +typedef enum { + GFP_KERNEL, + GFP_ATOMIC, +} gfp_t; +typedef enum { + IRQ_NONE, + IRQ_HANDLED +} irqreturn_t; + +static inline void *kmalloc(size_t s, gfp_t gfp) +{ + return malloc(s); +} + +static inline void kfree(void *p) +{ + free(p); +} + +#define container_of(ptr, type, member) ({ \ + const typeof( ((type *)0)->member ) *__mptr = (ptr); \ + (type *)( (char *)__mptr - offsetof(type,member) );}) + +#define uninitialized_var(x) x = x + +# ifndef likely +# define likely(x) (__builtin_expect(!!(x), 1)) +# endif +# ifndef unlikely +# define unlikely(x) (__builtin_expect(!!(x), 0)) +# endif + +#define pr_err(format, ...) fprintf (stderr, format, ## __VA_ARGS__) +#ifdef DEBUG +#define pr_debug(format, ...) fprintf (stderr, format, ## __VA_ARGS__) +#else +#define pr_debug(format, ...) do {} while (0) +#endif +#define dev_err(dev, format, ...) fprintf (stderr, format, ## __VA_ARGS__) +#define dev_warn(dev, format, ...) fprintf (stderr, format, ## __VA_ARGS__) + +/* TODO: empty stubs for now. Broken but enough for virtio_ring.c */ +#define list_add_tail(a, b) do {} while (0) +#define list_del(a) do {} while (0) + +#define BIT_WORD(nr) ((nr) / BITS_PER_LONG) +#define BITS_PER_BYTE 8 +#define BITS_PER_LONG (sizeof(long) * BITS_PER_BYTE) +#define BIT_MASK(nr) (1UL << ((nr) % BITS_PER_LONG)) +/* TODO: Not atomic as it should be: + * we don't use this for anything important. */ +static inline void clear_bit(int nr, volatile unsigned long *addr) +{ + unsigned long mask = BIT_MASK(nr); + unsigned long *p = ((unsigned long *)addr) + BIT_WORD(nr); + + *p &= ~mask; +} + +static inline int test_bit(int nr, const volatile unsigned long *addr) +{ + return 1UL & (addr[BIT_WORD(nr)] >> (nr & (BITS_PER_LONG-1))); +} + +/* The only feature we care to support */ +#define virtio_has_feature(dev, feature) \ + test_bit((feature), (dev)->features) +/* end of stubs */ + +struct virtio_device { + void *dev; + unsigned long features[1]; +}; + +struct virtqueue { + /* TODO: commented as list macros are empty stubs for now. + * Broken but enough for virtio_ring.c + * struct list_head list; */ + void (*callback)(struct virtqueue *vq); + const char *name; + struct virtio_device *vdev; + void *priv; +}; + +#define EXPORT_SYMBOL_GPL(__EXPORT_SYMBOL_GPL_name) \ + void __EXPORT_SYMBOL_GPL##__EXPORT_SYMBOL_GPL_name() { \ +} +#define MODULE_LICENSE(__MODULE_LICENSE_value) \ + const char *__MODULE_LICENSE_name = __MODULE_LICENSE_value + +#define CONFIG_SMP + +#if defined(__i386__) || defined(__x86_64__) +#define barrier() asm volatile("" ::: "memory") +#define mb() __sync_synchronize() + +#define smp_mb() mb() +# define smp_rmb() barrier() +# define smp_wmb() barrier() +#else +#error Please fill in barrier macros +#endif + +/* Interfaces exported by virtio_ring. */ +int virtqueue_add_buf_gfp(struct virtqueue *vq, + struct scatterlist sg[], + unsigned int out_num, + unsigned int in_num, + void *data, + gfp_t gfp); + +static inline int virtqueue_add_buf(struct virtqueue *vq, + struct scatterlist sg[], + unsigned int out_num, + unsigned int in_num, + void *data) +{ + return virtqueue_add_buf_gfp(vq, sg, out_num, in_num, data, GFP_ATOMIC); +} + +void virtqueue_kick(struct virtqueue *vq); + +void *virtqueue_get_buf(struct virtqueue *vq, unsigned int *len); + +void virtqueue_disable_cb(struct virtqueue *vq); + +bool virtqueue_enable_cb(struct virtqueue *vq); + +void *virtqueue_detach_unused_buf(struct virtqueue *vq); +struct virtqueue *vring_new_virtqueue(unsigned int num, + unsigned int vring_align, + struct virtio_device *vdev, + void *pages, + void (*notify)(struct virtqueue *vq), + void (*callback)(struct virtqueue *vq), + const char *name); +void vring_del_virtqueue(struct virtqueue *vq); + +#endif diff --git a/tools/virtio/vhost_test/Makefile b/tools/virtio/vhost_test/Makefile new file mode 100644 index 00000000000..a1d35b81b31 --- /dev/null +++ b/tools/virtio/vhost_test/Makefile @@ -0,0 +1,2 @@ +obj-m += vhost_test.o +EXTRA_CFLAGS += -Idrivers/vhost diff --git a/tools/virtio/vhost_test/vhost_test.c b/tools/virtio/vhost_test/vhost_test.c new file mode 100644 index 00000000000..18735189e62 --- /dev/null +++ b/tools/virtio/vhost_test/vhost_test.c @@ -0,0 +1 @@ +#include "test.c" diff --git a/tools/virtio/virtio_test.c b/tools/virtio/virtio_test.c new file mode 100644 index 00000000000..df0c6d2c386 --- /dev/null +++ b/tools/virtio/virtio_test.c @@ -0,0 +1,248 @@ +#define _GNU_SOURCE +#include <getopt.h> +#include <string.h> +#include <poll.h> +#include <sys/eventfd.h> +#include <stdlib.h> +#include <assert.h> +#include <unistd.h> +#include <sys/ioctl.h> +#include <sys/stat.h> +#include <sys/types.h> +#include <fcntl.h> +#include <linux/vhost.h> +#include <linux/virtio.h> +#include <linux/virtio_ring.h> +#include "../../drivers/vhost/test.h" + +struct vq_info { + int kick; + int call; + int num; + int idx; + void *ring; + /* copy used for control */ + struct vring vring; + struct virtqueue *vq; +}; + +struct vdev_info { + struct virtio_device vdev; + int control; + struct pollfd fds[1]; + struct vq_info vqs[1]; + int nvqs; + void *buf; + size_t buf_size; + struct vhost_memory *mem; +}; + +void vq_notify(struct virtqueue *vq) +{ + struct vq_info *info = vq->priv; + unsigned long long v = 1; + int r; + r = write(info->kick, &v, sizeof v); + assert(r == sizeof v); +} + +void vq_callback(struct virtqueue *vq) +{ +} + + +void vhost_vq_setup(struct vdev_info *dev, struct vq_info *info) +{ + struct vhost_vring_state state = { .index = info->idx }; + struct vhost_vring_file file = { .index = info->idx }; + unsigned long long features = dev->vdev.features[0]; + struct vhost_vring_addr addr = { + .index = info->idx, + .desc_user_addr = (uint64_t)(unsigned long)info->vring.desc, + .avail_user_addr = (uint64_t)(unsigned long)info->vring.avail, + .used_user_addr = (uint64_t)(unsigned long)info->vring.used, + }; + int r; + r = ioctl(dev->control, VHOST_SET_FEATURES, &features); + assert(r >= 0); + state.num = info->vring.num; + r = ioctl(dev->control, VHOST_SET_VRING_NUM, &state); + assert(r >= 0); + state.num = 0; + r = ioctl(dev->control, VHOST_SET_VRING_BASE, &state); + assert(r >= 0); + r = ioctl(dev->control, VHOST_SET_VRING_ADDR, &addr); + assert(r >= 0); + file.fd = info->kick; + r = ioctl(dev->control, VHOST_SET_VRING_KICK, &file); + assert(r >= 0); + file.fd = info->call; + r = ioctl(dev->control, VHOST_SET_VRING_CALL, &file); + assert(r >= 0); +} + +static void vq_info_add(struct vdev_info *dev, int num) +{ + struct vq_info *info = &dev->vqs[dev->nvqs]; + int r; + info->idx = dev->nvqs; + info->kick = eventfd(0, EFD_NONBLOCK); + info->call = eventfd(0, EFD_NONBLOCK); + r = posix_memalign(&info->ring, 4096, vring_size(num, 4096)); + assert(r >= 0); + memset(info->ring, 0, vring_size(num, 4096)); + vring_init(&info->vring, num, info->ring, 4096); + info->vq = vring_new_virtqueue(info->vring.num, 4096, &dev->vdev, info->ring, + vq_notify, vq_callback, "test"); + assert(info->vq); + info->vq->priv = info; + vhost_vq_setup(dev, info); + dev->fds[info->idx].fd = info->call; + dev->fds[info->idx].events = POLLIN; + dev->nvqs++; +} + +static void vdev_info_init(struct vdev_info* dev, unsigned long long features) +{ + int r; + memset(dev, 0, sizeof *dev); + dev->vdev.features[0] = features; + dev->vdev.features[1] = features >> 32; + dev->buf_size = 1024; + dev->buf = malloc(dev->buf_size); + assert(dev->buf); + dev->control = open("/dev/vhost-test", O_RDWR); + assert(dev->control >= 0); + r = ioctl(dev->control, VHOST_SET_OWNER, NULL); + assert(r >= 0); + dev->mem = malloc(offsetof(struct vhost_memory, regions) + + sizeof dev->mem->regions[0]); + assert(dev->mem); + memset(dev->mem, 0, offsetof(struct vhost_memory, regions) + + sizeof dev->mem->regions[0]); + dev->mem->nregions = 1; + dev->mem->regions[0].guest_phys_addr = (long)dev->buf; + dev->mem->regions[0].userspace_addr = (long)dev->buf; + dev->mem->regions[0].memory_size = dev->buf_size; + r = ioctl(dev->control, VHOST_SET_MEM_TABLE, dev->mem); + assert(r >= 0); +} + +/* TODO: this is pretty bad: we get a cache line bounce + * for the wait queue on poll and another one on read, + * plus the read which is there just to clear the + * current state. */ +static void wait_for_interrupt(struct vdev_info *dev) +{ + int i; + unsigned long long val; + poll(dev->fds, dev->nvqs, -1); + for (i = 0; i < dev->nvqs; ++i) + if (dev->fds[i].revents & POLLIN) { + read(dev->fds[i].fd, &val, sizeof val); + } +} + +static void run_test(struct vdev_info *dev, struct vq_info *vq, int bufs) +{ + struct scatterlist sl; + long started = 0, completed = 0; + long completed_before; + int r, test = 1; + unsigned len; + long long spurious = 0; + r = ioctl(dev->control, VHOST_TEST_RUN, &test); + assert(r >= 0); + for (;;) { + virtqueue_disable_cb(vq->vq); + completed_before = completed; + do { + if (started < bufs) { + sg_init_one(&sl, dev->buf, dev->buf_size); + r = virtqueue_add_buf(vq->vq, &sl, 1, 0, + dev->buf + started); + if (likely(r >= 0)) { + ++started; + virtqueue_kick(vq->vq); + } + } else + r = -1; + + /* Flush out completed bufs if any */ + if (virtqueue_get_buf(vq->vq, &len)) { + ++completed; + r = 0; + } + + } while (r >= 0); + if (completed == completed_before) + ++spurious; + assert(completed <= bufs); + assert(started <= bufs); + if (completed == bufs) + break; + if (virtqueue_enable_cb(vq->vq)) { + wait_for_interrupt(dev); + } + } + test = 0; + r = ioctl(dev->control, VHOST_TEST_RUN, &test); + assert(r >= 0); + fprintf(stderr, "spurious wakeus: 0x%llx\n", spurious); +} + +const char optstring[] = "h"; +const struct option longopts[] = { + { + .name = "help", + .val = 'h', + }, + { + .name = "indirect", + .val = 'I', + }, + { + .name = "no-indirect", + .val = 'i', + }, + { + } +}; + +static void help() +{ + fprintf(stderr, "Usage: virtio_test [--help] [--no-indirect]\n"); +} + +int main(int argc, char **argv) +{ + struct vdev_info dev; + unsigned long long features = 1ULL << VIRTIO_RING_F_INDIRECT_DESC; + int o; + + for (;;) { + o = getopt_long(argc, argv, optstring, longopts, NULL); + switch (o) { + case -1: + goto done; + case '?': + help(); + exit(2); + case 'h': + help(); + goto done; + case 'i': + features &= ~(1ULL << VIRTIO_RING_F_INDIRECT_DESC); + break; + default: + assert(0); + break; + } + } + +done: + vdev_info_init(&dev, features); + vq_info_add(&dev, 256); + run_test(&dev, &dev.vqs[0], 0x100000); + return 0; +} |