3 # I'm sure this could be one cleaner perl script instead of a weird combination
4 # of awk and shell. This was the fastest path to feeding 'pl' for me. I'm
5 # all for improvements. Let me know. -- zab
12 echo "$1 "'('`echo "($1 * 100) / $2" | bc`'%)'
15 tmpdir=`mktemp -d /tmp/.tmpdirXXXXXX` || die "couldn't create tmp dir"
17 [ ${#tmpdir} == 18 ] && [ -d $tmpdir ] && rm -rf $tmpdir
22 echo "ibase=16;scale=2;$*/FF" | bc
25 local md5=`echo "$*" | md5sum - | awk '{print $1}' | \
27 local r=`echo $md5 | cut -b 1,2`
28 local g=`echo $md5 | cut -b 3,4`
29 local b=`echo $md5 | cut -b 5,6`
30 echo "rgb(`hex2float $r`,`hex2float $g`,`hex2float $b`)"
35 0) echo "OST_REPLY" ;;
36 1) echo "OST_GETATTR" ;;
37 2) echo "OST_SETATTR" ;;
39 4) echo "OST_WRITE" ;;
40 5) echo "OST_CREATE" ;;
41 6) echo "OST_DESTROY" ;;
42 7) echo "OST_GET_INFO" ;;
43 8) echo "OST_CONNECT" ;;
44 9) echo "OST_DISCONNECT" ;;
45 10) echo "OST_PUNCH" ;;
46 11) echo "OST_OPEN" ;;
47 12) echo "OST_CLOSE" ;;
48 13) echo "OST_STATFS" ;;
49 14) echo "OST_SAN_READ" ;;
50 15) echo "OST_SAN_WRITE" ;;
51 16) echo "OST_SYNC" ;;
52 17) echo "OST_SET_INFO" ;;
53 33) echo "MDS_GETATTR" ;;
54 34) echo "MDS_GETATTR_NAME" ;;
55 35) echo "MDS_CLOSE" ;;
56 36) echo "MDS_REINT" ;;
57 37) echo "MDS_READPAGE" ;;
58 38) echo "MDS_CONNECT" ;;
59 39) echo "MDS_DISCONNECT" ;;
60 40) echo "MDS_GETSTATUS" ;;
61 41) echo "MDS_STATFS" ;;
63 43) echo "MDS_UNPIN" ;;
64 44) echo "MDS_SYNC" ;;
65 45) echo "MDS_DONE_WRITING" ;;
66 101) echo "LDLM_ENQUEUE" ;;
67 102) echo "LDLM_CONVERT" ;;
68 103) echo "LDLM_CANCEL" ;;
69 104) echo "LDLM_BL_CALLBACK" ;;
70 105) echo "LDLM_CP_CALLBACK" ;;
71 106) echo "LDLM_GL_CALLBACK" ;;
72 400) echo "OBD_PING" ;;
73 401) echo "OBD_LOG_CANCEL" ;;
80 echo " -l file (required)"
81 echo " Specifies a debug log that contains RPC 'Sending' and"
82 echo " 'Completed' entries generated by D_RPCTRACE."
84 echo " Usually the PostScript output is temporary and only"
85 echo " used for the gv instance during the script. This"
86 echo " option specifies a file to save the ps to."
88 echo " Usually the pl script is generated in a temporary"
89 echo " directoriy that is wiped as the script exits. This"
90 echo " saves the script as 'file' instead."
92 echo " Label the tail of each RPC bar with the XID of the"
93 echo " RPC. This can be illegible with dense bars."
96 echo " $0 -l /tmp/my-log -o out.ps && gv -landscape out.ps"
101 [ ${#*} == 0 ] && usage
103 if ! which pl > /dev/null 2>&1 ; then
104 echo "The ploticus 'pl' binary isn't in the PATH."
106 echo " (cd /tmp && wget http://ploticus.sourceforge.net/download/pl220linux.tar.gz)"
107 echo " (mkdir ~/ploticus && cd ~/ploticus && tar -zxf /tmp/pl220linux.tar.gz)"
108 echo ' export PATH=$PATH:~/ploticus/pl220linux/bin'
109 echo " chmod +x ~/ploticus/pl220linux/bin/pl"
111 echo "is sufficient. There are also rpms near"
112 echo " http://ploticus.sourceforge.net/doc/download.html"
117 while getopts ":l:o:p:x" opt; do
121 p) pl_save_file=$OPTARG ;;
122 x) labelfield="labelfield: 5" ;;
127 [ -z "$log" ] && die "need to specify a log file with -l"
128 [ ! -f "$log" ] && die "$log needs to be a file"
130 pl_script="$tmpdir/ploticus.script"
132 echo "$*" >> $pl_script
135 awk_vars="$tmpdir/awk_vars"
137 BEGIN { num_xids = 0 }
142 # sometimes the nid has our seperators
143 # in it, so we hope the last field is the op code
146 # the y position of the rpc bar in the graph is determined
147 # by the category which we use the process name for. when
148 # a process has multiple rpcs concurrently we generate
149 # seperate categories by appending ___slot to the name
150 # and then hide these slot categories in the graph.
152 # find the next slot that is empty
153 for (slot = 0; (pname, slot) in pname_slots; slot++) {
157 pname_slots[pname, slot] = xid
161 xid_pname[xid] = pname
163 xid_pname[xid] = pname "_____" slot
165 xid_start[xid] = tvtime
166 xid_opcode[xid] = opc
168 num_xids = num_xids + 1
172 if (initialized != 1) {
189 ($11 == "Completed") {
199 xid_stop[xid] = tvtime
200 this_time = xid_stop[xid] - xid_start[xid]
201 total_rpc_time = total_rpc_time + this_time;
202 rpc_total_time[opc] = rpc_total_time[opc] + this_time
206 delete pname_slots[pname, slot]
209 for (ind = 0; ind < num_xids; ind++) {
211 print xid_pname[xid], xid_start[xid] - min, \
212 xid_stop[xid] - min, xid_opcode[xid], xid \
215 print "FIRST_XID=" first_xid >> "'$awk_vars'"
216 print "LAST_XID=" last_xid >> "'$awk_vars'"
217 print "MIN=" 0.0 >> "'$awk_vars'"
218 print "TOTAL_RPCS=" total_rpcs >> "'$awk_vars'"
219 print "TOTAL_RPC_TIME=" total_rpc_time >> "'$awk_vars'"
220 print "MAX=" max - min >> "'$awk_vars'"
222 for (op in opcodes) {
223 all_opcodes = all_opcodes " " op
224 print "rpc_total_time[" op "]=\"" \
225 rpc_total_time[op] "\"" \
227 print "rpc_count[" op "]=\"" \
231 print "OP_CODES=\"" all_opcodes "\"" >> "'$awk_vars'"
232 print "NUM_OP_CODES=" asort(opcodes) >> "'$awk_vars'"
234 ' $log || die "awk failed"
238 [ ! -e $tmpdir/data ] && die "no RPCS found in $log"
243 # it seems neccesary to batch by category, sadly.
244 sort -n $tmpdir/data >> $pl_script || die "sorting failed"
245 # jeez. without another newline at the end pl doesn't read the last data row.
250 ops_per_pane=$(((NUM_OP_CODES + 2)/ 3))
253 for op in $OP_CODES; do
255 [ $name == "unknown" ] && die "unknown op code $op"
257 label="$name "`pct ${rpc_count[$op]} $TOTAL_RPCS`
258 label="$label, "`pct ${rpc_total_time[$op]} $TOTAL_RPC_TIME`
260 # this "tag:" is also included in the data and is used by the
261 # bar plot to define the color of the bar
262 # http://ploticus.sourceforge.net/doc/bars.html
263 # http://ploticus.sourceforge.net/doc/legendentry.html#legenddriven
264 to_pl "#proc legendentry
267 details: `make_color $name`
270 # ploticus makes you construct seperage legends stacked next to each
271 # other if you want to have a legend with multiple rows _and_ multiple
273 # http://ploticus.sourceforge.net/doc/legend.html
274 # http://ploticus.sourceforge.net/gallery/propbars1.htm
275 # we put each op code in part of a legend pane and then emit them
276 # all later on at the end of the script
277 if [ $legend_index == 0 ]; then
278 # XXX this should be standard
279 loc="min+$(($legend_pane * 3)) min-.5"
280 # loc="$loc min-"`echo "($ops_per_pane * .3)" | bc`
287 # all but the last get noclear
288 if [ $legend_pane != 2 ]; then
299 legend_index=$(($legend_index + 1))
300 if [ $legend_index == $ops_per_pane ]; then
302 legend_pane=$(($legend_pane + 1))
311 xautorange datafields=2,3
313 yscaletype: categories
314 ycategories: datafield 1
315 title: $TOTAL_RPCS RPCs found in \"$log\"
316 titledetails: align=C
320 grid: color=gray(0.9)
328 grid: color=gray(0.9)
329 label: Elapsed seconds
338 // see the legendentry generation above
343 if [ ! -z "$pl_save_file" ]; then
344 mv $pl_script $pl_save_file || \
345 die "couldn't save pl script as $pl_save_file"
346 pl_script="$pl_save_file"
349 # pl is very excited about not doing dynamic allocation.
350 NURR=$((TOTAL_RPCS * 10))
352 pl -maxproclines $NURR -maxfields $NURR -landscape -ps $pl_script \
353 -o $tmpdir/ps || die "pl failed"
355 if [ -z "$output" ]; then
356 gv -landscape $tmpdir/ps || die "couldn't start gv"
358 mv $tmpdir/ps $output || die "couldn't save ps output as $output"