Whamcloud - gitweb
a61b37c0de60e1033ed80e367fc7acfe50345976
[fs/lustre-release.git] / lustre-iokit / stats-collect / gather_stats_everywhere.sh
1 #!/bin/sh
2
3 # gather_stats_everywhere:
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 ]
20         then
21                 echo "INFO: $@"
22         fi
23 }
24
25 debug () {
26         if [ ${PRINT_DEBUG_MSGS} -gt 0 ]
27         then
28                 echo "DEBUG: $@"
29         fi
30 }
31
32 usage() {
33         printf $"Usage: gather_stats_everywhere [-help] config_file [start|stop|cleanup] <log_name>\n"
34         if [ x$1 = x-h ]
35         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 gather_stats_everywhere 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 gather_stats_everywhere 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 ]
68 then 
69         usage
70 fi
71
72 eval set -- "$options"
73
74 while true
75 do
76         case "$1" in
77                 -h)
78                         usage -h ;;
79                 --help)
80                         usage -h ;;
81                 --)
82                         shift
83                         break ;;
84         esac
85 done
86
87 if [ $# != 2 -a $# != 3 ] ; then
88         usage
89 fi
90
91 CONFIG=$1
92 OPTION=$2
93 shift
94 shift
95
96 GLOBAL_TIMESTAMP=""
97
98 if [ ! -r $CONFIG ]; then
99         error "Config_file: $CONFIG does not exist "
100         exit 1
101 fi
102
103 . $CONFIG
104
105 if [ -z "$SCRIPT" ]; then
106         error "SCRIPT in ${CONFIG} is empty"
107         exit 1
108 fi      
109
110 if [ -z "$TARGETS" ]; then
111         error "TARGETS in ${CONFIG} is empty"
112         exit 1
113 fi
114
115 #check nodes accessiable 
116 Check_nodes_available() {
117         local NODES_NOT_AVAILABLE=""
118
119         debug "Entering Check_nodes_available()"
120     
121         for TARGET in $TARGETS; do
122                 if ! ping -c 1 -w 3 $TARGET > /dev/null; then 
123                         NODES_NOT_AVAILABLE=$NODES_NOT_AVAILABLE$TARGET
124                 fi
125         done
126     
127         if [ -z "$NODES_NOT_AVAILABLE" ]; then
128                 debug "Check_nodes_available() returning 0 (success - all nodes available)"
129                 return 0
130         fi
131
132         error "Check_nodes_available: these nodes are not available (did not respond to pings): ${NODES_NOT_AVAILABLE}"
133         debug "Check_nodes_available() returning with errors"
134         
135         return 1
136 }
137
138 if ! Check_nodes_available;  then 
139         error "not all the nodes are available"
140         exit 1
141 fi
142
143 #
144 # returns 1 if copies of lstats are found running on any of the $TARGETS nodes
145 #
146 Nodes_are_not_clean() {
147         local DIRTY_NODES=""
148
149         debug "Entering Nodes_are_not_clean()"
150     
151         # check whether there are running threads on the targets
152         for TARGET in $TARGETS; do
153                 ps_str=`$DSH $TARGET "ps aux | grep -v grep | grep ${SCRIPT}-${TARGET}"`
154                 if [ -n "$ps_str" ]; then
155                         DIRTY_NODES="${DIRTY_NODES} ${TARGET}"
156                 fi
157         done
158
159         if [ -n "$DIRTY_NODES" ]; then
160                 debug "Nodes_are_not_clean() returning 1"
161                 return 1
162         fi
163
164         debug "Nodes_are_not_clean() returning 0"
165         return 0 
166 }
167
168 Clean_nodes() {
169
170         debug "Entering Clean_nodes()"
171     
172         #
173         # if debugging is enabled, show lists of lstats processes
174         # still running on the target nodes before the clean operation
175         #
176         if [ ${PRINT_DEBUG_MSGS} -gt 0 ]
177         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             
192                 ps_str=`$DSH $TARGET "ps aux | grep -v grep | grep ${SCRIPT}-${TARGET}"`
193                 if [ -n "$ps_str" ]; then
194                         debug "cleaning node ${TARGET}"
195                         $DSH $TARGET "ps aux | grep ${SCRIPT}-${TARGET} | grep -v grep | ${AWK} '{ print \$2 }' | ${XARGS} kill"
196                 fi
197         done
198
199         debug "Leaving Clean_nodes()"
200         return 0 
201 }
202
203 copy_target_script() {
204         local target=$1
205
206         debug "Entering copy_target_script()"
207     
208         #copy alex's run scripts to the target
209         copy_cmd="$DCP $SCRIPT ${USER}${target}:$TMP/${SCRIPT}-${target}"
210         ${copy_cmd} 1>/dev/null 2>&1 
211         if [ ${PIPESTATUS[0]} != 0 ]; then
212                 echo "copy command failed: ${copy_cmd}" 2>&1
213                 debug "Leaving copy_target_script() (error return)"
214                 return 1
215         fi
216         
217         echo "$SCRIPT copied to ${USER}${target} (into $TMP)"
218         debug "Leaving copy_target_script() (normal return)"
219         return 0
220 }
221
222 start_target_script() {
223         local target=$1
224
225         debug "Entering start_target_script()"
226     
227         if ! copy_target_script $target; then
228                 echo "copy_target_script $target failed." 2>&1
229                 debug "Leaving start_target_script() (error return)"
230                 return 1
231         fi
232
233         #run the script on the target
234         $DSH ${USER}${target} "VMSTAT_INTERVAL=${VMSTAT_INTERVAL} \
235                       SDIO_INTERVAL=${SDIO_INTERVAL} \
236                       SERVICE_INTERVAL=${SERVICE_INTERVAL} \
237                       BRW_INTERVAL=${BRW_INTERVAL}         \
238                       JBD_INTERVAL=${JBD_INTERVAL}         \
239                       IO_INTERVAL=${IO_INTERVAL}           \
240                       MBALLOC_INTERVAL=${MBALLOC_INTERVAL} \
241                       sh ${TMP}/${SCRIPT}-${target} start  \
242                       1> /dev/null 2>/dev/null </dev/null"
243
244         if [ ${PIPESTATUS[0]} != 0 ]; then
245                 echo "Start the ${SCRIPT} on ${target} failed"
246                 debug "Leaving start_target_script() (error return)"
247                 return 1
248         fi
249                 
250         echo "Start the ${SCRIPT} on ${target} success"
251         debug "Leaving start_target_script() (normal return)"
252         return 0
253 }
254
255 stop_target_script() {
256         local target=$1
257
258         debug "Entering stop_target_script()"
259     
260         #stop the target script first
261         $DSH ${USER}${target} "sh ${TMP}/${SCRIPT}-${target} stop" 1>/dev/null 2>&1
262         if [ ${PIPESTATUS[0]} != 0 ]; then
263                 echo  "stop the collecting stats script on ${target} failed"
264                 debug "Leaving stop_target_script() (error return)"
265                 return 1 
266         else    
267                 echo  "stop the collecting stats script on ${target} success"
268         fi
269
270         #remove those tmp file
271         $DSH ${USER}${target} "rm -rf $TMP/${SCRIPT}-${target}" 1>/dev/null 2>&1
272         echo "cleanup ${target} tmp file after stop "
273         
274         debug "Leaving stop_target_script() (normal return)"
275         return 0
276 }
277
278 #
279 # create a unique timestamp-based name which we can use for
280 # naming files on all the $TARGET nodes.
281 #
282 # By creating one timestamp here on the master node, we avoid
283 # the problem of clock skew on the $TARGET nodes causing them
284 # to use different filenames than we expect (if their clocks are
285 # different from the clock on this node)
286 #
287 generate_timestamp() {
288         if [ "X${GLOBAL_TIMESTAMP}" = "X" ]
289         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
387 gather_start() {
388         local -a pids_array
389         local -a clients_array
390         local n=0
391         
392         debug "Entering gather_start()"
393         
394         #check whether the collect scripts already start in some targets 
395
396         Nodes_are_not_clean
397         ret=$?
398     
399         if [ $ret -gt 0 ]
400         then
401             warning "$SCRIPT already running in some targets, attempting cleanup..."
402             
403             Clean_nodes
404             
405             Nodes_are_not_clean
406             ret=$?
407             
408             if [ $ret -gt 0 ]
409             then
410                 error "$SCRIPT automatic cleanup attempt failed."
411                 error "$SCRIPT Please make sure lstats is no longer running on target nodes and try again."
412                 debug "Error return from gather_start()"
413                 return 1
414             fi
415         fi
416         
417         for TARGET in $TARGETS; do
418                 (start_target_script ${TARGET}) &
419                 pids_array[$n]=$!
420                 clients_array[$n]=$TARGET
421                 let n=$n+1
422         done
423         
424         local num_pids=$n
425
426         local RC=0      
427         #Waiting log fetch finished
428         for ((n=0; $n < $num_pids; n++)); do
429                 if ! wait ${pids_array[$n]}; then
430                         echo "${clients_array[$n]}: can not start stats collect"
431                         let RC=$RC+1
432                 fi
433         done
434
435         if [ $RC != 0 ]; then
436                 stop_targets_script
437         fi
438         
439         debug "Leaving gather_start()"
440 }
441
442 gather_stop() {
443         log=$1
444
445         debug "Entering gather_stop()"
446     
447         if [ -n "$log" ]; then
448                 fetch_log $log
449         fi
450         
451         stop_targets_script
452         
453         debug "Leaving gather_stop()"
454 }
455
456 get_end_line_num()
457 {
458         local log_name=$1
459
460         ln=`grep -n snapshot_time ${log_name}  | awk -F":" '{ln=$1;} END{print ln;}'`
461         total_ln=`wc ${log_name} | awk '{print $1}'`                    
462
463         local endlen=$((${total_ln} - ${ln}))
464         echo $endlen
465 }
466
467 get_csv()
468 {
469         local logdir=$1
470         local statf=$2
471
472         local statf_name=`basename ${statf}`
473         type_name=`echo ${statf_name} | awk -F "." '{print $3}'`
474         stat_name=`head -n 1 ${statf} | awk '{print $4}'`
475         stat_type=`head -n 1 ${statf} | awk '{print $1}'`
476
477         #currently, it can only analyse client application log
478         if [ "$stat_type" != "client" ]; then
479                 error "can not analyse ${statf} ......."
480                 exit 1
481         fi
482
483         #create the header
484         echo "${node_name}_${type_name}, ${stat_name}" \
485                         >> $logdir/analyse_${type_name}.csv
486
487         #get total stats collection
488         end_len=`get_end_line_num ${statf}`
489         if [ $end_len != 1 -a $end_len != 0 ]; then
490                 if [ "$type_name" != "osc-rpc_stats" ]; then
491                         tail -n $end_len ${statf} | awk '{print $1 "," $2}' \
492                                 >> $logdir/analyse_${type_name}.csv
493                 else
494                         tail -n $end_len ${statf} |                     \
495                         awk  '/^[[:digit:]]/{print $1","$2","$6}        \
496                               /^page/{print "page per rpc,read,write"}  \
497                               /^rpcs/{print "rpcs,read,write"}          \
498                               /^offset/{print "offset, read,write"}'    \
499                         >> $logdir/analyse_${type_name}.csv
500                 fi
501         fi
502 }
503
504 gather_analyse()
505 {
506         local log_tarball=$1
507         local option=$2
508
509         debug "Entering gather_analyze()"
510     
511         #validating option
512         if [ -z "$log_tarball" -o -r "$option" ]; then
513                 usage;
514         fi
515
516         if [ ! -r $log_tarball ]; then
517                 error " not exist $log_tarball "
518                 return 1
519         fi
520
521         shift
522
523         local date=`date +%F-%H-%M`
524         local logdir="analyse-${date}" 
525
526         mkdir -p ${TMP}/${logdir}
527         mkdir -p ${TMP}/${logdir}/tmp
528
529         $UNTAR $log_tarball -C ${TMP}/${logdir}/tmp 1>/dev/null 2>&1
530         for log_file in `find $TMP/$logdir/tmp`; do
531                 if test -f $log_file; then
532                         #get the node name
533                         local file_name=`basename ${log_file}`
534                         node_name=`echo ${file_name} | awk -F "-" '{print $2}'`
535                         echo "analysing the sublog ...$log_file"
536                         mkdir -p ${TMP}/${logdir}/${node_name}
537                         mkdir -p ${TMP}/${logdir}/${node_name}/tmp
538
539                         $UNTAR $log_file -C ${TMP}/${logdir}/${node_name}/tmp 1>/dev/null 2>&1
540                         for statf in `find ${TMP}/${logdir}/${node_name}/tmp`; do
541                                 if test -f $statf ; then
542                                         if [ "$option" == "csv" ]; then
543                                                 get_csv "$TMP/$logdir/${node_name}" "$statf"
544                                         fi
545                                 fi
546                         done
547                         rm -rf ${TMP}/${logdir}/${node_name}/tmp
548                 fi
549         done
550
551         rm -rf ${TMP}/${logdir}/tmp
552         $TAR ${TMP}/${logdir}.tar.gz ${TMP}/${logdir} 1>/dev/null 2>&1
553
554         echo "create analysed tarball ${TMP}/${logdir}.tar.gz"
555         
556         debug "Leaving gather_analyze()"
557 }
558
559 case $OPTION in
560         start) gather_start ;;
561         stop)  gather_stop $@;;
562         analyse) gather_analyse $@;;
563         *) error "Unknown option ${OPTION}" ; exit 1
564 esac