Whamcloud - gitweb
bug=10851
[fs/lustre-release.git] / lustre-iokit / obdfilter-survey / obdfilter-survey
1 #!/bin/bash
2
3 ######################################################################
4 # customize per survey
5
6 # specify obd instances to exercise
7 # these can be either...
8 # obdfilter instances (set 'ost_names')
9 # ...or...
10 # echo_client instances (set 'client_names')
11 # ... use 'host:name' for obd instances on other nodes.
12
13 # allow these to be passed in via string...
14 ost_names_str=${ost_names_str:-""}
15 if [ -n "$ost_names_str" ]; then
16     declare -a ost_names
17     count=0
18     for name in $ost_names_str; do
19         ost_names[$count]=$name
20         count=$((count+1))
21     done
22 else
23     ost_names=(ost{1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16})
24 fi
25
26 #client_names=(ns8:ECHO_ns8 ns9:ECHO_ns9)
27 client_names_str=${client_names_str:-""}
28 if [ -n "$client_names_str" ]; then
29     # make sure we unset ost_names so that our client_names get noticed... 
30     unset ost_names
31     declare -a client_names
32     count=0
33     for name in $client_names_str; do
34         client_names[$count]=$name
35         count=$((count+1))
36     done
37 fi
38
39 # result file prefix (date/time + hostname makes unique)
40 # NB ensure path to it exists
41 rslt=${rslt:-"/tmp/obdfilter_survey_`date +%F@%R`_`uname -n`"}
42
43 # lustre root (if running with own source tree)
44 # lustre_root=${lustre_root:-"/my/directory/lustre"}
45
46 # what tests to run (first must be write)
47 tests_str=${tests_str:-""}
48 if [ -n "$tests_str" ]; then
49     declare -a tests
50     count=0
51     for name in $tests_str; do
52         tests[$count]=$name
53         count=$((count+1))
54     done
55 else
56     #tests=(write rewrite read reread rewrite_again)
57     tests=(write rewrite read)
58 fi
59
60 # Set this true to check file contents
61 verify=${verify:-0}
62
63 # total size (MBytes) per obd instance
64 # large enough to avoid cache effects 
65 # and to make test startup/shutdown overhead insignificant
66 size=${size:-16384}
67
68 # record size (KBytes)
69 rszlo=${rszlo:-1024}
70 rszhi=${rszhi:-1024}
71
72 # number of objects per OST
73 nobjlo=${nobjlo:-1}
74 nobjhi=${nobjhi:-512}
75
76 # threads per OST (1024 max)
77 thrlo=${thrlo:-1}
78 thrhi=${thrhi:-64}
79
80 # restart from here iff all are defined
81 restart_rsz=
82 restart_thr=1
83 restart_nobj=1
84
85 # machine's page size (K)
86 if [ -z "$PAGE_SIZE" ]; then
87     if which python >/dev/null; then
88         PAGE_SIZE=`echo 'import resource; print resource.getpagesize()/1024;' |python`
89     fi
90 fi
91 PAGE_SIZE=${PAGE_SIZE:-4}
92
93 # max buffer_mem (total_threads * buffer size)
94 # (to avoid lctl ENOMEM problems)
95 max_buffer_mem=$((1024*1024))
96
97 # how to run commands on other nodes
98 # You need to make this work on your cluster if you have specified
99 # non-local obd instances above
100 custom_remote_shell () {
101     host=$1
102     shift
103     cmds="$*"
104     here=`pwd`
105     # Hop on to the remote node, chdir to 'here' and run the given
106     # commands. One of the following will probably work.
107     ssh $host "cd $here; $cmds"
108     #rsh $host "cd $here; $cmds"
109     # we have to remove the leading `uname -n`: from pdsh output lines
110     #pdsh -w $host "cd $here; $cmds" | sed 's/^[^:]*://'
111 }
112
113 #####################################################################
114 # leave the rest of this alone unless you know what you're doing...
115
116 # binaries
117 lsmod="/sbin/lsmod"
118 modprobe="/sbin/modprobe"
119 insmod="/sbin/insmod"
120 rmmod="/sbin/rmmod"
121
122 # lctl::test_brw bandwidth snapshot interval (seconds)
123 snap=1
124
125
126 if [ ${#tests[@]} -eq 0 -o "${tests[0]}" != "write" ]; then
127     echo "tests: ${tests[@]}"
128     echo "First test must be 'write'" 1>&2
129     exit 1
130 fi
131
132 rsltf="${rslt}.summary"
133 workf="${rslt}.detail"
134 cmdsf="${rslt}.script"
135 vmstatf="${rslt}.vmstat"
136 echo -n > $rsltf
137 echo -n > $workf
138
139 declare -a vmstatpids
140
141 # hide a little trick to unset this from the command line
142 if [ "$lustre_root" == " " ]; then
143     unset lustre_root
144 fi
145
146 if [ -z "$lustre_root" ]; then
147     lctl=lctl
148 else
149     lctl=${lustre_root}/utils/lctl
150 fi
151
152 remote_shell () {
153     host=$1
154     shift
155     cmds="$*"
156     if [ "$host" = "localhost" -o "$host" = `uname -n` ]; then
157         eval "$cmds"
158     else
159         custom_remote_shell $host "$cmds"
160     fi
161 }
162
163 obdecho_loaded() {
164     local host=$1
165     remote_shell $host $lsmod | grep obdecho > /dev/null 2>&1
166 }
167
168 load_obdecho () {
169     local host=$1
170     if [ -z "$lustre_root" ]; then
171         remote_shell $host $modprobe obdecho
172     elif [ -f ${lustre_root}/obdecho/obdecho.ko ]; then
173         remote_shell $host $insmod ${lustre_root}/obdecho/obdecho.ko
174     else
175         remote_shell $host $insmod ${lustre_root}/obdecho/obdecho.o
176     fi
177 }
178
179 unload_obdecho () {
180     local host=$1
181     remote_shell $host $rmmod obdecho
182 }
183
184 get_devno () {
185     local host=$1
186     local type=$2
187     local name=$3
188     remote_shell $host $lctl device_list | \
189         awk "{if (\$2 == \"UP\" && \$3 == \"$type\" && \$4 == \"$name\") {\
190                   print \$1; exit}}"
191 }
192
193 get_ec_devno () {
194     local host=$1
195     local client_name="$2"
196     local ost_name="$3"
197     if [ -z "$client_name" ]; then
198         if [ -z "$ost_name" ]; then
199             echo "client and ost name both null" 1>&2
200             return
201         fi
202         client_name=${ost_name}_echo_client
203     fi
204     ec=`get_devno $host echo_client $client_name`
205     if [ -n "$ec" ]; then
206         echo $ec $client_name 0
207         return
208     fi
209     if [ -z "$ost_name" ]; then
210         echo "no echo client and ost_name not set, client: $client_name, host: $host" 1>&2
211         return
212     fi
213     ost=`get_devno $host obdfilter $ost_name`
214     if [ -z "$ost" ]; then
215         echo "OST $ost_name not setup" 1>&2
216         return
217     fi
218     remote_shell $host "$lctl <<EOF
219         attach echo_client $client_name ${client_name}_UUID
220         setup $ost_name
221 EOF"
222     ec=`get_devno $host echo_client $client_name`
223     if [ -z "$ec" ]; then
224         echo "Can't setup echo client" 1>&2
225         return
226     fi
227     echo $ec $client_name 1
228 }
229
230 teardown_ec_devno () {
231     local host=$1
232     local client_name=$2
233     remote_shell $host "$lctl <<EOF
234         cfg $client_name
235         cleanup
236         detach
237 EOF"
238 }
239
240 create_objects () {
241     # create a set of objects, check there are 'n' contiguous ones and
242     # return the first or 'ERROR'
243     local host=$1
244     local devno=$2
245     local nobj=$3
246     local rfile=$4
247     remote_shell $host $lctl --device $devno create $nobj > $rfile 2>&1
248     first=0
249     prev=0
250     count=0
251     error=0
252     while read line; do
253         echo "$line" | grep -q 'is object id'
254         if [ $?  -ne 0 ]; then
255             continue
256         fi
257         if [ $first -eq 0 ]; then
258             first=$(echo $line | awk '{print $6}')
259             first=$(printf "%d" $first)
260             prev=$first
261             count=1
262         else
263             obj=$(echo $line | awk '{print $6}') 
264             obj=$(printf "%d" $obj) 
265             diff=$((obj - (prev+1))) 
266             if [ $diff -ne 0 ]; then 
267                 error=1 
268             fi 
269             prev=$obj 
270             count=$((count+1)) 
271         fi 
272     done < $rfile 
273     if [ $nobj -ne $count ]; then 
274         echo "ERROR: $nobj != $count" >&2 
275         cat $rfile >&2 
276         echo "ERROR" 
277     elif [ $error -ne 0 ]; then 
278         echo "ERROR: non contiguous objs found" >&2 
279         echo "ERROR" 
280     else 
281         echo $first 
282     fi
283 }
284
285 destroy_objects () {
286     local host=$1
287     local devno=$2
288     local obj0=$3
289     local nobj=$4
290     local rfile=$5
291     remote_shell $host $lctl --device $devno destroy $obj0 $nobj > $rfile 2>&1
292 }
293
294 get_stats () {
295     local rfile=$1
296     awk < $rfile \
297         '/^Selected device [0-9]+$/ {n = 0; next}\
298         /error/ {n = -1; exit}\
299         /^[0-9]+\/[0-9]+ Total: [0-9]+\.[0-9]+\/second$/ {n++; v=strtonum($3); \
300                                                           if (n == 1 || v < min) min = v;\
301                                                           if (n == 1 || v > max) max = v;\
302                                                           next}\
303         {if (n != 0) {n = -1; exit}}\
304         END {printf "%d %f %f\n", n, min, max}'
305 }
306
307 get_global_stats () {
308     local rfile=$1
309     awk < $rfile 'BEGIN {n = 0;}\
310                   {n++; if (n == 1) {err = $1; min = $2; max = $3} else\
311                                     {if ($1 < err) err = $1;\
312                                      if ($2 < min) min = $2;\
313                                      if ($3 > max) max = $3}}\
314                   END {if (n == 0) err = 0;\
315                        printf "%d %f %f\n", err, min, max}'
316 }
317
318 testname2type () {
319     # 'x' disables data check
320     if ((verify)); then
321         x=""
322     else
323         x="x"
324     fi
325     case $1 in
326         *write*)  echo "w$x";;
327         *)        echo "r$x";;
328     esac
329 }
330
331 print_summary () {
332     if [ "$1" = "-n" ]; then
333         minusn=$1; shift
334     else
335         minusn=""
336     fi
337     echo $minusn "$*" >> $rsltf
338     echo $minusn "$*"
339 }
340
341 unique () {
342     echo "$@" | xargs -n1 echo | sort -u
343 }
344
345 split_hostname () {
346     local name=$1
347     case $name in
348     *:*) host=`echo $name | sed 's/:.*$//'`
349          name=`echo $name | sed 's/[^:]*://'`
350          ;;
351     *)   host=localhost
352          ;;
353     esac
354     echo "$host $name"
355 }
356
357 # split out hostnames from client/ost names
358 ndevs=${#client_names[@]}
359 if ((ndevs != 0)); then
360     if ((${#ost_names[@]} != 0)); then
361         echo "Please specify client_names or ost_names, but not both" 1>&2
362         exit 1
363     fi
364     for ((i=0; i<ndevs;i++)); do
365         str=(`split_hostname ${client_names[$i]}`)
366         host_names[$i]=${str[0]}
367         client_names[$i]=${str[1]}
368     done
369 else
370     ndevs=${#ost_names[@]}
371     if ((ndevs == 0)); then
372         echo "Please specify either client_names or ost_names" 1>&2
373         exit 1
374     fi
375     for ((i=0; i<ndevs;i++)); do
376         str=(`split_hostname ${ost_names[$i]}`)
377         host_names[$i]=${str[0]}
378         ost_names[$i]=${str[1]}
379     done
380 fi
381
382 # get vmstat started
383 # disable LNET debug and get obdecho loaded on all relevant hosts
384 unique_hosts=(`unique ${host_names[@]}`)
385 pidcount=0
386 for host in ${unique_hosts[@]}; do
387     remote_shell $host "echo 0 > /proc/sys/lnet/debug"
388     host_vmstatf=${vmstatf}_${host}
389     echo -n > $host_vmstatf
390     remote_shell $host "vmstat 5 >> $host_vmstatf" &
391     pid=$!
392     vmstatpids[$pidcount]=$pid
393     pidcount=$((pidcount+1))
394     do_unload_obdecho[$host]=0
395     if obdecho_loaded $host; then
396         continue
397     fi
398     load_obdecho $host
399     if obdecho_loaded $host; then
400         do_unload_obdecho[$host]=1
401         continue
402         fi
403     echo "Can't load obdecho on $host" 1>&2
404     exit 1
405 done
406
407 # get all the echo_client device numbers and names
408 for ((i=0; i<ndevs; i++)); do
409     host=${host_names[$i]}
410     devno=(`get_ec_devno $host "${client_names[$i]}" "${ost_names[$i]}"`)
411     if ((${#devno[@]} != 3)); then
412         exit 1
413     fi
414     devnos[$i]=${devno[0]}
415     client_names[$i]=${devno[1]}
416     do_teardown_ec[$i]=${devno[2]}
417 done
418
419 for ((rsz=$rszlo;rsz<=$rszhi;rsz*=2)); do
420     for ((nobj=$nobjlo;nobj<=$nobjhi;nobj*=2)); do 
421         for ((thr=$thrlo;thr<=$thrhi;thr*=2)); do
422             if ((thr % nobj)); then
423                 continue
424             fi
425             # restart?
426             if [ -n "$restart_rsz" -a\
427                  -n "$restart_nobj" -a\
428                  -n "$restart_thr" ]; then
429                 if ((rsz < restart_rsz ||\
430                      (rsz == restart_rsz &&\
431                       (nobj < restart_nobj ||\
432                        (nobj == restart_nobj &&\
433                         thr < restart_thr))))); then
434                     continue;
435                 fi
436             fi
437             # compute parameters
438             total_thr=$((ndevs*thr))
439             total_nobj=$((ndevs*nobj))
440             pages=$((rsz/PAGE_SIZE))
441             actual_rsz=$((pages*PAGE_SIZE))
442             count=$((size*1024/(actual_rsz*thr)))
443             actual_size=$((actual_rsz*count*thr))
444             total_size=$((actual_size*ndevs))
445             # show computed parameters
446             str=`printf 'ost %2d sz %8dK rsz %4d obj %4d thr %4d ' \
447                      $ndevs $total_size $actual_rsz $total_nobj $total_thr`
448             echo "=======================> $str" >> $workf
449             print_summary -n "$str"
450             if ((total_thr * actual_rsz > max_buffer_mem)); then
451                 print_summary "Too much buffer space"
452                 continue
453             fi
454             # create the objects
455             tmpf="${workf}_tmp"
456             for ((idx=0; idx < ndevs; idx++)); do
457                 host=${host_names[$idx]}
458                 devno=${devnos[$idx]}
459                 client_name="${host}:${client_names[$idx]}"
460                 echo "=============> Create $nobj on $client_name" >> $workf
461                 first_obj=`create_objects $host $devno $nobj $tmpf`
462                 cat $tmpf >> $workf
463                 rm -f $tmpf
464                 if [ $first_obj = "ERROR" ]; then
465                     print_summary "created object #s on $client_name not contiguous"
466                     exit 1
467                 fi
468                 first_objs[$idx]=$first_obj
469             done
470             # run tests
471             for test in ${tests[@]}; do
472                 declare -a pidarray
473                 for host in ${unique_hosts[@]}; do
474                     echo "starting run for test: $test rsz: $rsz threads: $thr objects: $nobj" >> ${vmstatf}_${host}
475                 done
476                 print_summary -n "$test "
477                 # create per-host script files
478                 for host in ${unique_hosts[@]}; do
479                     echo -n > ${cmdsf}_${host}
480                 done
481                 for ((idx=0; idx < ndevs; idx++)); do
482                     host=${host_names[$idx]}
483                     devno=${devnos[$idx]}
484                     tmpfi="${tmpf}_$idx"
485                     first_obj=${first_objs[$idx]}
486                     thr_per_obj=$((${thr}/${nobj}))
487                     echo >> ${cmdsf}_${host} \
488                         "$lctl > $tmpfi 2>&1 \\
489                          --threads $thr -$snap $devno \\
490                          test_brw $count `testname2type $test` q $pages ${thr_per_obj}t${first_obj} &"
491                 done
492                 pidcount=0
493                 for host in ${unique_hosts[@]}; do
494                     echo "wait" >> ${cmdsf}_${host}
495                     pidarray[$pidcount]=0
496                     pidcount=$((pidcount+1))
497                 done
498                 # timed run of all the per-host script files
499                 t0=`date +%s.%N`
500                 pidcount=0
501                 for host in ${unique_hosts[@]}; do
502                     # brutal hack to deal with a non-shared /tmp
503                     scp -q ${cmdsf}_${host} ${host}:/tmp > /dev/null
504                     remote_shell $host bash ${cmdsf}_${host} &
505                     pidarray[$pidcount]=$!
506                     pidcount=$((pidcount+1))
507                 done
508                 pidcount=0
509                 for host in ${unique_hosts[@]}; do
510                     wait ${pidarray[$pidcount]}
511                     pidcount=$((pidcount+1))
512                 done
513                 #wait
514                 t1=`date +%s.%N`
515                 # clean up per-host script files
516                 for host in ${unique_hosts[@]}; do
517                     rm -f ${cmdsf}_${host}
518                 done
519                 # compute bandwidth from total data / elapsed time
520                 str=`awk "BEGIN {printf \"%7.2f \",\
521                          $total_size / (( $t1 - $t0 ) * 1024)}"`
522                 print_summary -n "$str"
523                 # collect/check individual OST stats
524                 echo -n > $tmpf
525                 for ((idx=0; idx < ndevs; idx++)); do
526                     client_name="${host_names[$idx]}:${client_names[$idx]}"
527                     tmpfi="${tmpf}_$idx"
528                     echo "=============> $test $client_name" >> $workf
529                     host="${host_names[$idx]}"
530                     scp -q ${host}:$tmpfi $tmpfi > /dev/null
531                     cat $tmpfi >> $workf
532                     get_stats $tmpfi >> $tmpf
533                     rm -f $tmpfi
534                 done
535                 # compute/display global min/max stats
536                 echo "=============> $test global" >> $workf
537                 cat $tmpf >> $workf
538                 stats=(`get_global_stats $tmpf`)
539                 rm -f $tmpf
540                 if ((stats[0] <= 0)); then
541                     if ((stats[0] < 0)); then
542                         str=`printf "%17s " ERROR`
543                     else
544                         str=`printf "%17s " SHORT`
545                     fi
546                 else
547                     str=`awk "BEGIN {printf \"[%7.2f,%7.2f] \",\
548                              (${stats[1]} * $actual_rsz)/1024,\
549                              (${stats[2]} * $actual_rsz)/1024; exit}"`
550                 fi
551                 print_summary -n "$str"
552             done
553             print_summary ""
554             # destroy objects we created
555             for ((idx=0; idx < ndevs; idx++)); do
556                 host=${host_names[$idx]}
557                 devno=${devnos[$idx]}
558                 client_name="${host}:${client_names[$idx]}"
559                 first_obj=${first_objs[$idx]}
560                 echo "=============> Destroy $nobj on $client_name" >> $workf
561                 destroy_objects $host $devno $first_obj $nobj $tmpf
562                 cat $tmpf >> $workf
563                 rm -f $tmpf
564             done
565         done
566     done
567 done
568
569 # tear down any echo clients we created
570 for ((i=0; i<ndevs; i++)); do
571     host=${host_names[$i]}
572     if ((${do_teardown_ec[$i]})); then
573         teardown_ec_devno $host ${client_names[$i]}
574     fi
575 done
576
577 # unload any obdecho modules we loaded
578 pidcount=0
579 for host in ${unique_hosts[@]}; do
580     remote_shell $host "killall vmstat" &
581     pid=$!
582     kill -term ${vmstatpids[$pidcount]}
583     kill -kill ${vmstatpids[$pidcount]} 2>/dev/null
584     wait $pid
585     pidcount=$((pidcount+1))
586     if ((${do_unload_obdecho[$host]})); then
587         unload_obdecho $host
588     fi
589 done
590
591 exit 0