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