Add forgotten svn:keywords.
[util-vserver.git] / 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 #ifdef HAVE_CONFIG_H
21 #  include <config.h>
22 #endif
23
24 #include "vserver.h"
25 #include "util.h"
26 #include "internal.h"
27
28 #include <ensc_vector/vector.h>
29
30 #include <stdio.h>
31 #include <stdlib.h>
32 #include <unistd.h>
33 #include <fcntl.h>
34 #include <ctype.h>
35 #include <sys/types.h>
36 #include <sys/stat.h>
37 #include <dirent.h>
38 #include <string.h>
39 #include <errno.h>
40 #include <syscall.h>
41 #include <time.h>
42 #include <stdbool.h>
43 #include <getopt.h>
44 #include <sys/param.h>
45
46 #define ENSC_WRAPPERS_DIRENT    1
47 #define ENSC_WRAPPERS_VSERVER   1
48 #define ENSC_WRAPPERS_FCNTL     1
49 #define ENSC_WRAPPERS_UNISTD    1
50 #include "wrappers.h"
51
52 #define PROC_DIR_NAME "/proc"
53 #define CTX_DIR_NAME "/var/run/vservers/"
54 #define CTX_NAME_MAX_LEN 50
55
56 int     wrapper_exit_code = 1;
57
58 #ifndef AT_CLKTCK
59 #define AT_CLKTCK       17    /* frequency of times() */
60 #endif
61
62 static unsigned long    hertz   =0x42;
63 static unsigned long    pagesize=0x42;
64
65 struct XidData
66 {
67     xid_t               xid;
68     int                 process_count;
69     int                 VmSize_total;
70     int                 VmRSS_total;
71     uint64_t            start_time_oldest;
72     uint64_t            stime_total, utime_total;
73
74     vcCfgStyle          cfgstyle;
75     char const *        name;
76 };
77
78 struct process_info
79 {
80         long            VmSize;         // number of pages of virtual memory
81         long            VmRSS;          // resident set size from /proc/#/stat
82         uint64_t        start_time;     // start time of process -- milliseconds since 1-1-70
83         uint64_t        stime, utime;   // kernel & user-mode CPU time accumulated by process
84         uint64_t        cstime, cutime; // cumulative time of process and reaped children
85         xid_t           s_context;
86 };
87
88 struct ArgInfo {
89     enum { tpUNSET, tpCTX, tpPID }      type;
90     xid_t               ctx;
91     pid_t               pid;
92     unsigned int        interval;
93     bool                shutdown;
94     bool                omit_init;
95     size_t              argc;
96     char * const *      argv;
97 };
98
99 #define CMD_HELP                0x1000
100 #define CMD_VERSION             0x1001
101
102 struct option const
103 CMDLINE_OPTIONS[] = {
104   { "help",     no_argument,  0, CMD_HELP },
105   { "version",  no_argument,  0, CMD_VERSION },
106   { "sort",     required_argument, 0, 'O' },
107   {0,0,0,0}
108 };
109
110 static void
111 showHelp(char const *cmd)
112 {
113   WRITE_MSG(1, "Usage:  ");
114   WRITE_STR(1, cmd);
115   WRITE_MSG(1,
116             "\n"
117             "Show informations about all the active context.\n\n"
118             "   CTX#            Context number\n"
119             "                   #0 = root context\n"
120             "                   #1 = monitoring context\n"
121             "   PROC QTY        Quantity of processes in each context\n"
122             "   VSZ             Number of pages of virtual memory\n"
123             "   RSS             Resident set size\n"
124             "   userTIME        User-mode CPU time accumulated\n"
125             "   sysTIME         Kernel-mode CPU time accumulated\n"
126             "   UPTIME          Uptime/context\n"
127             "   NAME            Virtual server name\n"
128             "\n");
129   exit(0);
130 }
131
132 static void
133 showVersion()
134 {
135   WRITE_MSG(1,
136             "vserver-stat " VERSION " -- show virtual context statistics\n"
137             "This program is part of " PACKAGE_STRING "\n\n"
138             "Copyright (C) 2003,2005 Enrico Scholz\n"
139             VERSION_COPYRIGHT_DISCLAIMER);
140   exit(0);
141 }
142
143
144 // return uptime (in ms) from /proc/uptime
145 static uint64_t
146 getUptime()
147 {
148   int           fd;
149   char          buffer[64];
150   char *        errptr;
151   size_t        len;
152   uint64_t      secs;
153   uint32_t      msecs=0;
154
155     // open the /proc/uptime file
156   fd  = EopenD("/proc/uptime", O_RDONLY, 0);
157   len = Eread(fd, buffer, sizeof buffer);
158
159   if (len==sizeof(buffer)) {
160     WRITE_MSG(2, "Too much data in /proc/uptime; aborting...\n");
161     exit(1);
162   }
163   Eclose(fd);
164
165   while (len>0 && buffer[len-1]=='\n') --len;
166   buffer[len] = '\0';
167
168   secs = strtol(buffer, &errptr, 10);
169   if (*errptr!='.') errptr = buffer;
170   else {
171     unsigned int        mult;
172     switch (strlen(errptr+1)) {
173       case 0    :  mult = 1000; break;
174       case 1    :  mult = 100;  break;
175       case 2    :  mult = 10;   break;
176       case 3    :  mult = 1;    break;
177       default   :  mult = 0;    break;
178     }
179     msecs = strtol(errptr+1, &errptr, 10) * mult;
180   }
181
182   if ((*errptr!='\0' && *errptr!=' ') || errptr==buffer) {
183     WRITE_MSG(2, "Bad data in /proc/uptime\n");
184     exit(1);
185   }
186
187   return secs*1000 + msecs;
188 }
189
190 static int
191 cmpData(void const *xid_v, void const *map_v)
192 {
193   xid_t const * const                   xid = xid_v;
194   struct XidData const * const          map = map_v;
195   int   res = *xid - map->xid;
196
197   return res;
198 }
199
200 static void
201 registerXid(struct Vector *vec, struct process_info *process)
202 {
203   struct XidData        *res;
204
205   res = Vector_search(vec, &process->s_context, cmpData);
206   if (res==0) {
207     res = Vector_insert(vec, &process->s_context, cmpData);
208     res->xid           = process->s_context;
209     res->process_count = 0;
210     res->VmSize_total  = 0;
211     res->VmRSS_total   = 0;
212     res->utime_total   = 0;
213     res->stime_total   = 0;
214     res->start_time_oldest = process->start_time;
215   }
216
217   ++res->process_count;
218   res->VmSize_total += process->VmSize;
219   res->VmRSS_total  += process->VmRSS;
220   res->utime_total  += process->utime + process->cutime;
221   res->stime_total  += process->stime + process->cstime;
222
223   res->start_time_oldest = MIN(res->start_time_oldest, process->start_time);
224 }
225
226 static inline uint64_t
227 toMsec(uint64_t v)
228 {
229   return v*1000llu/hertz;
230 }
231
232
233 // shamelessly stolen from procps...
234 static unsigned long
235 find_elf_note(unsigned long findme){
236   unsigned long *ep = (unsigned long *)environ;
237   while(*ep++);
238   while(*ep){
239     if(ep[0]==findme) return ep[1];
240     ep+=2;
241   }
242   return (unsigned long)(-1);
243 }
244
245 static void initHertz()    __attribute__((__constructor__));
246 static void initPageSize() __attribute__((__constructor__));
247
248 static void
249 initHertz()
250 {
251   hertz = find_elf_note(AT_CLKTCK);
252   if (hertz==(unsigned long)(-1))
253     hertz = sysconf(_SC_CLK_TCK);
254 }
255
256 static void
257 initPageSize()
258 {
259   pagesize = sysconf(_SC_PAGESIZE);
260 }
261
262 // open the process's status file to get the ctx number, and other stat
263 struct process_info *
264 get_process_info(char *pid)
265 {
266   int                           fd;
267   char                          buffer[1024];
268   char                          *p;
269   size_t                        idx, l=strlen(pid);
270   static struct process_info    process;
271
272 #if 1
273   process.s_context = vc_get_task_xid(atoi(pid));
274 #else
275 #  warning Compiling in debug-code
276   process.s_context = random()%6;
277 #endif
278
279   if (process.s_context==VC_NOCTX) {
280     int         err=errno;
281     WRITE_MSG(2, "vc_get_task_xid(");
282     WRITE_STR(2, pid);
283     WRITE_MSG(2, "): ");
284     WRITE_STR(2, strerror(err));
285     WRITE_MSG(2, "\n");
286
287     return 0;
288   }
289   
290   memcpy(buffer,     "/proc/", 6); idx  = 6;
291   memcpy(buffer+idx, pid,      l); idx += l;
292   memcpy(buffer+idx, "/stat",  6);
293         
294     // open the /proc/#/stat file
295   if ((fd = open(buffer, O_RDONLY, 0)) == -1)
296     return NULL;
297     // put the file in a buffer
298   if (read(fd, buffer, sizeof(buffer)) < 1)
299     return NULL;
300
301   close(fd);
302
303   p   = strchr(buffer, ')');            // go after the PID (process_name)
304   for (idx = 0; idx<12 && *p!='\0'; ++p)
305     if ((*p)==' ') ++idx;
306
307   process.utime  = toMsec(strtol(p,   &p, 10));
308   process.stime  = toMsec(strtol(p+1, &p, 10));
309   process.cutime = toMsec(strtol(p+1, &p, 10));
310   process.cstime = toMsec(strtol(p+1, &p, 10));
311
312   for (idx = 0; idx<5 && *p!='\0'; ++p)
313     if ((*p)==' ') ++idx;
314
315   process.start_time = toMsec(strtol(p,   &p, 10));
316   process.VmSize     = strtol(p+1, &p, 10);
317   process.VmRSS      = strtol(p+1, &p, 10);
318
319   //printf("pid=%s, start_time=%llu\n", pid, process.start_time);
320   return &process;
321 }
322
323 static size_t
324 fillUintZero(char *buf, unsigned long val, size_t cnt)
325 {
326   size_t        l;
327   
328   l = utilvserver_fmt_ulong(buf, val);
329   if (l<cnt) {
330     memmove(buf+cnt-l, buf, l);
331     memset(buf, '0', cnt-l);
332   }
333   buf[cnt] = '\0';
334
335   return cnt;
336 }
337
338 static void
339 shortenMem(char *buf, unsigned long val)
340 {
341   char const *  SUFFIXES[] = { " ", "K", "M", "G", "T", "+" };
342   char          tmp[16];
343   char const *  suffix = "+";
344   size_t        i, l;
345   unsigned int  mod = 0;
346
347   for (i=0; i<6; ++i) {
348     if (val<1000) {
349       suffix = SUFFIXES[i];
350       break;
351     }
352     mod   = 10*(val & 1023)/1024;
353     val >>= 10;
354   }
355
356   if (val >9999) val=9999;
357   if (val>=1000) mod=0;
358
359   l = utilvserver_fmt_ulong(tmp, val);
360   if (mod!=0) {
361     tmp[l++] = '.';
362     l += utilvserver_fmt_ulong(tmp+l, mod);
363   }
364   i = 7-l-strlen(suffix);
365   
366   memcpy(buf+i,   tmp, l);
367   memcpy(buf+i+l, suffix, strlen(suffix));
368 }
369
370 static void
371 shortenTime(char *buf, uint64_t t)
372 {
373   char          tmp[32];
374   char          *ptr = tmp;
375
376   unsigned long hh, mm, ss, ms;
377
378   ms = t % 1000;
379   t /= 1000;
380
381   ss = t%60;
382   t /= 60;
383   mm = t%60;
384   t /= 60;
385   hh = t%24;
386   t /= 24;
387
388   if (t>999*999) {
389     memcpy(ptr, "INVALID", 7);
390     ptr   += 7;
391   }
392   else if (t>999) {
393     ptr   += utilvserver_fmt_ulong(ptr, t/365);
394     *ptr++ = 'y';
395     ptr   += fillUintZero(ptr, t%365, 2);
396     *ptr++ = 'd';
397     ptr   += fillUintZero(ptr, hh, 2);
398   }    
399   else if (t>0) {
400     ptr   += utilvserver_fmt_ulong(ptr, t);
401     *ptr++ = 'd';
402     ptr   += fillUintZero(ptr, hh, 2);
403     *ptr++ = 'h';
404     ptr   += fillUintZero(ptr, mm, 2);
405   }
406   else if (hh>0) {
407     ptr   += utilvserver_fmt_ulong(ptr, hh);
408     *ptr++ = 'h';
409     ptr   += fillUintZero(ptr, mm, 2);
410     *ptr++ = 'm';
411     ptr   += fillUintZero(ptr, ss, 2);    
412   }
413   else {
414     ptr   += utilvserver_fmt_ulong(ptr, mm);
415     *ptr++ = 'm';
416     ptr   += fillUintZero(ptr, ss, 2);
417     *ptr++ = 's';
418     ptr   += fillUintZero(ptr, ms, 2);
419   }
420
421   *ptr = ' ';
422   memcpy(buf+10-(ptr-tmp), tmp, ptr-tmp);
423 }
424
425 static char *
426 formatName(char *dst, vcCfgStyle style, char const *name)
427 {
428   size_t                len;
429   
430   if (name==0) name = "";
431   len = strlen(name);
432
433   switch (style) {
434     case vcCFG_LEGACY   :
435       len    = MIN(len, 18);
436       *dst++ = '[';
437       memcpy(dst, name, len);
438       dst   += len;
439       *dst++ = ']';
440       break;
441
442     default             :
443       len    = MIN(len, 20);
444       memcpy(dst, name, len);
445       dst   += len;
446       break;
447   }
448
449   return dst;
450 }
451
452 static void
453 showContexts(struct Vector const *vec)
454 {
455   uint64_t                      uptime  = getUptime();
456   struct XidData const *        ptr     = Vector_begin_const(vec);
457   struct XidData const * const  end_ptr = Vector_end_const(vec);
458   
459
460   WRITE_MSG(1, "CTX   PROC    VSZ    RSS  userTIME   sysTIME    UPTIME NAME\n");
461   for (; ptr<end_ptr; ++ptr) {
462     char        buf[sizeof(xid_t)*3 + 512];
463     char        tmp[sizeof(int)*3 + 2];
464     size_t      l;
465
466     memset(buf, ' ', sizeof(buf));
467     l = utilvserver_fmt_long(buf, ptr->xid);
468     l = utilvserver_fmt_long(tmp, ptr->process_count);
469     memcpy(buf+10-l, tmp, l);
470
471     shortenMem (buf+10, ptr->VmSize_total);
472     shortenMem (buf+17, ptr->VmRSS_total*pagesize);
473     shortenTime(buf+24, ptr->utime_total);
474     shortenTime(buf+34, ptr->stime_total);
475     //printf("%llu, %llu\n", uptime, ptr->start_time_oldest);
476     shortenTime(buf+44, uptime - ptr->start_time_oldest);
477
478     formatName(buf+55, ptr->cfgstyle, ptr->name)[0] = '\0';
479
480     Vwrite(1, buf, strlen(buf));
481     Vwrite(1, "\n", 1);
482   }
483 }
484
485 static void
486 fillName(void *obj_v, void UNUSED * a)
487 {
488   struct XidData *      obj = obj_v;
489
490   switch (obj->xid) {
491     case 0              :
492       obj->cfgstyle = vcCFG_NONE;
493       obj->name     = strdup("root server");
494       break;
495
496     case 1              :
497       obj->cfgstyle = vcCFG_NONE;
498       obj->name     = strdup("monitoring server");
499       break;
500
501     default             : {
502       char *            cfgpath;
503
504       obj->cfgstyle  = vcCFG_AUTO;
505
506       if ((cfgpath   = vc_getVserverByCtx(obj->xid, &obj->cfgstyle, 0))==0 ||
507           (obj->name = vc_getVserverName(cfgpath, obj->cfgstyle))==0) {
508         obj->name     = 0;
509         obj->cfgstyle = vcCFG_NONE;
510       }
511
512       free(cfgpath);
513
514       break;
515     }
516   }
517 }
518
519 static void UNUSED
520 freeXidData(void *obj_v, void UNUSED * a)
521 {
522   struct XidData *      obj = obj_v;
523
524   free(const_cast(char *)(obj->name));
525 }
526
527 int main(int argc, char **argv)
528 {
529   DIR *                 proc_dir;
530   struct dirent*        dir_entry;
531   pid_t                 my_pid;
532   struct Vector         xid_data;
533   char const *          errptr;
534
535   while (1) {
536     int         c = getopt_long(argc, argv, "+O:", CMDLINE_OPTIONS, 0);
537     if (c==-1) break;
538
539     switch (c) {
540       case CMD_HELP     :  showHelp(argv[0]);
541       case CMD_VERSION  :  showVersion();
542       case 'O'          :  break;
543       default           :
544         WRITE_MSG(2, "Try '");
545         WRITE_STR(2, argv[0]);
546         WRITE_MSG(2, " --help\" for more information.\n");
547         return EXIT_FAILURE;
548         break;
549     }
550   }
551     
552   if (optind!=argc) {
553     WRITE_MSG(2, "Unknown parameter, use '--help' for more information\n");
554     return EXIT_FAILURE;
555   }
556
557   if (hertz==0x42)    initHertz();
558   if (pagesize==0x42) initPageSize();
559   
560   my_pid = getpid();
561
562   if (!switchToWatchXid(&errptr)) {
563     perror(errptr);
564     exit(1);
565   }
566
567   if (access("/proc/uptime",R_OK)==-1 && errno==ENOENT)
568     WRITE_MSG(2,
569               "WARNING: can not access /proc/uptime. Usually, this is caused by\n"
570               "         procfs-security. Please read the FAQ for more details\n"
571               "         http://www.linux-vserver.org/index.php?page=Linux-Vserver+FAQ\n");
572
573   Vector_init(&xid_data, sizeof(struct XidData));
574
575   Echdir(PROC_DIR_NAME);
576   proc_dir = Eopendir(".");
577   while ((dir_entry = readdir(proc_dir)) != NULL)
578   {
579       // select only process file
580     if (!isdigit(*dir_entry->d_name))
581       continue;
582
583     if (atoi(dir_entry->d_name) != my_pid) {
584       struct process_info *     info = get_process_info(dir_entry->d_name);
585       if (info)
586         registerXid(&xid_data, info);
587     }
588   }
589   closedir(proc_dir);
590
591   Vector_foreach(&xid_data, fillName, 0);
592
593     // output the ctx_list      
594   showContexts(&xid_data);
595
596 #ifndef NDEBUG
597   Vector_foreach(&xid_data, freeXidData, 0);
598   Vector_free(&xid_data);
599 #endif
600   
601   return 0;
602 }