3 // Copyright (C) 2003 Enrico Scholz <enrico.scholz@informatik.tu-chemnitz.de>
4 // based on vserver-stat.cc by Guillaum Dallaire and Jacques Gelinas
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)
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.
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.
27 #include "pathconfig.h"
29 #include <ensc_vector/vector.h>
36 #include <sys/types.h>
45 #include <sys/param.h>
46 #include <sys/resource.h>
48 #define ENSC_WRAPPERS_DIRENT 1
49 #define ENSC_WRAPPERS_VSERVER 1
50 #define ENSC_WRAPPERS_FCNTL 1
51 #define ENSC_WRAPPERS_UNISTD 1
54 #define PROC_DIR_NAME "/proc"
55 #define PROC_VIRT_DIR_NAME "/proc/virtual"
56 #define CTX_DIR_NAME "/var/run/vservers/"
57 #define CTX_NAME_MAX_LEN 50
59 int wrapper_exit_code = 1;
62 #define AT_CLKTCK 17 /* frequency of times() */
65 static unsigned long hertz =0x42;
66 static unsigned long pagesize=0x42;
71 unsigned int process_count;
72 uint64_t VmSize_total;
74 uint64_t start_time_oldest;
75 uint64_t stime_total, utime_total;
83 long VmSize; // number of pages of virtual memory
84 long VmRSS; // resident set size from /proc/#/stat
85 uint64_t start_time; // start time of process -- milliseconds since 1-1-70
86 uint64_t stime, utime; // kernel & user-mode CPU time accumulated by process
87 uint64_t cstime, cutime; // cumulative time of process and reaped children
92 enum { tpUNSET, tpCTX, tpPID } type;
95 unsigned int interval;
102 #define CMD_HELP 0x1000
103 #define CMD_VERSION 0x1001
106 CMDLINE_OPTIONS[] = {
107 { "help", no_argument, 0, CMD_HELP },
108 { "version", no_argument, 0, CMD_VERSION },
109 { "sort", required_argument, 0, 'O' },
114 showHelp(char const *cmd)
116 WRITE_MSG(1, "Usage: ");
120 "Show information about all active contexts.\n\n"
121 " CTX# Context number\n"
122 " #0 = root context\n"
123 " #1 = monitoring context\n"
124 " PROC QTY Quantity of processes in each context\n"
125 " VSZ Number of pages of virtual memory\n"
126 " RSS Resident set size\n"
127 " userTIME User-mode CPU time accumulated\n"
128 " sysTIME Kernel-mode CPU time accumulated\n"
129 " UPTIME Uptime/context\n"
130 " NAME Virtual server name\n"
139 "vserver-stat " VERSION " -- show virtual context statistics\n"
140 "This program is part of " PACKAGE_STRING "\n\n"
141 "Copyright (C) 2003,2005 Enrico Scholz\n"
142 VERSION_COPYRIGHT_DISCLAIMER);
147 // return uptime (in ms) from /proc/uptime
158 // open the /proc/uptime file
159 fd = EopenD("/proc/uptime", O_RDONLY, 0);
160 len = Eread(fd, buffer, sizeof buffer);
162 if (len==sizeof(buffer)) {
163 WRITE_MSG(2, "Too much data in /proc/uptime; aborting...\n");
168 while (len>0 && buffer[len-1]=='\n') --len;
171 secs = strtol(buffer, &errptr, 10);
172 if (*errptr!='.') errptr = buffer;
175 switch (strlen(errptr+1)) {
176 case 0 : mult = 1000; break;
177 case 1 : mult = 100; break;
178 case 2 : mult = 10; break;
179 case 3 : mult = 1; break;
180 default : mult = 0; break;
182 msecs = strtol(errptr+1, &errptr, 10) * mult;
185 if ((*errptr!='\0' && *errptr!=' ') || errptr==buffer) {
186 WRITE_MSG(2, "Bad data in /proc/uptime\n");
190 return secs*1000 + msecs;
193 static inline uint64_t
196 return v*1000llu/hertz;
200 cmpData(void const *xid_v, void const *map_v)
202 xid_t const * const xid = xid_v;
203 struct XidData const * const map = map_v;
204 int res = *xid - map->xid;
210 registerXid(struct Vector *vec, struct process_info *process)
214 res = Vector_search(vec, &process->s_context, cmpData);
216 res = Vector_insert(vec, &process->s_context, cmpData);
217 res->xid = process->s_context;
218 res->process_count = 0;
219 res->VmSize_total = 0;
220 res->VmRSS_total = 0;
221 res->utime_total = 0;
222 res->stime_total = 0;
223 res->start_time_oldest = process->start_time;
226 ++res->process_count;
227 res->VmSize_total += process->VmSize;
228 res->VmRSS_total += process->VmRSS;
229 res->utime_total += process->utime + process->cutime;
230 res->stime_total += process->stime + process->cstime;
232 res->start_time_oldest = MIN(res->start_time_oldest, process->start_time);
236 registerXidVstat(struct Vector *vec, unsigned long xid_l)
238 xid_t xid = (xid_t) xid_l;
240 struct vc_rlimit_stat limit[3];
241 struct vc_virt_stat vstat;
242 struct vc_sched_info sched;
245 res = Vector_search(vec, &xid, cmpData);
247 WRITE_MSG(2, "Duplicate xid found?!\n");
250 if (vc_virt_stat(xid, &vstat) == -1) {
251 perror("vc_virt_stat()");
254 if (vc_rlimit_stat(xid, RLIMIT_NPROC, &limit[0]) == -1) {
255 perror("vc_rlimit_stat(RLIMIT_NRPOC)");
258 if (vc_rlimit_stat(xid, RLIMIT_AS, &limit[1]) == -1) {
259 perror("vc_rlimit_stat(RLIMIT_AS)");
262 if (vc_rlimit_stat(xid, RLIMIT_RSS, &limit[2]) == -1) {
263 perror("vc_rlimit_stat(RLIMIT_RSS)");
267 res = Vector_insert(vec, &xid, cmpData);
270 res->process_count = limit[0].value;
271 res->VmSize_total = limit[1].value * pagesize;
272 res->VmRSS_total = limit[2].value;
273 res->start_time_oldest= getUptime() - vstat.uptime/1000000;
275 res->utime_total = 0;
276 res->stime_total = 0;
277 // XXX: arbitrary CPU limit.
278 for (cpu = 0; cpu < 1024; cpu++) {
281 if (vc_sched_info(xid, &sched) == -1)
284 res->utime_total += sched.user_msec;
285 res->stime_total += sched.sys_msec;
290 registerXidCgroups(struct Vector *vec, struct process_info *process)
292 xid_t xid = (xid_t) process->s_context;
295 switch (vc_getXIDType(xid)) {
303 res = Vector_search(vec, &xid, cmpData);
305 struct vc_rlimit_stat limit;
306 struct vc_virt_stat vstat;
307 struct vc_sched_info sched;
315 ssize_t cgroup_len, name_len;
316 unsigned long long rss = 0;
319 unsigned long long stime_total, utime_total;
324 if (vc_virt_stat(xid, &vstat) == -1) {
325 perror("vc_virt_stat()");
328 if (vc_rlimit_stat(xid, RLIMIT_NPROC, &limit) == -1) {
329 perror("vc_rlimit_stat(RLIMIT_NRPOC)");
332 if (vc_get_vhi_name(xid, vcVHI_CONTEXT, vhi_name, sizeof(vhi_name)) == -1) {
333 perror("vc_get_vhi_name(CONTEXT)");
337 if ((fd = open(DEFAULTCONFDIR "/cgroup/mnt", O_RDONLY)) == -1) {
338 if (utilvserver_isDirectory("/sys/fs/cgroup", false)) {
339 strcpy(cgroup, "/sys/fs/cgroup/");
340 cgroup_len = sizeof("/sys/fs/cgroup");
343 strcpy(cgroup, "/dev/cgroup/");
344 cgroup_len = sizeof("/dev/cgroup");
348 cgroup_len = read(fd, cgroup, sizeof(cgroup));
349 if (cgroup_len == -1) {
350 perror("read(cgroup/mnt)");
353 if (cgroup_len > 0) {
355 while (cgroup[cgroup_len - 1] == '\n' || cgroup[cgroup_len - 1] == '\r')
357 cgroup[cgroup_len] = '/';
359 cgroup[cgroup_len] = 0;
363 if ((fd = open(DEFAULTCONFDIR "/cgroup/base", O_RDONLY)) != -1) {
364 len = read(fd, cgroup + cgroup_len, sizeof(cgroup) - cgroup_len);
366 perror("read(cgroup/base)");
371 while (cgroup[cgroup_len + len - 1] == '\n' || cgroup[cgroup_len + len - 1] == '\r')
374 if (cgroup[cgroup_len - 1] != '/') {
375 cgroup[cgroup_len] = '/';
378 cgroup[cgroup_len] = 0;
382 if (access(DEFAULTCONFDIR "/cgroup/per-ss", F_OK) == 0)
385 len = strlen(vhi_name);
386 if ((len + sizeof("/cgroup/name")) >= sizeof(filename)) {
387 WRITE_MSG(2, "too long context name: ");
388 WRITE_STR(2, vhi_name);
392 strcpy(filename, vhi_name);
393 strcpy(filename + len, "/cgroup/name");
395 if ((fd = open(filename, O_RDONLY)) == -1) {
396 char *dir = strrchr(vhi_name, '/');
398 WRITE_MSG(2, "invalid context name: ");
399 WRITE_STR(2, vhi_name);
403 name_len = strlen(dir);
404 if (name_len >= sizeof(name)) {
405 WRITE_MSG(2, "cgroup name too long: ");
413 name_len = read(fd, name, sizeof(name));
414 if (name_len == -1) {
415 perror("read(cgroup/name)");
419 while (name[name_len - 1] == '\n' || name[name_len - 1] == '\r')
421 name[name_len] = '\0';
426 if ((cgroup_len + name_len + sizeof("/memory/memory.stat")) > sizeof(filename)) {
427 WRITE_MSG(2, "cgroup name too long: ");
428 WRITE_STR(2, cgroup);
432 snprintf(filename, sizeof(filename), "%s%s%s/memory.stat", cgroup, (per_ss ? "/memory" : ""), name);
434 if ((fp = fopen(filename, "r")) == NULL)
435 perror("open(memory.stat)");
437 unsigned long long _rss = 0, _mapped_file = 0;
438 while (fgets(buf, sizeof(buf), fp) != NULL) {
439 if (strncmp(buf, "rss ", 4) == 0) {
440 if ((_rss = strtoull(buf + 4, &endptr, 0)) == ULLONG_MAX ||
441 (*endptr != '\n' && *endptr != '\0')) {
442 perror("strtoull(memory.stat:rss)");
446 else if (strncmp(buf, "mapped_file ", 12) == 0) {
447 if ((_mapped_file = strtoull(buf + 12, &endptr, 0)) == ULLONG_MAX ||
448 (*endptr != '\n' && *endptr != '\0')) {
449 perror("strtoull(memory.stat:mapped_file)");
454 rss = _rss + _mapped_file;
457 snprintf(filename, sizeof(filename), "%s%s%s/cpuacct.stat", cgroup, (per_ss ? "/cpuacct" : ""), name);
459 if ((fd = open(filename, O_RDONLY)) == -1) {
462 // XXX: arbitrary CPU limit.
463 for (cpu = 0; cpu < 1024; cpu++) {
466 if (vc_sched_info(xid, &sched) == -1)
469 utime_total += sched.user_msec;
470 stime_total += sched.sys_msec;
474 if (read(fd, buf, sizeof(buf)) == -1) {
475 perror("read(cpuacct.stat)");
480 if (sscanf(buf, "user %llu\nsystem %llu\n", &utime_total, &stime_total) != 2) {
481 perror("sscanf(cpuacct.stat)");
486 res = Vector_insert(vec, &xid, cmpData);
489 res->process_count = limit.value;
490 res->VmRSS_total = rss / 4096;
491 res->start_time_oldest= getUptime() - vstat.uptime/1000000;
493 res->utime_total = toMsec(utime_total);
494 res->stime_total = toMsec(stime_total);
497 res->VmSize_total += process->VmSize;
501 // shamelessly stolen from procps...
503 find_elf_note(unsigned long findme){
504 unsigned long *ep = (unsigned long *)environ;
507 if(ep[0]==findme) return ep[1];
510 return (unsigned long)(-1);
513 static void initHertz() __attribute__((__constructor__));
514 static void initPageSize() __attribute__((__constructor__));
519 hertz = find_elf_note(AT_CLKTCK);
520 if (hertz==(unsigned long)(-1))
521 hertz = sysconf(_SC_CLK_TCK);
527 pagesize = sysconf(_SC_PAGESIZE);
530 // open the process's status file to get the ctx number, and other stat
531 struct process_info *
532 get_process_info(char *pid)
537 size_t idx, l=strlen(pid);
538 static struct process_info process;
541 process.s_context = vc_get_task_xid(atoi(pid));
543 # warning Compiling in debug-code
544 process.s_context = random()%6;
547 if (process.s_context==VC_NOCTX) {
549 WRITE_MSG(2, "vc_get_task_xid(");
552 WRITE_STR(2, strerror(err));
558 memcpy(buffer, "/proc/", 6); idx = 6;
559 memcpy(buffer+idx, pid, l); idx += l;
560 memcpy(buffer+idx, "/stat", 6);
562 // open the /proc/#/stat file
563 if ((fd = open(buffer, O_RDONLY, 0)) == -1)
565 // put the file in a buffer
566 if (read(fd, buffer, sizeof(buffer)) < 1)
571 p = strchr(buffer, ')'); // go after the PID (process_name)
572 for (idx = 0; idx<12 && *p!='\0'; ++p)
573 if ((*p)==' ') ++idx;
575 process.utime = toMsec(strtol(p, &p, 10));
576 process.stime = toMsec(strtol(p+1, &p, 10));
577 process.cutime = toMsec(strtol(p+1, &p, 10));
578 process.cstime = toMsec(strtol(p+1, &p, 10));
580 for (idx = 0; idx<5 && *p!='\0'; ++p)
581 if ((*p)==' ') ++idx;
583 process.start_time = toMsec(strtol(p, &p, 10));
584 process.VmSize = strtol(p+1, &p, 10);
585 process.VmRSS = strtol(p+1, &p, 10);
587 //printf("pid=%s, start_time=%llu\n", pid, process.start_time);
592 fillUintZero(char *buf, unsigned long val, size_t cnt)
596 l = utilvserver_fmt_ulong(buf, val);
598 memmove(buf+cnt-l, buf, l);
599 memset(buf, '0', cnt-l);
607 shortenMem(char *buf, unsigned long val)
609 char const * SUFFIXES[] = { " ", "K", "M", "G", "T", "+" };
611 char const * suffix = "+";
613 unsigned int mod = 0;
615 for (i=0; i<6; ++i) {
617 suffix = SUFFIXES[i];
620 mod = 10*(val & 1023)/1024;
624 if (val >9999) val=9999;
625 if (val>=1000) mod=0;
627 l = utilvserver_fmt_ulong(tmp, val);
630 l += utilvserver_fmt_ulong(tmp+l, mod);
632 i = 7-l-strlen(suffix);
634 memcpy(buf+i, tmp, l);
635 memcpy(buf+i+l, suffix, strlen(suffix));
639 shortenTime(char *buf, uint64_t t)
644 unsigned long hh, mm, ss, ms;
657 memcpy(ptr, "INVALID", 7);
661 ptr += utilvserver_fmt_ulong(ptr, t/365);
663 ptr += fillUintZero(ptr, t%365, 2);
665 ptr += fillUintZero(ptr, hh, 2);
668 ptr += utilvserver_fmt_ulong(ptr, t);
670 ptr += fillUintZero(ptr, hh, 2);
672 ptr += fillUintZero(ptr, mm, 2);
675 ptr += utilvserver_fmt_ulong(ptr, hh);
677 ptr += fillUintZero(ptr, mm, 2);
679 ptr += fillUintZero(ptr, ss, 2);
682 ptr += utilvserver_fmt_ulong(ptr, mm);
684 ptr += fillUintZero(ptr, ss, 2);
686 ptr += fillUintZero(ptr, ms, 2);
690 memcpy(buf+10-(ptr-tmp), tmp, ptr-tmp);
694 formatName(char *dst, vcCfgStyle style, char const *name)
698 if (name==0) name = "";
705 memcpy(dst, name, len);
712 memcpy(dst, name, len);
721 showContexts(struct Vector const *vec)
723 uint64_t uptime = getUptime();
724 struct XidData const * ptr = Vector_begin_const(vec);
725 struct XidData const * const end_ptr = Vector_end_const(vec);
728 WRITE_MSG(1, "CTX PROC VSZ RSS userTIME sysTIME UPTIME NAME\n");
729 for (; ptr<end_ptr; ++ptr) {
730 char buf[sizeof(xid_t)*3 + 512];
731 char tmp[sizeof(int)*3 + 2];
734 memset(buf, ' ', sizeof(buf));
735 l = utilvserver_fmt_long(buf, ptr->xid);
736 l = utilvserver_fmt_long(tmp, ptr->process_count);
737 memcpy(buf+10-l, tmp, l);
739 shortenMem (buf+10, ptr->VmSize_total);
740 shortenMem (buf+17, ptr->VmRSS_total*pagesize);
741 shortenTime(buf+24, ptr->utime_total);
742 shortenTime(buf+34, ptr->stime_total);
743 //printf("%llu, %llu\n", uptime, ptr->start_time_oldest);
744 shortenTime(buf+44, uptime - ptr->start_time_oldest);
746 formatName(buf+55, ptr->cfgstyle, ptr->name)[0] = '\0';
748 Vwrite(1, buf, strlen(buf));
754 fillName(void *obj_v, void UNUSED * a)
756 struct XidData * obj = obj_v;
760 obj->cfgstyle = vcCFG_NONE;
761 obj->name = strdup("root server");
765 obj->cfgstyle = vcCFG_NONE;
766 obj->name = strdup("monitoring server");
772 obj->cfgstyle = vcCFG_AUTO;
774 if ((cfgpath = vc_getVserverByCtx(obj->xid, &obj->cfgstyle, 0))==0 ||
775 (obj->name = vc_getVserverName(cfgpath, obj->cfgstyle))==0) {
777 obj->cfgstyle = vcCFG_NONE;
788 freeXidData(void *obj_v, void UNUSED * a)
790 struct XidData * obj = obj_v;
792 free(const_cast(char *)(obj->name));
795 int main(int argc, char **argv)
798 struct dirent* dir_entry;
800 struct Vector xid_data;
804 int c = getopt_long(argc, argv, "+O:", CMDLINE_OPTIONS, 0);
808 case CMD_HELP : showHelp(argv[0]);
809 case CMD_VERSION : showVersion();
812 WRITE_MSG(2, "Try '");
813 WRITE_STR(2, argv[0]);
814 WRITE_MSG(2, " --help' for more information.\n");
821 WRITE_MSG(2, "Unknown parameter, use '--help' for more information\n");
825 if (hertz==0x42) initHertz();
826 if (pagesize==0x42) initPageSize();
828 Vector_init(&xid_data, sizeof(struct XidData));
830 if (vc_isSupported(vcFEATURE_VSTAT)) {
832 Echdir(PROC_VIRT_DIR_NAME);
833 proc_dir = Eopendir(".");
834 while ((dir_entry = readdir(proc_dir)) != NULL) {
835 if (!isNumberUnsigned(dir_entry->d_name, &xid, false))
838 registerXidVstat(&xid_data, xid);
843 void (*handler)(struct Vector *vec, struct process_info *process);
847 if (!switchToWatchXid(&errptr)) {
852 if (access("/proc/uptime",R_OK)==-1 && errno==ENOENT)
854 "WARNING: can not access /proc/uptime. Usually, this is caused by\n"
855 " procfs-security. Please read the FAQ for more details\n"
856 " http://linux-vserver.org/Proc-Security\n");
858 if (vc_isSupported(vcFEATURE_MEMCG))
859 handler = registerXidCgroups;
861 handler = registerXid;
863 Echdir(PROC_DIR_NAME);
864 proc_dir = Eopendir(".");
865 while ((dir_entry = readdir(proc_dir)) != NULL)
867 // select only process file
868 if (!isdigit(*dir_entry->d_name))
871 if (atoi(dir_entry->d_name) != my_pid) {
872 struct process_info * info = get_process_info(dir_entry->d_name);
874 handler(&xid_data, info);
880 Vector_foreach(&xid_data, fillName, 0);
882 // output the ctx_list
883 showContexts(&xid_data);
886 Vector_foreach(&xid_data, freeXidData, 0);
887 Vector_free(&xid_data);