/* * Copyright (C) [2004, 2005, 2006], Hyperic, Inc. * This file is part of SIGAR. * * SIGAR is free software; you can redistribute it and/or modify * it under the terms version 2 of the GNU General Public License as * published by the Free Software Foundation. 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. */ #include #include #include #include #include #include #include #include "sigar.h" #include "sigar_private.h" #include "sigar_util.h" #include "sigar_os.h" #define pageshift(x) ((x) << sigar->pagesize) #define PROC_MEMINFO PROC_FS_ROOT "meminfo" #define PROC_VMSTAT PROC_FS_ROOT "vmstat" #define PROC_MTRR PROC_FS_ROOT "mtrr" #define PROC_STAT PROC_FS_ROOT "stat" #define PROC_UPTIME PROC_FS_ROOT "uptime" #define PROC_LOADAVG PROC_FS_ROOT "loadavg" #define PROC_PSTAT "/stat" #define PROC_PSTATUS "/status" #define SYS_BLOCK "/sys/block" #define PROC_PARTITIONS PROC_FS_ROOT "partitions" #define PROC_DISKSTATS PROC_FS_ROOT "diskstats" /* * /proc/self/stat fields: * 1 - pid * 2 - comm * 3 - state * 4 - ppid * 5 - pgrp * 6 - session * 7 - tty_nr * 8 - tpgid * 9 - flags * 10 - minflt * 11 - cminflt * 12 - majflt * 13 - cmajflt * 14 - utime * 15 - stime * 16 - cutime * 17 - cstime * 18 - priority * 19 - nice * 20 - 0 (removed field) * 21 - itrealvalue * 22 - starttime * 23 - vsize * 24 - rss * 25 - rlim * 26 - startcode * 27 - endcode * 28 - startstack * 29 - kstkesp * 30 - kstkeip * 31 - signal * 32 - blocked * 33 - sigignore * 34 - sigcache * 35 - wchan * 36 - nswap * 37 - cnswap * 38 - exit_signal <-- looking for this. * 39 - processor * ... more for newer RH */ #define PROC_SIGNAL_IX 38 static int get_proc_signal_offset(void) { char buffer[BUFSIZ], *ptr=buffer; int fields = 0; int status = sigar_file2str("/proc/self/stat", buffer, sizeof(buffer)); if (status != SIGAR_OK) { return 1; } while (*ptr) { if (*ptr++ == ' ') { fields++; } } return (fields - PROC_SIGNAL_IX) + 1; } sigar_pid_t sigar_pid_get(sigar_t *sigar) { /* XXX cannot safely cache getpid unless using nptl */ /* we can however, cache it for optimizations in the * case of proc_env_get for example. */ sigar->pid = getpid(); return sigar->pid; } int sigar_os_open(sigar_t **sigar) { char buffer[BUFSIZ], *ptr; int i; int status = sigar_file2str(PROC_STAT, buffer, sizeof(buffer)); struct stat sb; *sigar = malloc(sizeof(**sigar)); if (status != SIGAR_OK) { return status; } (*sigar)->pagesize = 0; i = getpagesize(); while ((i >>= 1) > 0) { (*sigar)->pagesize++; } ptr = strstr(buffer, "\nbtime"); ptr = sigar_skip_token(ptr); (*sigar)->boot_time = sigar_strtoul(ptr); (*sigar)->ticks = sysconf(_SC_CLK_TCK); (*sigar)->ram = -1; (*sigar)->proc_signal_offset = -1; (*sigar)->last_proc_stat.pid = -1; (*sigar)->ht_enabled = -1; if (stat(PROC_DISKSTATS, &sb) == 0) { (*sigar)->iostat = IOSTAT_DISKSTATS; } else if (stat(SYS_BLOCK, &sb) == 0) { (*sigar)->iostat = IOSTAT_SYS; } else if (stat(PROC_PARTITIONS, &sb) == 0) { /* XXX file exists does not mean is has the fields */ (*sigar)->iostat = IOSTAT_PARTITIONS; } else { (*sigar)->iostat = IOSTAT_NONE; } (*sigar)->fsdev = NULL; /* hook for using mirrored /proc/net/tcp file */ (*sigar)->proc_net = getenv("SIGAR_PROC_NET"); return SIGAR_OK; } int sigar_os_close(sigar_t *sigar) { if (sigar->fsdev) { sigar_cache_destroy(sigar->fsdev); } free(sigar); return SIGAR_OK; } char *sigar_os_error_string(sigar_t *sigar, int err) { return NULL; } #define INTEL_ID 0x756e6547 #define AMD_ID 0x68747541 #if defined(__i386__) #define SIGAR_HAS_CPUID static void sigar_cpuid(sigar_uint32_t request, sigar_uint32_t *eax, sigar_uint32_t *ebx, sigar_uint32_t *ecx, sigar_uint32_t *edx) { #if 0 /* does not compile w/ -fPIC */ /* can't find a register in class `BREG' while reloading `asm' */ asm volatile ("cpuid" : "=a" (*eax), "=b" (*ebx), "=c" (*ecx), "=d" (*edx) : "a" (request)); #else /* derived from: */ /* http://svn.red-bean.com/repos/minor/trunk/gc/barriers-ia-32.c */ asm volatile ("mov %%ebx, %%esi\n\t" "cpuid\n\t" "xchgl %%ebx, %%esi" : "=a" (*eax), "=S" (*ebx), "=c" (*ecx), "=d" (*edx) : "0" (request) : "memory"); #endif } #elif defined(__amd64__) #define SIGAR_HAS_CPUID static void sigar_cpuid(unsigned int request, unsigned int *eax, unsigned int *ebx, unsigned int *ecx, unsigned int *edx) { /* http://svn.red-bean.com/repos/minor/trunk/gc/barriers-amd64.c */ asm volatile ("cpuid\n\t" : "=a" (*eax), "=b" (*ebx), "=c" (*ecx), "=d" (*edx) : "0" (request) : "memory"); } #endif #ifdef SIGAR_HAS_CPUID static int is_ht_enabled(sigar_t *sigar) { sigar_uint32_t eax, ebx, ecx, edx; if (sigar->ht_enabled != -1) { /* only check once */ return sigar->ht_enabled; } sigar->ht_enabled = 0; sigar->lcpu = 0; sigar_cpuid(0, &eax, &ebx, &ecx, &edx); if ((ebx == INTEL_ID) || (ebx == AMD_ID)) { sigar_cpuid(1, &eax, &ebx, &ecx, &edx); if (edx & (1<<28)) { #ifdef DETECT_HT_ENABLED sigar_uint32_t apic_id = (ebx & 0xFF000000) >> 24; sigar_uint32_t log_id, phy_id_mask=0xFF, i=1; #endif sigar->lcpu = (ebx & 0x00FF0000) >> 16; #ifdef DETECT_HT_ENABLED /* XXX disabled. process affinity mask can throw this off? */ while (i < sigar->lcpu) { i *= 2; phy_id_mask <<= 1; } log_id = apic_id & ~phy_id_mask; if (log_id == 0) { sigar->lcpu = 1; } #endif if (sigar->lcpu > 1) { sigar->ht_enabled = 1; sigar_log_printf(sigar, SIGAR_LOG_DEBUG, "[cpu] HT enabled, siblings: %d", sigar->lcpu); } else { sigar_log(sigar, SIGAR_LOG_DEBUG, "[cpu] HT supported, not enabled."); } } else { sigar_log(sigar, SIGAR_LOG_DEBUG, "[cpu] HT not supported."); } } else { sigar->lcpu = 1; } return sigar->ht_enabled; } #else #define is_ht_enabled(sigar) 0 #endif static int get_ram(sigar_t *sigar, sigar_mem_t *mem) { char buffer[BUFSIZ], *ptr; FILE *fp; int total = 0; sigar_uint64_t sys_total = (mem->total / (1024 * 1024)); if (sigar->ram > 0) { /* return cached value */ mem->ram = sigar->ram; return SIGAR_OK; } if (sigar->ram == 0) { return ENOENT; } /* * Memory Type Range Registers * write-back registers add up to the total. * Well, they are supposed to add up, but seen * at least one configuration where that is not the * case. */ if (!(fp = fopen(PROC_MTRR, "r"))) { return errno; } while ((ptr = fgets(buffer, sizeof(buffer), fp))) { if (!(ptr = strstr(ptr, "size="))) { continue; } if (!strstr(ptr, "write-back")) { continue; } ptr += 5; while (sigar_isspace(*ptr)) { ++ptr; } total += atoi(ptr); } fclose(fp); if ((total - sys_total) > 256) { /* mtrr write-back registers are way off * kernel should not be using more that 256MB of mem */ total = 0; /* punt */ } if (total == 0) { return ENOENT; } mem->ram = sigar->ram = total; return SIGAR_OK; } #define MEMINFO_PARAM(a) a ":", SSTRLEN(a ":") static SIGAR_INLINE sigar_uint64_t sigar_meminfo(char *buffer, char *attr, int len) { sigar_uint64_t val = 0; char *ptr, *tok; if ((ptr = strstr(buffer, attr))) { ptr += len; val = strtoull(ptr, &tok, 0); while (*tok == ' ') { ++tok; } if (*tok == 'k') { val *= 1024; } else if (*tok == 'M') { val *= (1024 * 1024); } } return val; } int sigar_mem_get(sigar_t *sigar, sigar_mem_t *mem) { sigar_uint64_t buffers, cached, kern; char buffer[BUFSIZ]; int status = sigar_file2str(PROC_MEMINFO, buffer, sizeof(buffer)); if (status != SIGAR_OK) { return status; } mem->total = sigar_meminfo(buffer, MEMINFO_PARAM("MemTotal")); mem->free = sigar_meminfo(buffer, MEMINFO_PARAM("MemFree")); mem->used = mem->total - mem->free; buffers = sigar_meminfo(buffer, MEMINFO_PARAM("Buffers")); cached = sigar_meminfo(buffer, MEMINFO_PARAM("Cached")); kern = buffers + cached; mem->actual_free = mem->free + kern; mem->actual_used = mem->used - kern; if (get_ram(sigar, mem) != SIGAR_OK) { /* XXX other options on failure? */ sigar_mem_calc_ram(sigar, mem); } return SIGAR_OK; } int sigar_swap_get(sigar_t *sigar, sigar_swap_t *swap) { char buffer[BUFSIZ], *ptr; /* XXX: we open/parse the same file here as sigar_mem_get */ int status = sigar_file2str(PROC_MEMINFO, buffer, sizeof(buffer)); if (status != SIGAR_OK) { return status; } swap->total = sigar_meminfo(buffer, MEMINFO_PARAM("SwapTotal")); swap->free = sigar_meminfo(buffer, MEMINFO_PARAM("SwapFree")); swap->used = swap->total - swap->free; swap->page_in = swap->page_out = -1; status = sigar_file2str(PROC_VMSTAT, buffer, sizeof(buffer)); if (status == SIGAR_OK) { /* 2.6+ kernel */ if ((ptr = strstr(buffer, "\npswpin"))) { ptr = sigar_skip_token(ptr); swap->page_in = sigar_strtoull(ptr); ptr = sigar_skip_token(ptr); swap->page_out = sigar_strtoull(ptr); } } else { /* 2.2, 2.4 kernels */ status = sigar_file2str(PROC_STAT, buffer, sizeof(buffer)); if (status != SIGAR_OK) { return status; } if ((ptr = strstr(buffer, "\nswap"))) { ptr = sigar_skip_token(ptr); swap->page_in = sigar_strtoull(ptr); swap->page_out = sigar_strtoull(ptr); } } return SIGAR_OK; } static void get_cpu_metrics(sigar_t *sigar, sigar_cpu_t *cpu, char *line) { char *ptr = sigar_skip_token(line); /* "cpu%d" */ cpu->user += SIGAR_TICK2MSEC(sigar_strtoull(ptr)); cpu->nice += SIGAR_TICK2MSEC(sigar_strtoull(ptr)); cpu->sys += SIGAR_TICK2MSEC(sigar_strtoull(ptr)); cpu->idle += SIGAR_TICK2MSEC(sigar_strtoull(ptr)); if (*ptr == ' ') { /* 2.6+ kernels only */ cpu->wait += SIGAR_TICK2MSEC(sigar_strtoull(ptr)); } cpu->total = cpu->user + cpu->nice + cpu->sys + cpu->idle + cpu->wait; } int sigar_cpu_get(sigar_t *sigar, sigar_cpu_t *cpu) { char buffer[BUFSIZ]; int status = sigar_file2str(PROC_STAT, buffer, sizeof(buffer)); if (status != SIGAR_OK) { return status; } SIGAR_ZERO(cpu); get_cpu_metrics(sigar, cpu, buffer); return SIGAR_OK; } int sigar_cpu_list_get(sigar_t *sigar, sigar_cpu_list_t *cpulist) { FILE *fp; char buffer[BUFSIZ], cpu_total[BUFSIZ], *ptr; int hthread = is_ht_enabled(sigar), i=0; sigar_cpu_t *cpu; if (!(fp = fopen(PROC_STAT, "r"))) { return errno; } /* skip first line */ (void)fgets(cpu_total, sizeof(cpu_total), fp); sigar_cpu_list_create(cpulist); /* XXX: merge times of logical processors if hyperthreading */ while ((ptr = fgets(buffer, sizeof(buffer), fp))) { if (!strnEQ(ptr, "cpu", 3)) { break; } if (hthread && (i % sigar->lcpu)) { /* merge times of logical processors */ cpu = &cpulist->data[cpulist->number-1]; } else { SIGAR_CPU_LIST_GROW(cpulist); cpu = &cpulist->data[cpulist->number++]; SIGAR_ZERO(cpu); } get_cpu_metrics(sigar, cpu, ptr); i++; } fclose(fp); if (cpulist->number == 0) { /* likely older kernel where cpu\d is not present */ cpu = &cpulist->data[cpulist->number++]; SIGAR_ZERO(cpu); get_cpu_metrics(sigar, cpu, cpu_total); } return SIGAR_OK; } int sigar_uptime_get(sigar_t *sigar, sigar_uptime_t *uptime) { char buffer[BUFSIZ], *ptr = buffer; int status = sigar_file2str(PROC_UPTIME, buffer, sizeof(buffer)); if (status != SIGAR_OK) { return status; } uptime->uptime = strtod(buffer, &ptr); return SIGAR_OK; } int sigar_loadavg_get(sigar_t *sigar, sigar_loadavg_t *loadavg) { char buffer[BUFSIZ], *ptr = buffer; int status = sigar_file2str(PROC_LOADAVG, buffer, sizeof(buffer)); if (status != SIGAR_OK) { return status; } loadavg->loadavg[0] = strtod(buffer, &ptr); loadavg->loadavg[1] = strtod(ptr, &ptr); loadavg->loadavg[2] = strtod(ptr, &ptr); return SIGAR_OK; } /* * seems the easiest/fastest way to tell if a process listed in /proc * is a thread is to check the "exit signal" flag in /proc/num/stat. * any value other than SIGCHLD seems to be a thread. this make hulk mad. * redhat's procps patch (named "threadbadhack.pat") does not use * this flag to filter out threads. instead does much more expensive * comparisions. their patch also bubbles up thread cpu times to the main * process. functionality we currently lack. * when nptl is in use, this is not the case and all threads spawned from * a process have the same pid. however, it seems both old-style linux * threads and nptl threads can be run on the same machine. * there is also the "Tgid" field in /proc/self/status which could be used * to detect threads, but this is not available in older kernels. */ static SIGAR_INLINE int proc_isthread(sigar_t *sigar, char *pidstr, int len) { char buffer[BUFSIZ], *ptr=buffer; int fd, n, offset=sigar->proc_signal_offset; /* sprintf(buffer, "/proc/%s/stat", pidstr) */ memcpy(ptr, PROCP_FS_ROOT, SSTRLEN(PROCP_FS_ROOT)); ptr += SSTRLEN(PROCP_FS_ROOT); memcpy(ptr, pidstr, len); ptr += len; memcpy(ptr, PROC_PSTAT, SSTRLEN(PROC_PSTAT)); ptr += SSTRLEN(PROC_PSTAT); *ptr = '\0'; if ((fd = open(buffer, O_RDONLY)) < 0) { /* unlikely if pid was from readdir proc */ return 0; } n = read(fd, buffer, sizeof(buffer)); close(fd); if (n < 0) { return 0; /* chances: slim..none */ } buffer[n--] = '\0'; /* exit_signal is the second to last field so we look backwards. * XXX if newer kernels drop more turds in this file we'll need * to go the other way. luckily linux has no real api for this shit. */ /* skip trailing crap */ while ((n > 0) && !isdigit(buffer[n--])) ; while (offset-- > 0) { /* skip last field */ while ((n > 0) && isdigit(buffer[n--])) ; /* skip whitespace */ while ((n > 0) && !isdigit(buffer[n--])) ; } if (n < 3) { return 0; /* hulk smashed /proc? */ } ptr = &buffer[n]; /* * '17' == SIGCHLD == real process. * '33' and '0' are threads */ if ((*ptr++ == '1') && (*ptr++ == '7') && (*ptr++ == ' ')) { return 0; } return 1; } int sigar_os_proc_list_get(sigar_t *sigar, sigar_proc_list_t *proclist) { DIR *dirp = opendir(PROCP_FS_ROOT); struct dirent *ent, dbuf; if (!dirp) { return errno; } if (sigar->proc_signal_offset == -1) { sigar->proc_signal_offset = get_proc_signal_offset(); } while (readdir_r(dirp, &dbuf, &ent) == 0) { if (!ent) { break; } if (!sigar_isdigit(*ent->d_name)) { continue; } if (proc_isthread(sigar, ent->d_name, strlen(ent->d_name))) { continue; } /* XXX: more sanity checking */ SIGAR_PROC_LIST_GROW(proclist); proclist->data[proclist->number++] = strtoul(ent->d_name, NULL, 10); } closedir(dirp); return SIGAR_OK; } static int proc_stat_read(sigar_t *sigar, sigar_pid_t pid) { char buffer[BUFSIZ], *ptr=buffer, *tmp; unsigned int len; linux_proc_stat_t *pstat = &sigar->last_proc_stat; int status; time_t timenow = time(NULL); /* * short-lived cache read/parse of last /proc/pid/stat * as this info is spread out across a few functions. */ if (pstat->pid == pid) { if ((timenow - pstat->mtime) < SIGAR_LAST_PROC_EXPIRE) { return SIGAR_OK; } } pstat->pid = pid; pstat->mtime = timenow; status = SIGAR_PROC_FILE2STR(buffer, pid, PROC_PSTAT); if (status != SIGAR_OK) { return status; } ptr = strchr(ptr, '(')+1; tmp = strrchr(ptr, ')'); len = tmp-ptr; if (len >= sizeof(pstat->name)) { len = sizeof(pstat->name)-1; } /* (1,2) */ memcpy(pstat->name, ptr, len); pstat->name[len] = '\0'; ptr = tmp+1; SIGAR_SKIP_SPACE(ptr); pstat->state = *ptr++; /* (3) */ SIGAR_SKIP_SPACE(ptr); pstat->ppid = sigar_strtoul(ptr); /* (4) */ ptr = sigar_skip_token(ptr); /* (5) pgrp */ ptr = sigar_skip_token(ptr); /* (6) session */ pstat->tty = sigar_strtoul(ptr); /* (7) */ ptr = sigar_skip_token(ptr); /* (8) tty pgrp */ ptr = sigar_skip_token(ptr); /* (9) flags */ pstat->minor_faults = sigar_strtoull(ptr); /* (10) */ ptr = sigar_skip_token(ptr); /* (11) cmin flt */ pstat->major_faults = sigar_strtoull(ptr); /* (12) */ ptr = sigar_skip_token(ptr); /* (13) cmaj flt */ pstat->utime = SIGAR_TICK2MSEC(sigar_strtoull(ptr)); /* (14) */ pstat->stime = SIGAR_TICK2MSEC(sigar_strtoull(ptr)); /* (15) */ ptr = sigar_skip_token(ptr); /* (16) cutime */ ptr = sigar_skip_token(ptr); /* (17) cstime */ pstat->priority = sigar_strtoul(ptr); /* (18) */ pstat->nice = sigar_strtoul(ptr); /* (19) */ ptr = sigar_skip_token(ptr); /* (20) timeout */ ptr = sigar_skip_token(ptr); /* (21) it_real_value */ pstat->start_time = sigar_strtoul(ptr); /* (22) */ pstat->start_time /= sigar->ticks; pstat->start_time += sigar->boot_time; /* seconds */ pstat->start_time *= 1000; /* milliseconds */ pstat->vsize = sigar_strtoull(ptr); /* (23) */ pstat->rss = pageshift(sigar_strtoull(ptr)); /* (24) */ ptr = sigar_skip_token(ptr); /* (25) rlim */ ptr = sigar_skip_token(ptr); /* (26) startcode */ ptr = sigar_skip_token(ptr); /* (27) endcode */ ptr = sigar_skip_token(ptr); /* (28) startstack */ ptr = sigar_skip_token(ptr); /* (29) kstkesp */ ptr = sigar_skip_token(ptr); /* (30) kstkeip */ ptr = sigar_skip_token(ptr); /* (31) signal */ ptr = sigar_skip_token(ptr); /* (32) blocked */ ptr = sigar_skip_token(ptr); /* (33) sigignore */ ptr = sigar_skip_token(ptr); /* (34) sigcache */ ptr = sigar_skip_token(ptr); /* (35) wchan */ ptr = sigar_skip_token(ptr); /* (36) nswap */ ptr = sigar_skip_token(ptr); /* (37) cnswap */ ptr = sigar_skip_token(ptr); /* (38) exit_signal */ pstat->processor = sigar_strtoul(ptr); /* (39) */ return SIGAR_OK; } int sigar_proc_mem_get(sigar_t *sigar, sigar_pid_t pid, sigar_proc_mem_t *procmem) { char buffer[BUFSIZ], *ptr=buffer; int status = proc_stat_read(sigar, pid); linux_proc_stat_t *pstat = &sigar->last_proc_stat; procmem->minor_faults = pstat->minor_faults; procmem->major_faults = pstat->major_faults; procmem->page_faults = procmem->minor_faults + procmem->major_faults; status = SIGAR_PROC_FILE2STR(buffer, pid, "/statm"); if (status != SIGAR_OK) { return status; } procmem->size = pageshift(sigar_strtoull(ptr)); procmem->resident = pageshift(sigar_strtoull(ptr)); procmem->share = pageshift(sigar_strtoull(ptr)); return SIGAR_OK; } #define NO_ID_MSG "[proc_cred] /proc/%lu" PROC_PSTATUS " missing " int sigar_proc_cred_get(sigar_t *sigar, sigar_pid_t pid, sigar_proc_cred_t *proccred) { char buffer[BUFSIZ], *ptr; int status = SIGAR_PROC_FILE2STR(buffer, pid, PROC_PSTATUS); if (status != SIGAR_OK) { return status; } if ((ptr = strstr(buffer, "\nUid:"))) { ptr = sigar_skip_token(ptr); proccred->uid = sigar_strtoul(ptr); proccred->euid = sigar_strtoul(ptr); } else { sigar_log_printf(sigar, SIGAR_LOG_WARN, NO_ID_MSG "Uid", pid); return ENOENT; } if ((ptr = strstr(ptr, "\nGid:"))) { ptr = sigar_skip_token(ptr); proccred->gid = sigar_strtoul(ptr); proccred->egid = sigar_strtoul(ptr); } else { sigar_log_printf(sigar, SIGAR_LOG_WARN, NO_ID_MSG "Gid", pid); return ENOENT; } return SIGAR_OK; } int sigar_proc_time_get(sigar_t *sigar, sigar_pid_t pid, sigar_proc_time_t *proctime) { int status = proc_stat_read(sigar, pid); linux_proc_stat_t *pstat = &sigar->last_proc_stat; if (status != SIGAR_OK) { return status; } proctime->user = pstat->utime; proctime->sys = pstat->stime; proctime->total = proctime->user + proctime->sys; proctime->start_time = pstat->start_time; return SIGAR_OK; } static int proc_status_get(sigar_t *sigar, sigar_pid_t pid, sigar_proc_state_t *procstate) { char buffer[BUFSIZ], *ptr; int status = SIGAR_PROC_FILE2STR(buffer, pid, PROC_PSTATUS); if (status != SIGAR_OK) { return status; } ptr = strstr(buffer, "\nThreads:"); if (ptr) { /* 2.6+ kernel only */ ptr = sigar_skip_token(ptr); procstate->threads = sigar_strtoul(ptr); } else { procstate->threads = SIGAR_FIELD_NOTIMPL; } return SIGAR_OK; } int sigar_proc_state_get(sigar_t *sigar, sigar_pid_t pid, sigar_proc_state_t *procstate) { int status = proc_stat_read(sigar, pid); linux_proc_stat_t *pstat = &sigar->last_proc_stat; if (status != SIGAR_OK) { return status; } memcpy(procstate->name, pstat->name, sizeof(procstate->name)); procstate->state = pstat->state; procstate->ppid = pstat->ppid; procstate->tty = pstat->tty; procstate->priority = pstat->priority; procstate->nice = pstat->nice; procstate->processor = pstat->processor; if (is_ht_enabled(sigar)) { procstate->processor /= sigar->lcpu; } proc_status_get(sigar, pid, procstate); return SIGAR_OK; } int sigar_os_proc_args_get(sigar_t *sigar, sigar_pid_t pid, sigar_proc_args_t *procargs) { return sigar_procfs_args_get(sigar, pid, procargs); } int sigar_proc_env_get(sigar_t *sigar, sigar_pid_t pid, sigar_proc_env_t *procenv) { int fd; char buffer[ARG_MAX]; /* XXX: ARG_MAX == 130k */ char name[BUFSIZ]; size_t len; char *ptr, *end; /* optimize if pid == $$ and type == ENV_KEY */ SIGAR_PROC_ENV_KEY_LOOKUP(); (void)SIGAR_PROC_FILENAME(name, pid, "/environ"); if ((fd = open(name, O_RDONLY)) < 0) { if (errno == ENOENT) { return ESRCH; } return errno; } len = read(fd, buffer, sizeof(buffer)); close(fd); buffer[len] = '\0'; ptr = buffer; end = buffer + len; while (ptr < end) { char *val = strchr(ptr, '='); int klen, vlen, status; char key[128]; /* XXX is there a max key size? */ if (val == NULL) { /* not key=val format */ break; } klen = val - ptr; SIGAR_SSTRCPY(key, ptr); key[klen] = '\0'; ++val; vlen = strlen(val); status = procenv->env_getter(procenv->data, key, klen, val, vlen); if (status != SIGAR_OK) { /* not an error; just stop iterating */ break; } ptr += (klen + 1 + vlen + 1); } return SIGAR_OK; } int sigar_proc_fd_get(sigar_t *sigar, sigar_pid_t pid, sigar_proc_fd_t *procfd) { int status = sigar_proc_fd_count(sigar, pid, &procfd->total); return status; } int sigar_proc_exe_get(sigar_t *sigar, sigar_pid_t pid, sigar_proc_exe_t *procexe) { int len; char name[BUFSIZ]; (void)SIGAR_PROC_FILENAME(name, pid, "/cwd"); if ((len = readlink(name, procexe->cwd, sizeof(procexe->cwd)-1)) < 0) { return errno; } procexe->cwd[len] = '\0'; (void)SIGAR_PROC_FILENAME(name, pid, "/exe"); if ((len = readlink(name, procexe->name, sizeof(procexe->name)-1)) < 0) { return errno; } procexe->name[len] = '\0'; (void)SIGAR_PROC_FILENAME(name, pid, "/root"); if ((len = readlink(name, procexe->root, sizeof(procexe->root)-1)) < 0) { return errno; } procexe->root[len] = '\0'; return SIGAR_OK; } int sigar_proc_modules_get(sigar_t *sigar, sigar_pid_t pid, sigar_proc_modules_t *procmods) { FILE *fp; char buffer[BUFSIZ], *ptr; unsigned long inode, last_inode = 0; (void)SIGAR_PROC_FILENAME(buffer, pid, "/maps"); if (!(fp = fopen(buffer, "r"))) { return errno; } while ((ptr = fgets(buffer, sizeof(buffer), fp))) { int len, status; /* skip region, flags, offset, dev */ ptr = sigar_skip_multiple_token(ptr, 4); inode = sigar_strtoul(ptr); if ((inode == 0) || (inode == last_inode)) { last_inode = 0; continue; } last_inode = inode; SIGAR_SKIP_SPACE(ptr); len = strlen(ptr); ptr[len-1] = '\0'; /* chop \n */ status = procmods->module_getter(procmods->data, ptr, len-1); if (status != SIGAR_OK) { /* not an error; just stop iterating */ break; } } fclose(fp); return SIGAR_OK; } int sigar_thread_cpu_get(sigar_t *sigar, sigar_uint64_t id, sigar_thread_cpu_t *cpu) { struct tms now; if (id != 0) { return SIGAR_ENOTIMPL; } times(&now); cpu->user = SIGAR_TICK2NSEC(now.tms_utime); cpu->sys = SIGAR_TICK2NSEC(now.tms_stime); cpu->total = SIGAR_TICK2NSEC(now.tms_utime + now.tms_stime); return SIGAR_OK; } #include #include int sigar_os_fs_type_get(sigar_file_system_t *fsp) { char *type = fsp->sys_type_name; switch (*type) { case 'e': if (strnEQ(type, "ext", 3)) { fsp->type = SIGAR_FSTYPE_LOCAL_DISK; } break; case 'h': if (strEQ(type, "hpfs")) { fsp->type = SIGAR_FSTYPE_LOCAL_DISK; } break; case 'r': if (strEQ(type, "reiserfs")) { fsp->type = SIGAR_FSTYPE_LOCAL_DISK; } break; case 'x': if (strEQ(type, "xfs") || strEQ(type, "xiafs")) { fsp->type = SIGAR_FSTYPE_LOCAL_DISK; } break; } return fsp->type; } int sigar_file_system_list_get(sigar_t *sigar, sigar_file_system_list_t *fslist) { struct mntent ent; char buf[1025]; /* buffer for strings within ent */ FILE *fp; sigar_file_system_t *fsp; if (!(fp = setmntent(MOUNTED, "r"))) { return errno; } sigar_file_system_list_create(fslist); while (getmntent_r(fp, &ent, buf, sizeof(buf))) { SIGAR_FILE_SYSTEM_LIST_GROW(fslist); fsp = &fslist->data[fslist->number++]; fsp->type = SIGAR_FSTYPE_UNKNOWN; /* unknown, will be set later */ SIGAR_SSTRCPY(fsp->dir_name, ent.mnt_dir); SIGAR_SSTRCPY(fsp->dev_name, ent.mnt_fsname); SIGAR_SSTRCPY(fsp->sys_type_name, ent.mnt_type); sigar_fs_type_get(fsp); } endmntent(fp); return SIGAR_OK; } #define FSDEV_NONE "__NONE__" #define FSDEV_ID(sb) (sb.st_ino + sb.st_dev) #define FSDEV_IS_DEV(dev) strnEQ(dev, "/dev/", 5) #define ST_MAJOR(sb) major((sb).st_rdev) #define ST_MINOR(sb) minor((sb).st_rdev) static void fsdev_free(void *ptr) { if (ptr != FSDEV_NONE) { free(ptr); } } static char *get_fsdev(sigar_t *sigar, const char *dirname, char *fsdev) { sigar_cache_entry_t *entry; struct stat sb; sigar_uint64_t id; int debug = SIGAR_LOG_IS_DEBUG(sigar); if (stat(dirname, &sb) < 0) { if (debug) { sigar_log_printf(sigar, SIGAR_LOG_DEBUG, "[fsdev] stat(%s) failed", dirname); } return NULL; } id = FSDEV_ID(sb); if (!sigar->fsdev) { sigar->fsdev = sigar_cache_new(15); sigar->fsdev->free_value = fsdev_free; } entry = sigar_cache_get(sigar->fsdev, id); if (entry->value == NULL) { sigar_file_system_list_t fslist; int status = sigar_file_system_list_get(sigar, &fslist); int i; if (status != SIGAR_OK) { sigar_log_printf(sigar, SIGAR_LOG_DEBUG, "[fsdev] file_system_list failed: %s", sigar_strerror(sigar, status)); return NULL; } for (i=0; itype == SIGAR_FSTYPE_LOCAL_DISK) { int retval = stat(fsp->dir_name, &sb); sigar_cache_entry_t *ent; char *ptr; if (retval < 0) { if (debug) { sigar_log_printf(sigar, SIGAR_LOG_DEBUG, "[fsdev] inode stat(%s) failed", fsp->dir_name); } return NULL; /* cant cache w/o inode */ } ent = sigar_cache_get(sigar->fsdev, FSDEV_ID(sb)); if (ent->value) { continue; /* already cached */ } ptr = fsp->dev_name; if (FSDEV_IS_DEV(ptr)) { ent->value = sigar_strdup(ptr); if (debug) { sigar_log_printf(sigar, SIGAR_LOG_DEBUG, "[fsdev] map %s -> %s", fsp->dir_name, (char*)ent->value); } continue; } ent->value = FSDEV_NONE; } } sigar_file_system_list_destroy(sigar, &fslist); } if (entry->value == FSDEV_NONE) { return NULL; } else if (entry->value == NULL) { entry->value = FSDEV_NONE; return NULL; } else { strcpy(fsdev, (char*)entry->value); return fsdev; } } static int get_iostat_sys(sigar_t *sigar, const char *dirname, sigar_file_system_usage_t *fsusage) { char stat[1025], dev[1025]; char *name, *ptr, *fsdev; int partition, status; name = fsdev = get_fsdev(sigar, dirname, dev); if (!name) { return ENOENT; } if (FSDEV_IS_DEV(name)) { name += 5; /* strip "/dev/" */ } while (!sigar_isdigit(*fsdev)) { fsdev++; } partition = strtoul(fsdev, NULL, 0); *fsdev = '\0'; snprintf(stat, sizeof(stat), SYS_BLOCK "/%s/%s%d/stat", name, name, partition); status = sigar_file2str(stat, dev, sizeof(dev)); if (status != SIGAR_OK) { return status; } ptr = dev; ptr = sigar_skip_token(ptr); fsusage->disk_reads = sigar_strtoull(ptr); ptr = sigar_skip_token(ptr); fsusage->disk_writes = sigar_strtoull(ptr); fsusage->disk_read_bytes = SIGAR_FIELD_NOTIMPL; fsusage->disk_write_bytes = SIGAR_FIELD_NOTIMPL; fsusage->disk_queue = SIGAR_FIELD_NOTIMPL; return SIGAR_OK; } static int get_iostat_proc_dstat(sigar_t *sigar, const char *dirname, sigar_file_system_usage_t *fsusage) { FILE *fp; char buffer[1025], dev[1025]; char *ptr; struct stat sb; if (!get_fsdev(sigar, dirname, dev)) { return ENOENT; } if (stat(dev, &sb) < 0) { return errno; } if (SIGAR_LOG_IS_DEBUG(sigar)) { sigar_log_printf(sigar, SIGAR_LOG_DEBUG, PROC_DISKSTATS " %s -> %s [%d,%d]", dirname, dev, ST_MAJOR(sb), ST_MINOR(sb)); } if (!(fp = fopen(PROC_DISKSTATS, "r"))) { return errno; } while ((ptr = fgets(buffer, sizeof(buffer), fp))) { unsigned long major, minor; major = sigar_strtoul(ptr); minor = sigar_strtoul(ptr); if ((major == ST_MAJOR(sb)) && (minor == ST_MINOR(sb))) { int num, status=SIGAR_OK; unsigned long rio, rmerge, rsect, ruse, wio, wmerge, wsect, wuse, running, use, aveq; ptr = sigar_skip_token(ptr); /* name */ num = sscanf(ptr, "%lu %lu %lu %lu " "%lu %lu %lu %lu " "%lu %lu %lu", &rio, /* 1 # reads issued */ &rmerge, /* 2 # reads merged */ &rsect, /* 3 # sectors read */ &ruse, /* 4 # millis spent reading */ &wio, /* 5 # writes completed */ &wmerge, /* 6 # writes merged */ &wsect, /* 7 # sectors written */ &wuse, /* 8 # millis spent writing */ &running, /* 9 # I/Os currently in progress */ &use, /* 10 # millis spent doing I/Os */ &aveq); /* 11 # of millis spent doing I/Os (weighted) */ if (num == 11) { fsusage->disk_queue = aveq / 1000; } else if (num == 4) { wio = rsect; rsect = rmerge; wsect = ruse; fsusage->disk_queue = SIGAR_FIELD_NOTIMPL; } else { status = ENOENT; } fsusage->disk_reads = rio; fsusage->disk_writes = wio; fsusage->disk_read_bytes = rsect; fsusage->disk_write_bytes = wsect; /* convert sectors to bytes (512 is fixed size in 2.6 kernels) */ fsusage->disk_read_bytes *= 512; fsusage->disk_write_bytes *= 512; fclose(fp); return status; } } fclose(fp); return ENOENT; } static int get_iostat_procp(sigar_t *sigar, const char *dirname, sigar_file_system_usage_t *fsusage) { FILE *fp; char buffer[1025], dev[1025]; char *ptr; struct stat sb; if (!get_fsdev(sigar, dirname, dev)) { return ENOENT; } if (stat(dev, &sb) < 0) { return errno; } if (SIGAR_LOG_IS_DEBUG(sigar)) { sigar_log_printf(sigar, SIGAR_LOG_DEBUG, PROC_PARTITIONS " %s -> %s [%d,%d]", dirname, dev, ST_MAJOR(sb), ST_MINOR(sb)); } if (!(fp = fopen(PROC_PARTITIONS, "r"))) { return errno; } (void)fgets(buffer, sizeof(buffer), fp); /* skip header */ while ((ptr = fgets(buffer, sizeof(buffer), fp))) { unsigned long major, minor; major = sigar_strtoul(ptr); minor = sigar_strtoul(ptr); if ((major == ST_MAJOR(sb)) && (minor == ST_MINOR(sb))) { ptr = sigar_skip_token(ptr); /* blocks */ ptr = sigar_skip_token(ptr); /* name */ fsusage->disk_reads = sigar_strtoull(ptr); /* rio */ ptr = sigar_skip_token(ptr); /* rmerge */ fsusage->disk_read_bytes = sigar_strtoull(ptr); /* rsect */ ptr = sigar_skip_token(ptr); /* ruse */ ptr = sigar_skip_token(ptr); /* wmerge */ fsusage->disk_write_bytes = sigar_strtoull(ptr); /* wsect */ fsusage->disk_writes = sigar_strtoull(ptr); /* wio */ /* wuse, running, use */ ptr = sigar_skip_multiple_token(ptr, 3); fsusage->disk_queue = sigar_strtoull(ptr); /* aveq */ fsusage->disk_queue /= 1000; /* convert sectors to bytes (512 is fixed size in 2.6 kernels) */ fsusage->disk_read_bytes *= 512; fsusage->disk_write_bytes *= 512; fclose(fp); return SIGAR_OK; } } fclose(fp); return ENOENT; } #include #define SIGAR_FS_BLOCKS_TO_BYTES(buf, f) \ (((sigar_uint64_t)buf.f * (buf.f_bsize / 512)) >> 1) int sigar_file_system_usage_get(sigar_t *sigar, const char *dirname, sigar_file_system_usage_t *fsusage) { struct statfs buf; if (statfs(dirname, &buf) != 0) { return errno; } fsusage->total = SIGAR_FS_BLOCKS_TO_BYTES(buf, f_blocks); fsusage->free = SIGAR_FS_BLOCKS_TO_BYTES(buf, f_bfree); fsusage->avail = SIGAR_FS_BLOCKS_TO_BYTES(buf, f_bavail); fsusage->used = fsusage->total - fsusage->free; fsusage->files = buf.f_files; fsusage->free_files = buf.f_ffree; fsusage->use_percent = sigar_file_system_usage_calc_used(sigar, fsusage); /* * 2.2 has metrics /proc/stat, but wtf is the device mapping? * 2.4 has /proc/partitions w/ the metrics. * 2.6 has /proc/partitions w/o the metrics. * instead the metrics are within the /proc-like /sys filesystem. * also has /proc/diskstats */ switch (sigar->iostat) { case IOSTAT_SYS: if (get_iostat_sys(sigar, dirname, fsusage) == SIGAR_OK) { return SIGAR_OK; } break; case IOSTAT_DISKSTATS: if (get_iostat_proc_dstat(sigar, dirname, fsusage) == SIGAR_OK) { return SIGAR_OK; } break; case IOSTAT_PARTITIONS: if (get_iostat_procp(sigar, dirname, fsusage) == SIGAR_OK) { return SIGAR_OK; } break; /* * case IOSTAT_SOME_OTHER_WIERD_THING: * break; */ case IOSTAT_NONE: break; } SIGAR_DISK_STATS_NOTIMPL(fsusage); return SIGAR_OK; } static SIGAR_INLINE char *cpu_info_strval(char *ptr) { if ((ptr = strchr(ptr, ':'))) { ptr++; while (isspace (*ptr)) ptr++; return ptr; } return NULL; } static SIGAR_INLINE void cpu_info_strcpy(char *ptr, char *buf, int len) { int slen; ptr = cpu_info_strval(ptr); if (!ptr) { return; } slen = strlen(ptr); strncpy(buf, ptr, len); buf[len] = '\0'; if (slen < len) { buf[slen-1] = '\0'; /* rid \n */ } } static int get_cpu_info(sigar_t *sigar, sigar_cpu_info_t *info, FILE *fp) { char buffer[BUFSIZ], *ptr; int found = 0; /* UML vm wont have "cpu MHz" or "cache size" fields */ info->mhz = 0; info->cache_size = 0; #ifdef __powerpc64__ SIGAR_SSTRCPY(info->vendor, "IBM"); #endif while ((ptr = fgets(buffer, sizeof(buffer), fp))) { switch (*ptr) { case 'p': /* processor : 0 */ if (strnEQ(ptr, "processor", 9)) { found = 1; } break; case 'v': /* "vendor_id" or "vendor" */ if (strnEQ(ptr, "vendor", 6)) { cpu_info_strcpy(ptr, info->vendor, sizeof(info->vendor)); if (strEQ(info->vendor, "GenuineIntel")) { SIGAR_SSTRCPY(info->vendor, "Intel"); } else if (strEQ(info->vendor, "AuthenticAMD")) { SIGAR_SSTRCPY(info->vendor, "AMD"); } } break; case 'f': if (strnEQ(ptr, "family", 6)) { /* IA64 version of "model name" */ cpu_info_strcpy(ptr, info->model, sizeof(info->model)); sigar_cpu_model_adjust(sigar, info); } break; case 'm': if (strnEQ(ptr, "model name", 10)) { cpu_info_strcpy(ptr, info->model, sizeof(info->model)); sigar_cpu_model_adjust(sigar, info); } break; case 'c': if (strnEQ(ptr, "cpu MHz", 7)) { ptr = cpu_info_strval(ptr); info->mhz = atoi(ptr); } else if (strnEQ(ptr, "cache size", 10)) { ptr = cpu_info_strval(ptr); info->cache_size = sigar_strtoul(ptr); } #ifdef __powerpc64__ /* each /proc/cpuinfo entry looks like so: * processor : 0 * cpu : POWER5 (gr) * clock : 1656.392000MHz * revision : 2.2 */ else if (strnEQ(ptr, "clock", 5)) { ptr = cpu_info_strval(ptr); info->mhz = atoi(ptr); } else if (strnEQ(ptr, "cpu", 3)) { cpu_info_strcpy(ptr, info->model, sizeof(info->model)); if ((ptr = strchr(info->model, ' '))) { /* "POWER5 (gr)" -> "POWER5" */ *ptr = '\0'; } } #endif break; /* lone \n means end of info for this processor */ case '\n': return found; } } return found; } /* /proc/cpuinfo MHz will change w/ AMD + PowerNow */ static void get_cpuinfo_max_freq(sigar_cpu_info_t *cpu_info, int num) { int status; char max_freq[PATH_MAX]; snprintf(max_freq, sizeof(max_freq), "/sys/devices/system/cpu/cpu%d" "/cpufreq/cpuinfo_max_freq", num); status = sigar_file2str(max_freq, max_freq, sizeof(max_freq)-1); if (status == SIGAR_OK) { cpu_info->mhz = atoi(max_freq) / 1000; } } int sigar_cpu_info_list_get(sigar_t *sigar, sigar_cpu_info_list_t *cpu_infos) { FILE *fp; int hthread = is_ht_enabled(sigar), i=0; if (!(fp = fopen(PROC_FS_ROOT "cpuinfo", "r"))) { return errno; } sigar_cpu_info_list_create(cpu_infos); while (get_cpu_info(sigar, &cpu_infos->data[cpu_infos->number], fp)) { if (hthread && (i++ % sigar->lcpu)) { continue; /* fold logical processors if HT */ } get_cpuinfo_max_freq(&cpu_infos->data[cpu_infos->number], cpu_infos->number); ++cpu_infos->number; SIGAR_CPU_INFO_LIST_GROW(cpu_infos); } fclose(fp); return SIGAR_OK; } static SIGAR_INLINE unsigned int hex2int(const char *x, int len) { int i; unsigned int j; for (i=0, j=0; isize = routelist->number = 0; if (!(fp = fopen(PROC_FS_ROOT "net/route", "r"))) { return errno; } sigar_net_route_list_create(routelist); (void)fgets(buffer, sizeof(buffer), fp); /* skip header */ while (fgets(buffer, sizeof(buffer), fp)) { int num; SIGAR_NET_ROUTE_LIST_GROW(routelist); route = &routelist->data[routelist->number++]; /* XXX rid sscanf */ num = sscanf(buffer, ROUTE_FMT, route->ifname, net_addr, gate_addr, &flags, &route->refcnt, &route->use, &route->metric, mask_addr, &route->mtu, &route->window, &route->irtt); if ((num < 10) || !(flags & RTF_UP)) { --routelist->number; continue; } route->flags = flags; sigar_net_address_set(route->destination, hex2int(net_addr, HEX_ENT_LEN)); sigar_net_address_set(route->gateway, hex2int(gate_addr, HEX_ENT_LEN)); sigar_net_address_set(route->mask, hex2int(mask_addr, HEX_ENT_LEN)); } fclose(fp); return SIGAR_OK; } int sigar_net_interface_stat_get(sigar_t *sigar, const char *name, sigar_net_interface_stat_t *ifstat) { int found = 0; char buffer[BUFSIZ]; FILE *fp = fopen(PROC_FS_ROOT "net/dev", "r"); if (!fp) { return errno; } /* skip header */ fgets(buffer, sizeof(buffer), fp); fgets(buffer, sizeof(buffer), fp); while (fgets(buffer, sizeof(buffer), fp)) { char *ptr, *dev; dev = buffer; while (isspace(*dev)) { dev++; } if (!(ptr = strchr(dev, ':'))) { continue; } *ptr++ = 0; if (!strEQ(dev, name)) { continue; } found = 1; ifstat->rx_bytes = sigar_strtoull(ptr); ifstat->rx_packets = sigar_strtoull(ptr); ifstat->rx_errors = sigar_strtoull(ptr); ifstat->rx_dropped = sigar_strtoull(ptr); ifstat->rx_overruns = sigar_strtoull(ptr); ifstat->rx_frame = sigar_strtoull(ptr); /* skip: compressed multicast */ ptr = sigar_skip_multiple_token(ptr, 2); ifstat->tx_bytes = sigar_strtoull(ptr); ifstat->tx_packets = sigar_strtoull(ptr); ifstat->tx_errors = sigar_strtoull(ptr); ifstat->tx_dropped = sigar_strtoull(ptr); ifstat->tx_overruns = sigar_strtoull(ptr); ifstat->tx_collisions = sigar_strtoull(ptr); ifstat->tx_carrier = sigar_strtoull(ptr); ifstat->speed = SIGAR_FIELD_NOTIMPL; break; } fclose(fp); return found ? SIGAR_OK : ENXIO; } static SIGAR_INLINE void convert_hex_address(sigar_net_address_t *address, char *ptr, int len) { if (len > HEX_ENT_LEN) { int i; for (i=0; i<=3; i++, ptr+=HEX_ENT_LEN) { address->addr.in6[i] = hex2int(ptr, HEX_ENT_LEN); } address->family = SIGAR_AF_INET6; } else { address->addr.in = (len == HEX_ENT_LEN) ? hex2int(ptr, HEX_ENT_LEN) : 0; address->family = SIGAR_AF_INET; } } typedef struct { sigar_net_connection_list_t *connlist; sigar_net_connection_t *conn; unsigned long port; } net_conn_getter_t; static int proc_net_walker(sigar_net_connection_walker_t *walker, sigar_net_connection_t *conn) { net_conn_getter_t *getter = (net_conn_getter_t *)walker->data; if (getter->connlist) { SIGAR_NET_CONNLIST_GROW(getter->connlist); memcpy(&getter->connlist->data[getter->connlist->number++], conn, sizeof(*conn)); } else { if ((getter->port == conn->local_port) && (conn->remote_port == 0)) { memcpy(getter->conn, conn, sizeof(*conn)); return !SIGAR_OK; /* break loop */ } } return SIGAR_OK; /* continue loop */ } #define SKIP_WHILE(p, c) while (*p == c) p++ #define SKIP_PAST(p, c) \ while(*p && (*p != c)) p++; \ SKIP_WHILE(p, c) static int proc_net_read(sigar_net_connection_walker_t *walker, const char *fname, int type) { FILE *fp = NULL; char buffer[8192]; sigar_t *sigar = walker->sigar; char *ptr = sigar->proc_net; int flags = walker->flags; if (ptr) { snprintf(buffer, sizeof(buffer), "%s/%s", ptr, fname + sizeof(PROC_FS_ROOT)-1); if ((fp = fopen(buffer, "r"))) { if (SIGAR_LOG_IS_DEBUG(sigar)) { sigar_log_printf(sigar, SIGAR_LOG_DEBUG, "[proc_net] using %s", buffer); } } else if (SIGAR_LOG_IS_DEBUG(sigar)) { sigar_log_printf(sigar, SIGAR_LOG_DEBUG, "[proc_net] cannot open %s", buffer); } } if (!(fp || (fp = fopen(fname, "r")))) { return errno; } fgets(buffer, sizeof(buffer), fp); /* skip header */ while ((ptr = fgets(buffer, sizeof(buffer), fp))) { sigar_net_connection_t conn; char *laddr, *raddr; int laddr_len=0, raddr_len=0; int more; /* skip leading space */ SKIP_WHILE(ptr, ' '); /* skip "%d: " */ SKIP_PAST(ptr, ' '); laddr = ptr; while (*ptr && (*ptr != ':')) { laddr_len++; ptr++; } SKIP_WHILE(ptr, ':'); conn.local_port = (strtoul(ptr, &ptr, 16) & 0xffff); SKIP_WHILE(ptr, ' '); raddr = ptr; while (*ptr && (*ptr != ':')) { raddr_len++; ptr++; } SKIP_WHILE(ptr, ':'); conn.remote_port = (strtoul(ptr, &ptr, 16) & 0xffff); SKIP_WHILE(ptr, ' '); if (!((conn.remote_port && (flags & SIGAR_NETCONN_CLIENT)) || (!conn.remote_port && (flags & SIGAR_NETCONN_SERVER)))) { continue; } conn.type = type; convert_hex_address(&conn.local_address, laddr, laddr_len); convert_hex_address(&conn.remote_address, raddr, raddr_len); /* SIGAR_TCP_* currently matches TCP_* in linux/tcp.h */ conn.state = hex2int(ptr, 2); ptr += 2; SKIP_WHILE(ptr, ' '); conn.send_queue = hex2int(ptr, HEX_ENT_LEN); ptr += HEX_ENT_LEN+1; /* tx + ':' */; conn.receive_queue = hex2int(ptr, HEX_ENT_LEN); ptr += HEX_ENT_LEN; SKIP_WHILE(ptr, ' '); SKIP_PAST(ptr, ' '); /* tr:tm->whem */ SKIP_PAST(ptr, ' '); /* retrnsmt */ conn.uid = sigar_strtoul(ptr); SKIP_WHILE(ptr, ' '); SKIP_PAST(ptr, ' '); /* timeout */ conn.inode = sigar_strtoul(ptr); more = walker->add_connection(walker, &conn); if (more != SIGAR_OK) { fclose(fp); return SIGAR_OK; } } fclose(fp); return SIGAR_OK; } int sigar_net_connection_walk(sigar_net_connection_walker_t *walker) { int flags = walker->flags; int status; if (flags & SIGAR_NETCONN_TCP) { status = proc_net_read(walker, PROC_FS_ROOT "net/tcp", SIGAR_NETCONN_TCP); if (status != SIGAR_OK) { return status; } status = proc_net_read(walker, PROC_FS_ROOT "net/tcp6", SIGAR_NETCONN_TCP); if (!((status == SIGAR_OK) || (status == ENOENT))) { return status; } } if (flags & SIGAR_NETCONN_UDP) { status = proc_net_read(walker, PROC_FS_ROOT "net/udp", SIGAR_NETCONN_UDP); if (status != SIGAR_OK) { return status; } status = proc_net_read(walker, PROC_FS_ROOT "net/udp6", SIGAR_NETCONN_UDP); if (!((status == SIGAR_OK) || (status == ENOENT))) { return status; } } if (flags & SIGAR_NETCONN_RAW) { status = proc_net_read(walker, PROC_FS_ROOT "net/raw", SIGAR_NETCONN_RAW); if (status != SIGAR_OK) { return status; } status = proc_net_read(walker, PROC_FS_ROOT "net/raw6", SIGAR_NETCONN_RAW); if (!((status == SIGAR_OK) || (status == ENOENT))) { return status; } } /* XXX /proc/net/unix */ return SIGAR_OK; } int sigar_net_connection_list_get(sigar_t *sigar, sigar_net_connection_list_t *connlist, int flags) { int status; sigar_net_connection_walker_t walker; net_conn_getter_t getter; sigar_net_connection_list_create(connlist); getter.conn = NULL; getter.connlist = connlist; walker.sigar = sigar; walker.flags = flags; walker.data = &getter; walker.add_connection = proc_net_walker; status = sigar_net_connection_walk(&walker); if (status != SIGAR_OK) { sigar_net_connection_list_destroy(sigar, connlist); } return status; } static int sigar_net_connection_get(sigar_t *sigar, sigar_net_connection_t *netconn, unsigned long port, int flags) { int status; sigar_net_connection_walker_t walker; net_conn_getter_t getter; getter.conn = netconn; getter.connlist = NULL; getter.port = port; walker.sigar = sigar; walker.flags = flags; walker.data = &getter; walker.add_connection = proc_net_walker; status = sigar_net_connection_walk(&walker); return status; } #define SNMP_TCP_PREFIX "Tcp: " SIGAR_DECLARE(int) sigar_tcp_get(sigar_t *sigar, sigar_tcp_t *tcp) { FILE *fp; char buffer[1024], *ptr=buffer; int status = SIGAR_ENOENT; if (!(fp = fopen(PROC_FS_ROOT "net/snmp", "r"))) { return errno; } while (fgets(buffer, sizeof(buffer), fp)) { if (strnEQ(buffer, SNMP_TCP_PREFIX, sizeof(SNMP_TCP_PREFIX)-1)) { if (fgets(buffer, sizeof(buffer), fp)) { status = SIGAR_OK; break; } } } fclose(fp); if (status == SIGAR_OK) { /* assuming field order, same in 2.2, 2.4 and 2.6 kernels */ /* Tcp: RtoAlgorithm RtoMin RtoMax MaxConn */ ptr = sigar_skip_multiple_token(ptr, 5); tcp->active_opens = sigar_strtoull(ptr); tcp->passive_opens = sigar_strtoull(ptr); tcp->attempt_fails = sigar_strtoull(ptr); tcp->estab_resets = sigar_strtoull(ptr); tcp->curr_estab = sigar_strtoull(ptr); tcp->in_segs = sigar_strtoull(ptr); tcp->out_segs = sigar_strtoull(ptr); tcp->retrans_segs = sigar_strtoull(ptr); tcp->in_errs = sigar_strtoull(ptr); tcp->out_rsts = sigar_strtoull(ptr); } return status; } static int sigar_proc_nfs_gets(char *file, char *tok, char *buffer, size_t size) { int status = ENOENT; int len = strlen(tok); FILE *fp = fopen(file, "r"); if (!fp) { return SIGAR_ENOTIMPL; } while (fgets(buffer, size, fp)) { if (strnEQ(buffer, tok, len)) { status = SIGAR_OK; break; } } fclose(fp); return status; } static int sigar_nfs_v2_get(char *file, sigar_nfs_v2_t *nfs) { char buffer[BUFSIZ], *ptr=buffer; int status = sigar_proc_nfs_gets(file, "proc2", buffer, sizeof(buffer)); if (status != SIGAR_OK) { return status; } ptr = sigar_skip_multiple_token(ptr, 2); nfs->null = sigar_strtoull(ptr); nfs->getattr = sigar_strtoull(ptr); nfs->setattr = sigar_strtoull(ptr); nfs->root = sigar_strtoull(ptr); nfs->lookup = sigar_strtoull(ptr); nfs->readlink = sigar_strtoull(ptr); nfs->read = sigar_strtoull(ptr); nfs->writecache = sigar_strtoull(ptr); nfs->write = sigar_strtoull(ptr); nfs->create = sigar_strtoull(ptr); nfs->remove = sigar_strtoull(ptr); nfs->rename = sigar_strtoull(ptr); nfs->link = sigar_strtoull(ptr); nfs->symlink = sigar_strtoull(ptr); nfs->mkdir = sigar_strtoull(ptr); nfs->rmdir = sigar_strtoull(ptr); nfs->readdir = sigar_strtoull(ptr); nfs->fsstat = sigar_strtoull(ptr); return SIGAR_OK; } int sigar_nfs_client_v2_get(sigar_t *sigar, sigar_nfs_client_v2_t *nfs) { return sigar_nfs_v2_get(PROC_FS_ROOT "net/rpc/nfs", (sigar_nfs_v2_t *)nfs); } int sigar_nfs_server_v2_get(sigar_t *sigar, sigar_nfs_server_v2_t *nfs) { return sigar_nfs_v2_get(PROC_FS_ROOT "net/rpc/nfsd", (sigar_nfs_v2_t *)nfs); } static int sigar_nfs_v3_get(char *file, sigar_nfs_v3_t *nfs) { char buffer[BUFSIZ], *ptr=buffer; int status = sigar_proc_nfs_gets(file, "proc3", buffer, sizeof(buffer)); if (status != SIGAR_OK) { return status; } ptr = sigar_skip_multiple_token(ptr, 2); nfs->null = sigar_strtoull(ptr); nfs->getattr = sigar_strtoull(ptr); nfs->setattr = sigar_strtoull(ptr); nfs->lookup = sigar_strtoull(ptr); nfs->access = sigar_strtoull(ptr); nfs->readlink = sigar_strtoull(ptr); nfs->read = sigar_strtoull(ptr); nfs->write = sigar_strtoull(ptr); nfs->create = sigar_strtoull(ptr); nfs->mkdir = sigar_strtoull(ptr); nfs->symlink = sigar_strtoull(ptr); nfs->mknod = sigar_strtoull(ptr); nfs->remove = sigar_strtoull(ptr); nfs->rmdir = sigar_strtoull(ptr); nfs->rename = sigar_strtoull(ptr); nfs->link = sigar_strtoull(ptr); nfs->readdir = sigar_strtoull(ptr); nfs->readdirplus = sigar_strtoull(ptr); nfs->fsstat = sigar_strtoull(ptr); nfs->fsinfo = sigar_strtoull(ptr); nfs->pathconf = sigar_strtoull(ptr); nfs->commit = sigar_strtoull(ptr); return SIGAR_OK; } int sigar_nfs_client_v3_get(sigar_t *sigar, sigar_nfs_client_v3_t *nfs) { return sigar_nfs_v3_get(PROC_FS_ROOT "net/rpc/nfs", (sigar_nfs_v3_t *)nfs); } int sigar_nfs_server_v3_get(sigar_t *sigar, sigar_nfs_server_v3_t *nfs) { return sigar_nfs_v3_get(PROC_FS_ROOT "net/rpc/nfsd", (sigar_nfs_v3_t *)nfs); } int sigar_proc_port_get(sigar_t *sigar, int protocol, unsigned long port, sigar_pid_t *pid) { int status; sigar_net_connection_t netconn; DIR *dirp; struct dirent *ent, dbuf; SIGAR_ZERO(&netconn); *pid = 0; status = sigar_net_connection_get(sigar, &netconn, port, SIGAR_NETCONN_SERVER|protocol); if (status != SIGAR_OK) { return status; } if (netconn.local_port != port) { return SIGAR_OK; /* XXX or ENOENT? */ } if (!(dirp = opendir(PROCP_FS_ROOT))) { return errno; } while (readdir_r(dirp, &dbuf, &ent) == 0) { DIR *fd_dirp; struct dirent *fd_ent, fd_dbuf; struct stat sb; char fd_name[BUFSIZ], pid_name[BUFSIZ]; int len, slen; if (ent == NULL) { break; } if (!sigar_isdigit(*ent->d_name)) { continue; } /* sprintf(pid_name, "/proc/%s", ent->d_name) */ memcpy(&pid_name[0], PROCP_FS_ROOT, SSTRLEN(PROCP_FS_ROOT)); len = SSTRLEN(PROCP_FS_ROOT); pid_name[len++] = '/'; slen = strlen(ent->d_name); memcpy(&pid_name[len], ent->d_name, slen); len += slen; pid_name[len] = '\0'; if (stat(pid_name, &sb) < 0) { continue; } if (sb.st_uid != netconn.uid) { continue; } /* sprintf(fd_name, "%s/fd", pid_name) */ memcpy(&fd_name[0], pid_name, len); memcpy(&fd_name[len], "/fd", 3); fd_name[len+=3] = '\0'; if (!(fd_dirp = opendir(fd_name))) { continue; } while (readdir_r(fd_dirp, &fd_dbuf, &fd_ent) == 0) { char fd_ent_name[BUFSIZ]; if (fd_ent == NULL) { break; } if (!sigar_isdigit(*fd_ent->d_name)) { continue; } /* sprintf(fd_ent_name, "%s/%s", fd_name, fd_ent->d_name) */ slen = strlen(fd_ent->d_name); memcpy(&fd_ent_name[0], fd_name, len); fd_ent_name[len] = '/'; memcpy(&fd_ent_name[len+1], fd_ent->d_name, slen); fd_ent_name[len+1+slen] = '\0'; if (stat(fd_ent_name, &sb) < 0) { continue; } if (sb.st_ino == netconn.inode) { closedir(fd_dirp); closedir(dirp); *pid = strtoul(ent->d_name, NULL, 10); return SIGAR_OK; } } closedir(fd_dirp); } closedir(dirp); return SIGAR_OK; } static void generic_vendor_parse(char *line, sigar_sys_info_t *info) { char *ptr; int len = 0; while (*line) { SIGAR_SKIP_SPACE(line); if (!isdigit(*line)) { ++line; continue; } ptr = line; while ((isdigit(*ptr) || (*ptr == '.'))) { ++ptr; ++len; } if (len) { /* sanity check */ if (len > sizeof(info->vendor_version)) { continue; } memcpy(info->vendor_version, line, len);/*XXX*/ info->vendor_version[len] = '\0'; return; } } } static void redhat_vendor_parse(char *line, sigar_sys_info_t *info) { char *start, *end; generic_vendor_parse(line, info); /* super.parse */ if ((start = strchr(line, '('))) { ++start; if ((end = strchr(start, ')'))) { int len = end-start; memcpy(info->vendor_code_name, start, len);/*XXX*/ info->vendor_code_name[len] = '\0'; } } #define RHEL_PREFIX "Red Hat Enterprise Linux " #define CENTOS_VENDOR "CentOS" if (strnEQ(line, RHEL_PREFIX, sizeof(RHEL_PREFIX)-1)) { snprintf(info->vendor_version, sizeof(info->vendor_version), "Enterprise Linux %c", info->vendor_version[0]); } else if (strnEQ(line, CENTOS_VENDOR, sizeof(CENTOS_VENDOR)-1)) { SIGAR_SSTRCPY(info->vendor, CENTOS_VENDOR); } } #define is_quote(c) ((c == '\'') || (c == '"')) static void kv_parse(char *data, sigar_sys_info_t *info, void (*func)(sigar_sys_info_t *, char *, char *)) { char *ptr = data; int len = strlen(data); char *end = data+len; while (ptr < end) { char *val = strchr(ptr, '='); int klen, vlen; char key[256], *ix; if (!val) { continue; } klen = val - ptr; SIGAR_SSTRCPY(key, ptr); key[klen] = '\0'; ++val; if ((ix = strchr(val, '\n'))) { *ix = '\0'; } vlen = strlen(val); if (is_quote(*val)) { if (is_quote(val[vlen-1])) { val[vlen-1] = '\0'; } ++val; } func(info, key, val); ptr += (klen + 1 + vlen + 1); } } static void lsb_parse(sigar_sys_info_t *info, char *key, char *val) { if (strEQ(key, "DISTRIB_ID")) { SIGAR_SSTRCPY(info->vendor, val); } else if (strEQ(key, "DISTRIB_RELEASE")) { SIGAR_SSTRCPY(info->vendor_version, val); } else if (strEQ(key, "DISTRIB_CODENAME")) { SIGAR_SSTRCPY(info->vendor_code_name, val); } } static void lsb_vendor_parse(char *data, sigar_sys_info_t *info) { kv_parse(data, info, lsb_parse); } static void xen_parse(sigar_sys_info_t *info, char *key, char *val) { if (strEQ(key, "PRODUCT_VERSION")) { SIGAR_SSTRCPY(info->vendor_version, val); } else if (strEQ(key, "KERNEL_VERSION")) { SIGAR_SSTRCPY(info->version, val); } } static void xen_vendor_parse(char *data, sigar_sys_info_t *info) { kv_parse(data, info, xen_parse); snprintf(info->description, sizeof(info->description), "XenServer %s", info->vendor_version); } typedef struct { const char *name; const char *file; void (*parse)(char *, sigar_sys_info_t *); } linux_vendor_info_t; static linux_vendor_info_t linux_vendors[] = { { "Fedora", "/etc/fedora-release", NULL }, { "SuSE", "/etc/SuSE-release", NULL }, { "Gentoo", "/etc/gentoo-release", NULL }, { "Slackware", "/etc/slackware-version", NULL }, { "Mandrake", "/etc/mandrake-release", NULL }, { "VMware", "/proc/vmware/version", NULL }, { "XenSource", "/etc/xensource-inventory", xen_vendor_parse }, { "Red Hat", "/etc/redhat-release", redhat_vendor_parse }, { "lsb", "/etc/lsb-release", lsb_vendor_parse }, { "Debian", "/etc/debian_version", NULL }, { NULL } }; static int get_linux_vendor_info(sigar_sys_info_t *info) { int i, status = ENOENT; /* env vars for testing */ const char *release_file = getenv("SIGAR_OS_RELEASE_FILE"); const char *vendor_name = getenv("SIGAR_OS_VENDOR_NAME"); char buffer[8192], *data; linux_vendor_info_t *vendor = NULL; for (i=0; linux_vendors[i].name; i++) { struct stat sb; vendor = &linux_vendors[i]; if (release_file && vendor_name) { if (!strEQ(vendor->name, vendor_name)) { continue; } } else { if (stat(vendor->file, &sb) < 0) { continue; } release_file = vendor->file; } status = sigar_file2str(release_file, buffer, sizeof(buffer)-1); break; } if (status != SIGAR_OK) { return status; } data = buffer; SIGAR_SSTRCPY(info->vendor, vendor->name); if (vendor->parse) { vendor->parse(data, info); } else { generic_vendor_parse(data, info); } if (info->description[0] == '\0') { snprintf(info->description, sizeof(info->description), "%s %s", info->vendor, info->vendor_version); } return SIGAR_OK; } int sigar_os_sys_info_get(sigar_t *sigar, sigar_sys_info_t *sysinfo) { get_linux_vendor_info(sysinfo); return SIGAR_OK; }