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