1#!/usr/bin/env bash
2#
3#  Copyright (C) 2013 eNovance SAS <licensing@enovance.com>
4#  Author: Erwan Velu  <erwan@enovance.com>
5#
6#  The license below covers all files distributed with fio unless otherwise
7#  noted in the file itself.
8#
9#  This program is free software; you can redistribute it and/or modify
10#  it under the terms of the GNU General Public License version 2 as
11#  published by the Free Software Foundation.
12#
13#  This program is distributed in the hope that it will be useful,
14#  but WITHOUT ANY WARRANTY; without even the implied warranty of
15#  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
16#  GNU General Public License for more details.
17#
18#  You should have received a copy of the GNU General Public License
19#  along with this program; if not, write to the Free Software
20#  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
21
22BLK_SIZE=
23BLOCK_SIZE=4k
24SEQ=-1
25TEMPLATE=/tmp/template.fio
26OUTFILE=
27DISKS=
28PRINTABLE_DISKS=
29RUNTIME=300
30ETA=0
31MODES="write,randwrite,read,randread"
32SHORT_HOSTNAME=
33CACHED_IO="FALSE"
34PREFIX=""
35PREFIX_FILENAME=""
36IODEPTH=1
37
38show_help() {
39	PROG=$(basename $0)
40	echo "usage of $PROG:"
41	cat << EOF
42-h				: Show this help & exit
43-c				: Enable cached-based IOs
44					Disabled by default
45-a				: Run sequential test then parallel one
46					Disabled by default
47-s				: Run sequential test (default value)
48					one test after another then one disk after another
49					Disabled by default
50-p				: Run parallel test
51					one test after anoter but all disks at the same time
52					Enabled by default
53-D iodepth			: Run with the specified iodepth
54					Default is $IODEPTH
55-d disk1[,disk2,disk3,..]	: Run the tests on the selected disks
56					Separated each disk with a comma
57-z filesize                     : Specify the working file size, if you are passing filepaths to -d
58                                        Disabled by default
59-r seconds			: Time in seconds per benchmark
60					0 means till the end of the device
61					Default is $RUNTIME seconds
62-b blocksize[,blocksize1, ...]  : The blocksizes to test under fio format (4k, 1m, ...)
63					Separated each blocksize with a comma
64					Default is $BLOCK_SIZE
65-m mode1,[mode2,mode3, ...]     : Define the fio IO profile to use like read, write, randread, randwrite
66					Default is "$MODES"
67-x prefix			: Add a prefix to the fio filename
68					Useful to let a context associated with the file
69					If the prefix features a / (slash), prefix will be considered as a directory
70-A cmd_to_run			: System command to run after each job (exec_postrun in fio)
71-B cmd_to_run			: System command to run before each job (exec_prerun in fio)
72
73Example:
74
75$PROG -d /dev/sdb,/dev/sdc,/dev/sdd,/dev/sde -a -b 4k,128k,1m -r 100 -a -x dellr720-day2/
76
77	Will generate an fio file that will run
78		- a sequential bench on /dev/sdb /dev/sdc /dev/sdd /dev/sde for block size = 4k with write,randwrite,read,randread tests
79			ETA ~ 4 tests * 4 disks * 100 seconds
80		- a sequential bench on /dev/sdb /dev/sdc /dev/sdd /dev/sde for block size = 128k with write,randwrite,read,randread tests
81			ETA ~ 4 tests * 4 disks * 100 seconds
82		- a sequential bench on /dev/sdb /dev/sdc /dev/sdd /dev/sde for block size = 1m with write,randwrite,read,randread tests
83			ETA ~ 4 tests * 4 disks * 100 seconds
84		- a parallel bench on /dev/sdb /dev/sdc /dev/sdd /dev/sde for block size = 4k with write,randwrite,read,randread tests
85			ETA ~ 4 tests * 100 seconds
86		- a parallel bench on /dev/sdb /dev/sdc /dev/sdd /dev/sde for block size = 128k with write,randwrite,read,randread tests
87			ETA ~ 4 tests * 100 seconds
88		- a parallel bench on /dev/sdb /dev/sdc /dev/sdd /dev/sde for block size = 1m with write,randwrite,read,randread tests
89			ETA ~ 4 tests * 100 seconds
90
91Generating dellr720-day2/localhost-4k,128k,1m-all-write,randwrite,read,randread-sdb,sdc,sdd,sde.fio
92Estimated Time = 6000 seconds : 1 hour 40 minutes
93EOF
94}
95
96finish_template() {
97echo "iodepth=$IODEPTH" >> $TEMPLATE
98
99if [ "$RUNTIME" != "0" ]; then
100	echo "runtime=$RUNTIME" >> $TEMPLATE
101	echo "time_based" >> $TEMPLATE
102fi
103
104if [ "$CACHED_IO" = "FALSE" ]; then
105	echo "direct=1" >> $TEMPLATE
106fi
107}
108
109
110diskname_to_printable() {
111COUNT=0
112for disk in $(echo $@ | tr "," " "); do
113	R=$(basename $disk | sed 's|/|_|g')
114	COUNT=$(($COUNT + 1))
115	if [ $COUNT -eq 1 ]; then
116		P="$R"
117	else
118		P="$P,$R"
119	fi
120done
121echo $P
122}
123
124gen_template() {
125cat >$TEMPLATE << EOF
126[global]
127ioengine=libaio
128invalidate=1
129ramp_time=5
130EOF
131}
132
133gen_seq_suite() {
134TYPE=$1
135disk=$2
136PRINTABLE_DISK=$(diskname_to_printable $disk)
137cat >> $OUTFILE << EOF
138[$TYPE-$PRINTABLE_DISK-$BLK_SIZE-seq]
139stonewall
140bs=$BLK_SIZE
141filename=$disk
142rw=$TYPE
143write_bw_log=${PREFIX_FILENAME}$SHORT_HOSTNAME-$BLK_SIZE-$PRINTABLE_DISK-$TYPE-seq.results
144write_iops_log=${PREFIX_FILENAME}$SHORT_HOSTNAME-$BLK_SIZE-$PRINTABLE_DISK-$TYPE-seq.results
145EOF
146ETA=$(($ETA + $RUNTIME))
147}
148
149gen_seq_fio() {
150for disk in $(echo $DISKS | tr "," " "); do
151	for mode in $(echo $MODES | tr "," " "); do
152		gen_seq_suite "$mode" "$disk"
153	done
154done
155}
156
157
158gen_para_suite() {
159TYPE=$1
160NEED_WALL=$2
161D=0
162for disk in $(echo $DISKS | tr "," " "); do
163    PRINTABLE_DISK=$(diskname_to_printable $disk)
164    cat >> $OUTFILE << EOF
165[$TYPE-$PRINTABLE_DISK-$BLK_SIZE-para]
166bs=$BLK_SIZE
167EOF
168
169if [ "$D" = 0 ]; then
170    echo "stonewall" >> $OUTFILE
171    D=1
172fi
173
174cat >> $OUTFILE << EOF
175filename=$disk
176rw=$TYPE
177write_bw_log=${PREFIX_FILENAME}$SHORT_HOSTNAME-$BLK_SIZE-$PRINTABLE_DISK-$TYPE-para.results
178write_iops_log=${PREFIX_FILENAME}$SHORT_HOSTNAME-$BLK_SIZE-$PRINTABLE_DISK-$TYPE-para.results
179EOF
180done
181
182ETA=$(($ETA + $RUNTIME))
183echo >> $OUTFILE
184}
185
186gen_para_fio() {
187for mode in $(echo $MODES | tr "," " "); do
188	gen_para_suite "$mode"
189done
190}
191
192gen_fio() {
193case $SEQ in
194	2)
195		gen_seq_fio
196		gen_para_fio
197	;;
198	1)
199		gen_seq_fio
200	;;
201	0)
202		gen_para_fio
203	;;
204esac
205}
206
207parse_cmdline() {
208while getopts "hacpsd:b:r:m:x:z:D:A:B:" opt; do
209  case $opt in
210    h)
211	show_help
212	exit 0
213	;;
214    b)
215	BLOCK_SIZE=$OPTARG
216	;;
217    c)
218	CACHED_IO="TRUE"
219	;;
220    s)
221	if [ "$SEQ" = "-1" ]; then
222		SEQ=1
223	fi
224      ;;
225    x)
226	PREFIX=$OPTARG
227	echo "$PREFIX" | grep -q "/"
228	if [ "$?" -eq 0 ]; then
229		mkdir -p $PREFIX
230		# No need to keep the prefix for the log files
231		# we do have a directory for that
232		PREFIX_FILENAME=""
233	else
234		# We need to keep the prefix for the log files
235		PREFIX_FILENAME=$PREFIX
236	fi
237	;;
238    r)
239	RUNTIME=$OPTARG
240      ;;
241    p)
242	if [ "$SEQ" = "-1" ]; then
243		SEQ=0
244	fi
245      ;;
246    m)
247        MODES=$OPTARG;
248      ;;
249    d)
250 	DISKS=$OPTARG
251	PRINTABLE_DISKS=$(diskname_to_printable "$DISKS")
252      ;;
253    D)
254	IODEPTH=$OPTARG
255      ;;
256    a)
257	SEQ=2
258      ;;
259    B)
260	echo "exec_prerun=$OPTARG" >> $TEMPLATE
261      ;;
262    A)
263	echo "exec_postrun=$OPTARG" >> $TEMPLATE
264      ;;
265    z)
266	FSIZE=$OPTARG
267	echo "size=$FSIZE" >> $TEMPLATE
268      ;;
269    \?)
270      echo "Invalid option: -$OPTARG" >&2
271      ;;
272  esac
273done
274
275if [ "$SEQ" = "-1" ]; then
276	SEQ=0
277fi
278
279SHORT_HOSTNAME=$(hostname -s)
280case $SEQ in
281	2)
282		OUTFILE=${PREFIX}$SHORT_HOSTNAME-$BLOCK_SIZE-all-$MODES-$PRINTABLE_DISKS.fio
283	;;
284
285	1)
286		OUTFILE=${PREFIX}$SHORT_HOSTNAME-$BLOCK_SIZE-sequential-$MODES-$PRINTABLE_DISKS.fio
287	;;
288	0)
289		OUTFILE=${PREFIX}$SHORT_HOSTNAME-$BLOCK_SIZE-parallel-$MODES-$PRINTABLE_DISKS.fio
290	;;
291esac
292
293if [ -z "$DISKS" ]; then
294	echo "Missing DISKS !"
295	echo "Please read the help !"
296	show_help
297	exit 1
298fi
299
300}
301
302check_mode_order() {
303FOUND_WRITE="NO"
304CAUSE="You are reading data before writing them          "
305
306# If no write occurs, let's show a different message
307echo $MODES | grep -q "write"
308if [ "$?" -ne 0 ]; then
309	CAUSE="You are reading data while never wrote them before"
310fi
311
312for mode in $(echo $MODES | tr "," " "); do
313	echo $mode | grep -q write
314	if [ "$?" -eq 0 ]; then
315		FOUND_WRITE="YES"
316	fi
317	echo $mode | grep -q "read"
318	if [ "$?" -eq 0 ]; then
319		if [ "$FOUND_WRITE" = "NO" ]; then
320			echo "###############################################################"
321			echo "# Warning : $CAUSE#"
322			echo "# On some storage devices, this could lead to invalid results #"
323			echo "#                                                             #"
324			echo "# Press Ctrl-C to adjust pattern order if you have doubts     #"
325			echo "# Or Wait 5 seconds before the file will be created           #"
326			echo "###############################################################"
327			sleep 5
328			# No need to try showing the message more than one time
329			return
330		fi
331	fi
332done
333}
334
335
336########## MAIN
337gen_template
338parse_cmdline "$@"
339finish_template
340check_mode_order
341
342echo "Generating $OUTFILE"
343cp -f $TEMPLATE $OUTFILE
344echo >> $OUTFILE
345
346for BLK_SIZE in $(echo $BLOCK_SIZE | tr "," " "); do
347	gen_fio
348done
349ETA_H=$(($ETA / 3600))
350ETA_M=$((($ETA - ($ETA_H*3600)) / 60))
351if [ "$ETA" = "0" ]; then
352	echo "Cannot estimate ETA as RUNTIME=0"
353else
354	echo "Estimated Time = $ETA seconds : $ETA_H hour $ETA_M minutes"
355fi
356