Permalink
Cannot retrieve contributors at this time
Fetching contributors…
| #!/bin/bash | |
| # | |
| # perf-stat-hist - perf_events stat histogram hack. | |
| # Written using Linux perf_events (aka "perf"). | |
| # | |
| # This is a proof-of-concept showing in-kernel histogram summaries of a | |
| # tracepoint variable. | |
| # | |
| # USAGE: perf-stat-hist [-h] [-b buckets|-P power] [-m max] tracepoint | |
| # variable [seconds] | |
| # | |
| # Run "perf-stat-hist -h" for full usage. | |
| # | |
| # This uses multiple counting tracepoints with different filters, one for each | |
| # histogram bucket. While this is summarized in-kernel, the use of multiple | |
| # tracepoints does add addiitonal overhead, which is more evident if you change | |
| # the power-of size from 4 to 2 (which creates more buckets). Hopefully, in the | |
| # future this this functionality will be provided in an efficient way from | |
| # perf_events itself, at which point this tool can be rewritten. | |
| # | |
| # From perf-tools: https://github.com/brendangregg/perf-tools | |
| # | |
| # COPYRIGHT: Copyright (c) 2014 Brendan Gregg. | |
| # | |
| # This program is free software; you can redistribute it and/or | |
| # modify it under the terms of the GNU General Public License | |
| # as published by the Free Software Foundation; either version 2 | |
| # of the License, or (at your option) any later version. | |
| # | |
| # This program is distributed in the hope that it will be useful, | |
| # but WITHOUT ANY WARRANTY; without even the implied warranty of | |
| # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | |
| # GNU 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., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. | |
| # | |
| # (http://www.gnu.org/copyleft/gpl.html) | |
| # | |
| # 30-Jun-2014 Brendan Gregg Created this. | |
| opt_buckets=0; buckets=; opt_power=0; power=4; opt_max=0; max=$((1024 * 1024)) | |
| opt_filter=0; filter=; duration=0; debug=0 | |
| trap ':' INT QUIT TERM PIPE HUP | |
| function usage { | |
| cat <<-END >&2 | |
| USAGE: perf-stat-hist [-h] [-b buckets|-P power] [-m max] [-f filter] | |
| tracepoint variable [seconds] | |
| -b buckets # specify histogram bucket points | |
| -P power # power-of (default is 4) | |
| -m max # max value for power-of | |
| -f filter # specify a filter | |
| -h # this usage message | |
| eg, | |
| perf-stat-hist syscalls:sys_enter_read count 5 | |
| # read() request histogram, 5 seconds | |
| perf-stat-hist syscalls:sys_exit_read ret 5 | |
| # read() return histogram, 5 seconds | |
| perf-stat-hist -P 10 syscalls:sys_exit_read ret 5 | |
| # ... use power-of-10 | |
| perf-stat-hist -P 2 -m 1024 syscalls:sys_exit_read ret 5 | |
| # ... use power-of-2, max 1024 | |
| perf-stat-hist -b "10 50 100 500" syscalls:sys_exit_read ret 5 | |
| # ... histogram based on these bucket ranges | |
| perf-stat-hist -b 10 syscalls:sys_exit_read ret 5 | |
| # ... bifurcate by the value 10 (lowest overhead) | |
| perf-stat-hist -f 'rwbs == "WS"' block:block_rq_complete nr_sector 5 | |
| # ... synchronous writes histogram, 5 seconds | |
| See the man page and example file for more info. | |
| END | |
| exit | |
| } | |
| function die { | |
| echo >&2 "$@" | |
| exit 1 | |
| } | |
| ### process options | |
| while getopts b:hm:P:f: opt | |
| do | |
| case $opt in | |
| b) opt_buckets=1; buckets=($OPTARG) ;; | |
| P) opt_power=1; power=$OPTARG ;; | |
| m) opt_max=1; max=$OPTARG ;; | |
| f) opt_filter=1; filter="$OPTARG && " ;; | |
| h|?) usage ;; | |
| esac | |
| done | |
| shift $(( $OPTIND - 1 )) | |
| (( $# < 2 )) && usage | |
| tpoint=$1 # tracepoint | |
| var=$2 # variable for histogram | |
| duration=${3} | |
| ### option logic | |
| (( opt_buckets && opt_power )) && die "ERROR: use either -b or -P" | |
| (( opt_power && power < 2 )) && die "ERROR: -P power must be 2 or higher" | |
| ### check that tracepoint exists | |
| if ! grep "^$tpoint\$" /sys/kernel/debug/tracing/available_events > /dev/null | |
| then | |
| echo >&2 "ERROR: tracepoint \"$tpoint\" not found. Exiting..." | |
| [[ "$USER" != "root" ]] && echo >&2 "Not root user?" | |
| exit 1 | |
| fi | |
| ### auto build power-of buckets | |
| if (( !opt_buckets )); then | |
| b=0 | |
| s=1 | |
| while (( s <= max )); do | |
| b="$b $s" | |
| (( s *= power )) | |
| done | |
| buckets=($b) | |
| fi | |
| ### build list of tracepoints and filters for each histogram bucket | |
| max=${buckets[${#buckets[@]} - 1]} # last element | |
| ((max_i = ${#buckets[*]} - 1)) | |
| tpoints="-e $tpoint --filter \"$filter $var < ${buckets[0]}\"" | |
| awkarray= | |
| i=0 | |
| while (( i < max_i )); do | |
| if (( i && ${buckets[$i]} <= ${buckets[$i - 1]} )); then | |
| die "ERROR: bucket list must increase in size." | |
| fi | |
| tpoints="$tpoints -e $tpoint --filter \"$filter $var >= ${buckets[$i]} && " | |
| tpoints="$tpoints $var < ${buckets[$i + 1]}\"" | |
| awkarray="$awkarray buckets[$i]=${buckets[$i]};" | |
| (( i++ )) | |
| done | |
| awkarray="$awkarray buckets[$max_i]=${buckets[$max_i]};" | |
| tpoints="$tpoints -e $tpoint --filter \"$filter $var >= ${buckets[$max_i]}\"" | |
| if (( debug )); then | |
| echo buckets: ${buckets[*]} | |
| echo tracepoints: $tpoints | |
| echo awkarray: ${awkarray[*]} | |
| fi | |
| ### prepare to run | |
| if (( duration )); then | |
| etext="for $duration seconds" | |
| cmd="sleep $duration" | |
| else | |
| etext="until Ctrl-C" | |
| cmd="sleep 999999" | |
| fi | |
| p_tpoint=$tpoint | |
| if [ -n "$filter" ]; then | |
| p_tpoint="$tpoint (Filter: ${filter%????})" | |
| fi | |
| if (( opt_buckets )); then | |
| echo "Tracing $p_tpoint, specified buckets, $etext..." | |
| else | |
| echo "Tracing $p_tpoint, power-of-$power, max $max, $etext..." | |
| fi | |
| ### run perf | |
| out="-o /dev/stdout" # a workaround needed in linux 3.2; not by 3.4.15 | |
| stat=$(eval perf stat $tpoints -a $out $cmd 2>&1) | |
| if (( $? != 0 )); then | |
| echo >&2 "ERROR running perf:" | |
| echo >&2 "$stat" | |
| exit | |
| fi | |
| if (( debug )); then | |
| echo raw output: | |
| echo "$stat" | |
| echo | |
| fi | |
| ### find max value for ASCII histogram | |
| most=$(echo "$stat" | awk -v tpoint=$tpoint ' | |
| $2 == tpoint { gsub(/,/, ""); if ($1 > m) { m = $1 } } | |
| END { print m }' | |
| ) | |
| ### process output | |
| echo | |
| echo "$stat" | awk -v tpoint=$tpoint -v max_i=$max_i -v most=$most ' | |
| function star(sval, smax, swidth) { | |
| stars = "" | |
| if (smax == 0) return "" | |
| for (si = 0; si < (swidth * sval / smax); si++) { | |
| stars = stars "#" | |
| } | |
| return stars | |
| } | |
| BEGIN { | |
| '"$awkarray"' | |
| printf(" %-15s: %-8s %s\n", "Range", "Count", | |
| "Distribution") | |
| } | |
| /Performance counter stats/ { i = -1 } | |
| # reverse order of rule set is important | |
| { ok = 0 } | |
| $2 == tpoint { num = $1; gsub(/,/, "", num); ok = 1 } | |
| ok && i >= max_i { | |
| printf(" %10d -> %-10s: %-8s |%-38s|\n", buckets[i], | |
| "", num, star(num, most, 38)) | |
| next | |
| } | |
| ok && i >= 0 && i < max_i { | |
| printf(" %10d -> %-10d: %-8s |%-38s|\n", buckets[i], | |
| buckets[i+1] - 1, num, star(num, most, 38)) | |
| i++ | |
| next | |
| } | |
| ok && i == -1 { | |
| printf(" %10s -> %-10d: %-8s |%-38s|\n", "", | |
| buckets[0] - 1, num, star(num, most, 38)) | |
| i++ | |
| } | |
| ' |