Whamcloud - gitweb
LU-6349 ptlrpc: remove LUSTRE_MSG_MAGIC_V1 support
[fs/lustre-release.git] / lustre / utils / ltrack_stats.c
1 /*
2  * GPL HEADER START
3  *
4  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
5  *
6  * This program is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License version 2 only,
8  * as published by the Free Software Foundation.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
13  * General Public License version 2 for more details (a copy is included
14  * in the LICENSE file that accompanied this code).
15  *
16  * You should have received a copy of the GNU General Public License
17  * version 2 along with this program; If not, see
18  * http://www.sun.com/software/products/lustre/docs/GPLv2.pdf
19  *
20  * Please contact Sun Microsystems, Inc., 4150 Network Circle, Santa Clara,
21  * CA 95054 USA or visit www.sun.com if you need additional information or
22  * have any questions.
23  *
24  * GPL HEADER END
25  */
26 /*
27  * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
28  * Use is subject to license terms.
29  *
30  * Copyright (c) 2014, Intel Corporation.
31  */
32 /*
33  * This file is part of Lustre, http://www.lustre.org/
34  * Lustre is a trademark of Sun Microsystems, Inc.
35  *
36  * lustre/utils/ltrack_stats.c
37  *
38  * Author: Milind Dumbare <milind@clusterfs.com>
39  */
40
41 #include <sys/types.h>
42 #include <sys/wait.h>
43 #include <getopt.h>
44 #include <stdio.h>
45 #include <unistd.h>
46 #include <glob.h>
47 #include <signal.h>
48 #include <string.h>
49 #include <stdlib.h>
50 #include <errno.h>
51
52 #define TRACK_BY_GID 0
53 #define TRACK_BY_PPID 1
54 #define TRACK_BY_PID 2
55 #define TRACK_FOR_ALL 3
56
57 /* We can have at the most 1024 llstats monitoring 1024 lustre clients
58  * at a time */
59 #define NO_OF_CLIENTS 1024
60
61 /* length of absolute path of vfs_ops_stats */
62 #define LEN_STATS 1024
63
64 /* Length of llstat command with all its switches and command line options */
65 #define LEN_LLSTAT (25 + LEN_STATS)
66
67 /* strlen of each lustre client entry in /proc/fs/lustre/llite/ */
68 #define LEN_CLIENT 1024
69
70 /* size of output of llstat command we read at a time */
71 #define LLSTAT_READ_SIZE 1024
72
73 /* Length of command given on command line */
74 #define COMM_LEN 4096
75
76 /* print usage */
77 void print_usage()
78 {
79         printf("Usage:\n\n");
80         printf("ltrack_stats runs command given and does one of the "
81                 "following:\n"
82                 "\t1. Writes its pid to "
83                 "/proc/fs/lustre/llite/.../stats_track_pid\n"
84                 " to collects stats for that process.\n"
85                 "\t2. Writes its ppid to "
86                 "/proc/fs/lustre/llite/.../stats_track_ppid\n"
87                 " to collect stats of that process and all its children \n"
88                 "\t3. Sets gid of process to some random gid (444) and also\n"
89                 " writes that to/proc/fs/lustre/llite/.../stats_track_gid to"
90                 " collect stats \nof all processes in that group\n\n"
91                 " It also uses llstat to generate output with interval of 1 "
92                 " second and duration\n of run of command for plot-llstat to "
93                 "generate a graph\n\n");
94         printf("ltrack_stats [-l filename] [-g <gid> | -a | -i | -c | -h ]\n"
95                "\t<command with arguments ...>\n\n");
96         printf("-l: outputs the llstat.pl's output to given <filename>"
97                "with interval of 1 second \nbetween each output and flag for"
98                "graphable output. If -l flag is not given llstat \nwont be"
99                "executed\n\n");
100
101         printf("-g: for displaying VFS operation statistics collected"
102                "for all processes having \ngroup id as given <gid> \n\n");
103
104         printf("-a: for displaying VFS operations statistics collected"
105                "for all processes\n\n");
106
107         printf("-i: for displaying VFS operation statistics collected"
108                "for only given <command's> \nPID.\n\n");
109
110         printf("-c: for displaying VFS operation statistics collected"
111                " for all processes whose \nparents' PID is same as pid of "
112                "<command> to be executed\n\n");
113
114         printf("-h: for showing this help\n");
115 }
116
117 /* - type: to which file data should be written to track VFS operations
118  * statistics
119  * - id: we either need to write gid which is given on command line or pid
120  * to collect statistics of that process or its children. */
121
122 void write_track_xid(int type, unsigned short id, char* stats_path)
123 {
124         FILE *fp;
125
126         /* for loop is used if we have more than one lustre clients on same
127          * machine. glob() return /proc entry for each lustre client */
128
129         switch(type) {
130                 case TRACK_BY_GID:
131                         strcat(stats_path, "/stats_track_gid");
132                         break;
133                 case TRACK_BY_PPID:
134                         strcat(stats_path, "/stats_track_ppid");
135                         break;
136                 case TRACK_BY_PID:
137                         strcat(stats_path, "/stats_track_pid");
138                         break;
139         }
140
141         fp = fopen(stats_path, "w+");
142         if (!fp) {
143                 fprintf(stderr, "Error: Couldn't open /proc entry file: %s\n",
144                         stats_path);
145                 exit(1);
146         }
147         if (fprintf(fp, "%d", id) < 0) {
148                 fprintf(stderr, "Error: Couldn't write id to tracking /proc"
149                         "entry file: %s\n", stats_path);
150                 exit(1);
151         }
152         if (fclose(fp)) {
153                 fprintf(stderr, "Error: Couldn't close tracking /proc entry"
154                         " file: %s\n", stats_path);
155                 exit(1);
156         }
157 }
158
159 /* Get extra command lines concatenated to "command Function getopt scans
160  *  one switch and its optional argument. So if command line is 
161  * "-g 1234 make bzImage" 1234 is optarg and "make bzImage" will be collected
162  *  with following function to exec it. */
163 char* get_command_from_argv(int optind, int argc, char** argv,char* optarg,
164                             char* command)
165 {
166         int index = 0;
167
168         strcpy(command, optarg);
169         strcat(command, " ");
170         for (index = optind; index < argc; index++) {
171                 strcat(command, argv[index]);
172                 strcat(command, " ");
173         }
174         if (strlen(command) == 1) {
175                 fprintf(stderr,"Error: command missing \n");
176                 print_usage();
177                 exit(1);
178         } else if (strlen(command) > COMM_LEN) {
179                 fprintf(stderr,"Error: Too long command \n");
180                 print_usage();
181                 exit(1);
182         }
183
184         return command;
185 }
186
187 /* Check for the llstat command in $PATH env variable. */
188 void check_llstat()
189 {
190         int status;
191
192         status = system("which llstat.pl &> /dev/null");
193         if (status) {
194                 fprintf(stderr,"Error: llstat.pl not found in PATH\n");
195                 exit(1);
196         }
197 }
198
199 pid_t fork_llstat_command(char* llstat_file,char* stats_path)
200 {
201         char truncate_command[100];
202         char llstat_command[LEN_LLSTAT];
203         pid_t pid_llstat_command;
204         FILE *fp_popen, *fp_out;
205         char buffer[LLSTAT_READ_SIZE];
206         int ret;
207         
208         /* Truncating llstat output file as it will be opened in while
209          * loop to append output */
210         sprintf(truncate_command,"> %s",llstat_file);
211         if ((ret = system(truncate_command)) != 0) {
212                 ret = WEXITSTATUS(ret);
213                 printf("error excuting truncate command: %d\n", ret);
214                 exit(ret);
215         }
216
217         strcat(stats_path, "/stats");
218
219         /* creating a string of llstat command to give to
220          * popen */
221         sprintf(llstat_command, "llstat -i 1 -g %s ",
222                 stats_path);
223
224         /* fork for llstat */
225         if ((pid_llstat_command = fork()) < 0)
226                 fprintf(stderr, "Error: Fork error\n");
227
228         /* in child (llstat) */
229         if (pid_llstat_command == 0) {
230                 /* Doing popen for llstat command */
231                 fp_popen = popen(llstat_command, "r");
232                 if (fp_popen == NULL) {
233                         fprintf(stderr,"Couldn't popen the llstat command:"
234                                 "\"%s\"n", llstat_command);
235                         exit(1);
236                 }
237                 while (fgets(buffer, LLSTAT_READ_SIZE, fp_popen) != NULL) {
238                         /* Following code should be in while loop as llstat
239                          * will keep on sending output each second and will
240                          * not exit on itself. It will be killed when we finsh
241                          * with our command so we must make the output file
242                          * consistent after writing each 1024 bytes chunk */
243
244                         /* opening file where llstat will write its output */
245                         fp_out = fopen(llstat_file, "a");
246                         if (!fp_out) {
247                                 fprintf(stderr, "Error: Couldn't open llstat"
248                                         "outfile file: %s\n",
249                                         llstat_file);
250                                 exit(1);
251                         }
252                         /* fgets reads the popen output and fprintf writes it to
253                          * output file */
254
255                         if (fputs(buffer, fp_out) == EOF) {
256                                 fprintf(stderr, "Error: Couldn't write output"
257                                         "of llstat to out file\n");
258                                 exit(1);
259                         }
260
261                         /* closing file opened for storing llstat's output */
262                         if (fclose(fp_out)) {
263                                 fprintf(stderr, "Error: Couldn't close llstat"
264                                         "outfile: %s\n", llstat_file);
265                                 exit(1);
266                         }
267                 }
268                 /* closing popen for llstat */
269                 if (pclose(fp_popen) < 0) {
270                         fprintf(stderr, "Error: Couldn't pclos"
271                                 " llstat popen call\n");
272                         exit(1);
273                 }
274         } /* child ends */
275         return pid_llstat_command;
276 }
277
278 pid_t fork_actual_command(int type, unsigned short id, char* stats_path,
279                           char* command)
280 {
281         pid_t pid;
282
283         /* starting ltrack_stats functionality here */
284         if ((pid = fork()) < 0)
285                 fprintf(stderr, "Error: Fork error\n");
286
287         /* fork for executing command */
288         if (pid == 0) {
289                 switch(type) {
290                         case TRACK_BY_GID:
291                                 if (setgid(id) < 0) {
292                                         fprintf(stderr, "Error: failed to"
293                                                " setgid\n");
294                                         exit(1);
295                                 }
296                                 pid = id;
297                                 break;
298
299                         case TRACK_BY_PID:
300                         case TRACK_BY_PPID:
301                                 pid = getpid();
302                                 break;
303
304                         /* 0 has to be written to vfs_track_pid to collect 
305                          * statistics of all processes */
306                         case TRACK_FOR_ALL:
307                                 pid = 0;
308                                 type = TRACK_BY_PID;
309                                 break;
310                 }
311                 write_track_xid(type, pid, stats_path);
312                 execl("/bin/sh", "sh", "-c", command, (char *)0);
313                 exit(0);
314         } /* child ends */
315
316         return(pid);
317 }
318
319 char* get_path_stats(int with_llstat, char* stats_path)
320 {
321         glob_t stats_glob_buffer;
322         int choice;
323         char error = 0;
324         int i;
325
326         /* No slots reserved in gl_pathv. Store the found path at 0 location */
327         stats_glob_buffer.gl_offs = 0;
328
329         /* doing glob() for attaching llstat to monitor each vfs_ops_stat for
330          * mulitiple lustre clients */
331         if (glob("/proc/fs/lustre/llite/*", GLOB_DOOFFS, NULL,
332                  &stats_glob_buffer) != 0) {
333                 fprintf(stderr,"Error: Couldn't find /proc entry for "
334                         "lustre\n");
335                 exit(1);
336         }
337
338         /* If multiple client entries found in /proc/fs/lustre/llite user will
339          * be prompted with choice of all */
340         if (stats_glob_buffer.gl_pathc > 1 && with_llstat) {
341                 check_llstat(); 
342                 printf("Multiple lustre clients found, continuing... \n");
343                 do {
344                         /* If flow is here again it means there was an error
345                          * and notifying that to user */
346                         if (error) {
347                                 int ret;
348                                 if ((ret = system("clear")) != 0) {
349                                         ret = WEXITSTATUS(ret);
350                                         printf("error excuting clear command: %d\n", ret);
351                                         exit(ret);
352                                 }
353                                 fprintf(stderr, "Error: Please give correct "
354                                         "choice.\n");
355                         }
356                         /* Simple menu based interface to avoid possible
357                          * spelling mistakes */
358                         printf("\t\tMenu.\n");
359                         for (i = 0; i < stats_glob_buffer.gl_pathc; i++)
360                                 printf("\t\t%d. %s\n", i+1, 
361                                        stats_glob_buffer.gl_pathv[i]);
362
363                         printf("\nEnter the lustre client number you want to "
364                                "use:");
365                         if (scanf(" %d", &choice) == EOF && ferror(stdin)) {
366                                 perror("reading from stdin");
367                                 exit(-1);
368                         }
369                         error++;
370                 } while (choice > stats_glob_buffer.gl_pathc || choice < 1);
371                 strcpy(stats_path, stats_glob_buffer.gl_pathv[choice - 1]);
372         } else {
373                 /*if only one client then simply copying the path from glob */
374                 strcpy(stats_path, stats_glob_buffer.gl_pathv[0]);
375         }
376         /* this frees dynamically allocated space by glob() for storing found
377          * paths */
378         globfree(&stats_glob_buffer);
379
380         return stats_path;
381 }
382
383 /* Writes the id (gid/ pid/ ppid) value in appropriate tracking proc entry file
384  * and EXECs the command given */
385 void fork_command(int type, unsigned short id, char* command, char* llstat_file)
386 {
387         pid_t pid_actual_command = 0;
388         pid_t pid_llstat_command = 0;
389
390         /* counters */
391         int with_llstat = 1;
392         int status;
393         char stats_path[1024];
394         char stats_path_temp[1024 + 6]; /* 6=strlen("/stats") */
395
396         if (strlen(llstat_file) == 0)
397                 with_llstat = 0;
398
399         get_path_stats(with_llstat, stats_path);
400         strcpy(stats_path_temp, stats_path);
401
402         /* llstat process attached to monitor given command */
403         if (with_llstat)
404                 pid_llstat_command = fork_llstat_command(llstat_file,
405                                                          stats_path_temp);
406
407         /* forking a process which will exec command given */
408         pid_actual_command = fork_actual_command(type, id, stats_path,
409                                                  command);
410
411         if (waitpid(pid_actual_command, NULL, 0) != pid_actual_command)
412                 fprintf(stderr, "Error: waitpid error\n");
413
414         if (with_llstat) {
415                 /* comment #25 of BUG 10968 */
416                 sleep(2);
417
418                 /* sending kill to all llstat commands created for each
419                  * lustre-client respectively */
420                 kill(pid_llstat_command, 9);
421                 waitpid(pid_llstat_command, &status, 0);
422
423                 /* if llstat child is killed by KILL only then print note for
424                  * plotting graph and if its exited normally with errornous
425                  * status then it means there were some error and llstat was
426                  * aborted*/
427                 if (!WIFEXITED(status))
428                         printf("\n\t[Note: Do \"$plot-llstat %s\" to plot a graph"
429                                " using GNU plot]\n", llstat_file);
430
431         }
432 }
433
434 /* main */
435 int main(int argc, char **argv)
436 {
437         char gid_string[5] = "";
438         gid_t gid;
439         int c;
440         char command[COMM_LEN] = "";
441         char llstat_file[100] = "";
442
443         /* Checking for root*/
444         if (getuid()) {
445                 fprintf(stderr, "Error: You need to be root\n");
446                 exit(1);
447         }
448
449         opterr = 0;
450         /* Parsing command line switches */
451         while ((c = getopt(argc, argv, "l:g:c:i:a:h")) != 1)
452                 switch (c) {
453                         case 'l':
454                                 if (strlen(optarg) > sizeof(llstat_file)-1) {
455                                         fprintf(stderr, "length of outfile file"
456                                                 " is too long\n");
457                                         exit(1);
458                                 }
459                                 strncpy(llstat_file, optarg,
460                                         sizeof(llstat_file));
461                                 break;
462
463                         /* When any value is written to vfs_track_gid, then VFS
464                          * operation statistics are collected for all
465                          * processes of that group ID.
466                          * write_track_xid writes given <gid> in vfs_track_gid
467                          * here. */
468                         case 'g':
469                                 if (strlen(optarg) > sizeof(gid_string)-1)
470                                         return -E2BIG;
471                                 strncpy(gid_string, optarg, sizeof(gid_string));
472                                 get_command_from_argv(optind, argc, argv, "",
473                                                       command);
474                                 gid = atoi(gid_string);
475
476                                 fork_command(TRACK_BY_GID, gid, command,
477                                              llstat_file); 
478                                 return(0);
479
480                         /* When any value is written to vfs_track_ppid, then VFS
481                          * operation statistics are collected for all processes
482                          * whose parents' PID is same as track_ppid.
483                          *- write_track_xid writes pid to vfs_track_ppid here */
484                         case 'c':
485                                 get_command_from_argv(optind, argc, argv,
486                                                       optarg, command);
487                                 fork_command(TRACK_BY_PPID, 0, command,
488                                              llstat_file);
489                                 return(0);
490
491                         /* When a non-zero value is written to vfs_track_pid,
492                          * then VFS operation statistics are collected for only
493                          * that PID.Write_track_xid writes pid to vfs_track_pid
494                          * here.Wei need to FORK a new process and EXEC it with
495                          * given <command>. */
496                         case 'i':
497                                 get_command_from_argv(optind, argc, argv,
498                                                       optarg, command);
499                                 fork_command(TRACK_BY_PID, 0, command,
500                                              llstat_file);
501                                 return(0);
502
503                         /* When VFS operation statistics for all processes are
504                          * to be collected, "0" is written to vfs_track_pid. */
505                         case 'a':
506                                 get_command_from_argv(optind, argc, argv,
507                                                       optarg, command);
508                                 fork_command(TRACK_FOR_ALL, 0, command,
509                                              llstat_file);
510                                 return(0);
511
512                         /* Help */
513                         case 'h':
514                                 print_usage();
515                                 return(0);
516
517                         default:
518                                 print_usage();
519                                 return(1);
520                 }
521         return(0);
522 } /* main ends */