Whamcloud - gitweb
LU-9514 test: sanity 51f add to ALWAYS_EXCEPT
[fs/lustre-release.git] / lustre-iokit / stats-collect / iokit-gather-stats
1 #!/bin/bash
2
3 # iokit-gather-stats:
4 # script on a selection of nodes and collect all the results into a single
5 # tar ball
6 #
7 # Copyright 2008 Sun Microsystems, Inc. All rights reserved
8 # Use is subject to license terms.
9 #
10 # Copyright (c) 2014, Intel Corporation.
11 #
12
13 error() {
14         echo "ERROR: $0: $@"
15 }
16
17 warning() {
18         echo "WARNING: $@"
19 }
20
21 info () {
22         if [ ${PRINT_INFO_MSGS} -gt 0 ]; then
23                 echo "INFO: $@"
24         fi
25 }
26
27 debug () {
28         if [ ${PRINT_DEBUG_MSGS} -gt 0 ]; then
29                 echo "DEBUG: $@"
30         fi
31 }
32
33 usage() {
34         printf $"Usage: iokit-gather-stats [-help] config_file [start|stop|cleanup] <log_name>\n"
35         if [ x$1 = x-h ]; then
36                  printf $"
37 The distribution script will run on a single node.  It is parameterised
38 with a set of target node names.  It may assume ssh/scp to these node
39 names works without requiring a password.  It will run in 2 modes...
40
41 iokit-gather-stats config_file start
42
43 ...will copy the script to /tmp everywhere described in
44 config_file running on all the target hosts.  And...
45
46 iokit-gather-stats config_file stop log_name
47
48 ...will stop script running on all the hosts it started on and collect
49 all the individual stats files into a single compressed tarball if the log_name is
50 provided.
51
52 The config file is just a list of shell variable assignments that can be
53 customised.
54
55 Serveral variables must be set in the config file
56
57 Targets: the nodes where run the script.
58 "
59                 exit 0
60         else
61                 exit 1
62         fi
63 }
64
65 options=`getopt -o h --long help:: -- "$@"`
66
67 if [ $? -ne 0 ]; then
68         usage
69 fi
70
71 eval set -- "$options"
72
73 while true
74 do
75         case "$1" in
76                 -h)
77                         usage -h ;;
78                 --help)
79                         usage -h ;;
80                 --)
81                         shift
82                         break ;;
83         esac
84 done
85
86 if [ $# != 2 -a $# != 3 ] ; then
87         usage
88 fi
89
90 CONFIG=$1
91 OPTION=$2
92 shift
93 shift
94
95 GLOBAL_TIMESTAMP=""
96
97 if [ ! -r $CONFIG ]; then
98         error "Config_file: $CONFIG does not exist "
99         exit 1
100 fi
101
102 . $CONFIG
103
104 if [ -z "$SCRIPT" ]; then
105         error "SCRIPT in ${CONFIG} is empty"
106         exit 1
107 fi
108
109 if [ -z "$TARGETS" ]; then
110         error "TARGETS in ${CONFIG} is empty"
111         exit 1
112 fi
113
114 #check nodes accessiable
115 Check_nodes_available() {
116         local NODES_NOT_AVAILABLE=""
117
118         debug "Entering Check_nodes_available()"
119
120         for TARGET in $TARGETS; do
121                 if ! ping -c 1 -w 3 $TARGET > /dev/null; then
122                         NODES_NOT_AVAILABLE=$NODES_NOT_AVAILABLE$TARGET
123                 fi
124         done
125
126         if [ -z "$NODES_NOT_AVAILABLE" ]; then
127                 debug "Check_nodes_available() returning 0 "
128                         "(success - all nodes available)"
129                 return 0
130         fi
131
132         error "Check_nodes_available: these nodes are not available "
133                 "(did not respond to pings): ${NODES_NOT_AVAILABLE}"
134         debug "Check_nodes_available() returning with errors"
135
136         return 1
137 }
138
139 if ! Check_nodes_available; then
140         error "not all the nodes are available"
141         exit 1
142 fi
143
144 #
145 # returns 1 if copies of lstats are found running on any of the $TARGETS nodes
146 #
147 Nodes_are_not_clean() {
148         local DIRTY_NODES=""
149
150         debug "Entering Nodes_are_not_clean()"
151
152         # check whether there are running threads on the targets
153         for TARGET in $TARGETS; do
154                 ps_str=`$DSH $TARGET "ps aux | grep -v grep | grep ${SCRIPT}-${TARGET}"`
155                 if [ -n "$ps_str" ]; then
156                         DIRTY_NODES="${DIRTY_NODES} ${TARGET}"
157                 fi
158         done
159
160         if [ -n "$DIRTY_NODES" ]; then
161                 debug "Nodes_are_not_clean() returning 1"
162                 return 1
163         fi
164
165         debug "Nodes_are_not_clean() returning 0"
166         return 0
167 }
168
169 Clean_nodes() {
170
171         debug "Entering Clean_nodes()"
172
173         #
174         # if debugging is enabled, show lists of lstats processes
175         # still running on the target nodes before the clean operation
176         #
177         if [ ${PRINT_DEBUG_MSGS} -gt 0 ]; then
178                 for TARGET in $TARGETS; do
179                         debug "List of processes which need to be cleaned up on ${TARGET}:"
180                         $DSH $TARGET "ps aux | grep -v grep | grep ${SCRIPT}-${TARGET}"
181                         debug "List of pids which need to be cleaned up on ${TARGET}:"
182                         $DSH $TARGET "ps aux | grep ${SCRIPT}-${TARGET} | grep -v grep | ${AWK} '{ print \$2 }'"
183                 done
184         fi
185
186         #
187         # do the actual cleanup
188         # kill any old lstats processes still running on the target nodes
189         #
190         for TARGET in $TARGETS; do
191                 ps_str=$($DSH $TARGET "ps aux | grep -v grep | grep ${SCRIPT}-${TARGET}")
192                 if [ -n "$ps_str" ]; then
193                         debug "cleaning node ${TARGET}"
194                         $DSH $TARGET "ps aux | grep ${SCRIPT}-${TARGET} |
195                                       grep -v grep | ${AWK} '{ print \$2 }' |
196                                       ${XARGS} kill"
197                 fi
198         done
199
200         debug "Leaving Clean_nodes()"
201         return 0
202 }
203
204 copy_target_script() {
205         local target=$1
206
207         debug "Entering copy_target_script()"
208
209         #copy alex's run scripts to the target
210         copy_cmd="$DCP $SCRIPT ${USER}${target}:$TMP/${SCRIPT}-${target}"
211         ${copy_cmd} 1>/dev/null 2>&1
212         if [ ${PIPESTATUS[0]} != 0 ]; then
213                 echo "copy command failed: ${copy_cmd}" 2>&1
214                 debug "Leaving copy_target_script() (error return)"
215                 return 1
216         fi
217
218         echo "$SCRIPT copied to ${USER}${target} (into $TMP)"
219         debug "Leaving copy_target_script() (normal return)"
220         return 0
221 }
222
223 start_target_script() {
224         local target=$1
225
226         debug "Entering start_target_script()"
227
228         if ! copy_target_script $target; then
229                 echo "copy_target_script $target failed." 2>&1
230                 debug "Leaving start_target_script() (error return)"
231                 return 1
232         fi
233
234         #run the script on the target
235         $DSH ${USER}${target} "VMSTAT_INTERVAL=${VMSTAT_INTERVAL} \
236                       SDIO_INTERVAL=${SDIO_INTERVAL}              \
237                       SERVICE_INTERVAL=${SERVICE_INTERVAL}        \
238                       BRW_INTERVAL=${BRW_INTERVAL}                \
239                       JBD_INTERVAL=${JBD_INTERVAL}                \
240                       IO_INTERVAL=${IO_INTERVAL}                  \
241                       MBALLOC_INTERVAL=${MBALLOC_INTERVAL}        \
242                       sh ${TMP}/${SCRIPT}-${target} start         \
243                       1> /dev/null 2>/dev/null </dev/null"
244
245         if [ ${PIPESTATUS[0]} != 0 ]; then
246                 echo "Start the ${SCRIPT} on ${target} failed"
247                 debug "Leaving start_target_script() (error return)"
248                 return 1
249         fi
250
251         echo "Start the ${SCRIPT} on ${target} success"
252         debug "Leaving start_target_script() (normal return)"
253         return 0
254 }
255
256 stop_target_script() {
257         local target=$1
258
259         debug "Entering stop_target_script()"
260
261         #stop the target script first
262         $DSH ${USER}${target} "sh ${TMP}/${SCRIPT}-${target} stop" 1>/dev/null 2>&1
263         if [ ${PIPESTATUS[0]} != 0 ]; then
264                 echo  "stop the collecting stats script on ${target} failed"
265                 debug "Leaving stop_target_script() (error return)"
266                 return 1
267         else
268                 echo  "stop the collecting stats script on ${target} success"
269         fi
270
271         #remove those tmp file
272         $DSH ${USER}${target} "rm -rf $TMP/${SCRIPT}-${target}" 1>/dev/null 2>&1
273         echo "cleanup ${target} tmp file after stop "
274
275         debug "Leaving stop_target_script() (normal return)"
276         return 0
277 }
278
279 #
280 # create a unique timestamp-based name which we can use for
281 # naming files on all the $TARGET nodes.
282 #
283 # By creating one timestamp here on the master node, we avoid
284 # the problem of clock skew on the $TARGET nodes causing them
285 # to use different filenames than we expect (if their clocks are
286 # different from the clock on this node)
287 #
288 generate_timestamp() {
289         if [ "X${GLOBAL_TIMESTAMP}" = "X" ]; then
290                 export GLOBAL_TIMESTAMP=`date +%F-%H.%M.%S`
291                 debug "Global Timestamp Created: ${GLOBAL_TIMESTAMP}"
292         fi
293 }
294
295 fetch_target_log() {
296         generate_timestamp
297         local target=$1
298         local date=${GLOBAL_TIMESTAMP}
299         local target_log_name="stats-${target}-${date}"
300
301         echo "Getting log: ${target_log_name}.tar.gz from ${target}"
302         $DSH ${USER}${target} "sh ${TMP}/${SCRIPT}-${target} fetch " \
303                       > $TMP/${target_log_name}.tar.gz
304         echo "Got log: ${target_log_name}.tar.gz from ${target}"
305
306         echo "Moving $TMP/${target_log_name}.tar.gz to $TMP/$log_name"
307         mv $TMP/${target_log_name}.tar.gz $TMP/$log_name
308 }
309
310 fetch_log() {
311         generate_timestamp
312         local log_name=${GLOBAL_TIMESTAMP}
313         local stat_tar_name=$1
314         local -a pids_array
315         local -a clients_array
316
317         debug "Entering fetch_log()"
318
319         if ! mkdir -p $TMP/$log_name ; then
320                 error "can not mkdir $log_name"
321                 exit 1
322         fi
323
324         #retrive the log_tarball from remote nodes background
325         local n=0
326         for TARGET in $TARGETS; do
327                 (fetch_target_log ${TARGET}) &
328                 pids_array[$n]=$!
329                 clients_array[$n]=$TARGET
330
331                 debug "fetch_log: spawned fetch_target_log process for ${TARGET} pid ${pids_array[$n]}"
332                 let n=$n+1
333         done
334
335         local num_pids=$n
336
337         #Waiting log fetch finished
338         for ((n=0; $n < $num_pids; n++)); do
339                 debug "fetch_log(): waiting for pid ${pids_array[$n]}"
340                 wait ${pids_array[$n]}
341
342                 #
343                 # TODO: add check of exit status from wait()
344                 #
345         done
346
347         #compress the log tarball
348         cmd="$TAR ${stat_tar_name} $TMP/${log_name}"
349         echo "Creating compressed tar file ${stat_tar_name} from log files in  $TMP/${log_name}"
350         ${cmd} 1>/dev/null 2>&1
351         if [ ${PIPESTATUS[0]} == 0 ]; then
352                 echo "removing temporary directory $TMP/${log_name}"
353                 rm -rf $TMP/${log_name}
354         else
355                 echo "Compressed logfiles are in $TMP/${stat_tar_name}"
356         fi
357
358         debug "Leaving fetch_log()"
359 }
360
361 stop_targets_script() {
362         local -a pids_array
363         local -a clients_array
364         local n=0
365
366         debug "Entering stop_targets_script()"
367
368         for TARGET in $TARGETS; do
369                 (stop_target_script ${TARGET}) &
370                 pids_array[$n]=$!
371                 clients_array[$n]=$TARGET
372                 let n=$n+1
373         done
374         local num_pids=$n
375
376         #Waiting log fetch finished
377         for ((n=0; $n < $num_pids; n++)); do
378                 if ! wait ${pids_array[$n]}; then
379                         echo "${clients_array[$n]}: can not stop stats collect"
380                 fi
381         done
382
383         debug "Leaving stop_targets_script()"
384 }
385
386 gather_start() {
387         local -a pids_array
388         local -a clients_array
389         local n=0
390
391         debug "Entering gather_start()"
392
393         #check whether the collect scripts already start in some targets
394
395         Nodes_are_not_clean
396         ret=$?
397
398         if [ $ret -gt 0 ]; then
399                 warning "$SCRIPT already running on some targets, try cleanup"
400
401                 Clean_nodes
402
403                 Nodes_are_not_clean
404                 ret=$?
405
406                 if [ $ret -gt 0 ]; then
407                         error "$SCRIPT automatic cleanup attempt failed."
408                         error "$SCRIPT Please make sure lstats is not running "\
409                                 "on target nodes and try again."
410                         debug "Error return from gather_start()"
411                         return 1
412                 fi
413         fi
414
415         for TARGET in $TARGETS; do
416                 (start_target_script ${TARGET}) &
417                 pids_array[$n]=$!
418                 clients_array[$n]=$TARGET
419                 let n=$n+1
420         done
421
422         local num_pids=$n
423
424         local RC=0
425         #Waiting log fetch finished
426         for ((n=0; $n < $num_pids; n++)); do
427                 if ! wait ${pids_array[$n]}; then
428                         echo "${clients_array[$n]}: can not start stats collect"
429                         let RC=$RC+1
430                 fi
431         done
432
433         if [ $RC != 0 ]; then
434                 stop_targets_script
435         fi
436
437         debug "Leaving gather_start()"
438 }
439
440 gather_stop() {
441         log=$1
442
443         debug "Entering gather_stop()"
444
445         if [ -n "$log" ]; then
446                 fetch_log $log
447         fi
448
449         stop_targets_script
450
451         debug "Leaving gather_stop()"
452 }
453
454 get_end_line_num()
455 {
456         local log_name=$1
457
458         local ln=$(grep -n snapshot_time ${log_name} |
459                    awk -F":" '{ln=$1;} END{print ln;}')
460         local total_ln=$(wc ${log_name} | awk '{print $1}')
461
462         local endlen=$((total_ln - $ln))
463         echo $endlen
464 }
465
466 get_csv()
467 {
468         local logdir=$1
469         local statf=$2
470
471         local statf_name=`basename ${statf}`
472         type_name=`echo ${statf_name} | awk -F "." '{print $3}'`
473         stat_name=`head -n 1 ${statf} | awk '{print $4}'`
474         stat_type=`head -n 1 ${statf} | awk '{print $1}'`
475
476         #currently, it can only analyse client application log
477         if [ "$stat_type" != "client" ]; then
478                 error "can not analyse ${statf} ......."
479                 exit 1
480         fi
481
482         #create the header
483         echo "${node_name}_${type_name}, ${stat_name}" \
484                         >> $logdir/analyse_${type_name}.csv
485
486         #get total stats collection
487         end_len=`get_end_line_num ${statf}`
488         if [ $end_len != 1 -a $end_len != 0 ]; then
489                 if [ "$type_name" != "osc-rpc_stats" ]; then
490                         tail -n $end_len ${statf} | awk '{print $1 "," $2}' \
491                                 >> $logdir/analyse_${type_name}.csv
492                 else
493                         tail -n $end_len ${statf} |                     \
494                         awk  '/^[[:digit:]]/{print $1","$2","$6}        \
495                               /^page/{print "page per rpc,read,write"}  \
496                               /^rpcs/{print "rpcs,read,write"}          \
497                               /^offset/{print "offset, read,write"}'    \
498                         >> $logdir/analyse_${type_name}.csv
499                 fi
500         fi
501 }
502
503 gather_analyse()
504 {
505         local log_tarball=$1
506         local option=$2
507
508         debug "Entering gather_analyze()"
509
510         #validating option
511         if [ -z "$log_tarball" -o -r "$option" ]; then
512                 usage;
513         fi
514
515         if [ ! -r $log_tarball ]; then
516                 error " not exist $log_tarball "
517                 return 1
518         fi
519
520         shift
521
522         local date=`date +%F-%H-%M`
523         local logdir="analyse-${date}"
524
525         mkdir -p ${TMP}/${logdir}
526         mkdir -p ${TMP}/${logdir}/tmp
527
528         $UNTAR $log_tarball -C ${TMP}/${logdir}/tmp 1>/dev/null 2>&1
529         for log_file in `find $TMP/$logdir/tmp`; do
530                 if test -f $log_file; then
531                         #get the node name
532                         local file_name=`basename ${log_file}`
533                         node_name=`echo ${file_name} | awk -F "-" '{print $2}'`
534                         echo "analysing the sublog ...$log_file"
535                         mkdir -p ${TMP}/${logdir}/${node_name}
536                         mkdir -p ${TMP}/${logdir}/${node_name}/tmp
537
538                         $UNTAR $log_file -C ${TMP}/${logdir}/${node_name}/tmp 1>/dev/null 2>&1
539                         for statf in `find ${TMP}/${logdir}/${node_name}/tmp`; do
540                                 if test -f $statf ; then
541                                         if [ "$option" == "csv" ]; then
542                                                 get_csv "$TMP/$logdir/${node_name}" "$statf"
543                                         fi
544                                 fi
545                         done
546                         rm -rf ${TMP}/${logdir}/${node_name}/tmp
547                 fi
548         done
549
550         rm -rf ${TMP}/${logdir}/tmp
551         $TAR ${TMP}/${logdir}.tar.gz ${TMP}/${logdir} 1>/dev/null 2>&1
552
553         echo "create analysed tarball ${TMP}/${logdir}.tar.gz"
554
555         debug "Leaving gather_analyze()"
556 }
557
558 case $OPTION in
559         start) gather_start ;;
560         stop)  gather_stop $@;;
561         analyse) gather_analyse $@;;
562         *) error "Unknown option ${OPTION}" ; exit 1
563 esac