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