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