Merged with SYSCALL_SWITCH branch (sswitch_merge tag)
[util-vserver.git] / util-vserver / src / vserver-stat.c
1 // $Id$
2
3 // Copyright (C) 2003 Enrico Scholz <enrico.scholz@informatik.tu-chemnitz.de>
4 // based on vserver-stat.cc by Guillaum Dallaire and Jacques Gelinas
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 as published by
8 // the Free Software Foundation; either version 2, or (at your option)
9 // any later version.
10 //  
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14 // GNU General Public License for more details.
15 //  
16 // You should have received a copy of the GNU General Public License
17 // along with this program; if not, write to the Free Software
18 // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19
20 /*
21         vserver-stat help you to see all the active context currently in the kernel
22         with some useful stat
23
24         Changelog:
25
26         2003-01-08 Jacques Gelinas: Shows vserver description
27         2002-02-28 Jacques Gelinas: Use dynamic system call
28         2002-06-05 Martial Rioux : fix memory output error
29         2002-12-05 Martial Rioux : fix output glitch
30         2001-11-29 added uptime/ctx stat
31         2001-11-20 added vmsize, rss, stime and utime stat
32 */
33 #ifdef HAVE_CONFIG_H
34 #  include <config.h>
35 #endif
36 #include "compat.h"
37
38 #include "vserver.h"
39
40 #include <stdio.h>
41 #include <stdlib.h>
42 #include <unistd.h>
43 #include <fcntl.h>
44 #include <ctype.h>
45 #include <sys/types.h>
46 #include <sys/stat.h>
47 #include <dirent.h>
48 #include <string.h>
49 #include <errno.h>
50 #include <syscall.h>
51 #include <time.h>
52
53 #define PROC_DIR_NAME "/proc"
54 #define CTX_DIR_NAME "/var/run/vservers/"
55 #define CTX_NAME_MAX_LEN 50
56
57 struct ctx_list
58 {
59         int ctx;
60         int process_count;
61         int VmSize_total;
62         int VmRSS_total;
63         long start_time_oldest;
64         long stime_total, utime_total;
65         char name[CTX_NAME_MAX_LEN];
66         struct ctx_list *next;
67 } *my_ctx_list;
68
69 struct process_info
70 {
71         long VmSize;            // number of pages of virtual memory
72         long VmRSS;             // resident set size from /proc/#/stat
73         long start_time;        // start time of process -- seconds since 1-1-70
74         long stime, utime;      // kernel & user-mode CPU time accumulated by process
75         long cstime, cutime;    // cumulative time of process and reaped children
76         int s_context;
77 };
78
79 char *process_name;
80
81 void usage()
82 {
83         fprintf(stderr, "%s: from vserver kit version %s\n", process_name, VERSION);
84         fprintf(stderr, "(no argument needed)\n\n");
85         fprintf(stderr, "Show informations about all the active context.\n\n");
86         fprintf(stderr, "       CTX#            Context number\n");
87         fprintf(stderr, "                       #0 = root context\n");
88         fprintf(stderr, "                       #1 = monitoring context\n");
89         fprintf(stderr, "       PROC QTY        Quantity of processes in each context\n");
90         fprintf(stderr, "       VSZ             Number of pages of virtual memory\n");
91         fprintf(stderr, "       RSS             Resident set size\n");
92         fprintf(stderr, "       userTIME        User-mode CPU time accumulated\n");
93         fprintf(stderr, "       sysTIME         Kernel-mode CPU time accumulated\n");
94         fprintf(stderr, "       UPTIME          Uptime/context\n");
95         fprintf(stderr, "       NAME            Virtual server name\n");
96         fprintf(stderr, "\n");
97
98 }
99
100 // return uptime (in ms) from /proc/uptime
101 long get_uptime()
102 {
103         int fd;
104         double up;
105         char buffer[64];
106
107         // open the /proc/uptime file
108         if ((fd = open("/proc/uptime", O_RDONLY, 0)) == -1)
109                 return 0;
110
111         if (read(fd, buffer, sizeof(buffer)) < 1)
112                 return 0;
113
114         close(fd);
115
116         if (sscanf(buffer, "%lf", &up) < 1)
117         {
118                 fprintf(stderr, "%s: bad data in /proc/uptime\n", process_name);
119                 return 0;
120         }
121
122         return up * 100;
123 }
124
125 // insert a new record to the list
126 struct ctx_list *insert_ctx(int ctx, struct ctx_list *next)
127 {
128         struct ctx_list *new;
129
130         new = (struct ctx_list *)malloc(sizeof(struct ctx_list));
131         new->ctx = ctx;
132         new->process_count = 0;
133         new->VmSize_total = 0;
134         new->VmRSS_total = 0;
135         new->utime_total = 0;
136         new->stime_total = 0;
137         new->start_time_oldest = 0;
138         new->next = next;
139         new->name[0] = '\0';
140
141         return new;
142 }
143
144 // find the ctx record with the ctx number
145 struct ctx_list *find_ctx(struct ctx_list *list, int ctx)
146 {
147         // very simple search engine...
148         while(list != NULL)
149         {
150                 // find
151                 if (list->ctx == ctx)
152                 {
153                         return list;
154                 }
155                 list = list->next;
156         }
157         return NULL;
158 }
159
160 // compute the process info into the list
161 void add_ctx(struct ctx_list *list, struct process_info *process)
162 {
163         list->process_count ++;
164         list->VmSize_total += process->VmSize;
165         list->VmRSS_total += process->VmRSS;
166         list->utime_total += process->utime + process->cutime;
167         list->stime_total += process->stime + process->cstime;
168
169         if (list->start_time_oldest == 0) // first entry
170                 list->start_time_oldest = process->start_time;
171         else
172                 if (list->start_time_oldest > process->start_time)
173                         list->start_time_oldest = process->start_time;
174 }
175
176 // increment the count number in the ctx record using ctx number
177 void count_ctx(struct ctx_list *list, struct process_info *process)
178 {
179         struct ctx_list *prev = list;
180
181         if (process == NULL) return;
182
183         // search
184         while(list != NULL)
185         {
186                 // find
187                 if (list->ctx == process->s_context)
188                 {
189                         add_ctx(list, process);
190                         return;
191                 }
192                 // insert between
193                 if (list->ctx > process->s_context)
194                 {
195                         prev->next = insert_ctx(process->s_context, list);
196                         add_ctx(prev->next, process);
197                         return;
198                 }
199                 // ++
200                 prev = list;
201                 list = list->next;
202         }
203         // add at the end
204         prev->next = insert_ctx(process->s_context, NULL);
205         add_ctx(prev->next, process);
206 }
207
208 // free mem
209 void free_ctx(struct ctx_list *list)
210 {
211         struct ctx_list *prev;
212
213         for(;list != NULL; list = prev)
214         {
215                 prev = list->next;              
216                 free(list);
217         }
218 }
219
220 /*
221         Read the vserver description
222 */
223 static void read_description(
224         const char *name,               // Vserver name
225         char descrip[1000])
226 {
227         char conf[PATH_MAX];
228         FILE *fin;
229         descrip[0] = '\0';
230         snprintf (conf,sizeof(conf)-1,"/etc/vservers/%s.conf",name);
231         fin = fopen (conf,"r");
232         if (fin != NULL){
233                 char line[1000];
234                 while (fgets(line,sizeof(line)-1,fin)!=NULL){
235                         if (line[0] == '#'){
236                                 char *pt = line+1;
237                                 while (isspace(*pt)) pt++;
238                                 if (strncmp(pt,"Description:",12)==0){
239                                         int last;
240                                         pt += 12;
241                                         while (isspace(*pt)) pt++;
242                                         strcpy (descrip,pt);
243                                         last = strlen(descrip)-1;
244                                         if (last >=0 && descrip[last] == '\n'){
245                                                 descrip[last] = '\0';
246                                         }
247                                 }
248                         }
249                 }
250                 fclose (fin);
251         }
252 }
253
254 // show the ctx_list with name from /var/run/servers/*.ctx
255 void show_ctx(struct ctx_list *list)
256 {
257         // fill the ctx_list using the /var/run/servers/*.ctx file(s)
258          __extension__ int bind_ctx_name(struct ctx_list *list)
259         {
260                 // fetch the context number in /var/run/vservers/'filename'
261                 int fetch_ctx_number(char *filename)
262                 {
263                         int fd;
264                         int ctx;
265                         char buf[25];
266
267                         // open file
268                         if ((fd = open(filename, O_RDONLY, 0)) == -1)
269                                 return -1;
270                         // put the file in a small buffer
271                         if (read(fd, buf, sizeof(buf)) < 1)
272                                 return -1;
273
274                         close(fd);
275
276                         sscanf(buf, "S_CONTEXT=%d", &ctx);
277                         return ctx;
278                 }
279
280                 /* begin bind_ctx_name */
281
282                 DIR *ctx_dir;
283                 struct dirent *dir_entry;
284                 char *p;
285                 char ctx_name[CTX_NAME_MAX_LEN];
286                 struct ctx_list *ctx;
287                 int ctx_number;
288
289                 // open the /var/run/vservers directory
290                 if ((ctx_dir = opendir(CTX_DIR_NAME)) == NULL)
291                 {
292                         fprintf(stderr, "%s: in openning %s: %s\n", process_name, CTX_DIR_NAME, strerror(errno));
293                         return -1;
294                 }
295         
296                 chdir(CTX_DIR_NAME);
297                 while ((dir_entry = readdir(ctx_dir)) != NULL)
298                 {
299                         strncpy(ctx_name, dir_entry->d_name, sizeof(ctx_name));
300                         p = strstr(ctx_name, ".ctx");
301                         if (p != NULL) // make sure that it is a .ctx file..
302                         {
303                                 *p = '\0'; // remove the .ctx in the file name
304                                 if ((ctx_number = fetch_ctx_number(dir_entry->d_name)) > 1)
305                                 {
306                                         if ((ctx = find_ctx(list, ctx_number)) != NULL)
307                                                 strncpy(ctx->name, ctx_name, CTX_NAME_MAX_LEN);
308                                 }
309                         }
310                         // else fprintf(stderr, "invalid file %s in %s\n", dir_entry->d_name, CTX_DIR_NAME);
311                 }
312                 closedir(ctx_dir);      
313                 return 0;
314         }
315
316          __extension__ char *convert_time(unsigned t, char *str)
317         {
318                 unsigned hh, mm, ss, ms;
319
320                 ms = t % 100;
321                 t /= 100;
322
323                 ss = t%60;
324                 t /= 60;
325                 mm = t%60;
326                 t /= 60;
327                 hh = t%60;
328                 t /= 24;
329
330                 if (t > 0)      // day > 0
331                 {
332                                 snprintf(str, 25, "%3.ud%02uh%02u", t, (hh%12) ? hh%12 : 12, mm);
333                 }
334                 else
335                 {
336                         if (hh > 0) // hour > 0
337                                 snprintf(str, 25, " %2.uh%02um%02u", hh, mm, ss);
338                         else
339                         {
340                                 snprintf(str, 25, " %2.um%02u.%02u", mm, ss, ms);
341                         }
342                 }
343                 return str;
344         }
345
346          __extension__ char *convert_mem(unsigned long total, char *str)
347         {
348                 // Byte
349                 if (total < 1024)
350                 {
351                         snprintf(str, 25, "%luB", total);
352                         return str;
353                 }
354
355                 total >>= 10; // kByte
356                 if (total < 1024)
357                 {
358                         snprintf(str, 25, "%lukB", total);
359                         return str;
360                 }
361
362                 total >>= 10; // MByte
363                 if (total < 1024)
364                 {
365                         snprintf(str, 25, "%luMB", total);
366                         return str;
367                 }
368
369                 total >>= 10; // GByte
370                 if (total < 1024)
371                 {
372                         snprintf(str, 25, "%luGB", total);
373                         return str;
374                 }
375                 total >>= 10; // TByte
376                 snprintf(str, 25, "%luTB", total);
377                 return str;
378         }
379
380         /* begin show_ctx */
381         char utime[25], stime[25], ctx_uptime[25];
382         char vmsize[25], vmrss[25];
383         long uptime = get_uptime();
384
385         // now we have all the active context, fetch the name
386         // from /var/run/vservers/*.ctx
387         bind_ctx_name(list);
388
389         printf("CTX  PROC    VSZ    RSS  userTIME   sysTIME    UPTIME NAME     DESCRIPTION\n");
390         while(list != NULL)
391         {
392                 char descrip[1000];
393                 if (list->ctx == 1)
394                         strncpy(list->name, "monitoring server", CTX_NAME_MAX_LEN);
395
396                 read_description (list->name,descrip);
397
398                 printf("%-4d %4d %6s %6s %9s %9s %9s %-8s %s\n", list->ctx, list->process_count,
399                         convert_mem(list->VmSize_total, vmsize), convert_mem(list->VmRSS_total, vmrss),
400                         convert_time(list->utime_total, utime), convert_time(list->stime_total, stime), convert_time(uptime - list->start_time_oldest, ctx_uptime)
401                         , list->name,descrip);
402                 list = list->next;
403         }
404 }
405
406 // open the process's status file to get the ctx number, and other stat
407 struct process_info *get_process_info(char *pid)
408 {
409         int fd;
410         char buffer[1024];
411         char *p;
412         static struct process_info process;
413
414         // open the proc/#/status file
415         snprintf(buffer, sizeof(buffer),  "/proc/%s/status", pid);
416         if ((fd = open(buffer, O_RDONLY, 0)) == -1)
417                 return NULL;
418         // put the file in a buffer
419         if (read(fd, buffer, sizeof(buffer)) < 1)
420                 return NULL;
421
422         close(fd);
423
424         // find the s_context entry
425         if ((p = strstr(buffer, "s_context:")) == NULL)
426                 return NULL;
427
428         sscanf(p, "s_context: %d", &process.s_context);
429
430         // open the /proc/#/stat file
431         snprintf(buffer, sizeof(buffer),  "/proc/%s/stat", pid);
432         if ((fd = open(buffer, O_RDONLY, 0)) == -1)
433                 return NULL;
434         // put the file in a buffer
435         if (read(fd, buffer, sizeof(buffer)) < 1)
436                 return NULL;
437
438         close(fd);
439
440         p = strchr(buffer, ')');                // go after the PID (process_name)
441         sscanf(p + 2,
442                 "%*s "
443                 "%*s %*s %*s %*s %*s "
444                 "%*s %*s %*s %*s %*s %ld %ld "
445                 "%ld %ld %*s %*s %*s %*s "
446                 "%ld %ld "
447                 "%ld ", &process.utime, &process.stime,
448                         &process.cutime, &process.cstime,
449                         &process.start_time,
450                         &process.VmSize, &process.VmRSS);
451
452         return &process;
453 }
454
455 int main(int argc, char **argv)
456 {
457         DIR *proc_dir;
458         struct dirent *dir_entry;
459         pid_t my_pid;
460
461         // for error msg
462         process_name = argv[0];
463
464         if (argc > 1)
465         {
466                 usage();
467                 return 0;
468         }
469
470         // do not include own stat
471         my_pid = getpid();
472
473         // try to switch in context 1
474         if (vc_new_s_context(1,0, 0) < 0)
475         {
476                 fprintf(stderr, "%s: unable to switch in context security #1\n", process_name);
477                 return -1;
478         }
479
480         // create the fist...
481         my_ctx_list = insert_ctx(0, NULL);
482         // init with the default name for the context 0
483         strncpy(my_ctx_list->name, "root server", CTX_NAME_MAX_LEN);
484
485         // open the /proc dir
486         if ((proc_dir = opendir(PROC_DIR_NAME)) == NULL)
487         {
488                 fprintf(stderr, "%s: %s\n", process_name, strerror(errno));
489                 return -1;
490         }
491         
492         chdir(PROC_DIR_NAME);
493         while ((dir_entry = readdir(proc_dir)) != NULL)
494         {
495                 // select only process file
496                 if (!isdigit(*dir_entry->d_name))
497                         continue;
498
499                 if (atoi(dir_entry->d_name) != my_pid)
500                         count_ctx(my_ctx_list, get_process_info(dir_entry->d_name));
501                 
502         }
503         closedir(proc_dir);
504
505         // output the ctx_list  
506         show_ctx(my_ctx_list);
507
508         // free the ctx_list
509         free_ctx(my_ctx_list);
510
511         return 0;
512 }