1#!/bin/sh
2
3# Sergey Senozhatsky, 2015
4# sergey.senozhatsky.work@gmail.com
5#
6# This software is licensed under the terms of the GNU General Public
7# License version 2, as published by the Free Software Foundation, and
8# may be copied, distributed, and modified under those terms.
9#
10# This program is distributed in the hope that it will be useful,
11# but WITHOUT ANY WARRANTY; without even the implied warranty of
12# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13# GNU General Public License for more details.
14
15
16# This program is intended to plot a `slabinfo -X' stats, collected,
17# for example, using the following command:
18#   while [ 1 ]; do slabinfo -X >> stats; sleep 1; done
19#
20# Use `slabinfo-gnuplot.sh stats' to pre-process collected records
21# and generate graphs (totals, slabs sorted by size, slabs sorted
22# by size).
23#
24# Graphs can be [individually] regenerate with different ranges and
25# size (-r %d,%d and -s %d,%d options).
26#
27# To visually compare N `totals' graphs, do
28# slabinfo-gnuplot.sh -t FILE1-totals FILE2-totals ... FILEN-totals
29#
30
31min_slab_name_size=11
32xmin=0
33xmax=0
34width=1500
35height=700
36mode=preprocess
37
38usage()
39{
40	echo "Usage: [-s W,H] [-r MIN,MAX] [-t|-l] FILE1 [FILE2 ..]"
41	echo "FILEs must contain 'slabinfo -X' samples"
42	echo "-t 			- plot totals for FILE(s)"
43	echo "-l 			- plot slabs stats for FILE(s)"
44	echo "-s %d,%d		- set image width and height"
45	echo "-r %d,%d		- use data samples from a given range"
46}
47
48check_file_exist()
49{
50	if [ ! -f "$1" ]; then
51		echo "File '$1' does not exist"
52		exit 1
53	fi
54}
55
56do_slabs_plotting()
57{
58	local file=$1
59	local out_file
60	local range="every ::$xmin"
61	local xtic=""
62	local xtic_rotate="norotate"
63	local lines=2000000
64	local wc_lines
65
66	check_file_exist "$file"
67
68	out_file=`basename "$file"`
69	if [ $xmax -ne 0 ]; then
70		range="$range::$xmax"
71		lines=$((xmax-xmin))
72	fi
73
74	wc_lines=`cat "$file" | wc -l`
75	if [ $? -ne 0 ] || [ "$wc_lines" -eq 0 ] ; then
76		wc_lines=$lines
77	fi
78
79	if [ "$wc_lines" -lt "$lines" ]; then
80		lines=$wc_lines
81	fi
82
83	if [ $((width / lines)) -gt $min_slab_name_size ]; then
84		xtic=":xtic(1)"
85		xtic_rotate=90
86	fi
87
88gnuplot -p << EOF
89#!/usr/bin/env gnuplot
90
91set terminal png enhanced size $width,$height large
92set output '$out_file.png'
93set autoscale xy
94set xlabel 'samples'
95set ylabel 'bytes'
96set style histogram columnstacked title textcolor lt -1
97set style fill solid 0.15
98set xtics rotate $xtic_rotate
99set key left above Left title reverse
100
101plot "$file" $range u 2$xtic title 'SIZE' with boxes,\
102	'' $range u 3 title 'LOSS' with boxes
103EOF
104
105	if [ $? -eq 0 ]; then
106		echo "$out_file.png"
107	fi
108}
109
110do_totals_plotting()
111{
112	local gnuplot_cmd=""
113	local range="every ::$xmin"
114	local file=""
115
116	if [ $xmax -ne 0 ]; then
117		range="$range::$xmax"
118	fi
119
120	for i in "${t_files[@]}"; do
121		check_file_exist "$i"
122
123		file="$file"`basename "$i"`
124		gnuplot_cmd="$gnuplot_cmd '$i' $range using 1 title\
125			'$i Memory usage' with lines,"
126		gnuplot_cmd="$gnuplot_cmd '' $range using 2 title \
127			'$i Loss' with lines,"
128	done
129
130gnuplot -p << EOF
131#!/usr/bin/env gnuplot
132
133set terminal png enhanced size $width,$height large
134set autoscale xy
135set output '$file.png'
136set xlabel 'samples'
137set ylabel 'bytes'
138set key left above Left title reverse
139
140plot $gnuplot_cmd
141EOF
142
143	if [ $? -eq 0 ]; then
144		echo "$file.png"
145	fi
146}
147
148do_preprocess()
149{
150	local out
151	local lines
152	local in=$1
153
154	check_file_exist "$in"
155
156	# use only 'TOP' slab (biggest memory usage or loss)
157	let lines=3
158	out=`basename "$in"`"-slabs-by-loss"
159	`cat "$in" | grep -A "$lines" 'Slabs sorted by loss' |\
160		egrep -iv '\-\-|Name|Slabs'\
161		| awk '{print $1" "$4+$2*$3" "$4}' > "$out"`
162	if [ $? -eq 0 ]; then
163		do_slabs_plotting "$out"
164	fi
165
166	let lines=3
167	out=`basename "$in"`"-slabs-by-size"
168	`cat "$in" | grep -A "$lines" 'Slabs sorted by size' |\
169		egrep -iv '\-\-|Name|Slabs'\
170		| awk '{print $1" "$4" "$4-$2*$3}' > "$out"`
171	if [ $? -eq 0 ]; then
172		do_slabs_plotting "$out"
173	fi
174
175	out=`basename "$in"`"-totals"
176	`cat "$in" | grep "Memory used" |\
177		awk '{print $3" "$7}' > "$out"`
178	if [ $? -eq 0 ]; then
179		t_files[0]=$out
180		do_totals_plotting
181	fi
182}
183
184parse_opts()
185{
186	local opt
187
188	while getopts "tlr::s::h" opt; do
189		case $opt in
190			t)
191				mode=totals
192				;;
193			l)
194				mode=slabs
195				;;
196			s)
197				array=(${OPTARG//,/ })
198				width=${array[0]}
199				height=${array[1]}
200				;;
201			r)
202				array=(${OPTARG//,/ })
203				xmin=${array[0]}
204				xmax=${array[1]}
205				;;
206			h)
207				usage
208				exit 0
209				;;
210			\?)
211				echo "Invalid option: -$OPTARG" >&2
212				exit 1
213				;;
214			:)
215				echo "-$OPTARG requires an argument." >&2
216				exit 1
217				;;
218		esac
219	done
220
221	return $OPTIND
222}
223
224parse_args()
225{
226	local idx=0
227	local p
228
229	for p in "$@"; do
230		case $mode in
231			preprocess)
232				files[$idx]=$p
233				idx=$idx+1
234				;;
235			totals)
236				t_files[$idx]=$p
237				idx=$idx+1
238				;;
239			slabs)
240				files[$idx]=$p
241				idx=$idx+1
242				;;
243		esac
244	done
245}
246
247parse_opts "$@"
248argstart=$?
249parse_args "${@:$argstart}"
250
251if [ ${#files[@]} -eq 0 ] && [ ${#t_files[@]} -eq 0 ]; then
252	usage
253	exit 1
254fi
255
256case $mode in
257	preprocess)
258		for i in "${files[@]}"; do
259			do_preprocess "$i"
260		done
261		;;
262	totals)
263		do_totals_plotting
264		;;
265	slabs)
266		for i in "${files[@]}"; do
267			do_slabs_plotting "$i"
268		done
269		;;
270	*)
271		echo "Unknown mode $mode" >&2
272		usage
273		exit 1
274	;;
275esac
276