#!/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. setglobal opt_buckets = '0'; setglobal buckets = ''; setglobal opt_power = '0'; setglobal power = '4'; setglobal opt_max = '0'; setglobal max = $shExpr('1024 * 1024') setglobal opt_filter = '0'; setglobal filter = ''; setglobal duration = '0'; setglobal debug = '0' trap ':' INT QUIT TERM PIPE HUP proc usage { cat << """ >&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. """ > !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 } proc die { echo >&2 @Argv> !2 "$@" exit 1 } ### process options while getopts b:hm:P:f: opt { match $opt { with b setglobal opt_buckets = '1'; setglobal buckets = ''($OPTARG) with P setglobal opt_power = '1'; setglobal power = $OPTARG with m setglobal opt_max = '1'; setglobal max = $OPTARG with f setglobal opt_filter = '1'; setglobal filter = ""$OPTARG && "" with h|? usage } } shift $shExpr(' $OPTIND - 1 ') sh-expr ' $# < 2 ' && usage setglobal tpoint = $1 # tracepoint setglobal var = $2 # variable for histogram setglobal duration = $(3) ### option logic sh-expr ' opt_buckets && opt_power ' && die "ERROR: use either -b or -P" sh-expr ' 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 { echo >&2 "ERROR: tracepoint \"$tpoint\" not found. Exiting...> !2 "ERROR: tracepoint \"$tpoint\" not found. Exiting..." [[ "$USER" != "root" ]] && echo >&2 "Not root user?> !2 "Not root user?" exit 1 } ### auto build power-of buckets if sh-expr ' !opt_buckets ' { setglobal b = '0' setglobal s = '1' while sh-expr ' s <= max ' { setglobal b = ""$b $s"" sh-expr ' s *= power ' } setglobal buckets = ''($b) } ### build list of tracepoints and filters for each histogram bucket setglobal max = $(buckets[${#buckets[@]} - 1]) # last element sh-expr 'max_i = ${#buckets[*]} - 1' setglobal tpoints = ""-e $tpoint --filter \"$filter $var < $(buckets[0])"\"" setglobal awkarray = '' setglobal i = '0' while sh-expr ' i < max_i ' { if sh-expr ' i && ${buckets[$i]} <= ${buckets[$i - 1]} ' { die "ERROR: bucket list must increase in size." } setglobal tpoints = ""$tpoints -e $tpoint --filter \"$filter $var >= $(buckets[$i]) && "" setglobal tpoints = ""$tpoints $var < $(buckets[$i + 1])"\"" setglobal awkarray = ""$awkarray buckets[$i]=$(buckets[$i]);"" sh-expr ' i++ ' } setglobal awkarray = ""$awkarray buckets[$max_i]=$(buckets[$max_i]);"" setglobal tpoints = ""$tpoints -e $tpoint --filter \"$filter $var >= $(buckets[$max_i])"\"" if sh-expr ' debug ' { echo buckets: $(buckets[*]) echo tracepoints: $tpoints echo awkarray: $(awkarray[*]) } ### prepare to run if sh-expr ' duration ' { setglobal etext = ""for $duration seconds"" setglobal cmd = ""sleep $duration"" } else { setglobal etext = '"until Ctrl-C'" setglobal cmd = '"sleep 999999'" } setglobal p_tpoint = $tpoint if test -n $filter { setglobal p_tpoint = ""$tpoint (Filter: $(filter%????))"" } if sh-expr ' opt_buckets ' { echo "Tracing $p_tpoint, specified buckets, $etext..." } else { echo "Tracing $p_tpoint, power-of-$power, max $max, $etext..." } ### run perf setglobal out = '"-o /dev/stdout'" # a workaround needed in linux 3.2; not by 3.4.15 setglobal stat = $[eval perf stat $tpoints -a $out $cmd !2 > !1] if sh-expr ' $? != 0 ' { echo >&2 "ERROR running perf:> !2 "ERROR running perf:" echo >&2 $stat> !2 "$stat" exit } if sh-expr ' debug ' { echo raw output: echo $stat echo } ### find max value for ASCII histogram setglobal 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++ } '