Whamcloud - gitweb
LU-6142 misc: SPDX for lustre-iokit
[fs/lustre-release.git] / lustre-iokit / stats-collect / iokit-lstats
1 #!/bin/bash
2 # SPDX-License-Identifier: GPL-2.0
3
4 #
5 # This file is part of Lustre, http://www.lustre.org/
6 #
7 # very short example:
8 #
9 # to start collection:
10 #   VMSTAT_INTERVAL=0 SERVICE_INTERVAL=2 SDIO_INTERVAL=0 iokit-lstats start
11 #
12 # where value of interval means:
13 #   0 - gather stats at start and stop only
14 #   N - gather stats every N seconds
15 # if some XXX_INTERVAL isn't specified, related stats won't be collected
16 # XXX can be: VMSTAT, SERVICE, BRW, SDIO, MBALLOC, IO
17 #
18 # to stop collection:
19 #   iokit-lstats stop
20 #
21 # to fetch collected stats:
22 #   iokit-lstats fetch >file
23 # in file you'll get a tarbal containing directory with stats
24 # directory's name consists of hostname and date,
25 # like: stats-bzzz-2007-05-13-22.52.31
26 #
27
28 #
29 # TODO
30 #  - close all file descriptors, otherwise sshd can't finish session
31 #  - for sd_iostats convert partition to whole device
32 #
33
34 # configuration variables
35 TMP=${TMP:-/tmp}
36 PREFIX=${PREFIX:-${TMP}/lstats.}
37 PIDFILE=${PREFIX}pid
38 STATPIDS=${PREFIX}pids
39 OUTPREFIX=${OUTPREFIX:-${PREFIX}out.}
40 STIMEPREFIX=${STIMEPREFIX:-${PREFIX}time.}
41
42
43 function ls_grab_control()
44 {
45         OCOMM=$(ps -p $$ -o comm=)
46         if [ "$OCOMM" == "" ]; then
47                 echo "Can't fetch process name"
48                 exit
49         fi
50
51         # check for running master first
52         PID=$(cat $PIDFILE 2>/dev/null)
53 #echo "check master $PID"
54         if [ "x$PID" != "x" ]; then
55                 COMM=$(ps -p $PID -o comm=)
56                 if [ "$COMM" == "$OCOMM" ]; then
57                         echo "Master is already running by $PID"
58                         return 1
59                 fi
60         fi
61
62         # XXX: race -- two process can do this at same time, use rename instead
63         echo $$ >${PIDFILE}.$$
64         mv ${PIDFILE}.$$ ${PIDFILE}
65         a=$(cat ${PIDFILE})
66         if [ "$$" != "$a" ]; then
67                 echo "Some one $a won the race"
68                 return 1
69         fi
70
71         HAS_CONTROL="yes"
72 #echo "We've got control"
73
74         return 0
75
76 }
77
78 function ls_release_control()
79 {
80 #echo "Release control"
81
82         rm -f $PIDFILE
83 }
84
85 trap ls_atexit EXIT
86 function ls_atexit()
87 {
88         if [ "$HAS_CONTROL" != "" ]; then
89                 ls_release_control
90         fi
91 }
92
93
94 function usr1signal()
95 {
96         stop_collector=1
97 }
98
99 function idle_collector()
100 {
101         while [ "$stop_collector" != "1" ]; do
102                 sleep 100;
103         done
104 }
105
106 #
107 # args:
108 # - type
109 # - collector function
110 # - collector arguments
111 function run_collector()
112 {
113         local pid
114         local stime
115         local ctype=$1
116         local cfunc=$2
117         shift
118         shift
119
120         read pid NN </proc/self/stat
121         stime=$(ps -p $pid -o bsdstart=)
122         echo -n "$pid " >>$STATPIDS
123         echo -n "$stime" >>${STIMEPREFIX}${pid}
124
125         trap "usr1signal" SIGUSR1
126
127 #       echo "$pid: new collector $ctype $cfunc"
128         $cfunc $@ </dev/null >&${OUTPREFIX}${ctype}.${pid}
129
130 }
131
132 #
133 # vmstat collector
134 #
135 # VMSTAT_INTERVAL:
136 # - 0       - collect at start and stop only
137 # - N       - collect each N seconds
138 function vmstat_collector()
139 {
140         echo "vmstat " $(date)
141
142         if let "VMSTAT_INTERVAL==0"; then
143                 date
144                 vmstat
145                 idle_collector
146                 date
147                 vmstat
148         elif let "VMSTAT_INTERVAL>0"; then
149                 vmstat $VMSTAT_INTERVAL
150         else
151                 echo "Invalid VMSTAT_INTERVAL=$VMSTAT_INTERVAL"
152                 idle_collector
153         fi
154 }
155
156 function vmstat_start()
157 {
158         if [ "$VMSTAT_INTERVAL" == "" ]; then
159                 return;
160         fi
161
162         run_collector "vmstat" vmstat_collector &
163 }
164
165 #
166 # brw_stats collector
167 #
168 # BRW_INVERVAL:
169 # - 0 - collect at start and stop only
170 # - N - collect each N seconds
171 #
172 function brw_collector()
173 {
174         local filter=$1
175
176         echo "brw_* for $filter " $(date)
177
178         # clear old stats
179         lctl set_param -n obdfilter.${filter}.brw_*=0
180
181         if let "BRW_INTERVAL==0"; then
182                 lctl get_param -n obdfilter.${filter}.brw_*
183                 idle_collector
184                 lctl get_param -n obdfilter.${filter}.brw_*
185         elif let "BRW_INTERVAL>0"; then
186                 while [ "$stop_collector" != "1" ]; do
187                         lctl get_param -n obdfilter.${filter}.brw_*
188                         sleep $BRW_INTERVAL
189                 done
190         else
191                 echo "Invalid BRW_INTERVAL=$BRW_INTERVAL"
192                 idle_collector
193         fi
194 }
195
196 function brw_start()
197 {
198         if [ "$BRW_INTERVAL" == "" ]; then
199                 return;
200         fi
201
202         # find all obdfilters
203         for i in $(lctl list_param obdfilter.*); do
204                 filter=$(echo "$i" | awk -F"." '{print $2}')
205                 if [ "$filter" == "num_refs" ]; then
206                         continue;
207                 fi
208                 run_collector "brw" brw_collector $filter &
209         done
210 }
211
212 #
213 # service_stats collector
214 #
215 # SERVICE_INVERVAL:
216 # - 0 - collect at start and stop only
217 # - N - collect each N seconds
218 #
219 function service_collector()
220 {
221         local file=$1
222         local target=$2
223         local srv=$3
224
225         echo "service stats for ${target}/${srv} " $(date)
226
227         # clear old stats
228         lctl set_param -n $file=0
229
230         if let "SERVICE_INTERVAL==0"; then
231                 lctl get_param -n $file | grep -v "^[^ ]*[^0-9]*0 samples"
232                 idle_collector
233                 lctl get_param -n $file | grep -v "^[^ ]*[^0-9]*0 samples"
234         elif let "SERVICE_INTERVAL>0"; then
235                 while [ "$stop_collector" != "1" ]; do
236                         lctl get_param -n $file | grep -v "^[^ ]*[^0-9]*0 samples"
237                         sleep $SERVICE_INTERVAL
238                 done
239         else
240                 echo "Invalid SERVICE_INTERVAL=$SERVICE_INTERVAL"
241                 idle_collector
242         fi
243 }
244
245 function service_start()
246 {
247         if [ "$SERVICE_INTERVAL" == "" ]; then
248                 return;
249         fi
250
251         # find all OSTs and MDTs
252         for i in $(lctl list_param ost.* mdt.*); do
253                 target=$(echo "$i" | awk -F"." '{print $2}')
254                 if [ "$target" == "num_refs" ]; then
255                         continue;
256                 fi
257                 for j in $(lctl list_param ${i}.*); do
258                         srv=$(echo "$j" | awk -F"." '{print $3}')
259                         if [ "$srv" == "uuid" ]; then
260                                 continue;
261                         fi
262                         run_collector "service-${srv}" service_collector \
263                                 ${j}.stats $target $srv &
264                 done
265         done
266
267         # find all LDLM services
268         for i in $(lctl list_param ldlm.services.*); do
269                 srv=$(echo "$i" | awk -F"." '{print $3}')
270                 run_collector "service" service_collector ${i}.stats "ldlm" $srv &
271         done
272
273 }
274
275 #
276 # client_stats collector
277 #
278 # CLIENT_INTERVAL:
279 # - 0 - collect at start and stop only
280 # - N - collect each N seconds
281 #
282 function client_collector()
283 {
284         local file=$1
285         local target=$2
286         local srv=$3
287
288         echo "client stats for ${target}/${srv} " $(date)
289
290         # clear old stats
291         echo 0 >$file
292
293         if let "CLIENT_INTERVAL==0"; then
294                 grep -v "^[^ ]*[^0-9]*0 samples" $file
295                 idle_collector
296                 grep -v "^[^ ]*[^0-9]*0 samples" $file
297         elif let "CLIENT_INTERVAL>0"; then
298                 while [ "$stop_collector" != "1" ]; do
299                         grep -v "^[^ ]*[^0-9]*0 samples" $file
300                         sleep $CLIENT_INTERVAL
301                 done
302         else
303                 echo "Invalid CLIENT_INTERVAL=$CLIENT_INTERVAL"
304                 idle_collector
305         fi
306 }
307
308 function client_start()
309 {
310         if [ "$CLIENT_INTERVAL" == "" ]; then
311                 return;
312         fi
313
314         # find all osc
315         for i in $(lctl list_param osc.*); do
316                 target=$(echo "$i" | awk -F"." '{print $2}')
317                 if [ "$target" == "num_refs" ]; then
318                         continue;
319                 fi
320                 i=$(echo "$i" |awk '{gsub(/\./,"/");print}')
321                 for j in ${i}/*; do
322                         local stats=$(basename $j)
323                         if [ "$stats" == "stats" -o "$stats" == "rpc_stats" ]; then
324                                 run_collector "osc-${stats}" client_collector \
325                                         ${j} $target $stats &
326                         fi
327                 done
328         done
329         # find all llite stats
330         for i in $(lctl list_param llite.*); do
331                 target=$(echo "$i" | awk -F"." '{print $2}')
332                 i=$(echo "$i" |awk '{gsub(/\./,"/");print}')
333                 for j in ${i}/*; do
334                         stats=$(basename $j)
335                         if [ "$stats" == "stats" -o "$stats" == "vfs_ops_stats" ]; then
336                                 run_collector "llite-${stats}" client_collector \
337                                         ${j} $target ${stats} &
338                         fi
339                 done
340         done
341 }
342
343 #
344 # sdio_stats collector
345 #
346 # SDIO_INVERVAL:
347 # - 0 - collect at start and stop only
348 # - N - collect each N seconds
349 #
350 function sdio_collector()
351 {
352         local obd=$1
353         local uuid=$(lctl get_param -n obd.uuid 2>&1)
354         local tmp=$(lctl get_param -n obd.mntdev 2>&1)
355         local disk=$(basename $tmp)
356         local file="/proc/scsi/sd_iostats/${disk}"
357
358         echo "sd_iostats for ${uuid}/${disk} " $(date)
359
360         # clear old stats
361         echo 0 >$file
362
363         if let "SDIO_INTERVAL==0"; then
364                 cat $file
365                 idle_collector
366                 cat $file
367         elif let "SDIO_INTERVAL>0"; then
368                 while [ "$stop_collector" != "1" ]; do
369                         cat $file
370                         sleep $SDIO_INTERVAL
371                 done
372         else
373                 echo "Invalid SDIO_INTERVAL=$SDIO_INTERVAL"
374                 idle_collector
375         fi
376 }
377
378 function sdio_start()
379 {
380         if [ "$SDIO_INTERVAL" == "" ]; then
381                 return;
382         fi
383
384         # find all obdfilters and MDSs
385         for i in $(lctl list_param obdfilter.* mds.*); do
386                 obd=$(echo "$i" | awk -F"." '{print $2}')
387                 if [ "$obd" == "num_refs" ]; then
388                         continue;
389                 fi
390                 tmp=$(lctl get_param -n ${i}.mntdev 2>&1)
391                 if [ $? != 0 ]; then
392                         continue;
393                 fi
394                 local disk=$(basename $tmp)
395                 if [ ! -f /proc/scsi/sd_iostats/${disk} ]; then
396                         continue;
397                 fi
398                 run_collector "sdio" sdio_collector ${i} &
399         done
400 }
401
402 #
403 # mballoc_stats collector
404 #
405 # MBALLOC_INVERVAL:
406 # - 0 - collect at start and stop only
407 # - N - isn't implemented yet, works as with 0
408 #
409 function mballoc_collector()
410 {
411         local obd=$1
412         local uuid=$(lctl get_param -n obd.uuid 2>&1)
413         local tmp=$(lctl get_param -n obd.mntdev 2>&1)
414         local disk=$(basename $tmp)
415         local file="/proc/fs/ldiskfs*/${disk}/mb_history"
416
417         echo "mballoc history for ${uuid}/${disk} " $(date)
418
419         # log allocations only
420         for i in $file; do
421                 echo 3 >$i
422         done
423
424         if let "MBALLOC_INTERVAL==0"; then
425                 idle_collector
426                 cat $file
427         elif let "MBALLOC_INTERVAL>0"; then
428                 idle_collector
429                 cat $file
430         else
431                 echo "Invalid MBALLOC_INTERVAL=$MBALLOC_INTERVAL"
432                 idle_collector
433         fi
434 }
435
436 function mballoc_start()
437 {
438         if [ "$MBALLOC_INTERVAL" == "" ]; then
439                 return;
440         fi
441
442         # find all obdfilters and MDSs
443         for i in $(lctl list_param obdfilter.* mds.*); do
444                 obd=$(echo "$i" | awk -F"." '{print $2}')
445                 if [ "$obd" == "num_refs" ]; then
446                         continue;
447                 fi
448                 tmp=$(lctl get_param -n ${i}.mntdev 2>&1)
449                 if [ $? != 0 ]; then
450                         continue;
451                 fi
452                 disk=$(basename $tmp)
453                 if [ ! -f /proc/fs/ldiskfs*/${disk}/mb_history ]; then
454                         continue;
455                 fi
456                 run_collector "mballoc" mballoc_collector ${i} &
457         done
458 }
459
460 #
461 # io_stats collector
462 #
463 # IO_INVERVAL:
464 # - 0 - collect at start and stop only
465 # - N - collect each N seconds
466 #
467 function io_collector()
468 {
469         local obd=$1
470         local uuid=$(lctl get_param -n obd.uuid 2>&1)
471         local tmp=$(lctl get_param -n obd.mntdev 2>&1)
472         local disk=$(basename $tmp)
473         local file="/sys/block/${disk}/stat"
474
475         echo "iostats for ${uuid}/${disk} " $(date)
476
477         if let "IO_INTERVAL==0"; then
478                 cat $file
479                 idle_collector
480                 cat $file
481         elif let "IO_INTERVAL>0"; then
482                 while [ "$stop_collector" != "1" ]; do
483                         cat $file
484                         sleep $IO_INTERVAL
485                 done
486         else
487                 echo "Invalid IO_INTERVAL=$IO_INTERVAL"
488                 idle_collector
489         fi
490 }
491
492 function io_start()
493 {
494         if [ "$IO_INTERVAL" == "" ]; then
495                 return;
496         fi
497
498         # find all obdfilters and MDSs
499         for i in $(lctl list_param obdfilter.* mds.*); do
500                 obd=$(echo "$i" | awk -F"." '{print $2}')
501                 if [ "$obd" == "num_refs" ]; then
502                         continue;
503                 fi
504                 local tmp=$(lctl get_param -n ${i}.mntdev 2>&1)
505                 if [ $? != 0 ]; then
506                         continue;
507                 fi
508                 local disk=$(basename $tmp)
509                 if [ ! -f /sys/block/${disk}/stat ]; then
510                         continue;
511                 fi
512                 run_collector "io" io_collector ${i} &
513         done
514 }
515
516 #
517 # start entry point
518 #
519 function ls_start()
520 {
521         if ! ls_grab_control; then
522                 exit
523         fi
524
525         local PID=$(cat $STATPIDS 2>/dev/null)
526         if [ "x$PID" != "x" ]; then
527                 for i in $PID; do
528                         local i=$(echo $i | sed 's/^[^:]*://')
529                         local TO=$(cat ${STIMEPREFIX}$i)
530                         local TN=$(ps -p $i -o bsdstart=)
531                         if [ "$TO" != "" -a "$TO" == "$TN" ]; then
532                                 echo "Some slave is already running by $i"
533                                 exit
534                         fi
535                 done
536         fi
537
538         # clean all all stuff
539         rm -rf ${STATPIDS}* ${OUTPREFIX}* ${STIMEPREFIX}
540
541         vmstat_start
542         brw_start
543         service_start
544         sdio_start
545         mballoc_start
546         io_start
547         client_start
548 }
549
550 #
551 # stop entry point
552 #
553 # should stop collection, gather all collected data
554 #
555 function ls_stop()
556 {
557         if ! ls_grab_control; then
558                 exit
559         fi
560
561         local PID=$(cat $STATPIDS 2>/dev/null)
562         if [ "x$PID" != "x" ]; then
563                 local pids2wait=""
564                 for i in $PID; do
565                         local i=$(echo $i | sed 's/^[^:]*://')
566                         local TO=$(cat ${STIMEPREFIX}$i 2>/dev/null)
567                         local TN=$(ps -p $i -o bsdstart=)
568                         if [ "$TO" == "" -o "$TO" != "$TN" ]; then
569                                 echo "No collector with $i found"
570                                 continue
571                         fi
572                         /bin/kill -s USR1 -- -${i}
573                         pids2wait="$pids2wait $i"
574                 done
575 #echo "XXX: wait collectors $pids2wait"
576                 for i in $pids2wait; do
577                         TO=$(cat ${STIMEPREFIX}$i 2>/dev/null)
578                         TN=$(ps -p $i -o bsdstart=)
579                         while [ "$TO" != "" -a "$TO" == "$TN" ]; do
580                                 sleep 1
581                                 TN=$(ps -p $i -o bsdstart=)
582                         done
583                 done
584         fi
585         rm -f $STATPIDS ${STIMEPREFIX}*
586 }
587
588 #
589 # fetch entry point
590 #
591 # creates tarball of all collected stats
592 # current version is silly - just finds all *out* files in $TMP
593 ls_fetch()
594 {
595         if [ "X${GLOBAL_TIMESTAMP}" = "X" ]; then
596                 local date=$(date +%F-%H.%M.%S)
597         else
598                 date=${GLOBAL_TIMESTAMP}
599         fi
600
601         local hostname=$(hostname -s)
602         local name="stats-$hostname-$date"
603
604         stats=${OUTPREFIX}*
605         if ! mkdir ${TMP}/${name}; then
606                 echo "Can't create ${TMP}/${name}"
607                 exit
608         fi
609
610         let found=0
611         for i in ${OUTPREFIX}*; do
612                 mv $i ${TMP}/${name}/
613                 let "found++"
614         done
615
616         if let "found > 0"; then
617                 (cd ${TMP}; tar -zcf "./${name}.tar.gz" "./${name}")
618                 cat ${TMP}/${name}.tar.gz
619         else
620                 echo "No stats found"
621         fi
622         rm -rf ${TMP}/${name}*
623 }
624
625 #
626 # abort entry point
627 #
628 # should kill all running collections
629 #
630 function ls_abort()
631 {
632         echo "Abort isn't implemented yet"
633 }
634
635 #########
636 #  main
637 #########
638
639 # required to put all background processes into different process groups
640 # so that we can manage whole groups sending them a single signal
641 set -m
642
643 case $1 in
644         start) ls_start ;;
645         stop)  ls_stop ;;
646         fetch) ls_fetch ;;
647         abort)  ls_abort ;;
648         *) echo "Unknown command"
649 esac
650