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