├── .gitignore ├── Makefile ├── README.md ├── LICENSE └── cpubars.c /.gitignore: -------------------------------------------------------------------------------- 1 | cpubars 2 | cpubars.o 3 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | cpubars: LDLIBS += -lncurses 2 | cpubars: cpubars.o 3 | 4 | clean: 5 | rm -f cpubars cpubars.o 6 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | cpubars is a simple terminal-based tool for monitoring CPU load in 2 | real-time, especially tailored to monitoring large multicores over 3 | SSH. 4 | 5 | ![Screenshot](/../screenshots/80.png) 6 | 7 | cpubars uses Unicode block drawing characters to show the precise 8 | breakdown of CPU usage for each CPU. 9 | 10 | cpubars itself consumes almost no CPU, so it won't perturb other 11 | processes like benchmarks, and uses the terminal efficiently (and 12 | hence remote connections over SSH, Mosh, etc.). 13 | 14 | 15 | Terminal support 16 | ---------------- 17 | 18 | cpubars can operate in either Unicode or ASCII mode. In Unicode mode, 19 | cpubars can show fine-grained divisions in each CPU usage bar. ASCII 20 | mode is more broadly compatible, but can only show coarse-grained 21 | divisions. 22 | 23 | If cpubars looks funky, pass `-a` to force it into ASCII mode. 24 | cpubars will default to Unicode if your locale advertises Unicode 25 | support (e.g., the `LANG` environment variable is `en_US.utf8`), but 26 | this doesn't necessarily mean your terminal supports Unicode (or that 27 | it supports it correctly). 28 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011 Austin Clements 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /cpubars.c: -------------------------------------------------------------------------------- 1 | // -*- c-file-style: "bsd" -*- 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include 23 | #include 24 | 25 | #ifdef __FreeBSD__ 26 | #include 27 | #include 28 | #endif 29 | 30 | /****************************************************************** 31 | * Utilities 32 | */ 33 | 34 | #define MIN(a, b) ({ \ 35 | __typeof__(a) __a = (a); \ 36 | __typeof__(b) __b = (b); \ 37 | (__a < __b) ? __a : __b; \ 38 | }) 39 | 40 | #define MAX(a, b) ({ \ 41 | __typeof__(a) __a = (a); \ 42 | __typeof__(b) __b = (b); \ 43 | (__a > __b) ? __a : __b; \ 44 | }) 45 | 46 | #define SWAP(a, b) ({ \ 47 | __typeof__(a) __a = (a); \ 48 | (a) = (b); \ 49 | (b) = __a; \ 50 | }) 51 | 52 | static void term_reset(void); 53 | 54 | void 55 | panic(const char *fmt, ...) 56 | { 57 | va_list ap; 58 | 59 | term_reset(); 60 | va_start(ap, fmt); 61 | vfprintf(stderr, fmt, ap); 62 | va_end(ap); 63 | fputs("\n", stderr); 64 | exit(-1); 65 | } 66 | 67 | void 68 | epanic(const char *fmt, ...) 69 | { 70 | va_list ap; 71 | 72 | term_reset(); 73 | va_start(ap, fmt); 74 | vfprintf(stderr, fmt, ap); 75 | va_end(ap); 76 | fputs(": ", stderr); 77 | fputs(strerror(errno), stderr); 78 | fputs("\n", stderr); 79 | exit(-1); 80 | } 81 | 82 | ssize_t 83 | readn_str(int fd, char *buf, size_t count) 84 | { 85 | count -= 1; 86 | off_t pos = 0; 87 | while (1) { 88 | ssize_t r = read(fd, buf + pos, count - pos); 89 | if (r < 0) 90 | return r; 91 | else if (r == 0) 92 | break; 93 | pos += r; 94 | } 95 | buf[pos] = 0; 96 | return pos; 97 | } 98 | 99 | char * 100 | read_all(const char *path) 101 | { 102 | FILE *fp = fopen(path, "rb"); 103 | if (!fp) 104 | epanic("failed to open %s", path); 105 | if (fseek(fp, 0, SEEK_END) < 0) 106 | epanic("failed to seek in %s", path); 107 | long len = ftell(fp); 108 | char *buf = malloc(len + 1); 109 | if (!buf) 110 | epanic("read_all"); 111 | rewind(fp); 112 | size_t rlen = fread(buf, 1, len, fp); 113 | if ((errno = ferror(fp))) 114 | epanic("failed to read %s", path); 115 | buf[rlen] = 0; 116 | fclose(fp); 117 | return buf; 118 | } 119 | 120 | int 121 | cpuset_max(const char *cpuset) 122 | { 123 | // Since all we care about is the max, we can cut a lot of 124 | // corners 125 | int max = 0; 126 | const char *p = cpuset; 127 | while (*p) { 128 | if (isspace(*p) || *p == ',' || *p == '-') 129 | ++p; 130 | else if (!isdigit(*p)) 131 | panic("invalid cpu set: %s", cpuset); 132 | else { 133 | char *end; 134 | int cpu = strtol(p, &end, 10); 135 | p = end; 136 | if (cpu > max) 137 | max = cpu; 138 | } 139 | } 140 | return max; 141 | } 142 | 143 | uint64_t 144 | time_usec(void) 145 | { 146 | struct timeval tv; 147 | gettimeofday(&tv, 0); 148 | return (uint64_t)tv.tv_sec * 1000000 + tv.tv_usec; 149 | } 150 | 151 | 152 | /****************************************************************** 153 | * Stat parser 154 | */ 155 | 156 | struct cpustat 157 | { 158 | bool online; 159 | unsigned long long user, nice, sys, iowait, irq, softirq; 160 | }; 161 | 162 | struct cpustats 163 | { 164 | int online, max; 165 | unsigned long long real; 166 | struct cpustat avg; 167 | struct cpustat *cpus; 168 | }; 169 | 170 | // File descriptors for /proc/stat and /proc/loadavg 171 | static int cpustats_fd, cpustats_load_fd; 172 | // Maximum number of CPU's this system supports 173 | static int cpustats_cpus; 174 | // A buffer large enough to read cpustats_cpus worth of /proc/stat 175 | static char *cpustats_buf; 176 | static int cpustats_buf_size; 177 | 178 | static const char *proc_path = NULL; 179 | 180 | void 181 | cpustats_findproc(void) 182 | { 183 | // A list of paths to try to find a Linux-ish /proc mount at. 184 | #define NUM_CAND_PROC_PATHS 2 185 | const char *cand_proc_paths[NUM_CAND_PROC_PATHS] = 186 | { "/proc", 187 | "/compat/linux/proc" }; 188 | 189 | // Look for a "stat" file as an indicator that we have a Linuxy procfs 190 | // available as opposed to some other procfs that has no "stat" file. 191 | int i; 192 | for (i=0; icpus = malloc(cpustats_cpus * sizeof *res->cpus); 260 | if (!res->cpus) 261 | epanic("allocating per-CPU cputats"); 262 | memset(res->cpus, 0, cpustats_cpus * sizeof *res->cpus); 263 | return res; 264 | } 265 | 266 | void 267 | cpustats_read(struct cpustats *out) 268 | { 269 | // On kernels prior to 2.6.37, this can take a long time on 270 | // large systems because updating IRQ counts is slow. See 271 | // https://lkml.org/lkml/2010/9/29/259 272 | 273 | int i; 274 | for (i = 0; i < cpustats_cpus; i++) 275 | out->cpus[i].online = false; 276 | 277 | out->online = out->max = 0; 278 | out->real = time_usec() * sysconf(_SC_CLK_TCK) / 1000000; 279 | 280 | if ((readn_str(cpustats_fd, cpustats_buf, cpustats_buf_size)) < 0) 281 | epanic("failed to read %s/stat", proc_path); 282 | 283 | char *pos = cpustats_buf; 284 | while (strncmp(pos, "cpu", 3) == 0) { 285 | pos += 3; 286 | 287 | struct cpustat *st; 288 | int cpu = -1; 289 | if (*pos == ' ') { 290 | // Aggregate line 291 | st = &out->avg; 292 | } else if (isdigit(*pos)) { 293 | cpu = strtol(pos, &pos, 10); 294 | if (cpu >= cpustats_cpus) 295 | goto next; 296 | st = &out->cpus[cpu]; 297 | } else { 298 | goto next; 299 | } 300 | 301 | // Earlier versions of Linux only reported user, nice, 302 | // sys, and idle. 303 | st->iowait = st->irq = st->softirq = 0; 304 | unsigned long long toss; 305 | if (sscanf(pos, " %llu %llu %llu %llu %llu %llu %llu", 306 | &st->user, &st->nice, &st->sys, &toss, 307 | &st->iowait, &st->irq, &st->softirq) < 4) 308 | continue; 309 | st->online = true; 310 | if (cpu != -1) 311 | out->online++; 312 | if (cpu > out->max) 313 | out->max = cpu; 314 | 315 | next: 316 | // Go to the next line 317 | while (*pos && *pos != '\n') 318 | pos++; 319 | if (*pos) pos++; 320 | } 321 | 322 | if ((lseek(cpustats_fd, 0, SEEK_SET)) < 0) 323 | epanic("failed to seek %s/stat", proc_path); 324 | } 325 | 326 | static void 327 | cpustats_subtract1(struct cpustat *out, 328 | const struct cpustat *a, const struct cpustat *b) 329 | { 330 | out->online = a->online && b->online; 331 | if (out->online) { 332 | #define SUB(field) out->field = a->field - b->field 333 | SUB(user); 334 | SUB(nice); 335 | SUB(sys); 336 | SUB(iowait); 337 | SUB(irq); 338 | SUB(softirq); 339 | #undef SUB 340 | } 341 | } 342 | 343 | void 344 | cpustats_subtract(struct cpustats *out, 345 | const struct cpustats *a, const struct cpustats *b) 346 | { 347 | out->online = out->max = 0; 348 | out->real = a->real - b->real; 349 | cpustats_subtract1(&out->avg, &a->avg, &b->avg); 350 | 351 | int i; 352 | for (i = 0; i < cpustats_cpus; i++) { 353 | cpustats_subtract1(&out->cpus[i], &a->cpus[i], &b->cpus[i]); 354 | if (out->cpus[i].online) { 355 | out->online++; 356 | out->max = i; 357 | } 358 | } 359 | } 360 | 361 | // Test if `a' and `b' have the same set of online CPU's. 362 | bool 363 | cpustats_sets_equal(const struct cpustats *a, const struct cpustats *b) 364 | { 365 | if (a->max != b->max || a->online != b->online) 366 | return false; 367 | 368 | int i; 369 | for (i = 0; i < a->max; i++) 370 | if (a->cpus[i].online != b->cpus[i].online) 371 | return false; 372 | return true; 373 | } 374 | 375 | /****************************************************************** 376 | * Terminal 377 | */ 378 | 379 | static sig_atomic_t term_need_resize; 380 | static struct termios term_init_termios; 381 | static bool term_initialized; 382 | 383 | static void 384 | term_on_sigwinch(int sig) 385 | { 386 | term_need_resize = 1; 387 | } 388 | 389 | static void 390 | term_reset(void) 391 | { 392 | if (!term_initialized) 393 | return; 394 | // Leave invisible mode 395 | putp(cursor_normal); 396 | // Leave cursor mode 397 | putp(exit_ca_mode); 398 | fflush(stdout); 399 | // Reset terminal modes 400 | tcsetattr(0, TCSADRAIN, &term_init_termios); 401 | term_initialized = false; 402 | } 403 | 404 | void 405 | term_init(void) 406 | { 407 | setupterm(NULL, 1, NULL); 408 | if (tcgetattr(0, &term_init_termios) < 0) 409 | epanic("failed to get terminal attributes"); 410 | 411 | // Handle terminal resize 412 | struct sigaction act = { 413 | .sa_handler = term_on_sigwinch 414 | }; 415 | if (sigaction(SIGWINCH, &act, NULL) < 0) 416 | epanic("failed to install SIGWINCH handler"); 417 | 418 | atexit(term_reset); 419 | term_initialized = true; 420 | 421 | // Enter cursor mode 422 | putp(enter_ca_mode); 423 | // Enter invisible mode 424 | putp(cursor_invisible); 425 | // Disable echo and enter canonical (aka cbreak) mode so we 426 | // get input without waiting for newline 427 | struct termios tc = term_init_termios; 428 | tc.c_lflag &= ~(ICANON | ECHO); 429 | tc.c_iflag &= ~ICRNL; 430 | tc.c_lflag |= ISIG; 431 | tc.c_cc[VMIN] = 1; 432 | tc.c_cc[VTIME] = 0; 433 | if (tcsetattr(0, TCSAFLUSH, &tc) < 0) 434 | epanic("failed to set terminal attributes"); 435 | } 436 | 437 | // Handle any terminal resize that has happened since the last 438 | // `term_init' or `term_check_resize'. Return true if there was a 439 | // resize. 440 | bool 441 | term_check_resize(void) 442 | { 443 | if (!term_need_resize) 444 | return false; 445 | 446 | term_need_resize = 0; 447 | // restartterm is overkill, but appears to be the only way to 448 | // get ncurses to update the terminal size when using the 449 | // low-level routines. 450 | restartterm(NULL, 1, NULL); 451 | return true; 452 | } 453 | 454 | /****************************************************************** 455 | * UI 456 | */ 457 | 458 | static const struct ui_stat 459 | { 460 | const char *name; 461 | int color; 462 | int offset; 463 | } ui_stats[] = { 464 | #define FIELD(name, color) {#name, color, offsetof(struct cpustat, name)} 465 | FIELD(nice, COLOR_GREEN), FIELD(user, COLOR_BLUE), 466 | FIELD(sys, COLOR_RED), FIELD(iowait, COLOR_CYAN), 467 | FIELD(irq, COLOR_MAGENTA), FIELD(softirq, COLOR_YELLOW), 468 | // We set the color of the sentinel stat to 0xff so we can 469 | // safely refer to ui_stats[NSTATS].color as the last, "idle" 470 | // segment of a bar. 471 | {NULL, 0xff, 0} 472 | #undef FIELD 473 | }; 474 | #define NSTATS (sizeof(ui_stats)/sizeof(ui_stats[0]) - 1) 475 | 476 | // If we have too many bars to fit on the screen, we divide the screen 477 | // into "panes". Wrapping the display into these panes is handled by 478 | // the final output routine. 479 | static struct ui_pane 480 | { 481 | // start is the "length dimension" of the start of this pane 482 | // (for vertical bars, the row, relative to the bottom). 483 | // barpos is the first barpos that appears in this pane (for 484 | // vertical bars, the column). width is the size of this pane 485 | // in the width dimension (for vertical bars, the number of 486 | // columns). 487 | int start, barpos, width; 488 | } *ui_panes; 489 | static int ui_num_panes; 490 | 491 | static struct ui_bar 492 | { 493 | int start, width, cpu; 494 | } *ui_bars; 495 | static int ui_num_bars; 496 | 497 | // The layout of ui_display, etc is independent of final display 498 | // layout, hence we avoid the terms "row", "column", "x", and "y". 499 | // Rather, bar display is laid out as 500 | // 501 | // len 502 | // 012345678 <- ui_bar_length 503 | // barpos 0 |--bar--| 504 | // 1 505 | // 2 |--bar--| 506 | // ^- ui_bar_width 507 | static int ui_bar_length, ui_bar_width; 508 | // ui_display, ui_fore, and ui_back are 2-D arrays that should be 509 | // indexed using UIXY. ui_display stores indexes into ui_chars. 510 | // ui_fore and ui_back store color codes or 0xff for default 511 | // attributes. 512 | static unsigned char *ui_display, *ui_fore, *ui_back; 513 | #define UIXY(array, barpos, len) (array[(barpos)*ui_bar_length + (len)]) 514 | 515 | #define NCHARS 8 516 | static char ui_chars[NCHARS][MB_LEN_MAX]; 517 | static bool ui_ascii; 518 | 519 | void 520 | ui_init(bool force_ascii) 521 | { 522 | // Cell character 0 is always a space 523 | strcpy(ui_chars[0], " "); 524 | 525 | #ifdef __STDC_ISO_10646__ 526 | if (force_ascii) { 527 | ui_ascii = true; 528 | return; 529 | } 530 | 531 | // Encode Unicode cell characters using system locale 532 | char *origLocale = setlocale(LC_CTYPE, NULL); 533 | setlocale(LC_CTYPE, ""); 534 | 535 | int ch; 536 | mbstate_t mbs; 537 | memset(&mbs, 0, sizeof mbs); 538 | for (ch = 1; ch < NCHARS; ch++) { 539 | int len = wcrtomb(ui_chars[ch], 0x2580 + ch, &mbs); 540 | if (len == -1 || !mbsinit(&mbs)) { 541 | ui_ascii = true; 542 | break; 543 | } 544 | ui_chars[ch][len] = 0; 545 | } 546 | 547 | // Restore the original locale 548 | setlocale(LC_CTYPE, origLocale); 549 | #else 550 | ui_ascii = true; 551 | #endif 552 | } 553 | 554 | static void 555 | ui_init_panes(int n) 556 | { 557 | free(ui_panes); 558 | ui_num_panes = n; 559 | if (!(ui_panes = malloc(n * sizeof *ui_panes))) 560 | epanic("allocating panes"); 561 | 562 | } 563 | 564 | void 565 | ui_layout(struct cpustats *cpus) 566 | { 567 | int i; 568 | 569 | putp(exit_attribute_mode); 570 | putp(clear_screen); 571 | 572 | // Draw key at the top 573 | const struct ui_stat *si; 574 | for (si = ui_stats; si->name; si++) { 575 | putp(tiparm(set_a_background, si->color)); 576 | printf(" "); 577 | putp(exit_attribute_mode); 578 | printf(" %s ", si->name); 579 | } 580 | 581 | // Create one pane by default 582 | ui_init_panes(1); 583 | ui_panes[0].barpos = 0; 584 | 585 | // Create bar info 586 | free(ui_bars); 587 | ui_num_bars = cpus->online + 1; 588 | ui_bars = malloc(ui_num_bars * sizeof *ui_bars); 589 | if (!ui_bars) 590 | epanic("allocating bars"); 591 | 592 | // Create average bar 593 | ui_bars[0].start = 0; 594 | ui_bars[0].width = 3; 595 | ui_bars[0].cpu = -1; 596 | 597 | // Lay out labels 598 | char buf[16]; 599 | snprintf(buf, sizeof buf, "%d", cpus->max); 600 | int length = strlen(buf); 601 | int label_len; 602 | int w = COLS - 4; 603 | 604 | if ((length + 1) * cpus->online < w) { 605 | // Lay out the labels horizontally 606 | ui_panes[0].start = 1; 607 | ui_bar_length = MAX(0, LINES - ui_panes[0].start - 2); 608 | label_len = 1; 609 | putp(tiparm(cursor_address, LINES, 0)); 610 | int bar = 1; 611 | for (i = 0; i <= cpus->max; ++i) { 612 | if (cpus->cpus[i].online) { 613 | ui_bars[bar].start = 4 + (bar-1)*(length+1); 614 | ui_bars[bar].width = length; 615 | ui_bars[bar].cpu = i; 616 | bar++; 617 | } 618 | } 619 | } else { 620 | // Lay out the labels vertically 621 | int pad = 0, count = cpus->online; 622 | ui_panes[0].start = length; 623 | ui_bar_length = MAX(0, LINES - ui_panes[0].start - 2); 624 | label_len = length; 625 | 626 | if (cpus->online * 2 < w) { 627 | // We have space for padding 628 | pad = 1; 629 | } else if (cpus->online >= w && COLS >= 2) { 630 | // We don't have space for all of them 631 | int totalw = 4 + cpus->online; 632 | ui_init_panes((totalw + COLS - 2) / (COLS - 1)); 633 | int plength = (LINES - 2) / ui_num_panes; 634 | for (i = 0; i < ui_num_panes; ++i) { 635 | ui_panes[i].start = 636 | (ui_num_panes-i-1) * plength + length; 637 | ui_panes[i].barpos = i * (COLS - 1); 638 | ui_panes[i].width = COLS - 1; 639 | } 640 | ui_bar_length = MAX(0, plength - length); 641 | } 642 | 643 | int bar = 1; 644 | for (i = 0; i <= cpus->max; ++i) { 645 | if (cpus->cpus[i].online) { 646 | ui_bars[bar].start = 4 + (bar-1)*(pad+1); 647 | ui_bars[bar].width = 1; 648 | ui_bars[bar].cpu = i; 649 | bar++; 650 | } 651 | } 652 | } 653 | 654 | // Allocate bar display buffers 655 | free(ui_display); 656 | free(ui_fore); 657 | free(ui_back); 658 | ui_bar_width = ui_bars[ui_num_bars-1].start + ui_bars[ui_num_bars-1].width; 659 | if (!(ui_display = malloc(ui_bar_length * ui_bar_width))) 660 | epanic("allocating display buffer"); 661 | if (!(ui_fore = malloc(ui_bar_length * ui_bar_width))) 662 | epanic("allocating foreground buffer"); 663 | if (!(ui_back = malloc(ui_bar_length * ui_bar_width))) 664 | epanic("allocating background buffer"); 665 | 666 | if (ui_ascii) { 667 | // ui_display and ui_fore don't change in ASCII mode 668 | memset(ui_display, 0, ui_bar_length * ui_bar_width); 669 | memset(ui_fore, 0xff, ui_bar_length * ui_bar_width); 670 | } 671 | 672 | // Trim down the last pane to the right width 673 | ui_panes[ui_num_panes - 1].width = 674 | ui_bar_width - ui_panes[ui_num_panes - 1].barpos; 675 | 676 | // Draw labels 677 | char *label_buf = malloc(ui_bar_width * label_len); 678 | if (!label_buf) 679 | epanic("allocating label buffer"); 680 | memset(label_buf, ' ', ui_bar_width * label_len); 681 | int bar; 682 | for (bar = 0; bar < ui_num_bars; ++bar) { 683 | char *out = &label_buf[ui_bars[bar].start]; 684 | int len; 685 | if (bar == 0) { 686 | strcpy(buf, "avg"); 687 | len = 3; 688 | } else 689 | len = snprintf(buf, sizeof buf, "%d", ui_bars[bar].cpu); 690 | if (label_len == 1 || bar == 0) 691 | memcpy(out, buf, len); 692 | else 693 | for (i = 0; i < len; i++) 694 | out[i * ui_bar_width] = buf[i]; 695 | } 696 | for (i = 0; i < ui_num_panes; ++i) { 697 | putp(tiparm(cursor_address, LINES - ui_panes[i].start, 0)); 698 | 699 | int row; 700 | for (row = 0; row < label_len; ++row) { 701 | if (row > 0) 702 | putchar('\n'); 703 | fwrite(&label_buf[row*ui_bar_width + ui_panes[i].barpos], 704 | 1, ui_panes[i].width, stdout); 705 | } 706 | } 707 | free(label_buf); 708 | 709 | } 710 | 711 | void 712 | ui_show_load(float load[3]) 713 | { 714 | char buf[1024]; 715 | int pos; 716 | snprintf(buf, sizeof buf, "%0.2f %0.2f %0.2f", 717 | load[0], load[1], load[2]); 718 | pos = COLS - strlen(buf) - 8; 719 | if (pos < 0) 720 | pos = 0; 721 | putp(tiparm(cursor_address, 0, pos)); 722 | putp(exit_attribute_mode); 723 | putp(tiparm(set_a_foreground, COLOR_WHITE)); 724 | fputs(" load: ", stdout); 725 | putp(exit_attribute_mode); 726 | fputs(buf, stdout); 727 | } 728 | 729 | void 730 | ui_compute_bars(struct cpustats *delta) 731 | { 732 | if (!ui_ascii) { 733 | // ui_display and ui_fore are only used in Unicode mode 734 | memset(ui_display, 0, ui_bar_length * ui_bar_width); 735 | memset(ui_fore, 0xff, ui_bar_length * ui_bar_width); 736 | } 737 | memset(ui_back, 0xff, ui_bar_length * ui_bar_width); 738 | 739 | int i, bar; 740 | for (bar = 0; bar < ui_num_bars; bar++) { 741 | int barpos = ui_bars[bar].start; 742 | struct cpustat *cpu = ui_bars[bar].cpu == -1 ? &delta->avg : 743 | &delta->cpus[ui_bars[bar].cpu]; 744 | 745 | // Calculate cut-offs between segments. We divide 746 | // each display cell into `subcells' steps so we can 747 | // use integer math. 748 | enum { subcells = 256 }; 749 | // Values in delta are from 0 to `scale'. For per-CPU 750 | // bars this is just the real time, but for the 751 | // average bar, it's multiplied by the number of 752 | // online CPU's. 753 | int scale = delta->real; 754 | if (ui_bars[bar].cpu == -1) 755 | scale *= delta->online; 756 | // To simplify the code, we include one additional 757 | // cutoff fixed at the very top of the bar so we can 758 | // treat the empty region above the bar as a segment. 759 | int cutoff[NSTATS + 1]; 760 | unsigned long long cumm = 0; 761 | for (i = 0; i < NSTATS; i++) { 762 | cumm += *(unsigned long long*) 763 | ((char*)cpu + ui_stats[i].offset); 764 | cutoff[i] = cumm * ui_bar_length * subcells / scale; 765 | } 766 | cutoff[NSTATS] = ui_bar_length * subcells; 767 | 768 | // Construct bar cells 769 | int len, stat; 770 | for (len = stat = 0; len < ui_bar_length && stat < NSTATS; len++) { 771 | int lo = len * subcells, hi = (len + 1) * subcells; 772 | if (cutoff[stat] >= hi) { 773 | // Cell is entirely covered 774 | UIXY(ui_back, barpos, len) = 775 | ui_stats[stat].color; 776 | continue; 777 | } 778 | 779 | // Find the two segments the cover this cell 780 | // the most 781 | int topStat[2] = {0, 0}; 782 | int topVal[2] = {-1, -1}; 783 | int val, prev = lo; 784 | for (; stat < NSTATS + 1; stat++) { 785 | val = MIN(cutoff[stat], hi) - prev; 786 | prev = cutoff[stat]; 787 | if (val > topVal[0]) { 788 | topStat[1] = topStat[0]; 789 | topVal[1] = topVal[0]; 790 | topStat[0] = stat; 791 | topVal[0] = val; 792 | } else if (val > topVal[1]) { 793 | topStat[1] = stat; 794 | topVal[1] = val; 795 | } 796 | if (cutoff[stat] >= hi) 797 | break; 798 | } 799 | if (topVal[0] == -1 || topVal[1] == -1) 800 | panic("bug: topVal={%d,%d}", 801 | topVal[0], topVal[1]); 802 | 803 | if (ui_ascii) { 804 | // We only care about the biggest 805 | // cover 806 | UIXY(ui_back, barpos, len) = 807 | ui_stats[topStat[0]].color; 808 | continue; 809 | } 810 | 811 | // Order the segments by stat so we put the 812 | // earlier stat on the bottom 813 | if (topStat[0] > topStat[1]) { 814 | SWAP(topStat[0], topStat[1]); 815 | SWAP(topVal[0], topVal[1]); 816 | } 817 | 818 | // Re-scale and choose a split 819 | int cell = topVal[0] * NCHARS / (topVal[0] + topVal[1]); 820 | 821 | // Fill the cell 822 | if (cell == NCHARS - 1) { 823 | // We leave this as a space, which 824 | // means the color roles are reversed 825 | UIXY(ui_back, barpos, len) = 826 | ui_stats[topStat[0]].color; 827 | } else { 828 | UIXY(ui_display, barpos, len) = cell; 829 | UIXY(ui_fore, barpos, len) = 830 | ui_stats[topStat[0]].color; 831 | UIXY(ui_back, barpos, len) = 832 | ui_stats[topStat[1]].color; 833 | } 834 | } 835 | 836 | // Copy across bar length 837 | for (i = 1; i < ui_bars[bar].width; ++i) { 838 | memcpy(&UIXY(ui_display, barpos+i, 0), 839 | &UIXY(ui_display, barpos, 0), ui_bar_length); 840 | memcpy(&UIXY(ui_fore, barpos+i, 0), 841 | &UIXY(ui_fore, barpos, 0), ui_bar_length); 842 | memcpy(&UIXY(ui_back, barpos+i, 0), 843 | &UIXY(ui_back, barpos, 0), ui_bar_length); 844 | } 845 | } 846 | } 847 | 848 | static void 849 | ui_show_pane(struct ui_pane *pane) 850 | { 851 | int row, col; 852 | int lastBack = -1, lastFore = -1; 853 | for (row = 0; row < ui_bar_length; row++) { 854 | putp(tiparm(cursor_address, LINES - pane->start - row - 1, 0)); 855 | 856 | // What's the width of this row? Beyond this, we can 857 | // just clear the line. 858 | int endCol = 0; 859 | for (col = pane->barpos; col < pane->barpos + pane->width; 860 | col++) { 861 | if (UIXY(ui_back, col, row) != 0xff || 862 | UIXY(ui_display, col, row) != 0) 863 | endCol = col + 1; 864 | } 865 | 866 | for (col = pane->barpos; col < endCol; col++) { 867 | int cell = UIXY(ui_display, col, row); 868 | int back = UIXY(ui_back, col, row); 869 | int fore = UIXY(ui_fore, col, row); 870 | 871 | // If it's a space, we don't care what the 872 | // foreground color is. 873 | if (ui_chars[cell][0] == ' ' && lastFore != -1) 874 | fore = lastFore; 875 | 876 | // Set attributes 877 | if (lastBack != back || lastFore != fore) { 878 | if (back == 0xff || fore == 0xff) { 879 | putp(exit_attribute_mode); 880 | lastBack = lastFore = 0xff; 881 | } 882 | if (lastBack != back) { 883 | putp(tiparm(set_a_background, back)); 884 | lastBack = back; 885 | } 886 | if (lastFore != fore) { 887 | putp(tiparm(set_a_foreground, fore)); 888 | lastFore = fore; 889 | } 890 | } 891 | 892 | fputs(ui_chars[cell], stdout); 893 | } 894 | 895 | // Clear to the end of the line 896 | if (endCol < pane->barpos + pane->width) { 897 | if (lastBack != 0xff || lastFore != 0xff) { 898 | putp(exit_attribute_mode); 899 | lastBack = lastFore = 0xff; 900 | } 901 | putp(clr_eol); 902 | } 903 | } 904 | } 905 | 906 | void 907 | ui_show_bars(void) 908 | { 909 | int pane; 910 | for (pane = 0; pane < ui_num_panes; ++pane) 911 | ui_show_pane(&ui_panes[pane]); 912 | } 913 | 914 | /****************************************************************** 915 | * Main 916 | */ 917 | 918 | static sig_atomic_t need_exit; 919 | 920 | void 921 | on_sigint(int sig) 922 | { 923 | need_exit = 1; 924 | } 925 | 926 | int 927 | main(int argc, char **argv) 928 | { 929 | bool force_ascii = false; 930 | int delay = 500; 931 | 932 | int opt; 933 | while ((opt = getopt(argc, argv, "ad:h")) != -1) { 934 | switch (opt) { 935 | case 'a': 936 | force_ascii = true; 937 | break; 938 | case 'd': 939 | { 940 | char *end; 941 | float val = strtof(optarg, &end); 942 | if (*end) { 943 | fprintf(stderr, "Delay argument (-d) requires " 944 | "a number\n"); 945 | exit(2); 946 | } 947 | delay = 1000 * val; 948 | break; 949 | } 950 | default: 951 | fprintf(stderr, "Usage: %s [-a] [-d delay]\n", argv[0]); 952 | if (opt == 'h') { 953 | fprintf(stderr, 954 | "\n" 955 | "Display CPU usage as a bar chart.\n" 956 | "\n" 957 | "Options:\n" 958 | " -a Use ASCII-only bars (instead of Unicode)\n" 959 | " -d SECS Specify delay between updates (decimals accepted)\n" 960 | "\n" 961 | "If your bars look funky, use -a or specify LANG=C.\n" 962 | "\n" 963 | "For kernels prior to 2.6.37, using a small delay on a large system can\n" 964 | "induce significant system time overhead.\n"); 965 | exit(0); 966 | } 967 | exit(2); 968 | } 969 | } 970 | if (optind < argc) { 971 | fprintf(stderr, "Unexpected arguments\n"); 972 | exit(2); 973 | } 974 | 975 | struct sigaction sa = { 976 | .sa_handler = on_sigint 977 | }; 978 | sigaction(SIGINT, &sa, NULL); 979 | 980 | cpustats_init(); 981 | term_init(); 982 | ui_init(force_ascii); 983 | 984 | struct cpustats *before = cpustats_alloc(), 985 | *after = cpustats_alloc(), 986 | *delta = cpustats_alloc(), 987 | *prevLayout = cpustats_alloc(); 988 | 989 | cpustats_read(before); 990 | cpustats_subtract(prevLayout, before, before); 991 | ui_layout(prevLayout); 992 | fflush(stdout); 993 | while (!need_exit) { 994 | // Sleep or take input 995 | struct pollfd pollfd = { 996 | .fd = 0, 997 | .events = POLLIN 998 | }; 999 | if (poll(&pollfd, 1, delay) < 0 && errno != EINTR) 1000 | epanic("poll failed"); 1001 | if (pollfd.revents & POLLIN) { 1002 | char ch = 0; 1003 | if (read(0, &ch, 1) < 0) 1004 | epanic("read failed"); 1005 | if (ch == 'q') 1006 | break; 1007 | } 1008 | 1009 | // Get new statistics 1010 | cpustats_read(after); 1011 | cpustats_subtract(delta, after, before); 1012 | 1013 | // Recompute the layout if necessary 1014 | if (term_check_resize() || !cpustats_sets_equal(delta, prevLayout)) 1015 | ui_layout(delta); 1016 | 1017 | // Show the load average 1018 | float loadavg[3]; 1019 | cpustats_loadavg(loadavg); 1020 | ui_show_load(loadavg); 1021 | 1022 | if (delta->real) { 1023 | ui_compute_bars(delta); 1024 | ui_show_bars(); 1025 | } 1026 | 1027 | // Done updating UI 1028 | fflush(stdout); 1029 | 1030 | SWAP(before, after); 1031 | SWAP(delta, prevLayout); 1032 | } 1033 | 1034 | return 0; 1035 | } 1036 | --------------------------------------------------------------------------------