/* cc -o xobsdview -I/usr/X11R6/include xobsdview.c -L/usr/X11R6/lib -lX11 -lm */ #include #include #include #include #include #include #include #include #include //#include #include #include /* for struct timespec needed in sched.h */ #include /* for CPUSTATES and CP_* state constants */ #include #include #include #include #include #include #include #include #define WINWIDTH 150 #define WINHEIGHT 300 #define PADDING 2 // #define FONTNAME "6x10" #define FONTNAME "-misc-fixed-*-*-*-*-10-*-*-*-*-*-*-*" #define METERMINWIDTH 40 #define METERMINHEIGHT 4 #define METERSPLIT 1 /* display lowpass-filtered stats in lower half if != 0 */ #define METERDECAY2 1.0 /* decay time to half value in seconds */ #define MAXLOAD 5 #define MAXDISKBW (300ull << 20) /* disk bandwidth in bytes/s, in+out */ #define MAXNETBW (25ull << 20) /* network bandwidth in bytes/s, in+out */ #define LABELCOL 0 #define VALUECOL 0x808080 #define IDLECOL 0x7FFFD4 #define PSUCOL 0x20B2AA #ifndef CPUSTATES #define CPUSTATES 6 #endif #define MEMSTATES 5 /* confer SC_* constants in /usr/include/sys/sched.h */ static const char *cpustabbr[CPUSTATES]= { "US", "NI", "SY", "SP", "IN", "ID" }; static const uint32_t cpustcol[CPUSTATES]= { 0x2E8B57, 0xD0C000, 0xFFA500, 0xB060D3, 0xFF0000, IDLECOL }; /* see https://en.wikipedia.org/wiki/X11_color_names for standard colours */ static const char *memabbr[MEMSTATES]= { "AC", "IN", "WR", "CA", "FR" }; static const uint32_t memcol[MEMSTATES]= { 0xFF, 0x90B8D0, 0xB060D3, 0xFF0000, IDLECOL }; static const char *swapabbr[3]= { "ONLY", "USED", "FREE" }; static const uint32_t swapcol[3]= { 0xFF, 0x90B8D0, IDLECOL }; static const char *ioabbr[3]= { "IN", "OUT", "IDLE" }; static const char *diskabbr[3]= { "READ", "WRITE", "IDLE" }; static const uint32_t iocol[3]= { 0x87CEEB, 0x6A5ACD, IDLECOL }; #define NWARNCOL 4 static const uint32_t warncol[NWARNCOL]= { 0x2E8B57, 0xD0C000, 0xFFA500, 0xFF0000 }; static const uint32_t chargingcol[2]= { 0x57EB90, IDLECOL }; /* Update interval in seconds */ #define INTERVAL 0.1 typedef struct { int firstcall, ncpus; char *tabbuf; size_t tabbufsize; double loadavg[3]; double *cpus; struct cpustats *cpustates, *oldcpustates; double membytes[MEMSTATES], swapbytes[3]; double memtotal, swaptotal, pagein, pageout; int oldpageins, oldpageouts; uint64_t netin, netout, oldtotalnetin, oldtotalnetout; uint64_t diskin, diskout, oldtotaldiskin, oldtotaldiskout; int sensordev_psu, sensordev_bat, psu_on; int64_t bat_curr, bat_max; double *filtered; } obv_stats; typedef struct { Display *disp; Window win; GC gc; XGCValues gcval; char *name; int windowx, windowy, havepos; unsigned windoww, windowh; int labelx, label0y, valuex, meterx, metery, meteroff; unsigned meterw, meterh; /* without frame */ unsigned fontasc, fontdesc, fontw, fontgap; } obv_view; int init(obv_stats *st); void dealloc(obv_stats *st); void getstat(obv_stats *st); void update_filtered(obv_stats *st, int firstcall); void init_view(obv_view *view); void resize_view(obv_view *view, unsigned ncpus); void update_view(obv_view *view, obv_stats *st); void exit_view(obv_view *view); void draw_meter(obv_view *view, unsigned metind, int split, const double *fractions, const uint32_t *colours, unsigned n); void draw_warnmeter(obv_view *view, unsigned metind, int split, double value, int reverse); void draw_value(obv_view *view, unsigned metind, int type, double value); void draw_key(obv_view *view, unsigned metind, const char **strs, const uint32_t *colours, int n); void sprintbytes(char *dest, double bytes); int main(int argc, char **argv) { XEvent event; KeySym key; Atom protocol[2], message; XRectangle cliprect; obv_stats st; obv_view v= { .windoww= WINWIDTH, .windowh= WINHEIGHT, .windowx= 0, .windowy= 0, .name= "xobsdview", .havepos= 0 }; int argind, count, quit, updated; for( argind= 1; argind < argc; argind += 2 ) { if( !strcmp(argv[argind], "-geometry") && argind < argc-1 ) { count= sscanf(argv[argind+1], "%ux%u%d%d", &v.windoww, &v.windowh, &v.windowx, &v.windowy); v.havepos= count == 4; } else if( !strcmp(argv[argind], "-name") && argind < argc-1 ) v.name= argv[argind+1]; else { fprintf(stderr, "usage: xobsdview [ -geometry ] [ -name ]\n"); return 1; } } init(&st); getstat(&st); if( METERSPLIT ) update_filtered(&st, 1); init_view(&v); XSelectInput(v.disp, v.win, KeyPressMask|KeyReleaseMask|ExposureMask|StructureNotifyMask ); protocol[0]= XInternAtom(v.disp, "WM_PROTOCOLS", False ); protocol[1]= XInternAtom(v.disp, "WM_DELETE_WINDOW", False ); XSetWMProtocols( v.disp, v.win, protocol, 2 ); quit= 0; do { updated= 0; while( XPending(v.disp) > 0 ) { XNextEvent( v.disp, &event ); if( event.type == KeyPress ) { key= XLookupKeysym( &event.xkey, 0 ); if( key == XK_q ) quit= 1; } else if( event.type == ClientMessage ) { if( event.xclient.message_type == protocol[0] ) { if( event.xclient.format == 8 ) message= event.xclient.data.b[0]; else if( event.xclient.format == 16 ) message= event.xclient.data.s[0]; else if( event.xclient.format == 32 ) message= event.xclient.data.l[0]; else message= protocol[1] - 1; if( message == protocol[1] ) quit= 1; } } else if( event.type == ConfigureNotify ) { if( v.windoww != event.xconfigure.width || v.windowh != event.xconfigure.height ) { v.windoww= event.xconfigure.width; v.windowh= event.xconfigure.height; resize_view(&v, st.ncpus); update_view(&v, &st); updated= 1; } } else if( event.type == Expose && event.xexpose.count == 0 ) { resize_view(&v, st.ncpus); update_view(&v, &st); updated= 1; } } if( ! updated ) update_view(&v, &st); usleep(1e6 * INTERVAL); getstat(&st); if( METERSPLIT ) update_filtered(&st, 0); } while( ! quit ); exit_view(&v); dealloc(&st); return 0; } static int sensordev_mib[]= { CTL_HW, HW_SENSORS, /* sensor device index */ 0 }; int init(obv_stats *st) { struct sensordev sd; size_t size; int status, ind, typeind; st->sensordev_psu= -1; st->sensordev_bat= -1; for( ind= 0; st->sensordev_psu < 0 || st->sensordev_bat < 0; ++ind ) { sensordev_mib[2]= ind; size= sizeof(sd); status= sysctl(sensordev_mib, 3, &sd, &size, NULL, 0); if( status < 0 || size < sizeof(sd) ) break; if( !strcmp(sd.xname, "acpiac0") ) st->sensordev_psu= ind; else if( !strcmp(sd.xname, "acpibat0") ) st->sensordev_bat= ind; } st->ncpus= sysconf(_SC_NPROCESSORS_CONF); st->cpustates= malloc(2 * st->ncpus * sizeof(*st->cpustates)); st->oldcpustates= st->cpustates + st->ncpus; st->cpus= malloc(CPUSTATES * st->ncpus * sizeof(double)); st->memtotal= 1; st->swaptotal= 1; st->tabbufsize= 2048; st->tabbuf= malloc(st->tabbufsize); if( METERSPLIT ) st->filtered= calloc(st->ncpus*CPUSTATES + MEMSTATES + 3 + 3 * 2, sizeof(double)); else st->filtered= NULL; st->firstcall= 1; return st->cpustates && st->cpus && st->tabbuf && (!METERSPLIT || st->filtered); } void dealloc(obv_stats *st) { free(st->cpustates); st->cpustates= NULL; free(st->cpus); st->cpus= NULL; free(st->tabbuf); st->tabbuf= NULL; free(st->filtered); st->filtered= NULL; } static int cpustats_mib[] = { CTL_KERN, KERN_CPUSTATS, /* CPU index */ 0 }; /* for struct cpustats, see /usr/include/sys/sched.h */ static const int uvmexp_mib[]= { CTL_VM, VM_UVMEXP }; /* for struct uvmexp, see /usr/include/uvm/uvmexp.h */ static const int iflist_mib[]= { CTL_NET, PF_ROUTE, 0, 0, NET_RT_IFLIST, 0 }; /* for structs rt_msghdr and if_msghdr, see /usr/include/net/route.h and if.h */ static const int diskstats_mib[]= { CTL_HW, HW_DISKSTATS }; /* for struct diskstats, see /usr/include/sys/disk.h */ static int sensor_mib[]= { CTL_HW, HW_SENSORS, /* sensor device index */ 0, /* sensor type */ 0, /* sensor index */ 0 }; void getstat(obv_stats *st) { struct uvmexp ue; struct if_msghdr *ifm; struct diskstats *ds; struct sensor se; char *tryalloc; double diff, total; size_t size, needsize, off; int cpuind, stateind; getloadavg(st->loadavg, 3); for( cpuind= 0; cpuind < st->ncpus; ++cpuind ) { cpustats_mib[2]= cpuind; size= sizeof(*st->cpustates); memcpy(st->oldcpustates + cpuind, st->cpustates + cpuind, size); if( sysctl(cpustats_mib, 3, st->cpustates + cpuind, &size, NULL, 0) < 0 || (st->cpustates[cpuind].cs_flags & CPUSTATS_ONLINE) == 0 || st->firstcall ) { memset(st->cpus + cpuind * CPUSTATES, 0, CPUSTATES * sizeof(double)); st->cpus[cpuind*CPUSTATES+CP_IDLE]= 1.0; continue; } /* Now compute fractions of CPU time spent in each state since the previous call, using the total change as a normalisation constant */ total= 0; for( stateind= 0; stateind < CPUSTATES; ++stateind ) { diff= st->cpustates[cpuind].cs_time[stateind] - st->oldcpustates[cpuind].cs_time[stateind]; if( diff < 0.0 ) /* wraparound */ diff += 0x1p64; st->cpus[cpuind*CPUSTATES+stateind]= diff; total += diff; } if( total == 0.0 ) total= 1.0; for( stateind= 0; stateind < CPUSTATES; ++stateind ) st->cpus[cpuind*CPUSTATES+stateind] /= total; } size= sizeof(ue); if( sysctl(uvmexp_mib, 2, &ue, &size, NULL, 0) < 0 ) { st->membytes[0]= st->membytes[1]= st->membytes[2]= st->membytes[3]= 0; st->membytes[4]= st->memtotal; st->pagein= st->pageout= 0; st->swapbytes[0]= st->swapbytes[1]= 0; st->swapbytes[2]= st->swaptotal; } else { st->memtotal= ue.npages * (double)ue.pagesize; st->membytes[0]= ue.active * (double)ue.pagesize; st->membytes[1]= ue.inactive * (double)ue.pagesize; st->membytes[2]= ue.wired * (double)ue.pagesize; st->membytes[4]= ue.free * (double)ue.pagesize; st->membytes[3]= st->memtotal - st->membytes[0] - st->membytes[1] - st->membytes[2] - st->membytes[4]; /* TODO: check this */ if( st->firstcall ) { st->pagein= 0; st->pageout= 0; } else { st->pagein= (ue.pageins - st->oldpageins) * (double)ue.pagesize; st->pageout= (ue.pdpageouts - st->oldpageouts) * (double)ue.pagesize; } st->oldpageins= ue.pageins; st->oldpageouts= ue.pdpageouts; st->swaptotal= ue.swpages * (double)ue.pagesize; st->swapbytes[0]= ue.swpgonly * (double)ue.pagesize; st->swapbytes[1]= (ue.swpginuse - ue.swpgonly) * (double)ue.pagesize; st->swapbytes[2]= (ue.swpages - ue.swpginuse) * (double)ue.pagesize; } size= st->tabbufsize; while( sysctl(iflist_mib, 6, st->tabbuf, &size, NULL, 0) < 0 && errno == ENOMEM ) { needsize= 0; if( sysctl(iflist_mib, 6, NULL, &needsize, NULL, 0) < 0 ) break; tryalloc= malloc(needsize); if( ! tryalloc ) break; free(st->tabbuf); st->tabbuf= tryalloc; st->tabbufsize= size= needsize; } st->netin= 0; st->netout= 0; for( off= 0; off < size - ((char*)&ifm->ifm_msglen - (char*)ifm) - sizeof(ifm->ifm_msglen); off += ifm->ifm_msglen ) { ifm= (struct if_msghdr *)(st->tabbuf + off); if( off + ifm->ifm_msglen > size ) break; if( ifm->ifm_version != RTM_VERSION || ifm->ifm_type != RTM_IFINFO ) continue; st->netin += ifm->ifm_data.ifi_ibytes; st->netout += ifm->ifm_data.ifi_obytes; } if( st->firstcall ) { st->oldtotalnetin= st->netin; st->oldtotalnetout= st->netout; st->netin= 0; st->netout= 0; } else { st->netin -= st->oldtotalnetin; st->netout -= st->oldtotalnetout; st->oldtotalnetin += st->netin; st->oldtotalnetout += st->netout; } size= st->tabbufsize; while( sysctl(diskstats_mib, 2, st->tabbuf, &size, NULL, 0) < 0 && errno == ENOMEM ) { needsize= 0; if( sysctl(diskstats_mib, 2, NULL, &needsize, NULL, 0) < 0 ) break; tryalloc= malloc(needsize); if( ! tryalloc ) break; free(st->tabbuf); st->tabbuf= tryalloc; st->tabbufsize= size= needsize; } st->diskin= 0; st->diskout= 0; for( off= 0; (off+1) * sizeof(struct diskstats) <= size; ++off ) { ds= (struct diskstats*)st->tabbuf + off; st->diskin += ds->ds_rbytes; st->diskout += ds->ds_wbytes; } if( st->firstcall ) { st->oldtotaldiskin= st->diskin; st->oldtotaldiskout= st->diskout; st->diskin= 0; st->diskout= 0; } else { st->diskin -= st->oldtotaldiskin; st->diskout -= st->oldtotaldiskout; st->oldtotaldiskin += st->diskin; st->oldtotaldiskout += st->diskout; } if( st->sensordev_psu >= 0 ) { sensor_mib[2]= st->sensordev_psu; sensor_mib[3]= SENSOR_INDICATOR; sensor_mib[4]= 0; size= sizeof(se); if( sysctl(sensor_mib, 5, &se, &size, NULL, 0) >= 0 && size == sizeof(se) ) st->psu_on= se.value; else st->psu_on= 0; } if( st->sensordev_bat >= 0 ) { sensor_mib[2]= st->sensordev_bat; sensor_mib[3]= SENSOR_AMPHOUR; sensor_mib[4]= 0; size= sizeof(se); if( sysctl(sensor_mib, 5, &se, &size, NULL, 0) >= 0 && size == sizeof(se) && se.flags == 0 ) st->bat_max= se.value; else st->bat_max= 0; sensor_mib[4]= 3; size= sizeof(se); if( sysctl(sensor_mib, 5, &se, &size, NULL, 0) >= 0 && size == sizeof(se) && se.flags == 0 ) st->bat_curr= se.value; else st->bat_curr= 0; } st->firstcall= 0; } void update_filtered(obv_stats *st, int firstcall) { double ffcoeff, fbcoeff; int ind; if( firstcall ) { fbcoeff= 0.0; ffcoeff= 1.0; } else { fbcoeff= exp2(-INTERVAL / METERDECAY2); ffcoeff= 1.0 - fbcoeff; } for( ind= 0; ind< st->ncpus*CPUSTATES; ++ind ) st->filtered[ind]= fbcoeff * st->filtered[ind] + ffcoeff * st->cpus[ind]; for( ind= 0; ind< MEMSTATES; ++ind ) st->filtered[st->ncpus*CPUSTATES+ind]= fbcoeff * st->filtered[st->ncpus*CPUSTATES+ind] + ffcoeff * st->membytes[ind]; for( ind= 0; ind< 3; ++ind ) st->filtered[st->ncpus*CPUSTATES+MEMSTATES+ind]= fbcoeff * st->filtered[st->ncpus*CPUSTATES+MEMSTATES+ind] + ffcoeff * st->swapbytes[ind]; st->filtered[st->ncpus*CPUSTATES+MEMSTATES+3]= fbcoeff * st->filtered[st->ncpus*CPUSTATES+MEMSTATES+3] + ffcoeff * st->pagein; st->filtered[st->ncpus*CPUSTATES+MEMSTATES+4]= fbcoeff * st->filtered[st->ncpus*CPUSTATES+MEMSTATES+4] + ffcoeff * st->pageout; st->filtered[st->ncpus*CPUSTATES+MEMSTATES+5]= fbcoeff * st->filtered[st->ncpus*CPUSTATES+MEMSTATES+5] + ffcoeff * st->diskin; st->filtered[st->ncpus*CPUSTATES+MEMSTATES+6]= fbcoeff * st->filtered[st->ncpus*CPUSTATES+MEMSTATES+6] + ffcoeff * st->diskout; st->filtered[st->ncpus*CPUSTATES+MEMSTATES+7]= fbcoeff * st->filtered[st->ncpus*CPUSTATES+MEMSTATES+7] + ffcoeff * st->netin; st->filtered[st->ncpus*CPUSTATES+MEMSTATES+8]= fbcoeff * st->filtered[st->ncpus*CPUSTATES+MEMSTATES+8] + ffcoeff * st->netout; } void init_view(obv_view *view) { XTextProperty name; XSizeHints *size_hints; XWMHints *wm_hints; XClassHint *class_hints; XCharStruct fontextents; char *namestr; int screen, dummy; view->disp= XOpenDisplay(NULL); if( !view->disp ) { fprintf(stderr, "xdrawtest: Error: could not connect to X server!\n"); exit(1); } screen= DefaultScreen( view->disp ); view->win= XCreateSimpleWindow( view->disp, RootWindow(view->disp, screen), view->windowx, view->windowy, view->windoww, view->windowh, 0, BlackPixel(view->disp, screen), WhitePixel(view->disp, screen) ); size_hints= XAllocSizeHints(); size_hints->flags = 0; if( view->havepos ) { size_hints->flags |= PPosition; size_hints->x= view->windowx; /* ignored in favour of create window arg */ size_hints->y= view->windowy; } wm_hints= XAllocWMHints(); wm_hints->flags = StateHint | InputHint; wm_hints->initial_state= NormalState; wm_hints->input= True; class_hints= XAllocClassHint(); class_hints->res_name= "xobsdview"; class_hints->res_class= "xobsdview"; namestr= view->name; XStringListToTextProperty( &namestr, 1, &name ); XSetWMProperties(view->disp, view->win, &name, &name, NULL, 0, size_hints, wm_hints, class_hints ); view->gc= XCreateGC(view->disp, view->win, 0, NULL); view->gcval.font= XLoadFont(view->disp, FONTNAME); if( view->gcval.font == BadName ) { fprintf(stderr, "Could not load font %s\n", FONTNAME); exit_view(view); XFree(size_hints); XFree(wm_hints); XFree(class_hints); exit(1); } else XChangeGC(view->disp, view->gc, GCFont, &view->gcval); XMapWindow(view->disp, view->win); XSetBackground(view->disp, view->gc, WhitePixel(view->disp, screen)); XQueryTextExtents(view->disp, XGContextFromGC(view->gc), "N", 1, &dummy, &dummy, &dummy, &fontextents); view->fontasc= fontextents.ascent; view->fontdesc= fontextents.descent; view->fontw= fontextents.width; view->fontgap= view->fontw / 2; XFree(size_hints); XFree(wm_hints); XFree(class_hints); } void resize_view(obv_view *view, unsigned ncpus) { char cpustr[6]; const char *label; int nmeters, ind, y; nmeters= ncpus + 7; view->meteroff= (view->windowh - PADDING) / nmeters; if( view->meteroff < METERMINHEIGHT + 2 + view->fontasc + view->fontdesc + 1 + 4 ) { view->meteroff= METERMINHEIGHT + 2 + view->fontasc + view->fontdesc + 1 + 4; view->meterh= METERMINHEIGHT; } else view->meterh= view->meteroff - (2 + view->fontasc + view->fontdesc + 1 + 4); view->labelx= PADDING; view->valuex= view->labelx + 5*view->fontw; view->meterx= view->valuex + 4*view->fontw + view->fontgap; view->meterw= view->windoww - PADDING - 1 - view->meterx; if( view->meterw < METERMINWIDTH ) view->meterw= METERMINWIDTH; view->metery= PADDING + view->fontasc + view->fontdesc + 3; view->label0y= view->metery + (view->meterh - view->fontasc - view->fontdesc) / 2 + view->fontasc; XSetForeground(view->disp, view->gc, 0xFFFFFF); XFillRectangle(view->disp, view->win, view->gc, 0, 0, view->windoww, view->windowh); XSetForeground(view->disp, view->gc, 0); y= view->metery - 1; for( ind= 0; ind< ncpus+7; ++ind, y += view->meteroff ) XDrawRectangle(view->disp, view->win, view->gc, view->meterx-1, y, view->meterw+1, view->meterh+1); y= view->label0y; XDrawString(view->disp, view->win, view->gc, view->labelx, y, "LOAD", 4); y += view->meteroff; for( ind= 0; ind< ncpus; ++ind ) { snprintf(cpustr, 6, "CPU%d", ind); XDrawString(view->disp, view->win, view->gc, view->labelx, y, cpustr, 4 + (ind > 9)); y += view->meteroff; } XDrawString(view->disp, view->win, view->gc, view->labelx, y, "MEM", 3); y += view->meteroff; XDrawString(view->disp, view->win, view->gc, view->labelx, y, "SWAP", 4); y += view->meteroff; XDrawString(view->disp, view->win, view->gc, view->labelx, y, "PAGE", 4); y += view->meteroff; XDrawString(view->disp, view->win, view->gc, view->labelx, y, "DISK", 4); y += view->meteroff; XDrawString(view->disp, view->win, view->gc, view->labelx, y, "NET", 3); y += view->meteroff; XDrawString(view->disp, view->win, view->gc, view->labelx, y, "BAT", 3); label= "PROCS"; draw_key(view, 0, &label, warncol, 1); for( ind= 0; ind< ncpus; ++ind ) draw_key(view, ind+1, cpustabbr, cpustcol, CPUSTATES); draw_key(view, ncpus+1, memabbr, memcol, MEMSTATES); draw_key(view, ncpus+2, swapabbr, swapcol, 3); draw_key(view, ncpus+3, ioabbr, iocol, 3); draw_key(view, ncpus+4, diskabbr, iocol, 3); draw_key(view, ncpus+5, ioabbr, iocol, 3); label= "CHARGE"; draw_key(view, ncpus+6, &label, warncol, 1); } void update_view(obv_view *view, obv_stats *st) { double values[MEMSTATES+3]; int ind; draw_warnmeter(view, 0, !!METERSPLIT, st->loadavg[0]/MAXLOAD, 0); draw_value(view, 0, 0, st->loadavg[0]); if( METERSPLIT ) draw_warnmeter(view, 0, 2, st->loadavg[1]/MAXLOAD, 0); for( ind= 0; ind< st->ncpus; ++ind ) { draw_meter(view, ind+1, !!METERSPLIT, st->cpus + ind*CPUSTATES, cpustcol, CPUSTATES); draw_value(view, ind+1, 1, 1.0 - st->cpus[(ind+1)*CPUSTATES-1]); if( METERSPLIT ) draw_meter(view, ind+1, 2, st->filtered + ind*CPUSTATES, cpustcol, CPUSTATES); } for( ind= 0; ind< MEMSTATES; ++ind ) values[ind]= st->membytes[ind] / st->memtotal; draw_meter(view, st->ncpus+1, !!METERSPLIT, values, memcol, MEMSTATES); draw_value(view, st->ncpus+1, 2, st->memtotal - st->membytes[MEMSTATES-2] - st->membytes[MEMSTATES-1]); if( METERSPLIT ) { for( ind= 0; ind< MEMSTATES; ++ind ) values[ind]= st->filtered[st->ncpus*CPUSTATES+ind] / st->memtotal; draw_meter(view, st->ncpus+1, 2, values, memcol, MEMSTATES); } for( ind= 0; ind< 3; ++ind ) values[ind]= st->swapbytes[ind] / st->swaptotal; draw_meter(view, st->ncpus+2, !!METERSPLIT, values, swapcol, 3); draw_value(view, st->ncpus+2, 2, st->swapbytes[0] + st->swapbytes[1]); if( METERSPLIT ) { for( ind= 0; ind< 3; ++ind ) values[ind]= st->filtered[st->ncpus*CPUSTATES+MEMSTATES+ind] / st->swaptotal; draw_meter(view, st->ncpus+2, 2, values, swapcol, 3); } values[0]= st->pagein / INTERVAL / MAXDISKBW; values[1]= st->pageout / INTERVAL / MAXDISKBW; draw_meter(view, st->ncpus+3, !!METERSPLIT, values, iocol, 3); draw_value(view, st->ncpus+3, 2, (st->pagein + st->pageout) / INTERVAL); if( METERSPLIT ) { values[0]= st->filtered[st->ncpus*CPUSTATES+MEMSTATES+3] / INTERVAL / MAXDISKBW; values[1]= st->filtered[st->ncpus*CPUSTATES+MEMSTATES+4] / INTERVAL / MAXDISKBW; draw_meter(view, st->ncpus+3, 2, values, iocol, 3); } values[0]= (double)st->diskin / INTERVAL / MAXDISKBW; values[1]= (double)st->diskout / INTERVAL / MAXDISKBW; draw_meter(view, st->ncpus+4, !!METERSPLIT, values, iocol, 3); draw_value(view, st->ncpus+4, 2, (double)(st->diskin + st->diskout) / INTERVAL); if( METERSPLIT ) { values[0]= st->filtered[st->ncpus*CPUSTATES+MEMSTATES+5] / INTERVAL / MAXDISKBW; values[1]= st->filtered[st->ncpus*CPUSTATES+MEMSTATES+6] / INTERVAL / MAXDISKBW; draw_meter(view, st->ncpus+4, 2, values, iocol, 3); } values[0]= (double)st->netin / INTERVAL / MAXNETBW; values[1]= (double)st->netout / INTERVAL / MAXNETBW; draw_meter(view, st->ncpus+5, !!METERSPLIT, values, iocol, 3); draw_value(view, st->ncpus+5, 2, (double)(st->netin + st->netout) / INTERVAL); if( METERSPLIT ) { values[0]= st->filtered[st->ncpus*CPUSTATES+MEMSTATES+7] / INTERVAL / MAXNETBW; values[1]= st->filtered[st->ncpus*CPUSTATES+MEMSTATES+8] / INTERVAL / MAXNETBW; draw_meter(view, st->ncpus+5, 2, values, iocol, 3); } if( st->bat_max > 0 ) { values[0]= (double)st->bat_curr / st->bat_max; if( st->psu_on ) draw_meter(view, st->ncpus+6, 0, values, chargingcol, 2); else draw_warnmeter(view, st->ncpus+6, 0, values[0], 1); draw_value(view, st->ncpus+6, 1, values[0]); } } void exit_view(obv_view *view) { XUnmapWindow(view->disp, view->win); XUnloadFont(view->disp, view->gcval.font); XFreeGC(view->disp, view->gc); XCloseDisplay(view->disp); } /* Draw horizontally stacked coloured bars of a meter of multiple values that add up to 1.0. The meter index determines the y position. If split is 1, only the upper half of the bar is painted; if it is 2, only the lower half. The last 3 arguments give the values and colours. The last value is ignored; its bar is filled in to the right end of the meter. */ void draw_meter(obv_view *view, unsigned metind, int split, const double *fractions, const uint32_t *colours, unsigned n) { double sum; unsigned y, h, xoff, xtop, ind; y= view->metery + metind * view->meteroff; if( split ) { h= (view->meterh + 1) / 2; if( split == 2 ) { y += h; if( view->meterh & 1 ) --h; } } else h= view->meterh; xoff= 0; sum= 0.0; for( ind= 0; ind < n-1; ++ind ) { if( fractions[ind] <= 0.0 ) continue; sum += fractions[ind]; if( sum > 1.0 ) sum= 1.0; xtop= (unsigned)(view->meterw * sum + 0.5); if( xtop <= xoff ) continue; XSetForeground(view->disp, view->gc, colours[ind]); XFillRectangle(view->disp, view->win, view->gc, view->meterx+xoff, y, xtop-xoff, h); xoff= xtop; if( xoff >= view->meterw ) return; } XSetForeground(view->disp, view->gc, colours[n-1]); XFillRectangle(view->disp, view->win, view->gc, view->meterx+xoff, y, view->meterw-xoff, h); } /* Draw a horizontal bar indicating a single value, in a colour also indicating the value. The meter index determines the y position. If split is 1, only the upper half of the bar is painted; if it is 2, only the lower half. The colour is interpolated from warncol[] for values between 0.1 and 0.9. */ void draw_warnmeter(obv_view *view, unsigned metind, int split, double value, int reverse) { double colfrac; uint32_t colour; unsigned y, h; int w, ind, colind; y= view->metery + metind * view->meteroff; if( split ) { h= (view->meterh + 1) / 2; if( split == 2 ) { y += h; if( view->meterh & 1 ) --h; } } else h= view->meterh; w= value * view->meterw + 0.5; if( w < 0 ) w= 0; else if( w > view->meterw ) w= view->meterw; if( reverse ) value= 1.0 - value; colfrac= (value - 0.1) / 0.8 * (NWARNCOL - 1); if( colfrac <= 0.0 ) colour= warncol[0]; else if( colfrac >= NWARNCOL-1 ) colour= warncol[NWARNCOL-1]; else { colind= (int)colfrac; colfrac -= colind; for( ind= 0; ind< 4; ++ind ) ((uint8_t*)&colour)[ind]= colfrac * ((uint8_t*)(warncol+colind+1))[ind] + (1.0 - colfrac) * ((uint8_t*)(warncol+colind))[ind]; } XSetForeground(view->disp, view->gc, colour); XFillRectangle(view->disp, view->win, view->gc, view->meterx, y, w, h); if( w < view->meterw ) { XSetForeground(view->disp, view->gc, IDLECOL); XFillRectangle(view->disp, view->win, view->gc, view->meterx + w, y, view->meterw - w, h); } } /* Draw value as text to the left of meter. type is 0 for numeric, 1 for percentage, 2 for data volume. */ void draw_value(obv_view *view, unsigned metind, int type, double value) { char valstr[5]; if( type == 0 ) snprintf(valstr, 5, "%4g", value); else if( type == 1 ) snprintf(valstr, 5, "%3d%%", (int)(100*value+0.5)); else sprintbytes(valstr, value); XSetForeground(view->disp, view->gc, VALUECOL); XDrawImageString(view->disp, view->win, view->gc, view->valuex, view->label0y + metind*view->meteroff, valstr, 4); } /* Draw a concatenated string in multiple colours above the meter with the given index. */ void draw_key(obv_view *view, unsigned metind, const char **strs, const uint32_t *colours, int n) { int x, y, w, ind, xoff, nchars, dummy; w= 0; for( ind= 0; ind < n; ++ind ) w += strlen(strs[ind]); w= w * view->fontw + (n - 1) * view->fontgap; x= view->meterx; if( w > view->meterw ) x -= w - view->meterw; y= view->metery + metind * view->meteroff - 3 - view->fontdesc; for( ind= 0; ind < n; ++ind ) { nchars= strlen(strs[ind]); XSetForeground(view->disp, view->gc, colours[ind]); XDrawString(view->disp, view->win, view->gc, x, y, strs[ind], nchars); x += view->fontw * nchars; x += view->fontgap; } } /* Print abbreviated byte count in 5 chars including terminating 0. */ static const char suffix[]= "kMGTPE"; void sprintbytes(char *dest, double bytes) { int tenbits; tenbits= bytes > 0.0 ? (int)floor(log2(bytes)/10) : 0; bytes /= exp2(10.0 * tenbits); if( bytes < 10 ) snprintf(dest, 4, "%.1f", bytes); else if( bytes < 100 ) snprintf(dest, 4, " %d", (int)floor(bytes)); else snprintf(dest, 4, "%d", (int)floor(bytes)); if( tenbits == 0 ) dest[3]= ' '; else if( tenbits < 7 ) dest[3]= suffix[tenbits-1]; else dest[3]= '!'; dest[4]= 0; }