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-r seconds			: Time in seconds per benchmark
58					0 means till the end of the device
59					Default is $RUNTIME seconds
60-b blocksize[,blocksize1, ...]  : The blocksizes to test under fio format (4k, 1m, ...)
61					Separated each blocksize with a comma
62					Default is $BLOCK_SIZE
63-m mode1,[mode2,mode3, ...]     : Define the fio IO profile to use like read, write, randread, randwrite
64					Default is "$MODES"
65-x prefix			: Add a prefix to the fio filename
66					Useful to let a context associated with the file
67					If the prefix features a / (slash), prefix will be considered as a directory
68-A cmd_to_run			: System command to run after each job (exec_postrun in fio)
69-B cmd_to_run			: System command to run before each job (exec_prerun in fio)
70
71Example:
72
73$PROG -d /dev/sdb,/dev/sdc,/dev/sdd,/dev/sde -a -b 4k,128k,1m -r 100 -a -x dellr720-day2/
74
75	Will generate an fio file that will run
76		- a sequential bench on /dev/sdb /dev/sdc /dev/sdd /dev/sde for block size = 4k with write,randwrite,read,randread tests
77			ETA ~ 4 tests * 4 disks * 100 seconds
78		- a sequential bench on /dev/sdb /dev/sdc /dev/sdd /dev/sde for block size = 128k 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 = 1m with write,randwrite,read,randread tests
81			ETA ~ 4 tests * 4 disks * 100 seconds
82		- a parallel bench on /dev/sdb /dev/sdc /dev/sdd /dev/sde for block size = 4k with write,randwrite,read,randread tests
83			ETA ~ 4 tests * 100 seconds
84		- a parallel bench on /dev/sdb /dev/sdc /dev/sdd /dev/sde for block size = 128k 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 = 1m with write,randwrite,read,randread tests
87			ETA ~ 4 tests * 100 seconds
88
89Generating dellr720-day2/localhost-4k,128k,1m-all-write,randwrite,read,randread-sdb,sdc,sdd,sde.fio
90Estimated Time = 6000 seconds : 1 hour 40 minutes
91EOF
92}
93
94finish_template() {
95echo "iodepth=$IODEPTH" >> $TEMPLATE
96
97if [ "$RUNTIME" != "0" ]; then
98	echo "runtime=$RUNTIME" >> $TEMPLATE
99	echo "time_based" >> $TEMPLATE
100fi
101
102if [ "$CACHED_IO" = "FALSE" ]; then
103	echo "direct=1" >> $TEMPLATE
104fi
105}
106
107
108diskname_to_printable() {
109COUNT=0
110for disk in $(echo $@ | tr "," " "); do
111	R=$(basename $disk | sed 's|/|_|g')
112	COUNT=$(($COUNT + 1))
113	if [ $COUNT -eq 1 ]; then
114		P="$R"
115	else
116		P="$P,$R"
117	fi
118done
119echo $P
120}
121
122gen_template() {
123cat >$TEMPLATE << EOF
124[global]
125ioengine=libaio
126invalidate=1
127ramp_time=5
128EOF
129}
130
131gen_seq_suite() {
132TYPE=$1
133disk=$2
134PRINTABLE_DISK=$(diskname_to_printable $disk)
135cat >> $OUTFILE << EOF
136[$TYPE-$PRINTABLE_DISK-$BLK_SIZE-seq]
137stonewall
138bs=$BLK_SIZE
139filename=$disk
140rw=$TYPE
141write_bw_log=${PREFIX_FILENAME}$SHORT_HOSTNAME-$BLK_SIZE-$PRINTABLE_DISK-$TYPE-seq.results
142write_iops_log=${PREFIX_FILENAME}$SHORT_HOSTNAME-$BLK_SIZE-$PRINTABLE_DISK-$TYPE-seq.results
143EOF
144ETA=$(($ETA + $RUNTIME))
145}
146
147gen_seq_fio() {
148for disk in $(echo $DISKS | tr "," " "); do
149	for mode in $(echo $MODES | tr "," " "); do
150		gen_seq_suite "$mode" "$disk"
151	done
152done
153}
154
155
156gen_para_suite() {
157TYPE=$1
158NEED_WALL=$2
159D=0
160for disk in $(echo $DISKS | tr "," " "); do
161    PRINTABLE_DISK=$(diskname_to_printable $disk)
162    cat >> $OUTFILE << EOF
163[$TYPE-$PRINTABLE_DISK-$BLK_SIZE-para]
164bs=$BLK_SIZE
165EOF
166
167if [ "$D" = 0 ]; then
168    echo "stonewall" >> $OUTFILE
169    D=1
170fi
171
172cat >> $OUTFILE << EOF
173filename=$disk
174rw=$TYPE
175write_bw_log=${PREFIX_FILENAME}$SHORT_HOSTNAME-$BLK_SIZE-$PRINTABLE_DISK-$TYPE-para.results
176write_iops_log=${PREFIX_FILENAME}$SHORT_HOSTNAME-$BLK_SIZE-$PRINTABLE_DISK-$TYPE-para.results
177EOF
178done
179
180ETA=$(($ETA + $RUNTIME))
181echo >> $OUTFILE
182}
183
184gen_para_fio() {
185for mode in $(echo $MODES | tr "," " "); do
186	gen_para_suite "$mode"
187done
188}
189
190gen_fio() {
191case $SEQ in
192	2)
193		gen_seq_fio
194		gen_para_fio
195	;;
196	1)
197		gen_seq_fio
198	;;
199	0)
200		gen_para_fio
201	;;
202esac
203}
204
205parse_cmdline() {
206while getopts "hacpsd:b:r:m:x:D:A:B:" opt; do
207  case $opt in
208    h)
209	show_help
210	exit 0
211	;;
212    b)
213	BLOCK_SIZE=$OPTARG
214	;;
215    c)
216	CACHED_IO="TRUE"
217	;;
218    s)
219	if [ "$SEQ" = "-1" ]; then
220		SEQ=1
221	fi
222      ;;
223    x)
224	PREFIX=$OPTARG
225	echo "$PREFIX" | grep -q "/"
226	if [ "$?" -eq 0 ]; then
227		mkdir -p $PREFIX
228		# No need to keep the prefix for the log files
229		# we do have a directory for that
230		PREFIX_FILENAME=""
231	else
232		# We need to keep the prefix for the log files
233		PREFIX_FILENAME=$PREFIX
234	fi
235	;;
236    r)
237	RUNTIME=$OPTARG
238      ;;
239    p)
240	if [ "$SEQ" = "-1" ]; then
241		SEQ=0
242	fi
243      ;;
244    m)
245        MODES=$OPTARG;
246      ;;
247    d)
248 	DISKS=$OPTARG
249	PRINTABLE_DISKS=$(diskname_to_printable "$DISKS")
250      ;;
251    D)
252	IODEPTH=$OPTARG
253      ;;
254    a)
255	SEQ=2
256      ;;
257    B)
258	echo "exec_prerun=$OPTARG" >> $TEMPLATE
259      ;;
260    A)
261	echo "exec_postrun=$OPTARG" >> $TEMPLATE
262      ;;
263    \?)
264      echo "Invalid option: -$OPTARG" >&2
265      ;;
266  esac
267done
268
269if [ "$SEQ" = "-1" ]; then
270	SEQ=0
271fi
272
273SHORT_HOSTNAME=$(hostname -s)
274case $SEQ in
275	2)
276		OUTFILE=${PREFIX}$SHORT_HOSTNAME-$BLOCK_SIZE-all-$MODES-$PRINTABLE_DISKS.fio
277	;;
278
279	1)
280		OUTFILE=${PREFIX}$SHORT_HOSTNAME-$BLOCK_SIZE-sequential-$MODES-$PRINTABLE_DISKS.fio
281	;;
282	0)
283		OUTFILE=${PREFIX}$SHORT_HOSTNAME-$BLOCK_SIZE-parallel-$MODES-$PRINTABLE_DISKS.fio
284	;;
285esac
286
287if [ -z "$DISKS" ]; then
288	echo "Missing DISKS !"
289	echo "Please read the help !"
290	show_help
291	exit 1
292fi
293
294}
295
296check_mode_order() {
297FOUND_WRITE="NO"
298CAUSE="You are reading data before writing them          "
299
300# If no write occurs, let's show a different message
301echo $MODES | grep -q "write"
302if [ "$?" -ne 0 ]; then
303	CAUSE="You are reading data while never wrote them before"
304fi
305
306for mode in $(echo $MODES | tr "," " "); do
307	echo $mode | grep -q write
308	if [ "$?" -eq 0 ]; then
309		FOUND_WRITE="YES"
310	fi
311	echo $mode | grep -q "read"
312	if [ "$?" -eq 0 ]; then
313		if [ "$FOUND_WRITE" = "NO" ]; then
314			echo "###############################################################"
315			echo "# Warning : $CAUSE#"
316			echo "# On some storage devices, this could lead to invalid results #"
317			echo "#                                                             #"
318			echo "# Press Ctrl-C to adjust pattern order if you have doubts     #"
319			echo "# Or Wait 5 seconds before the file will be created           #"
320			echo "###############################################################"
321			sleep 5
322			# No need to try showing the message more than one time
323			return
324		fi
325	fi
326done
327}
328
329
330########## MAIN
331gen_template
332parse_cmdline "$@"
333finish_template
334check_mode_order
335
336echo "Generating $OUTFILE"
337cp -f $TEMPLATE $OUTFILE
338echo >> $OUTFILE
339
340for BLK_SIZE in $(echo $BLOCK_SIZE | tr "," " "); do
341	gen_fio
342done
343ETA_H=$(($ETA / 3600))
344ETA_M=$((($ETA - ($ETA_H*3600)) / 60))
345if [ "$ETA" = "0" ]; then
346	echo "Cannot estimate ETA as RUNTIME=0"
347else
348	echo "Estimated Time = $ETA seconds : $ETA_H hour $ETA_M minutes"
349fi
350