void term_init(void) { setupterm(NULL, 1, NULL); if (tcgetattr(0, &term_init_termios) < 0) epanic("failed to get terminal attributes"); // Handle terminal resize struct sigaction act = { .sa_handler = term_on_sigwinch }; if (sigaction(SIGWINCH, &act, NULL) < 0) epanic("failed to install SIGWINCH handler"); atexit(term_reset); term_initialized = true; // Enter cursor mode putp(enter_ca_mode); // Enter invisible mode putp(cursor_invisible); // Disable echo and enter canonical (aka cbreak) mode so we // get input without waiting for newline struct termios tc = term_init_termios; tc.c_lflag &= ~(ICANON | ECHO); tc.c_iflag &= ~ICRNL; tc.c_lflag |= ISIG; tc.c_cc[VMIN] = 1; tc.c_cc[VTIME] = 0; if (tcsetattr(0, TCSAFLUSH, &tc) < 0) epanic("failed to set terminal attributes"); }
void cpustats_read(struct cpustats *out) { // On kernels prior to 2.6.37, this can take a long time on // large systems because updating IRQ counts is slow. See // https://lkml.org/lkml/2010/9/29/259 int i; for (i = 0; i < cpustats_cpus; i++) out->cpus[i].online = false; out->online = out->max = 0; out->real = time_usec() * sysconf(_SC_CLK_TCK) / 1000000; if ((readn_str(cpustats_fd, cpustats_buf, cpustats_buf_size)) < 0) epanic("failed to read %s/stat", proc_path); char *pos = cpustats_buf; while (strncmp(pos, "cpu", 3) == 0) { pos += 3; struct cpustat *st; int cpu = -1; if (*pos == ' ') { // Aggregate line st = &out->avg; } else if (isdigit(*pos)) { cpu = strtol(pos, &pos, 10); if (cpu >= cpustats_cpus) goto next; st = &out->cpus[cpu]; } else { goto next; } // Earlier versions of Linux only reported user, nice, // sys, and idle. st->iowait = st->irq = st->softirq = 0; unsigned long long toss; if (sscanf(pos, " %llu %llu %llu %llu %llu %llu %llu", &st->user, &st->nice, &st->sys, &toss, &st->iowait, &st->irq, &st->softirq) < 4) continue; st->online = true; if (cpu != -1) out->online++; if (cpu > out->max) out->max = cpu; next: // Go to the next line while (*pos && *pos != '\n') pos++; if (*pos) pos++; } if ((lseek(cpustats_fd, 0, SEEK_SET)) < 0) epanic("failed to seek %s/stat", proc_path); }
void cpustats_loadavg(float load[3]) { if ((readn_str(cpustats_load_fd, cpustats_buf, cpustats_buf_size)) < 0) epanic("failed to read %s/loadavg", proc_path); if (sscanf(cpustats_buf, "%f %f %f", &load[0], &load[1], &load[2]) != 3) epanic("failed to parse %s/loadavg", proc_path); if ((lseek(cpustats_load_fd, 0, SEEK_SET)) < 0) epanic("failed to seek %s/loadavg", proc_path); }
struct cpustats* cpustats_alloc(void) { struct cpustats *res = malloc(sizeof *res); if (!res) epanic("allocating cpustats"); memset(res, 0, sizeof *res); res->cpus = malloc(cpustats_cpus * sizeof *res->cpus); if (!res->cpus) epanic("allocating per-CPU cputats"); memset(res->cpus, 0, cpustats_cpus * sizeof *res->cpus); return res; }
void CPU_SetAffinity(CPU_Set_t *cs) { int res = numa_sched_setaffinity(0, cs); if (res < 0) epanic("numa_sched_setaffinity"); }
static void ui_init_panes(int n) { free(ui_panes); ui_num_panes = n; if (!(ui_panes = malloc(n * sizeof *ui_panes))) epanic("allocating panes"); }
CPU_Set_t * CPU_GetAffinity(void) { struct bitmask *cs = numa_allocate_cpumask(); int res = numa_sched_getaffinity(0, cs); if (res < 0) epanic("numa_sched_getaffinity"); return cs; }
void cpustats_init(void) { if ((cpustats_fd = open("/proc/stat", O_RDONLY)) < 0) epanic("failed to open /proc/stat"); if ((cpustats_load_fd = open("/proc/loadavg", O_RDONLY)) < 0) epanic("failed to open /proc/loadavg"); // Find the maximum number of CPU's we'll need char *poss = read_all("/sys/devices/system/cpu/possible"); cpustats_cpus = cpuset_max(poss) + 1; free(poss); // Allocate a big buffer to read /proc/stat in to cpustats_buf_size = cpustats_cpus * 128; if (!(cpustats_buf = malloc(cpustats_buf_size))) epanic("allocating cpustats file buffer"); }
void CPU_Bind(int cpu) { CPU_Set_t *cs = numa_allocate_cpumask(); numa_bitmask_setbit(cs, cpu); int res = numa_sched_setaffinity(0, cs); if (res < 0) epanic("bindToCPU(%d)", cpu); CPU_FreeSet(cs); }
char * read_all(const char *path) { FILE *fp = fopen(path, "rb"); if (!fp) epanic("failed to open %s", path); if (fseek(fp, 0, SEEK_END) < 0) epanic("failed to seek in %s", path); long len = ftell(fp); char *buf = malloc(len + 1); if (!buf) epanic("read_all"); rewind(fp); size_t rlen = fread(buf, 1, len, fp); if ((errno = ferror(fp))) epanic("failed to read %s", path); buf[rlen] = 0; fclose(fp); return buf; }
void cpustats_init(void) { cpustats_findproc(); // Find the maximum number of CPU's we'll need #ifdef __FreeBSD__ size_t oldlenp = sizeof(cpustats_cpus); if (sysctlbyname("kern.smp.maxcpus", &cpustats_cpus, &oldlenp, NULL, 0)) epanic("failed to read kern.smp.maxcpus sysctl"); #else char *poss = read_all("/sys/devices/system/cpu/possible"); cpustats_cpus = cpuset_max(poss) + 1; free(poss); #endif //__FreeBSD__ // Allocate a big buffer to read /proc/stat in to cpustats_buf_size = cpustats_cpus * 128; if (!(cpustats_buf = malloc(cpustats_buf_size))) epanic("allocating cpustats file buffer"); }
int main(int argc, char **argv) { bool force_ascii = false; int delay = 500; int opt; while ((opt = getopt(argc, argv, "ad:h")) != -1) { switch (opt) { case 'a': force_ascii = true; break; case 'd': { char *end; float val = strtof(optarg, &end); if (*end) { fprintf(stderr, "Delay argument (-d) requires " "a number\n"); exit(2); } delay = 1000 * val; break; } default: fprintf(stderr, "Usage: %s [-a] [-d delay]\n", argv[0]); if (opt == 'h') { fprintf(stderr, "\n" "Display CPU usage as a bar chart.\n" "\n" "Options:\n" " -a Use ASCII-only bars (instead of Unicode)\n" " -d SECS Specify delay between updates (decimals accepted)\n" "\n" "If your bars look funky, use -a or specify LANG=C.\n" "\n" "For kernels prior to 2.6.37, using a small delay on a large system can\n" "induce significant system time overhead.\n"); exit(0); } exit(2); } } if (optind < argc) { fprintf(stderr, "Unexpected arguments\n"); exit(2); } struct sigaction sa = { .sa_handler = on_sigint }; sigaction(SIGINT, &sa, NULL); cpustats_init(); term_init(); ui_init(force_ascii); struct cpustats *before = cpustats_alloc(), *after = cpustats_alloc(), *delta = cpustats_alloc(), *prevLayout = cpustats_alloc(); cpustats_read(before); cpustats_subtract(prevLayout, before, before); ui_layout(prevLayout); fflush(stdout); while (!need_exit) { // Sleep or take input struct pollfd pollfd = { .fd = 0, .events = POLLIN }; if (poll(&pollfd, 1, delay) < 0 && errno != EINTR) epanic("poll failed"); if (pollfd.revents & POLLIN) { char ch = 0; if (read(0, &ch, 1) < 0) epanic("read failed"); if (ch == 'q') break; } // Get new statistics cpustats_read(after); cpustats_subtract(delta, after, before); // Recompute the layout if necessary if (term_check_resize() || !cpustats_sets_equal(delta, prevLayout)) ui_layout(delta); // Show the load average float loadavg[3]; cpustats_loadavg(loadavg); ui_show_load(loadavg); if (delta->real) { ui_compute_bars(delta); ui_show_bars(); } // Done updating UI fflush(stdout); SWAP(before, after); SWAP(delta, prevLayout); } return 0; }
void ui_layout(struct cpustats *cpus) { int i; putp(exit_attribute_mode); putp(clear_screen); // Draw key at the top const struct ui_stat *si; for (si = ui_stats; si->name; si++) { putp(tiparm(set_a_background, si->color)); printf(" "); putp(exit_attribute_mode); printf(" %s ", si->name); } // Create one pane by default ui_init_panes(1); ui_panes[0].barpos = 0; // Create bar info free(ui_bars); ui_num_bars = cpus->online + 1; ui_bars = malloc(ui_num_bars * sizeof *ui_bars); if (!ui_bars) epanic("allocating bars"); // Create average bar ui_bars[0].start = 0; ui_bars[0].width = 3; ui_bars[0].cpu = -1; // Lay out labels char buf[16]; snprintf(buf, sizeof buf, "%d", cpus->max); int length = strlen(buf); int label_len; int w = COLS - 4; if ((length + 1) * cpus->online < w) { // Lay out the labels horizontally ui_panes[0].start = 1; ui_bar_length = MAX(0, LINES - ui_panes[0].start - 2); label_len = 1; putp(tiparm(cursor_address, LINES, 0)); int bar = 1; for (i = 0; i <= cpus->max; ++i) { if (cpus->cpus[i].online) { ui_bars[bar].start = 4 + (bar-1)*(length+1); ui_bars[bar].width = length; ui_bars[bar].cpu = i; bar++; } } } else { // Lay out the labels vertically int pad = 0, count = cpus->online; ui_panes[0].start = length; ui_bar_length = MAX(0, LINES - ui_panes[0].start - 2); label_len = length; if (cpus->online * 2 < w) { // We have space for padding pad = 1; } else if (cpus->online >= w && COLS >= 2) { // We don't have space for all of them int totalw = 4 + cpus->online; ui_init_panes((totalw + COLS - 2) / (COLS - 1)); int plength = (LINES - 2) / ui_num_panes; for (i = 0; i < ui_num_panes; ++i) { ui_panes[i].start = (ui_num_panes-i-1) * plength + length; ui_panes[i].barpos = i * (COLS - 1); ui_panes[i].width = COLS - 1; } ui_bar_length = MAX(0, plength - length); } int bar = 1; for (i = 0; i <= cpus->max; ++i) { if (cpus->cpus[i].online) { ui_bars[bar].start = 4 + (bar-1)*(pad+1); ui_bars[bar].width = 1; ui_bars[bar].cpu = i; bar++; } } } // Allocate bar display buffers free(ui_display); free(ui_fore); free(ui_back); ui_bar_width = ui_bars[ui_num_bars-1].start + ui_bars[ui_num_bars-1].width; if (!(ui_display = malloc(ui_bar_length * ui_bar_width))) epanic("allocating display buffer"); if (!(ui_fore = malloc(ui_bar_length * ui_bar_width))) epanic("allocating foreground buffer"); if (!(ui_back = malloc(ui_bar_length * ui_bar_width))) epanic("allocating background buffer"); if (ui_ascii) { // ui_display and ui_fore don't change in ASCII mode memset(ui_display, 0, ui_bar_length * ui_bar_width); memset(ui_fore, 0xff, ui_bar_length * ui_bar_width); } // Trim down the last pane to the right width ui_panes[ui_num_panes - 1].width = ui_bar_width - ui_panes[ui_num_panes - 1].barpos; // Draw labels char *label_buf = malloc(ui_bar_width * label_len); if (!label_buf) epanic("allocating label buffer"); memset(label_buf, ' ', ui_bar_width * label_len); int bar; for (bar = 0; bar < ui_num_bars; ++bar) { char *out = &label_buf[ui_bars[bar].start]; int len; if (bar == 0) { strcpy(buf, "avg"); len = 3; } else len = snprintf(buf, sizeof buf, "%d", ui_bars[bar].cpu); if (label_len == 1 || bar == 0) memcpy(out, buf, len); else for (i = 0; i < len; i++) out[i * ui_bar_width] = buf[i]; } for (i = 0; i < ui_num_panes; ++i) { putp(tiparm(cursor_address, LINES - ui_panes[i].start, 0)); int row; for (row = 0; row < label_len; ++row) { if (row > 0) putchar('\n'); fwrite(&label_buf[row*ui_bar_width + ui_panes[i].barpos], 1, ui_panes[i].width, stdout); } } free(label_buf); }
/** * Run fn on each CPU in cs, passing the CPU number and the opaque * argument, then join with all threads. If cs is NULL, fn is run on * all affinitized CPU's (all CPU's by default). If results is * non-NULL, it will be pointed to an array of *nResults results. The * caller must free this array. */ void CPU_RunOnSet(int (*fn)(int, void*), void *opaque, CPU_Set_t *cs, int **results, int *nResults) { int r; CPU_Set_t *aff = CPU_GetAffinity(); if (!cs) cs = aff; int count = CPU_GetCount(cs); int cpus = cpuSetMax(cs); struct runOnCPU roc[cpus]; pthread_t threads[cpus]; pthread_barrier_t barrier; r = pthread_barrier_init(&barrier, NULL, count + 1); if (r != 0) { errno = r; epanic("failed to create pthread barrier"); } if (results) *results = malloc(count * sizeof(*results)); if (nResults) *nResults = count; int i = 0; for (int cpu = 0; cpu < cpus; ++cpu) { if (numa_bitmask_isbitset(cs, cpu)) { roc[cpu].fn = fn; roc[cpu].cpu = cpu; roc[cpu].i = i++; roc[cpu].results = results ? *results : NULL; roc[cpu].barrier = &barrier; // Make sure the stack and such are allocated // on this core. CPU_Bind(cpu); r = pthread_create(&threads[cpu], NULL, runOnCPUThread, &roc[cpu]); if (r != 0) { errno = r; epanic("failed to spawn thread on CPU %d", cpu); } } } CPU_SetAffinity(aff); pthread_barrier_wait(&barrier); for (int cpu = 0; cpu < cpus; ++cpu) { if (numa_bitmask_isbitset(cs, cpu)) { r = pthread_join(threads[cpu], NULL); if (r != 0) { errno = r; epanic("failed to join thread on CPU %d", cpu); } } } pthread_barrier_destroy(&barrier); CPU_FreeSet(aff); }