4 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
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.
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).
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
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
27 * Copyright (c) 2009, 2010, Oracle and/or its affiliates. All rights reserved.
28 * Use is subject to license terms.
30 * Copyright (c) 2014, Intel Corporation.
33 * This file is part of Lustre, http://www.lustre.org/
34 * Lustre is a trademark of Sun Microsystems, Inc.
36 * lustre/utils/ltrack_stats.c
38 * Author: Milind Dumbare <milind@clusterfs.com>
41 #include <sys/types.h>
52 #define TRACK_BY_GID 0
53 #define TRACK_BY_PPID 1
54 #define TRACK_BY_PID 2
55 #define TRACK_FOR_ALL 3
57 /* We can have at the most 1024 llstats monitoring 1024 lustre clients
59 #define NO_OF_CLIENTS 1024
61 /* length of absolute path of vfs_ops_stats */
62 #define LEN_STATS 1024
64 /* Length of llstat command with all its switches and command line options */
65 #define LEN_LLSTAT (25 + LEN_STATS)
67 /* strlen of each lustre client entry in /proc/fs/lustre/llite/ */
68 #define LEN_CLIENT 1024
70 /* size of output of llstat command we read at a time */
71 #define LLSTAT_READ_SIZE 1024
73 /* Length of command given on command line */
80 printf("ltrack_stats runs command given and does one of the "
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"
101 printf("-g: for displaying VFS operation statistics collected"
102 "for all processes having \ngroup id as given <gid> \n\n");
104 printf("-a: for displaying VFS operations statistics collected"
105 "for all processes\n\n");
107 printf("-i: for displaying VFS operation statistics collected"
108 "for only given <command's> \nPID.\n\n");
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");
114 printf("-h: for showing this help\n");
117 /* - type: to which file data should be written to track VFS operations
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. */
122 void write_track_xid(int type, unsigned short id, char* stats_path)
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 */
131 strcat(stats_path, "/stats_track_gid");
134 strcat(stats_path, "/stats_track_ppid");
137 strcat(stats_path, "/stats_track_pid");
141 fp = fopen(stats_path, "w+");
143 fprintf(stderr, "Error: Couldn't open /proc entry file: %s\n",
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);
153 fprintf(stderr, "Error: Couldn't close tracking /proc entry"
154 " file: %s\n", stats_path);
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,
168 strcpy(command, optarg);
169 strcat(command, " ");
170 for (index = optind; index < argc; index++) {
171 strcat(command, argv[index]);
172 strcat(command, " ");
174 if (strlen(command) == 1) {
175 fprintf(stderr,"Error: command missing \n");
178 } else if (strlen(command) > COMM_LEN) {
179 fprintf(stderr,"Error: Too long command \n");
187 /* Check for the llstat command in $PATH env variable. */
192 status = system("which llstat.pl &> /dev/null");
194 fprintf(stderr,"Error: llstat.pl not found in PATH\n");
199 pid_t fork_llstat_command(char* llstat_file,char* stats_path)
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];
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);
217 strcat(stats_path, "/stats");
219 /* creating a string of llstat command to give to
221 sprintf(llstat_command, "llstat -i 1 -g %s ",
224 /* fork for llstat */
225 if ((pid_llstat_command = fork()) < 0)
226 fprintf(stderr, "Error: Fork error\n");
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);
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 */
244 /* opening file where llstat will write its output */
245 fp_out = fopen(llstat_file, "a");
247 fprintf(stderr, "Error: Couldn't open llstat"
248 "outfile file: %s\n",
252 /* fgets reads the popen output and fprintf writes it to
255 if (fputs(buffer, fp_out) == EOF) {
256 fprintf(stderr, "Error: Couldn't write output"
257 "of llstat to out file\n");
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);
268 /* closing popen for llstat */
269 if (pclose(fp_popen) < 0) {
270 fprintf(stderr, "Error: Couldn't pclos"
271 " llstat popen call\n");
275 return pid_llstat_command;
278 pid_t fork_actual_command(int type, unsigned short id, char* stats_path,
283 /* starting ltrack_stats functionality here */
284 if ((pid = fork()) < 0)
285 fprintf(stderr, "Error: Fork error\n");
287 /* fork for executing command */
291 if (setgid(id) < 0) {
292 fprintf(stderr, "Error: failed to"
304 /* 0 has to be written to vfs_track_pid to collect
305 * statistics of all processes */
311 write_track_xid(type, pid, stats_path);
312 execl("/bin/sh", "sh", "-c", command, (char *)0);
319 char* get_path_stats(int with_llstat, char* stats_path)
321 glob_t stats_glob_buffer;
326 /* No slots reserved in gl_pathv. Store the found path at 0 location */
327 stats_glob_buffer.gl_offs = 0;
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 "
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) {
342 printf("Multiple lustre clients found, continuing... \n");
344 /* If flow is here again it means there was an error
345 * and notifying that to user */
348 if ((ret = system("clear")) != 0) {
349 ret = WEXITSTATUS(ret);
350 printf("error excuting clear command: %d\n", ret);
353 fprintf(stderr, "Error: Please give correct "
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]);
363 printf("\nEnter the lustre client number you want to "
365 if (scanf(" %d", &choice) == EOF && ferror(stdin)) {
366 perror("reading from stdin");
370 } while (choice > stats_glob_buffer.gl_pathc || choice < 1);
371 strcpy(stats_path, stats_glob_buffer.gl_pathv[choice - 1]);
373 /*if only one client then simply copying the path from glob */
374 strcpy(stats_path, stats_glob_buffer.gl_pathv[0]);
376 /* this frees dynamically allocated space by glob() for storing found
378 globfree(&stats_glob_buffer);
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)
387 pid_t pid_actual_command = 0;
388 pid_t pid_llstat_command = 0;
393 char stats_path[1024];
394 char stats_path_temp[1024 + 6]; /* 6=strlen("/stats") */
396 if (strlen(llstat_file) == 0)
399 get_path_stats(with_llstat, stats_path);
400 strcpy(stats_path_temp, stats_path);
402 /* llstat process attached to monitor given command */
404 pid_llstat_command = fork_llstat_command(llstat_file,
407 /* forking a process which will exec command given */
408 pid_actual_command = fork_actual_command(type, id, stats_path,
411 if (waitpid(pid_actual_command, NULL, 0) != pid_actual_command)
412 fprintf(stderr, "Error: waitpid error\n");
415 /* comment #25 of BUG 10968 */
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);
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
427 if (!WIFEXITED(status))
428 printf("\n\t[Note: Do \"$plot-llstat %s\" to plot a graph"
429 " using GNU plot]\n", llstat_file);
435 int main(int argc, char **argv)
437 char gid_string[5] = "";
440 char command[COMM_LEN] = "";
441 char llstat_file[100] = "";
443 /* Checking for root*/
445 fprintf(stderr, "Error: You need to be root\n");
450 /* Parsing command line switches */
451 while ((c = getopt(argc, argv, "l:g:c:i:a:h")) != 1)
454 if (strlen(optarg) > sizeof(llstat_file)-1) {
455 fprintf(stderr, "length of outfile file"
459 strncpy(llstat_file, optarg,
460 sizeof(llstat_file));
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
469 if (strlen(optarg) > sizeof(gid_string)-1)
471 strncpy(gid_string, optarg, sizeof(gid_string));
472 get_command_from_argv(optind, argc, argv, "",
474 gid = atoi(gid_string);
476 fork_command(TRACK_BY_GID, gid, command,
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 */
485 get_command_from_argv(optind, argc, argv,
487 fork_command(TRACK_BY_PPID, 0, command,
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>. */
497 get_command_from_argv(optind, argc, argv,
499 fork_command(TRACK_BY_PID, 0, command,
503 /* When VFS operation statistics for all processes are
504 * to be collected, "0" is written to vfs_track_pid. */
506 get_command_from_argv(optind, argc, argv,
508 fork_command(TRACK_FOR_ALL, 0, command,