#!/bin/sh # Sergey Senozhatsky, 2015 # sergey.senozhatsky.work@gmail.com # # This software is licensed under the terms of the GNU General Public # License version 2, as published by the Free Software Foundation, and # may be copied, distributed, and modified under those terms. # # 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. # This program is intended to plot a `slabinfo -X' stats, collected, # for example, using the following command: # while [ 1 ]; do slabinfo -X >> stats; sleep 1; done # # Use `slabinfo-gnuplot.sh stats' to pre-process collected records # and generate graphs (totals, slabs sorted by size, slabs sorted # by size). # # Graphs can be [individually] regenerate with different ranges and # size (-r %d,%d and -s %d,%d options). # # To visually compare N `totals' graphs, do # slabinfo-gnuplot.sh -t FILE1-totals FILE2-totals ... FILEN-totals # setglobal min_slab_name_size = '11' setglobal xmin = '0' setglobal xmax = '0' setglobal width = '1500' setglobal height = '700' setglobal mode = 'preprocess' proc usage { echo "Usage: [-s W,H] [-r MIN,MAX] [-t|-l] FILE1 [FILE2 ..]" echo "FILEs must contain 'slabinfo -X' samples" echo "-t - plot totals for FILE(s)" echo "-l - plot slabs stats for FILE(s)" echo "-s %d,%d - set image width and height" echo "-r %d,%d - use data samples from a given range" } proc check_file_exist { if test ! -f $1 { echo "File '$1' does not exist" exit 1 } } proc do_slabs_plotting { local file=$1 local out_file local range="every ::$xmin" local xtic="" local xtic_rotate="norotate" local lines=2000000 local wc_lines check_file_exist $file setglobal out_file = $[basename $file] if test $xmax -ne 0 { setglobal range = ""$range::$xmax"" setglobal lines = $shExpr('xmax-xmin') } setglobal wc_lines = $[cat $file | wc -l] if test $Status -ne 0 || test $wc_lines -eq 0 { setglobal wc_lines = $lines } if test $wc_lines -lt $lines { setglobal lines = $wc_lines } if test $shExpr('width / lines') -gt $min_slab_name_size { setglobal xtic = '":xtic(1)'" setglobal xtic_rotate = '90' } gnuplot -p << """ #!/usr/bin/env gnuplot set terminal png enhanced size $width,$height large set output '$out_file.png' set autoscale xy set xlabel 'samples' set ylabel 'bytes' set style histogram columnstacked title textcolor lt -1 set style fill solid 0.15 set xtics rotate $xtic_rotate set key left above Left title reverse plot "$file" $range u 2$xtic title 'SIZE' with boxes,\ '' $range u 3 title 'LOSS' with boxes """ if test $Status -eq 0 { echo "$out_file.png" } } proc do_totals_plotting { local gnuplot_cmd="" local range="every ::$xmin" local file="" if test $xmax -ne 0 { setglobal range = ""$range::$xmax"" } for i in [$(t_files[@])] { check_file_exist $i setglobal file = ""$file"$[basename $i]" setglobal gnuplot_cmd = ""$gnuplot_cmd '$i' $range using 1 title\ '$i Memory usage' with lines,"" setglobal gnuplot_cmd = ""$gnuplot_cmd '' $range using 2 title \ '$i Loss' with lines,"" } gnuplot -p << """ #!/usr/bin/env gnuplot set terminal png enhanced size $width,$height large set autoscale xy set output '$file.png' set xlabel 'samples' set ylabel 'bytes' set key left above Left title reverse plot $gnuplot_cmd """ if test $Status -eq 0 { echo "$file.png" } } proc do_preprocess { local out local lines local in=$1 check_file_exist $in # use only 'TOP' slab (biggest memory usage or loss) let lines=3 setglobal out = "$[basename $in]"-slabs-by-loss"" $[cat $in | grep -A $lines 'Slabs sorted by loss' |\ egrep -iv '\-\-|Name|Slabs'\ | awk '{print $1" "$4+$2*$3" "$4}' > $out] if test $Status -eq 0 { do_slabs_plotting $out } let lines=3 setglobal out = "$[basename $in]"-slabs-by-size"" $[cat $in | grep -A $lines 'Slabs sorted by size' |\ egrep -iv '\-\-|Name|Slabs'\ | awk '{print $1" "$4" "$4-$2*$3}' > $out] if test $Status -eq 0 { do_slabs_plotting $out } setglobal out = "$[basename $in]"-totals"" $[cat $in | grep "Memory used" |\ awk '{print $3" "$7}' > $out] if test $Status -eq 0 { compat array-assign t_files '0' $out do_totals_plotting } } proc parse_opts { local opt while getopts "tlr::s::h" opt { match $opt { with t setglobal mode = 'totals' with l setglobal mode = 'slabs' with s setglobal array = ''(${OPTARG//,/ }) setglobal width = $(array[0]) setglobal height = $(array[1]) with r setglobal array = ''(${OPTARG//,/ }) setglobal xmin = $(array[0]) setglobal xmax = $(array[1]) with h usage exit 0 with \? echo "Invalid option: -$OPTARG" > !2 exit 1 with : echo "-$OPTARG requires an argument." > !2 exit 1 } } return $OPTIND } proc parse_args { local idx=0 local p for p in [@Argv] { match $mode { with preprocess compat array-assign files '$idx' $p setglobal idx = "$idx+1" with totals compat array-assign t_files '$idx' $p setglobal idx = "$idx+1" with slabs compat array-assign files '$idx' $p setglobal idx = "$idx+1" } } } parse_opts @Argv setglobal argstart = $Status parse_args $(@:$argstart) if test $(#files[@]) -eq 0 && test $(#t_files[@]) -eq 0 { usage exit 1 } match $mode { with preprocess for i in [$(files[@])] { do_preprocess $i } with totals do_totals_plotting with slabs for i in [$(files[@])] { do_slabs_plotting $i } with * echo "Unknown mode $mode" > !2 usage exit 1 }