// $Id: vserver-stat.c 2450 2007-01-10 19:27:56Z dhozac $ // Copyright (C) 2003 Enrico Scholz // based on vserver-stat.cc by Guillaum Dallaire and Jacques Gelinas // // This program is free software; you can redistribute it and/or modify // it under the terms of the GNU General Public License as published by // the Free Software Foundation; either version 2, or (at your option) // any later version. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU General Public License for more details. // // You should have received a copy of the GNU General Public License // along with this program; if not, write to the Free Software // Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. #ifdef HAVE_CONFIG_H # include #endif #include "vserver.h" #include "util.h" #include "internal.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #define ENSC_WRAPPERS_DIRENT 1 #define ENSC_WRAPPERS_VSERVER 1 #define ENSC_WRAPPERS_FCNTL 1 #define ENSC_WRAPPERS_UNISTD 1 #include "wrappers.h" #define PROC_DIR_NAME "/proc" #define PROC_VIRT_DIR_NAME "/proc/virtual" #define CTX_DIR_NAME "/var/run/vservers/" #define CTX_NAME_MAX_LEN 50 int wrapper_exit_code = 1; #ifndef AT_CLKTCK #define AT_CLKTCK 17 /* frequency of times() */ #endif static unsigned long hertz =0x42; static unsigned long pagesize=0x42; struct XidData { xid_t xid; int process_count; int VmSize_total; int VmRSS_total; uint64_t start_time_oldest; uint64_t stime_total, utime_total; vcCfgStyle cfgstyle; char const * name; }; struct process_info { long VmSize; // number of pages of virtual memory long VmRSS; // resident set size from /proc/#/stat uint64_t start_time; // start time of process -- milliseconds since 1-1-70 uint64_t stime, utime; // kernel & user-mode CPU time accumulated by process uint64_t cstime, cutime; // cumulative time of process and reaped children xid_t s_context; }; struct ArgInfo { enum { tpUNSET, tpCTX, tpPID } type; xid_t ctx; pid_t pid; unsigned int interval; bool shutdown; bool omit_init; size_t argc; char * const * argv; }; #define CMD_HELP 0x1000 #define CMD_VERSION 0x1001 struct option const CMDLINE_OPTIONS[] = { { "help", no_argument, 0, CMD_HELP }, { "version", no_argument, 0, CMD_VERSION }, { "sort", required_argument, 0, 'O' }, {0,0,0,0} }; static void showHelp(char const *cmd) { WRITE_MSG(1, "Usage: "); WRITE_STR(1, cmd); WRITE_MSG(1, "\n" "Show informations about all the active context.\n\n" " CTX# Context number\n" " #0 = root context\n" " #1 = monitoring context\n" " PROC QTY Quantity of processes in each context\n" " VSZ Number of pages of virtual memory\n" " RSS Resident set size\n" " userTIME User-mode CPU time accumulated\n" " sysTIME Kernel-mode CPU time accumulated\n" " UPTIME Uptime/context\n" " NAME Virtual server name\n" "\n"); exit(0); } static void showVersion() { WRITE_MSG(1, "vserver-stat " VERSION " -- show virtual context statistics\n" "This program is part of " PACKAGE_STRING "\n\n" "Copyright (C) 2003,2005 Enrico Scholz\n" VERSION_COPYRIGHT_DISCLAIMER); exit(0); } // return uptime (in ms) from /proc/uptime static uint64_t getUptime() { int fd; char buffer[64]; char * errptr; size_t len; uint64_t secs; uint32_t msecs=0; // open the /proc/uptime file fd = EopenD("/proc/uptime", O_RDONLY, 0); len = Eread(fd, buffer, sizeof buffer); if (len==sizeof(buffer)) { WRITE_MSG(2, "Too much data in /proc/uptime; aborting...\n"); exit(1); } Eclose(fd); while (len>0 && buffer[len-1]=='\n') --len; buffer[len] = '\0'; secs = strtol(buffer, &errptr, 10); if (*errptr!='.') errptr = buffer; else { unsigned int mult; switch (strlen(errptr+1)) { case 0 : mult = 1000; break; case 1 : mult = 100; break; case 2 : mult = 10; break; case 3 : mult = 1; break; default : mult = 0; break; } msecs = strtol(errptr+1, &errptr, 10) * mult; } if ((*errptr!='\0' && *errptr!=' ') || errptr==buffer) { WRITE_MSG(2, "Bad data in /proc/uptime\n"); exit(1); } return secs*1000 + msecs; } static int cmpData(void const *xid_v, void const *map_v) { xid_t const * const xid = xid_v; struct XidData const * const map = map_v; int res = *xid - map->xid; return res; } static void registerXid(struct Vector *vec, struct process_info *process) { struct XidData *res; res = Vector_search(vec, &process->s_context, cmpData); if (res==0) { res = Vector_insert(vec, &process->s_context, cmpData); res->xid = process->s_context; res->process_count = 0; res->VmSize_total = 0; res->VmRSS_total = 0; res->utime_total = 0; res->stime_total = 0; res->start_time_oldest = process->start_time; } ++res->process_count; res->VmSize_total += process->VmSize; res->VmRSS_total += process->VmRSS; res->utime_total += process->utime + process->cutime; res->stime_total += process->stime + process->cstime; res->start_time_oldest = MIN(res->start_time_oldest, process->start_time); } static void registerXidVstat(struct Vector *vec, unsigned long xid_l) { xid_t xid = (xid_t) xid_l; struct XidData *res; struct vc_rlimit_stat limit[3]; struct vc_virt_stat vstat; struct vc_sched_info sched; int cpu; res = Vector_search(vec, &xid, cmpData); if (res!=0) { WRITE_MSG(2, "Duplicate xid found?!\n"); return; } if (vc_virt_stat(xid, &vstat) == -1) { perror("vc_virt_stat()"); return; } if (vc_rlimit_stat(xid, RLIMIT_NPROC, &limit[0]) == -1) { perror("vc_rlimit_stat(RLIMIT_NRPOC)"); return; } if (vc_rlimit_stat(xid, RLIMIT_AS, &limit[1]) == -1) { perror("vc_rlimit_stat(RLIMIT_AS)"); return; } if (vc_rlimit_stat(xid, RLIMIT_RSS, &limit[2]) == -1) { perror("vc_rlimit_stat(RLIMIT_RSS)"); return; } res = Vector_insert(vec, &xid, cmpData); res->xid = xid; res->process_count = limit[0].value; res->VmSize_total = limit[1].value * pagesize; res->VmRSS_total = limit[2].value; res->start_time_oldest= getUptime() - vstat.uptime/1000000; res->utime_total = 0; res->stime_total = 0; // XXX: arbitrary CPU limit. for (cpu = 0; cpu < 1024; cpu++) { sched.cpu_id = cpu; sched.bucket_id = 0; if (vc_sched_info(xid, &sched) == -1) break; res->utime_total += sched.user_msec; res->stime_total += sched.sys_msec; } } static inline uint64_t toMsec(uint64_t v) { return v*1000llu/hertz; } // shamelessly stolen from procps... static unsigned long find_elf_note(unsigned long findme){ unsigned long *ep = (unsigned long *)environ; while(*ep++); while(*ep){ if(ep[0]==findme) return ep[1]; ep+=2; } return (unsigned long)(-1); } static void initHertz() __attribute__((__constructor__)); static void initPageSize() __attribute__((__constructor__)); static void initHertz() { hertz = find_elf_note(AT_CLKTCK); if (hertz==(unsigned long)(-1)) hertz = sysconf(_SC_CLK_TCK); } static void initPageSize() { pagesize = sysconf(_SC_PAGESIZE); } // open the process's status file to get the ctx number, and other stat struct process_info * get_process_info(char *pid) { int fd; char buffer[1024]; char *p; size_t idx, l=strlen(pid); static struct process_info process; #if 1 process.s_context = vc_get_task_xid(atoi(pid)); #else # warning Compiling in debug-code process.s_context = random()%6; #endif if (process.s_context==VC_NOCTX) { int err=errno; WRITE_MSG(2, "vc_get_task_xid("); WRITE_STR(2, pid); WRITE_MSG(2, "): "); WRITE_STR(2, strerror(err)); WRITE_MSG(2, "\n"); return 0; } memcpy(buffer, "/proc/", 6); idx = 6; memcpy(buffer+idx, pid, l); idx += l; memcpy(buffer+idx, "/stat", 6); // open the /proc/#/stat file if ((fd = open(buffer, O_RDONLY, 0)) == -1) return NULL; // put the file in a buffer if (read(fd, buffer, sizeof(buffer)) < 1) return NULL; close(fd); p = strchr(buffer, ')'); // go after the PID (process_name) for (idx = 0; idx<12 && *p!='\0'; ++p) if ((*p)==' ') ++idx; process.utime = toMsec(strtol(p, &p, 10)); process.stime = toMsec(strtol(p+1, &p, 10)); process.cutime = toMsec(strtol(p+1, &p, 10)); process.cstime = toMsec(strtol(p+1, &p, 10)); for (idx = 0; idx<5 && *p!='\0'; ++p) if ((*p)==' ') ++idx; process.start_time = toMsec(strtol(p, &p, 10)); process.VmSize = strtol(p+1, &p, 10); process.VmRSS = strtol(p+1, &p, 10); //printf("pid=%s, start_time=%llu\n", pid, process.start_time); return &process; } static size_t fillUintZero(char *buf, unsigned long val, size_t cnt) { size_t l; l = utilvserver_fmt_ulong(buf, val); if (l>= 10; } if (val >9999) val=9999; if (val>=1000) mod=0; l = utilvserver_fmt_ulong(tmp, val); if (mod!=0) { tmp[l++] = '.'; l += utilvserver_fmt_ulong(tmp+l, mod); } i = 7-l-strlen(suffix); memcpy(buf+i, tmp, l); memcpy(buf+i+l, suffix, strlen(suffix)); } static void shortenTime(char *buf, uint64_t t) { char tmp[32]; char *ptr = tmp; unsigned long hh, mm, ss, ms; ms = t % 1000; t /= 1000; ss = t%60; t /= 60; mm = t%60; t /= 60; hh = t%24; t /= 24; if (t>999*999) { memcpy(ptr, "INVALID", 7); ptr += 7; } else if (t>999) { ptr += utilvserver_fmt_ulong(ptr, t/365); *ptr++ = 'y'; ptr += fillUintZero(ptr, t%365, 2); *ptr++ = 'd'; ptr += fillUintZero(ptr, hh, 2); } else if (t>0) { ptr += utilvserver_fmt_ulong(ptr, t); *ptr++ = 'd'; ptr += fillUintZero(ptr, hh, 2); *ptr++ = 'h'; ptr += fillUintZero(ptr, mm, 2); } else if (hh>0) { ptr += utilvserver_fmt_ulong(ptr, hh); *ptr++ = 'h'; ptr += fillUintZero(ptr, mm, 2); *ptr++ = 'm'; ptr += fillUintZero(ptr, ss, 2); } else { ptr += utilvserver_fmt_ulong(ptr, mm); *ptr++ = 'm'; ptr += fillUintZero(ptr, ss, 2); *ptr++ = 's'; ptr += fillUintZero(ptr, ms, 2); } *ptr = ' '; memcpy(buf+10-(ptr-tmp), tmp, ptr-tmp); } static char * formatName(char *dst, vcCfgStyle style, char const *name) { size_t len; if (name==0) name = ""; len = strlen(name); switch (style) { case vcCFG_LEGACY : len = MIN(len, 18); *dst++ = '['; memcpy(dst, name, len); dst += len; *dst++ = ']'; break; default : len = MIN(len, 20); memcpy(dst, name, len); dst += len; break; } return dst; } static void showContexts(struct Vector const *vec) { uint64_t uptime = getUptime(); struct XidData const * ptr = Vector_begin_const(vec); struct XidData const * const end_ptr = Vector_end_const(vec); WRITE_MSG(1, "CTX PROC VSZ RSS userTIME sysTIME UPTIME NAME\n"); for (; ptrxid); l = utilvserver_fmt_long(tmp, ptr->process_count); memcpy(buf+10-l, tmp, l); shortenMem (buf+10, ptr->VmSize_total); shortenMem (buf+17, ptr->VmRSS_total*pagesize); shortenTime(buf+24, ptr->utime_total); shortenTime(buf+34, ptr->stime_total); //printf("%llu, %llu\n", uptime, ptr->start_time_oldest); shortenTime(buf+44, uptime - ptr->start_time_oldest); formatName(buf+55, ptr->cfgstyle, ptr->name)[0] = '\0'; Vwrite(1, buf, strlen(buf)); Vwrite(1, "\n", 1); } } static void fillName(void *obj_v, void UNUSED * a) { struct XidData * obj = obj_v; switch (obj->xid) { case 0 : obj->cfgstyle = vcCFG_NONE; obj->name = strdup("root server"); break; case 1 : obj->cfgstyle = vcCFG_NONE; obj->name = strdup("monitoring server"); break; default : { char * cfgpath; obj->cfgstyle = vcCFG_AUTO; if ((cfgpath = vc_getVserverByCtx(obj->xid, &obj->cfgstyle, 0))==0 || (obj->name = vc_getVserverName(cfgpath, obj->cfgstyle))==0) { obj->name = 0; obj->cfgstyle = vcCFG_NONE; } free(cfgpath); break; } } } static void UNUSED freeXidData(void *obj_v, void UNUSED * a) { struct XidData * obj = obj_v; free(const_cast(char *)(obj->name)); } int main(int argc, char **argv) { DIR * proc_dir; struct dirent* dir_entry; pid_t my_pid; struct Vector xid_data; char const * errptr; while (1) { int c = getopt_long(argc, argv, "+O:", CMDLINE_OPTIONS, 0); if (c==-1) break; switch (c) { case CMD_HELP : showHelp(argv[0]); case CMD_VERSION : showVersion(); case 'O' : break; default : WRITE_MSG(2, "Try '"); WRITE_STR(2, argv[0]); WRITE_MSG(2, " --help' for more information.\n"); return EXIT_FAILURE; break; } } if (optind!=argc) { WRITE_MSG(2, "Unknown parameter, use '--help' for more information\n"); return EXIT_FAILURE; } if (hertz==0x42) initHertz(); if (pagesize==0x42) initPageSize(); Vector_init(&xid_data, sizeof(struct XidData)); if (vc_isSupported(vcFEATURE_VSTAT)) { unsigned long xid; Echdir(PROC_VIRT_DIR_NAME); proc_dir = Eopendir("."); while ((dir_entry = readdir(proc_dir)) != NULL) { if (!isNumberUnsigned(dir_entry->d_name, &xid, false)) continue; registerXidVstat(&xid_data, xid); } closedir(proc_dir); } else { my_pid = getpid(); if (!switchToWatchXid(&errptr)) { perror(errptr); exit(1); } if (access("/proc/uptime",R_OK)==-1 && errno==ENOENT) WRITE_MSG(2, "WARNING: can not access /proc/uptime. Usually, this is caused by\n" " procfs-security. Please read the FAQ for more details\n" " http://linux-vserver.org/Proc-Security\n"); Echdir(PROC_DIR_NAME); proc_dir = Eopendir("."); while ((dir_entry = readdir(proc_dir)) != NULL) { // select only process file if (!isdigit(*dir_entry->d_name)) continue; if (atoi(dir_entry->d_name) != my_pid) { struct process_info * info = get_process_info(dir_entry->d_name); if (info) registerXid(&xid_data, info); } } closedir(proc_dir); } Vector_foreach(&xid_data, fillName, 0); // output the ctx_list showContexts(&xid_data); #ifndef NDEBUG Vector_foreach(&xid_data, freeXidData, 0); Vector_free(&xid_data); #endif return 0; }