Whamcloud - gitweb
b=12421
[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 (c) 2007 - Cluster File Systems, Inc.
9 #########################################################################
10
11 error() {
12         echo "ERROR: $0: $@"
13 }
14
15 warning() {
16         echo "WARNING: $@"
17 }
18
19 info () {
20         if [ ${PRINT_INFO_MSGS} -gt 0 ]
21         then
22                 echo "INFO: $@"
23         fi
24 }
25
26 debug () {
27         if [ ${PRINT_DEBUG_MSGS} -gt 0 ]
28         then
29                 echo "DEBUG: $@"
30         fi
31 }
32
33 usage() {
34         printf $"Usage: gather_stats_everywhere [-help] config_file [start|stop|cleanup] <log_name>\n"
35         if [ x$1 = x-h ]
36         then
37                  printf $"
38 The distribution script will run on a single node.  It is parameterised 
39 with a set of target node names.  It may assume ssh/scp to these node 
40 names works without requiring a password.  It will run in 2 modes...
41
42 gather_stats_everywhere config_file start
43
44 ...will copy the script to /tmp everywhere described in
45 config_file running on all the target hosts.  And...
46
47 gather_stats_everywhere config_file stop log_name
48
49 ...will stop script running on all the hosts it started on and collect 
50 all the individual stats files into a single compressed tarball if the log_name is
51 provided.
52
53 The config file is just a list of shell variable assignments that can be
54 customised. 
55
56 Serveral variables must be set in the config file
57
58 Targets: the nodes where run the script.
59 "
60                  exit 0
61         else
62                  exit 1
63         fi
64 }
65
66 options=`getopt -o h --long help:: -- "$@"`
67
68 if [ $? -ne 0 ]
69 then 
70         usage
71 fi
72
73 eval set -- "$options"
74
75 while true
76 do
77         case "$1" in
78                 -h)
79                         usage -h ;;
80                 --help)
81                         usage -h ;;
82                 --)
83                         shift
84                         break ;;
85         esac
86 done
87
88 if [ $# != 2 -a $# != 3 ] ; then
89         usage
90 fi
91
92 CONFIG=$1
93 OPTION=$2
94 shift
95 shift
96
97 GLOBAL_TIMESTAMP=""
98
99 if [ ! -r $CONFIG ]; then
100         error "Config_file: $CONFIG does not exist "
101         exit 1
102 fi
103
104 . $CONFIG
105
106 if [ -z "$SCRIPT" ]; then
107         error "SCRIPT in ${CONFIG} is empty"
108         exit 1
109 fi      
110
111 if [ -z "$TARGETS" ]; then
112         error "TARGETS in ${CONFIG} is empty"
113         exit 1
114 fi
115
116 #check nodes accessiable 
117 Check_nodes_available() {
118         local NODES_NOT_AVAILABLE=""
119
120         debug "Entering Check_nodes_available()"
121     
122         for TARGET in $TARGETS; do
123                 if ! ping -c 1 -w 3 $TARGET > /dev/null; then 
124                         NODES_NOT_AVAILABLE=$NODES_NOT_AVAILABLE$TARGET
125                 fi
126         done
127     
128         if [ -z "$NODES_NOT_AVAILABLE" ]; then
129                 debug "Check_nodes_available() returning 0 (success - all nodes available)"
130                 return 0
131         fi
132
133         error "Check_nodes_available: these nodes are not available (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 ]
178         then
179                 for TARGET in $TARGETS; do
180                         debug "List of processes which need to be cleaned up on ${TARGET}:"
181                         $DSH $TARGET "ps aux | grep -v grep | grep ${SCRIPT}-${TARGET}"
182                         debug "List of pids which need to be cleaned up on ${TARGET}:"
183                         $DSH $TARGET "ps aux | grep ${SCRIPT}-${TARGET} | grep -v grep | ${AWK} '{ print \$2 }'"
184                 done
185         fi
186     
187         #
188         # do the actual cleanup
189         # kill any old lstats processes still running on the target nodes
190         #
191         for TARGET in $TARGETS; do
192             
193                 ps_str=`$DSH $TARGET "ps aux | grep -v grep | grep ${SCRIPT}-${TARGET}"`
194                 if [ -n "$ps_str" ]; then
195                         debug "cleaning node ${TARGET}"
196                         $DSH $TARGET "ps aux | grep ${SCRIPT}-${TARGET} | grep -v grep | ${AWK} '{ print \$2 }' | ${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" ]
290         then
291                 export GLOBAL_TIMESTAMP=`date +%F-%H.%M.%S`
292                 debug "Global Timestamp Created: ${GLOBAL_TIMESTAMP}"
293         fi
294 }
295
296 fetch_target_log() {
297         generate_timestamp
298         local target=$1
299         local date=${GLOBAL_TIMESTAMP}
300         local target_log_name="stats-${target}-${date}"
301
302         echo "Getting log: ${target_log_name}.tar.gz from ${target}"
303         $DSH ${USER}${target} "sh ${TMP}/${SCRIPT}-${target} fetch " \
304                       > $TMP/${target_log_name}.tar.gz
305         echo "Got log: ${target_log_name}.tar.gz from ${target}"
306
307         echo "Moving $TMP/${target_log_name}.tar.gz to $TMP/$log_name"
308         mv $TMP/${target_log_name}.tar.gz $TMP/$log_name
309 }
310
311 fetch_log() {
312         generate_timestamp
313         local log_name=${GLOBAL_TIMESTAMP}
314         local stat_tar_name=$1
315         local -a pids_array
316         local -a clients_array
317
318         debug "Entering fetch_log()"
319     
320         if ! mkdir -p $TMP/$log_name ; then
321                 error "can not mkdir $log_name"
322                 exit 1
323         fi
324
325         #retrive the log_tarball from remote nodes background 
326         local n=0
327         for TARGET in $TARGETS; do
328                 (fetch_target_log ${TARGET}) & 
329                 pids_array[$n]=$!
330                 clients_array[$n]=$TARGET
331                 
332                 debug "fetch_log: spawned fetch_target_log process for ${TARGET} pid ${pids_array[$n]}"
333                 let n=$n+1
334         done
335         
336         local num_pids=$n
337
338         #Waiting log fetch finished
339         for ((n=0; $n < $num_pids; n++)); do
340                 debug "fetch_log(): waiting for pid ${pids_array[$n]}"
341                 wait ${pids_array[$n]}
342                 
343                 #
344                 # TODO: add check of exit status from wait()
345                 #
346         done
347
348         #compress the log tarball
349         cmd="$TAR ${stat_tar_name} $TMP/${log_name}"
350         echo "Creating compressed tar file ${stat_tar_name} from log files in  $TMP/${log_name}"
351         ${cmd} 1>/dev/null 2>&1 
352         if [ ${PIPESTATUS[0]} == 0 ]; then
353                 echo "removing temporary directory $TMP/${log_name}"
354                 rm -rf $TMP/${log_name}
355         else
356                 echo "Compressed logfiles are in $TMP/${stat_tar_name}"
357         fi
358         
359         debug "Leaving fetch_log()"
360 }
361
362 stop_targets_script() {
363         local -a pids_array
364         local -a clients_array
365         local n=0
366         
367         debug "Entering stop_targets_script()"
368         
369         for TARGET in $TARGETS; do
370                 (stop_target_script ${TARGET}) &
371                 pids_array[$n]=$!
372                 clients_array[$n]=$TARGET
373                 let n=$n+1
374         done
375         local num_pids=$n
376         
377         #Waiting log fetch finished
378         for ((n=0; $n < $num_pids; n++)); do
379                 if ! wait ${pids_array[$n]}; then
380                         echo "${clients_array[$n]}: can not stop stats collect"
381                 fi
382         done
383         
384         debug "Leaving stop_targets_script()"
385         
386 }
387
388 gather_start() {
389         local -a pids_array
390         local -a clients_array
391         local n=0
392         
393         debug "Entering gather_start()"
394         
395         #check whether the collect scripts already start in some targets 
396
397         Nodes_are_not_clean
398         ret=$?
399     
400         if [ $ret -gt 0 ]
401         then
402             warning "$SCRIPT already running in some targets, attempting cleanup..."
403             
404             Clean_nodes
405             
406             Nodes_are_not_clean
407             ret=$?
408             
409             if [ $ret -gt 0 ]
410             then
411                 error "$SCRIPT automatic cleanup attempt failed."
412                 error "$SCRIPT Please make sure lstats is no longer running on target nodes and try again."
413                 debug "Error return from gather_start()"
414                 return 1
415             fi
416         fi
417         
418         for TARGET in $TARGETS; do
419                 (start_target_script ${TARGET}) &
420                 pids_array[$n]=$!
421                 clients_array[$n]=$TARGET
422                 let n=$n+1
423         done
424         
425         local num_pids=$n
426
427         local RC=0      
428         #Waiting log fetch finished
429         for ((n=0; $n < $num_pids; n++)); do
430                 if ! wait ${pids_array[$n]}; then
431                         echo "${clients_array[$n]}: can not start stats collect"
432                         let RC=$RC+1
433                 fi
434         done
435
436         if [ $RC != 0 ]; then
437                 stop_targets_script
438         fi
439         
440         debug "Leaving gather_start()"
441 }
442
443 gather_stop() {
444         log=$1
445
446         debug "Entering gather_stop()"
447     
448         if [ -n "$log" ]; then
449                 fetch_log $log
450         fi
451         
452         stop_targets_script
453         
454         debug "Leaving gather_stop()"
455 }
456
457 get_end_line_num()
458 {
459         local log_name=$1
460
461         ln=`grep -n snapshot_time ${log_name}  | awk -F":" '{ln=$1;} END{print ln;}'`
462         total_ln=`wc ${log_name} | awk '{print $1}'`                    
463
464         local endlen=$((${total_ln} - ${ln}))
465         echo $endlen
466 }
467
468 get_csv()
469 {
470         local logdir=$1
471         local statf=$2
472
473         local statf_name=`basename ${statf}`
474         type_name=`echo ${statf_name} | awk -F "." '{print $3}'`
475         stat_name=`head -n 1 ${statf} | awk '{print $4}'`
476         stat_type=`head -n 1 ${statf} | awk '{print $1}'`
477
478         #currently, it can only analyse client application log
479         if [ "$stat_type" != "client" ]; then
480                 error "can not analyse ${statf} ......."
481                 exit 1
482         fi
483
484         #create the header
485         echo "${node_name}_${type_name}, ${stat_name}" \
486                         >> $logdir/analyse_${type_name}.csv
487
488         #get total stats collection
489         end_len=`get_end_line_num ${statf}`
490         if [ $end_len != 1 -a $end_len != 0 ]; then
491                 if [ "$type_name" != "osc-rpc_stats" ]; then
492                         tail -n $end_len ${statf} | awk '{print $1 "," $2}' \
493                                 >> $logdir/analyse_${type_name}.csv
494                 else
495                         tail -n $end_len ${statf} |                     \
496                         awk  '/^[[:digit:]]/{print $1","$2","$6}        \
497                               /^page/{print "page per rpc,read,write"}  \
498                               /^rpcs/{print "rpcs,read,write"}          \
499                               /^offset/{print "offset, read,write"}'    \
500                         >> $logdir/analyse_${type_name}.csv
501                 fi
502         fi
503 }
504
505 gather_analyse()
506 {
507         local log_tarball=$1
508         local option=$2
509
510         debug "Entering gather_analyze()"
511     
512         #validating option
513         if [ -z "$log_tarball" -o -r "$option" ]; then
514                 usage;
515         fi
516
517         if [ ! -r $log_tarball ]; then
518                 error " not exist $log_tarball "
519                 return 1
520         fi
521
522         shift
523
524         local date=`date +%F-%H-%M`
525         local logdir="analyse-${date}" 
526
527         mkdir -p ${TMP}/${logdir}
528         mkdir -p ${TMP}/${logdir}/tmp
529
530         $UNTAR $log_tarball -C ${TMP}/${logdir}/tmp 1>/dev/null 2>&1
531         for log_file in `find $TMP/$logdir/tmp`; do
532                 if test -f $log_file; then
533                         #get the node name
534                         local file_name=`basename ${log_file}`
535                         node_name=`echo ${file_name} | awk -F "-" '{print $2}'`
536                         echo "analysing the sublog ...$log_file"
537                         mkdir -p ${TMP}/${logdir}/${node_name}
538                         mkdir -p ${TMP}/${logdir}/${node_name}/tmp
539
540                         $UNTAR $log_file -C ${TMP}/${logdir}/${node_name}/tmp 1>/dev/null 2>&1
541                         for statf in `find ${TMP}/${logdir}/${node_name}/tmp`; do
542                                 if test -f $statf ; then
543                                         if [ "$option" == "csv" ]; then
544                                                 get_csv "$TMP/$logdir/${node_name}" "$statf"
545                                         fi
546                                 fi
547                         done
548                         rm -rf ${TMP}/${logdir}/${node_name}/tmp
549                 fi
550         done
551
552         rm -rf ${TMP}/${logdir}/tmp
553         $TAR ${TMP}/${logdir}.tar.gz ${TMP}/${logdir} 1>/dev/null 2>&1
554
555         echo "create analysed tarball ${TMP}/${logdir}.tar.gz"
556         
557         debug "Leaving gather_analyze()"
558 }
559
560 case $OPTION in
561         start) gather_start ;;
562         stop)  gather_stop $@;;
563         analyse) gather_analyse $@;;
564         *) error "Unknown option ${OPTION}" ; exit 1
565 esac