| #!/bin/bash |
| # SPDX-License-Identifier: GPL-2.0-only |
| |
| # Sergey Senozhatsky, 2015 |
| # sergey.senozhatsky.work@gmail.com |
| # |
| |
| |
| # 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 |
| # |
| |
| min_slab_name_size=11 |
| xmin=0 |
| xmax=0 |
| width=1500 |
| height=700 |
| mode=preprocess |
| |
| 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" |
| } |
| |
| check_file_exist() |
| { |
| if [ ! -f "$1" ]; then |
| echo "File '$1' does not exist" |
| exit 1 |
| fi |
| } |
| |
| 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" |
| |
| out_file=`basename "$file"` |
| if [ $xmax -ne 0 ]; then |
| range="$range::$xmax" |
| lines=$((xmax-xmin)) |
| fi |
| |
| wc_lines=`cat "$file" | wc -l` |
| if [ $? -ne 0 ] || [ "$wc_lines" -eq 0 ] ; then |
| wc_lines=$lines |
| fi |
| |
| if [ "$wc_lines" -lt "$lines" ]; then |
| lines=$wc_lines |
| fi |
| |
| if [ $((width / lines)) -gt $min_slab_name_size ]; then |
| xtic=":xtic(1)" |
| xtic_rotate=90 |
| fi |
| |
| gnuplot -p << EOF |
| #!/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 |
| EOF |
| |
| if [ $? -eq 0 ]; then |
| echo "$out_file.png" |
| fi |
| } |
| |
| do_totals_plotting() |
| { |
| local gnuplot_cmd="" |
| local range="every ::$xmin" |
| local file="" |
| |
| if [ $xmax -ne 0 ]; then |
| range="$range::$xmax" |
| fi |
| |
| for i in "${t_files[@]}"; do |
| check_file_exist "$i" |
| |
| file="$file"`basename "$i"` |
| gnuplot_cmd="$gnuplot_cmd '$i' $range using 1 title\ |
| '$i Memory usage' with lines," |
| gnuplot_cmd="$gnuplot_cmd '' $range using 2 title \ |
| '$i Loss' with lines," |
| done |
| |
| gnuplot -p << EOF |
| #!/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 |
| EOF |
| |
| if [ $? -eq 0 ]; then |
| echo "$file.png" |
| fi |
| } |
| |
| 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 |
| 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 [ $? -eq 0 ]; then |
| do_slabs_plotting "$out" |
| fi |
| |
| let lines=3 |
| 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 [ $? -eq 0 ]; then |
| do_slabs_plotting "$out" |
| fi |
| |
| out=`basename "$in"`"-totals" |
| `cat "$in" | grep "Memory used" |\ |
| awk '{print $3" "$7}' > "$out"` |
| if [ $? -eq 0 ]; then |
| t_files[0]=$out |
| do_totals_plotting |
| fi |
| } |
| |
| parse_opts() |
| { |
| local opt |
| |
| while getopts "tlr::s::h" opt; do |
| case $opt in |
| t) |
| mode=totals |
| ;; |
| l) |
| mode=slabs |
| ;; |
| s) |
| array=(${OPTARG//,/ }) |
| width=${array[0]} |
| height=${array[1]} |
| ;; |
| r) |
| array=(${OPTARG//,/ }) |
| xmin=${array[0]} |
| xmax=${array[1]} |
| ;; |
| h) |
| usage |
| exit 0 |
| ;; |
| \?) |
| echo "Invalid option: -$OPTARG" >&2 |
| exit 1 |
| ;; |
| :) |
| echo "-$OPTARG requires an argument." >&2 |
| exit 1 |
| ;; |
| esac |
| done |
| |
| return $OPTIND |
| } |
| |
| parse_args() |
| { |
| local idx=0 |
| local p |
| |
| for p in "$@"; do |
| case $mode in |
| preprocess) |
| files[$idx]=$p |
| idx=$idx+1 |
| ;; |
| totals) |
| t_files[$idx]=$p |
| idx=$idx+1 |
| ;; |
| slabs) |
| files[$idx]=$p |
| idx=$idx+1 |
| ;; |
| esac |
| done |
| } |
| |
| parse_opts "$@" |
| argstart=$? |
| parse_args "${@:$argstart}" |
| |
| if [ ${#files[@]} -eq 0 ] && [ ${#t_files[@]} -eq 0 ]; then |
| usage |
| exit 1 |
| fi |
| |
| case $mode in |
| preprocess) |
| for i in "${files[@]}"; do |
| do_preprocess "$i" |
| done |
| ;; |
| totals) |
| do_totals_plotting |
| ;; |
| slabs) |
| for i in "${files[@]}"; do |
| do_slabs_plotting "$i" |
| done |
| ;; |
| *) |
| echo "Unknown mode $mode" >&2 |
| usage |
| exit 1 |
| ;; |
| esac |