stats.c 27 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376
  1. #include <u.h>
  2. #include <libc.h>
  3. #include <ctype.h>
  4. #include <auth.h>
  5. #include <fcall.h>
  6. #include <draw.h>
  7. #include <event.h>
  8. #define MAXNUM 10 /* maximum number of numbers on data line */
  9. typedef struct Graph Graph;
  10. typedef struct Machine Machine;
  11. struct Graph
  12. {
  13. int colindex;
  14. Rectangle r;
  15. int *data;
  16. int ndata;
  17. char *label;
  18. void (*newvalue)(Machine*, ulong*, ulong*, int);
  19. void (*update)(Graph*, ulong, ulong);
  20. Machine *mach;
  21. int overflow;
  22. Image *overtmp;
  23. };
  24. enum
  25. {
  26. /* old /dev/swap */
  27. Mem = 0,
  28. Maxmem,
  29. Swap,
  30. Maxswap,
  31. /* /dev/sysstats */
  32. Procno = 0,
  33. Context,
  34. Interrupt,
  35. Syscall,
  36. Fault,
  37. TLBfault,
  38. TLBpurge,
  39. Load,
  40. Idle,
  41. InIntr,
  42. /* /net/ether0/stats */
  43. In = 0,
  44. Link,
  45. Out,
  46. Err0,
  47. };
  48. struct Machine
  49. {
  50. char *name;
  51. int remote;
  52. int statsfd;
  53. int swapfd;
  54. int etherfd;
  55. int ifstatsfd;
  56. int batteryfd;
  57. int bitsybatfd;
  58. int disable;
  59. ulong devswap[4];
  60. ulong devsysstat[10];
  61. ulong prevsysstat[10];
  62. int nproc;
  63. ulong netetherstats[8];
  64. ulong prevetherstats[8];
  65. ulong batterystats[2];
  66. ulong netetherifstats[2];
  67. char buf[1024];
  68. char *bufp;
  69. char *ebufp;
  70. };
  71. enum
  72. {
  73. Mainproc,
  74. Mouseproc,
  75. NPROC,
  76. };
  77. enum
  78. {
  79. Ncolor = 6,
  80. Ysqueeze = 2, /* vertical squeezing of label text */
  81. Labspace = 2, /* room around label */
  82. Dot = 2, /* height of dot */
  83. Opwid = 5, /* strlen("add ") or strlen("drop ") */
  84. Nlab = 3, /* max number of labels on y axis */
  85. Lablen = 16, /* max length of label */
  86. Lx = 4, /* label tick length */
  87. };
  88. enum Menu2
  89. {
  90. Mbattery,
  91. Mcontext,
  92. Mether,
  93. Methererr,
  94. Metherin,
  95. Metherout,
  96. Mfault,
  97. Midle,
  98. Minintr,
  99. Mintr,
  100. Mload,
  101. Mmem,
  102. Mswap,
  103. Msyscall,
  104. Mtlbmiss,
  105. Mtlbpurge,
  106. Msignal,
  107. Nmenu2,
  108. };
  109. char *menu2str[Nmenu2+1] = {
  110. "add battery ",
  111. "add context ",
  112. "add ether ",
  113. "add ethererr",
  114. "add etherin ",
  115. "add etherout",
  116. "add fault ",
  117. "add idle ",
  118. "add inintr ",
  119. "add intr ",
  120. "add load ",
  121. "add mem ",
  122. "add swap ",
  123. "add syscall ",
  124. "add tlbmiss ",
  125. "add tlbpurge",
  126. "add 802.11b ",
  127. nil,
  128. };
  129. void contextval(Machine*, ulong*, ulong*, int),
  130. etherval(Machine*, ulong*, ulong*, int),
  131. ethererrval(Machine*, ulong*, ulong*, int),
  132. etherinval(Machine*, ulong*, ulong*, int),
  133. etheroutval(Machine*, ulong*, ulong*, int),
  134. faultval(Machine*, ulong*, ulong*, int),
  135. intrval(Machine*, ulong*, ulong*, int),
  136. inintrval(Machine*, ulong*, ulong*, int),
  137. loadval(Machine*, ulong*, ulong*, int),
  138. idleval(Machine*, ulong*, ulong*, int),
  139. memval(Machine*, ulong*, ulong*, int),
  140. swapval(Machine*, ulong*, ulong*, int),
  141. syscallval(Machine*, ulong*, ulong*, int),
  142. tlbmissval(Machine*, ulong*, ulong*, int),
  143. tlbpurgeval(Machine*, ulong*, ulong*, int),
  144. batteryval(Machine*, ulong*, ulong*, int),
  145. signalval(Machine*, ulong*, ulong*, int);
  146. Menu menu2 = {menu2str, nil};
  147. int present[Nmenu2];
  148. void (*newvaluefn[Nmenu2])(Machine*, ulong*, ulong*, int init) = {
  149. batteryval,
  150. contextval,
  151. etherval,
  152. ethererrval,
  153. etherinval,
  154. etheroutval,
  155. faultval,
  156. idleval,
  157. inintrval,
  158. intrval,
  159. loadval,
  160. memval,
  161. swapval,
  162. syscallval,
  163. tlbmissval,
  164. tlbpurgeval,
  165. signalval,
  166. };
  167. Image *cols[Ncolor][3];
  168. Graph *graph;
  169. Machine *mach;
  170. Font *mediumfont;
  171. char *mysysname;
  172. char argchars[] = "8bceEfiImlnpstw";
  173. int pids[NPROC];
  174. int parity; /* toggled to avoid patterns in textured background */
  175. int nmach;
  176. int ngraph; /* totaly number is ngraph*nmach */
  177. double scale = 1.0;
  178. int logscale = 0;
  179. int ylabels = 0;
  180. int oldsystem = 0;
  181. int sleeptime = 1000;
  182. char *procnames[NPROC] = {"main", "mouse"};
  183. void
  184. killall(char *s)
  185. {
  186. int i, pid;
  187. pid = getpid();
  188. for(i=0; i<NPROC; i++)
  189. if(pids[i] && pids[i]!=pid)
  190. postnote(PNPROC, pids[i], "kill");
  191. exits(s);
  192. }
  193. void*
  194. emalloc(ulong sz)
  195. {
  196. void *v;
  197. v = malloc(sz);
  198. if(v == nil) {
  199. fprint(2, "stats: out of memory allocating %ld: %r\n", sz);
  200. killall("mem");
  201. }
  202. memset(v, 0, sz);
  203. return v;
  204. }
  205. void*
  206. erealloc(void *v, ulong sz)
  207. {
  208. v = realloc(v, sz);
  209. if(v == nil) {
  210. fprint(2, "stats: out of memory reallocating %ld: %r\n", sz);
  211. killall("mem");
  212. }
  213. return v;
  214. }
  215. char*
  216. estrdup(char *s)
  217. {
  218. char *t;
  219. if((t = strdup(s)) == nil) {
  220. fprint(2, "stats: out of memory in strdup(%.10s): %r\n", s);
  221. killall("mem");
  222. }
  223. return t;
  224. }
  225. void
  226. mkcol(int i, int c0, int c1, int c2)
  227. {
  228. cols[i][0] = allocimagemix(display, c0, DWhite);
  229. cols[i][1] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, c1);
  230. cols[i][2] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, c2);
  231. }
  232. void
  233. colinit(void)
  234. {
  235. mediumfont = openfont(display, "/lib/font/bit/pelm/latin1.8.font");
  236. if(mediumfont == nil)
  237. mediumfont = font;
  238. /* Peach */
  239. mkcol(0, 0xFFAAAAFF, 0xFFAAAAFF, 0xBB5D5DFF);
  240. /* Aqua */
  241. mkcol(1, DPalebluegreen, DPalegreygreen, DPurpleblue);
  242. /* Yellow */
  243. mkcol(2, DPaleyellow, DDarkyellow, DYellowgreen);
  244. /* Green */
  245. mkcol(3, DPalegreen, DMedgreen, DDarkgreen);
  246. /* Blue */
  247. mkcol(4, 0x00AAFFFF, 0x00AAFFFF, 0x0088CCFF);
  248. /* Grey */
  249. cols[5][0] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, 0xEEEEEEFF);
  250. cols[5][1] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, 0xCCCCCCFF);
  251. cols[5][2] = allocimage(display, Rect(0,0,1,1), CMAP8, 1, 0x888888FF);
  252. }
  253. int
  254. loadbuf(Machine *m, int *fd)
  255. {
  256. int n;
  257. if(*fd < 0)
  258. return 0;
  259. seek(*fd, 0, 0);
  260. n = read(*fd, m->buf, sizeof m->buf-1);
  261. if(n <= 0){
  262. close(*fd);
  263. *fd = -1;
  264. return 0;
  265. }
  266. m->bufp = m->buf;
  267. m->ebufp = m->buf+n;
  268. m->buf[n] = 0;
  269. return 1;
  270. }
  271. void
  272. label(Point p, int dy, char *text)
  273. {
  274. char *s;
  275. Rune r[2];
  276. int w, maxw, maxy;
  277. p.x += Labspace;
  278. maxy = p.y+dy;
  279. maxw = 0;
  280. r[1] = '\0';
  281. for(s=text; *s; ){
  282. if(p.y+mediumfont->height-Ysqueeze > maxy)
  283. break;
  284. w = chartorune(r, s);
  285. s += w;
  286. w = runestringwidth(mediumfont, r);
  287. if(w > maxw)
  288. maxw = w;
  289. runestring(screen, p, display->black, ZP, mediumfont, r);
  290. p.y += mediumfont->height-Ysqueeze;
  291. }
  292. }
  293. Point
  294. paritypt(int x)
  295. {
  296. return Pt(x+parity, 0);
  297. }
  298. Point
  299. datapoint(Graph *g, int x, ulong v, ulong vmax)
  300. {
  301. Point p;
  302. double y;
  303. p.x = x;
  304. y = ((double)v)/(vmax*scale);
  305. if(logscale){
  306. /*
  307. * Arrange scale to cover a factor of 1000.
  308. * vmax corresponds to the 100 mark.
  309. * 10*vmax is the top of the scale.
  310. */
  311. if(y <= 0.)
  312. y = 0;
  313. else{
  314. y = log10(y);
  315. /* 1 now corresponds to the top; -2 to the bottom; rescale */
  316. y = (y+2.)/3.;
  317. }
  318. }
  319. p.y = g->r.max.y - Dy(g->r)*y - Dot;
  320. if(p.y < g->r.min.y)
  321. p.y = g->r.min.y;
  322. if(p.y > g->r.max.y-Dot)
  323. p.y = g->r.max.y-Dot;
  324. return p;
  325. }
  326. void
  327. drawdatum(Graph *g, int x, ulong prev, ulong v, ulong vmax)
  328. {
  329. int c;
  330. Point p, q;
  331. c = g->colindex;
  332. p = datapoint(g, x, v, vmax);
  333. q = datapoint(g, x, prev, vmax);
  334. if(p.y < q.y){
  335. draw(screen, Rect(p.x, g->r.min.y, p.x+1, p.y), cols[c][0], nil, paritypt(p.x));
  336. draw(screen, Rect(p.x, p.y, p.x+1, q.y+Dot), cols[c][2], nil, ZP);
  337. draw(screen, Rect(p.x, q.y+Dot, p.x+1, g->r.max.y), cols[c][1], nil, ZP);
  338. }else{
  339. draw(screen, Rect(p.x, g->r.min.y, p.x+1, q.y), cols[c][0], nil, paritypt(p.x));
  340. draw(screen, Rect(p.x, q.y, p.x+1, p.y+Dot), cols[c][2], nil, ZP);
  341. draw(screen, Rect(p.x, p.y+Dot, p.x+1, g->r.max.y), cols[c][1], nil, ZP);
  342. }
  343. }
  344. void
  345. redraw(Graph *g, ulong vmax)
  346. {
  347. int i, c;
  348. c = g->colindex;
  349. draw(screen, g->r, cols[c][0], nil, paritypt(g->r.min.x));
  350. for(i=1; i<Dx(g->r); i++)
  351. drawdatum(g, g->r.max.x-i, g->data[i-1], g->data[i], vmax);
  352. drawdatum(g, g->r.min.x, g->data[i], g->data[i], vmax);
  353. g->overflow = 0;
  354. }
  355. void
  356. update1(Graph *g, ulong v, ulong vmax)
  357. {
  358. char buf[48];
  359. int overflow;
  360. if(g->overflow && g->overtmp!=nil)
  361. draw(screen, g->overtmp->r, g->overtmp, nil, g->overtmp->r.min);
  362. draw(screen, g->r, screen, nil, Pt(g->r.min.x+1, g->r.min.y));
  363. drawdatum(g, g->r.max.x-1, g->data[0], v, vmax);
  364. memmove(g->data+1, g->data, (g->ndata-1)*sizeof(g->data[0]));
  365. g->data[0] = v;
  366. g->overflow = 0;
  367. if(logscale)
  368. overflow = (v>10*vmax*scale);
  369. else
  370. overflow = (v>vmax*scale);
  371. if(overflow && g->overtmp!=nil){
  372. g->overflow = 1;
  373. draw(g->overtmp, g->overtmp->r, screen, nil, g->overtmp->r.min);
  374. sprint(buf, "%lud", v);
  375. string(screen, g->overtmp->r.min, display->black, ZP, mediumfont, buf);
  376. }
  377. }
  378. /* read one line of text from buffer and process integers */
  379. int
  380. readnums(Machine *m, int n, ulong *a, int spanlines)
  381. {
  382. int i;
  383. char *p, *ep;
  384. if(spanlines)
  385. ep = m->ebufp;
  386. else
  387. for(ep=m->bufp; ep<m->ebufp; ep++)
  388. if(*ep == '\n')
  389. break;
  390. p = m->bufp;
  391. for(i=0; i<n && p<ep; i++){
  392. while(p<ep && !isdigit(*p) && *p!='-')
  393. p++;
  394. if(p == ep)
  395. break;
  396. a[i] = strtoul(p, &p, 10);
  397. }
  398. if(ep < m->ebufp)
  399. ep++;
  400. m->bufp = ep;
  401. return i == n;
  402. }
  403. /* Network on fd1, mount driver on fd0 */
  404. static int
  405. filter(int fd)
  406. {
  407. int p[2];
  408. if(pipe(p) < 0){
  409. fprint(2, "stats: can't pipe: %r\n");
  410. killall("pipe");
  411. }
  412. switch(rfork(RFNOWAIT|RFPROC|RFFDG)) {
  413. case -1:
  414. sysfatal("rfork record module");
  415. case 0:
  416. dup(fd, 1);
  417. close(fd);
  418. dup(p[0], 0);
  419. close(p[0]);
  420. close(p[1]);
  421. execl("/bin/aux/fcall", "fcall", nil);
  422. fprint(2, "stats: can't exec fcall: %r\n");
  423. killall("fcall");
  424. default:
  425. close(fd);
  426. close(p[0]);
  427. }
  428. return p[1];
  429. }
  430. /*
  431. * 9fs
  432. */
  433. int
  434. connect9fs(char *addr)
  435. {
  436. char dir[256], *na;
  437. int fd;
  438. fprint(2, "connect9fs...");
  439. na = netmkaddr(addr, 0, "9fs");
  440. fprint(2, "dial %s...", na);
  441. if((fd = dial(na, 0, dir, 0)) < 0)
  442. return -1;
  443. fprint(2, "dir %s...", dir);
  444. // if(strstr(dir, "tcp"))
  445. // fd = filter(fd);
  446. return fd;
  447. }
  448. int
  449. old9p(int fd)
  450. {
  451. int p[2];
  452. if(pipe(p) < 0)
  453. return -1;
  454. switch(rfork(RFPROC|RFFDG|RFNAMEG)) {
  455. case -1:
  456. return -1;
  457. case 0:
  458. if(fd != 1){
  459. dup(fd, 1);
  460. close(fd);
  461. }
  462. if(p[0] != 0){
  463. dup(p[0], 0);
  464. close(p[0]);
  465. }
  466. close(p[1]);
  467. if(0){
  468. fd = open("/sys/log/cpu", OWRITE);
  469. if(fd != 2){
  470. dup(fd, 2);
  471. close(fd);
  472. }
  473. execl("/bin/srvold9p", "srvold9p", "-ds", nil);
  474. } else
  475. execl("/bin/srvold9p", "srvold9p", "-s", nil);
  476. return -1;
  477. default:
  478. close(fd);
  479. close(p[0]);
  480. }
  481. return p[1];
  482. }
  483. /*
  484. * exportfs
  485. */
  486. int
  487. connectexportfs(char *addr)
  488. {
  489. char buf[ERRMAX], dir[256], *na;
  490. int fd, n;
  491. char *tree;
  492. AuthInfo *ai;
  493. tree = "/";
  494. na = netmkaddr(addr, 0, "exportfs");
  495. if((fd = dial(na, 0, dir, 0)) < 0)
  496. return -1;
  497. ai = auth_proxy(fd, auth_getkey, "proto=p9any role=client");
  498. if(ai == nil)
  499. return -1;
  500. n = write(fd, tree, strlen(tree));
  501. if(n < 0){
  502. close(fd);
  503. return -1;
  504. }
  505. strcpy(buf, "can't read tree");
  506. n = read(fd, buf, sizeof buf - 1);
  507. if(n!=2 || buf[0]!='O' || buf[1]!='K'){
  508. buf[sizeof buf - 1] = '\0';
  509. werrstr("bad remote tree: %s\n", buf);
  510. close(fd);
  511. return -1;
  512. }
  513. // if(strstr(dir, "tcp"))
  514. // fd = filter(fd);
  515. if(oldsystem)
  516. return old9p(fd);
  517. return fd;
  518. }
  519. int
  520. readswap(Machine *m, ulong *a)
  521. {
  522. if(strstr(m->buf, "memory\n")){
  523. /* new /dev/swap - skip first 3 numbers */
  524. if(!readnums(m, 7, a, 1))
  525. return 0;
  526. a[0] = a[3];
  527. a[1] = a[4];
  528. a[2] = a[5];
  529. a[3] = a[6];
  530. return 1;
  531. }
  532. return readnums(m, nelem(m->devswap), a, 0);
  533. }
  534. int
  535. initmach(Machine *m, char *name)
  536. {
  537. int n, fd;
  538. ulong a[MAXNUM];
  539. char *p, mpt[256], buf[256];
  540. p = strchr(name, '!');
  541. if(p)
  542. p++;
  543. else
  544. p = name;
  545. m->name = estrdup(p);
  546. m->remote = (strcmp(p, mysysname) != 0);
  547. if(m->remote == 0)
  548. strcpy(mpt, "");
  549. else{
  550. snprint(mpt, sizeof mpt, "/n/%s", p);
  551. fd = connectexportfs(name);
  552. if(fd < 0){
  553. fprint(2, "can't connect to %s: %r\n", name);
  554. return 0;
  555. }
  556. /* BUG? need to use amount() now? */
  557. if(mount(fd, -1, mpt, MREPL, "") < 0){
  558. fprint(2, "stats: mount %s on %s failed (%r); trying /n/sid\n", name, mpt);
  559. strcpy(mpt, "/n/sid");
  560. if(mount(fd, -1, mpt, MREPL, "") < 0){
  561. fprint(2, "stats: mount %s on %s failed: %r\n", name, mpt);
  562. return 0;
  563. }
  564. }
  565. }
  566. snprint(buf, sizeof buf, "%s/dev/swap", mpt);
  567. m->swapfd = open(buf, OREAD);
  568. if(loadbuf(m, &m->swapfd) && readswap(m, a))
  569. memmove(m->devswap, a, sizeof m->devswap);
  570. else
  571. m->devswap[Maxmem] = m->devswap[Maxswap] = 100;
  572. snprint(buf, sizeof buf, "%s/dev/sysstat", mpt);
  573. m->statsfd = open(buf, OREAD);
  574. if(loadbuf(m, &m->statsfd)){
  575. for(n=0; readnums(m, nelem(m->devsysstat), a, 0); n++)
  576. ;
  577. m->nproc = n;
  578. }else
  579. m->nproc = 1;
  580. snprint(buf, sizeof buf, "%s/net/ether0/stats", mpt);
  581. m->etherfd = open(buf, OREAD);
  582. if(loadbuf(m, &m->etherfd) && readnums(m, nelem(m->netetherstats), a, 1))
  583. memmove(m->netetherstats, a, sizeof m->netetherstats);
  584. snprint(buf, sizeof buf, "%s/net/ether0/ifstats", mpt);
  585. m->ifstatsfd = open(buf, OREAD);
  586. if(loadbuf(m, &m->ifstatsfd)){
  587. /* need to check that this is a wavelan interface */
  588. if(strncmp(m->buf, "Signal: ", 8) == 0 && readnums(m, nelem(m->netetherifstats), a, 1))
  589. memmove(m->netetherifstats, a, sizeof m->netetherifstats);
  590. }
  591. snprint(buf, sizeof buf, "%s/mnt/apm/battery", mpt);
  592. m->batteryfd = open(buf, OREAD);
  593. m->bitsybatfd = -1;
  594. if(m->batteryfd >= 0){
  595. if(loadbuf(m, &m->batteryfd) && readnums(m, nelem(m->batterystats), a, 0))
  596. memmove(m->batterystats, a, sizeof(m->batterystats));
  597. }else{
  598. snprint(buf, sizeof buf, "%s/dev/battery", mpt);
  599. m->bitsybatfd = open(buf, OREAD);
  600. if(loadbuf(m, &m->bitsybatfd) && readnums(m, 1, a, 0))
  601. memmove(m->batterystats, a, sizeof(m->batterystats));
  602. }
  603. return 1;
  604. }
  605. jmp_buf catchalarm;
  606. void
  607. alarmed(void *a, char *s)
  608. {
  609. if(strcmp(s, "alarm") == 0)
  610. notejmp(a, catchalarm, 1);
  611. noted(NDFLT);
  612. }
  613. int
  614. needswap(int init)
  615. {
  616. return init | present[Mmem] | present[Mswap];
  617. }
  618. int
  619. needstat(int init)
  620. {
  621. return init | present[Mcontext] | present[Mfault] | present[Mintr] | present[Mload] | present[Midle] |
  622. present[Minintr] | present[Msyscall] | present[Mtlbmiss] | present[Mtlbpurge];
  623. }
  624. int
  625. needether(int init)
  626. {
  627. return init | present[Mether] | present[Metherin] | present[Metherout] | present[Methererr];
  628. }
  629. int
  630. needbattery(int init)
  631. {
  632. return init | present[Mbattery];
  633. }
  634. int
  635. needsignal(int init)
  636. {
  637. return init | present[Msignal];
  638. }
  639. void
  640. readmach(Machine *m, int init)
  641. {
  642. int n, i;
  643. ulong a[8];
  644. char buf[32];
  645. if(m->remote && (m->disable || setjmp(catchalarm))){
  646. if (m->disable++ >= 5)
  647. m->disable = 0; /* give it another chance */
  648. memmove(m->devsysstat, m->prevsysstat, sizeof m->devsysstat);
  649. memmove(m->netetherstats, m->prevetherstats, sizeof m->netetherstats);
  650. return;
  651. }
  652. snprint(buf, sizeof buf, "%s", m->name);
  653. if (strcmp(m->name, buf) != 0){
  654. free(m->name);
  655. m->name = estrdup(buf);
  656. if(display != nil) /* else we're still initializing */
  657. eresized(0);
  658. }
  659. if(m->remote){
  660. notify(alarmed);
  661. alarm(5000);
  662. }
  663. if(needswap(init) && loadbuf(m, &m->swapfd) && readswap(m, a))
  664. memmove(m->devswap, a, sizeof m->devswap);
  665. if(needstat(init) && loadbuf(m, &m->statsfd)){
  666. memmove(m->prevsysstat, m->devsysstat, sizeof m->devsysstat);
  667. memset(m->devsysstat, 0, sizeof m->devsysstat);
  668. for(n=0; n<m->nproc && readnums(m, nelem(m->devsysstat), a, 0); n++)
  669. for(i=0; i<nelem(m->devsysstat); i++)
  670. m->devsysstat[i] += a[i];
  671. }
  672. if(needether(init) && loadbuf(m, &m->etherfd) && readnums(m, nelem(m->netetherstats), a, 1)){
  673. memmove(m->prevetherstats, m->netetherstats, sizeof m->netetherstats);
  674. memmove(m->netetherstats, a, sizeof m->netetherstats);
  675. }
  676. if(needsignal(init) && loadbuf(m, &m->ifstatsfd) && strncmp(m->buf, "Signal: ", 8)==0 && readnums(m, nelem(m->netetherifstats), a, 1)){
  677. memmove(m->netetherifstats, a, sizeof m->netetherifstats);
  678. }
  679. if(needbattery(init) && loadbuf(m, &m->batteryfd) && readnums(m, nelem(m->batterystats), a, 0))
  680. memmove(m->batterystats, a, sizeof(m->batterystats));
  681. if(needbattery(init) && loadbuf(m, &m->bitsybatfd) && readnums(m, 1, a, 0))
  682. memmove(m->batterystats, a, sizeof(m->batterystats));
  683. if(m->remote){
  684. alarm(0);
  685. notify(nil);
  686. }
  687. }
  688. void
  689. memval(Machine *m, ulong *v, ulong *vmax, int)
  690. {
  691. *v = m->devswap[Mem];
  692. *vmax = m->devswap[Maxmem];
  693. }
  694. void
  695. swapval(Machine *m, ulong *v, ulong *vmax, int)
  696. {
  697. *v = m->devswap[Swap];
  698. *vmax = m->devswap[Maxswap];
  699. }
  700. void
  701. contextval(Machine *m, ulong *v, ulong *vmax, int init)
  702. {
  703. *v = m->devsysstat[Context]-m->prevsysstat[Context];
  704. *vmax = sleeptime*m->nproc;
  705. if(init)
  706. *vmax = sleeptime;
  707. }
  708. void
  709. intrval(Machine *m, ulong *v, ulong *vmax, int init)
  710. {
  711. *v = m->devsysstat[Interrupt]-m->prevsysstat[Interrupt];
  712. *vmax = sleeptime*m->nproc;
  713. if(init)
  714. *vmax = sleeptime;
  715. }
  716. void
  717. syscallval(Machine *m, ulong *v, ulong *vmax, int init)
  718. {
  719. *v = m->devsysstat[Syscall]-m->prevsysstat[Syscall];
  720. *vmax = sleeptime*m->nproc;
  721. if(init)
  722. *vmax = sleeptime;
  723. }
  724. void
  725. faultval(Machine *m, ulong *v, ulong *vmax, int init)
  726. {
  727. *v = m->devsysstat[Fault]-m->prevsysstat[Fault];
  728. *vmax = sleeptime*m->nproc;
  729. if(init)
  730. *vmax = sleeptime;
  731. }
  732. void
  733. tlbmissval(Machine *m, ulong *v, ulong *vmax, int init)
  734. {
  735. *v = m->devsysstat[TLBfault]-m->prevsysstat[TLBfault];
  736. *vmax = (sleeptime/1000)*10*m->nproc;
  737. if(init)
  738. *vmax = (sleeptime/1000)*10;
  739. }
  740. void
  741. tlbpurgeval(Machine *m, ulong *v, ulong *vmax, int init)
  742. {
  743. *v = m->devsysstat[TLBpurge]-m->prevsysstat[TLBpurge];
  744. *vmax = (sleeptime/1000)*10*m->nproc;
  745. if(init)
  746. *vmax = (sleeptime/1000)*10;
  747. }
  748. void
  749. loadval(Machine *m, ulong *v, ulong *vmax, int init)
  750. {
  751. *v = m->devsysstat[Load];
  752. *vmax = 1000*m->nproc;
  753. if(init)
  754. *vmax = 1000;
  755. }
  756. void
  757. idleval(Machine *m, ulong *v, ulong *vmax, int)
  758. {
  759. *v = m->devsysstat[Idle]/m->nproc;
  760. *vmax = 100;
  761. }
  762. void
  763. inintrval(Machine *m, ulong *v, ulong *vmax, int)
  764. {
  765. *v = m->devsysstat[InIntr]/m->nproc;
  766. *vmax = 100;
  767. }
  768. void
  769. etherval(Machine *m, ulong *v, ulong *vmax, int init)
  770. {
  771. *v = m->netetherstats[In]-m->prevetherstats[In] + m->netetherstats[Out]-m->prevetherstats[Out];
  772. *vmax = sleeptime*m->nproc;
  773. if(init)
  774. *vmax = sleeptime;
  775. }
  776. void
  777. etherinval(Machine *m, ulong *v, ulong *vmax, int init)
  778. {
  779. *v = m->netetherstats[In]-m->prevetherstats[In];
  780. *vmax = sleeptime*m->nproc;
  781. if(init)
  782. *vmax = sleeptime;
  783. }
  784. void
  785. etheroutval(Machine *m, ulong *v, ulong *vmax, int init)
  786. {
  787. *v = m->netetherstats[Out]-m->prevetherstats[Out];
  788. *vmax = sleeptime*m->nproc;
  789. if(init)
  790. *vmax = sleeptime;
  791. }
  792. void
  793. ethererrval(Machine *m, ulong *v, ulong *vmax, int init)
  794. {
  795. int i;
  796. *v = 0;
  797. for(i=Err0; i<nelem(m->netetherstats); i++)
  798. *v += m->netetherstats[i];
  799. *vmax = (sleeptime/1000)*10*m->nproc;
  800. if(init)
  801. *vmax = (sleeptime/1000)*10;
  802. }
  803. void
  804. batteryval(Machine *m, ulong *v, ulong *vmax, int)
  805. {
  806. *v = m->batterystats[0];
  807. if(m->bitsybatfd >= 0)
  808. *vmax = 184; // at least on my bitsy...
  809. else
  810. *vmax = 100;
  811. }
  812. void
  813. signalval(Machine *m, ulong *v, ulong *vmax, int)
  814. {
  815. ulong l;
  816. *vmax = sleeptime;
  817. l = m->netetherifstats[0];
  818. /*
  819. * Range is seen to be from about -45 (strong) to -95 (weak); rescale
  820. */
  821. if(l == 0){ /* probably not present */
  822. *v = 0;
  823. return;
  824. }
  825. *v = 20*(l+95);
  826. }
  827. void
  828. usage(void)
  829. {
  830. fprint(2, "usage: stats [-O] [-S scale] [-LY] [-%s] [machine...]\n", argchars);
  831. exits("usage");
  832. }
  833. void
  834. addgraph(int n)
  835. {
  836. Graph *g, *ograph;
  837. int i, j;
  838. static int nadd;
  839. if(n > nelem(menu2str))
  840. abort();
  841. /* avoid two adjacent graphs of same color */
  842. if(ngraph>0 && graph[ngraph-1].colindex==nadd%Ncolor)
  843. nadd++;
  844. ograph = graph;
  845. graph = emalloc(nmach*(ngraph+1)*sizeof(Graph));
  846. for(i=0; i<nmach; i++)
  847. for(j=0; j<ngraph; j++)
  848. graph[i*(ngraph+1)+j] = ograph[i*ngraph+j];
  849. free(ograph);
  850. ngraph++;
  851. for(i=0; i<nmach; i++){
  852. g = &graph[i*ngraph+(ngraph-1)];
  853. memset(g, 0, sizeof(Graph));
  854. g->label = menu2str[n]+Opwid;
  855. g->newvalue = newvaluefn[n];
  856. g->update = update1; /* no other update functions yet */
  857. g->mach = &mach[i];
  858. g->colindex = nadd%Ncolor;
  859. }
  860. present[n] = 1;
  861. nadd++;
  862. }
  863. void
  864. dropgraph(int which)
  865. {
  866. Graph *ograph;
  867. int i, j, n;
  868. if(which > nelem(menu2str))
  869. abort();
  870. /* convert n to index in graph table */
  871. n = -1;
  872. for(i=0; i<ngraph; i++)
  873. if(strcmp(menu2str[which]+Opwid, graph[i].label) == 0){
  874. n = i;
  875. break;
  876. }
  877. if(n < 0){
  878. fprint(2, "stats: internal error can't drop graph\n");
  879. killall("error");
  880. }
  881. ograph = graph;
  882. graph = emalloc(nmach*(ngraph-1)*sizeof(Graph));
  883. for(i=0; i<nmach; i++){
  884. for(j=0; j<n; j++)
  885. graph[i*(ngraph-1)+j] = ograph[i*ngraph+j];
  886. free(ograph[i*ngraph+j].data);
  887. freeimage(ograph[i*ngraph+j].overtmp);
  888. for(j++; j<ngraph; j++)
  889. graph[i*(ngraph-1)+j-1] = ograph[i*ngraph+j];
  890. }
  891. free(ograph);
  892. ngraph--;
  893. present[which] = 0;
  894. }
  895. int
  896. addmachine(char *name)
  897. {
  898. if(ngraph > 0){
  899. fprint(2, "stats: internal error: ngraph>0 in addmachine()\n");
  900. usage();
  901. }
  902. if(mach == nil)
  903. nmach = 0; /* a little dance to get us started with local machine by default */
  904. mach = erealloc(mach, (nmach+1)*sizeof(Machine));
  905. memset(mach+nmach, 0, sizeof(Machine));
  906. if (initmach(mach+nmach, name)){
  907. nmach++;
  908. return 1;
  909. } else
  910. return 0;
  911. }
  912. void
  913. labelstrs(Graph *g, char strs[Nlab][Lablen], int *np)
  914. {
  915. int j;
  916. ulong v, vmax;
  917. g->newvalue(g->mach, &v, &vmax, 1);
  918. if(logscale){
  919. for(j=1; j<=2; j++)
  920. sprint(strs[j-1], "%g", scale*pow(10., j)*(double)vmax/100.);
  921. *np = 2;
  922. }else{
  923. for(j=1; j<=3; j++)
  924. sprint(strs[j-1], "%g", scale*(double)j*(double)vmax/4.0);
  925. *np = 3;
  926. }
  927. }
  928. int
  929. labelwidth(void)
  930. {
  931. int i, j, n, w, maxw;
  932. char strs[Nlab][Lablen];
  933. maxw = 0;
  934. for(i=0; i<ngraph; i++){
  935. /* choose value for rightmost graph */
  936. labelstrs(&graph[ngraph*(nmach-1)+i], strs, &n);
  937. for(j=0; j<n; j++){
  938. w = stringwidth(mediumfont, strs[j]);
  939. if(w > maxw)
  940. maxw = w;
  941. }
  942. }
  943. return maxw;
  944. }
  945. void
  946. resize(void)
  947. {
  948. int i, j, k, n, startx, starty, x, y, dx, dy, ly, ondata, maxx, wid, nlab;
  949. Graph *g;
  950. Rectangle machr, r;
  951. ulong v, vmax;
  952. char buf[128], labs[Nlab][Lablen];
  953. draw(screen, screen->r, display->white, nil, ZP);
  954. /* label left edge */
  955. x = screen->r.min.x;
  956. y = screen->r.min.y + Labspace+mediumfont->height+Labspace;
  957. dy = (screen->r.max.y - y)/ngraph;
  958. dx = Labspace+stringwidth(mediumfont, "0")+Labspace;
  959. startx = x+dx+1;
  960. starty = y;
  961. for(i=0; i<ngraph; i++,y+=dy){
  962. draw(screen, Rect(x, y-1, screen->r.max.x, y), display->black, nil, ZP);
  963. draw(screen, Rect(x, y, x+dx, screen->r.max.y), cols[graph[i].colindex][0], nil, paritypt(x));
  964. label(Pt(x, y), dy, graph[i].label);
  965. draw(screen, Rect(x+dx, y, x+dx+1, screen->r.max.y), cols[graph[i].colindex][2], nil, ZP);
  966. }
  967. /* label top edge */
  968. dx = (screen->r.max.x - startx)/nmach;
  969. for(x=startx, i=0; i<nmach; i++,x+=dx){
  970. draw(screen, Rect(x-1, starty-1, x, screen->r.max.y), display->black, nil, ZP);
  971. j = dx/stringwidth(mediumfont, "0");
  972. n = mach[i].nproc;
  973. if(n>1 && j>=1+3+(n>10)+(n>100)){ /* first char of name + (n) */
  974. j -= 3+(n>10)+(n>100);
  975. if(j <= 0)
  976. j = 1;
  977. snprint(buf, sizeof buf, "%.*s(%d)", j, mach[i].name, n);
  978. }else
  979. snprint(buf, sizeof buf, "%.*s", j, mach[i].name);
  980. string(screen, Pt(x+Labspace, screen->r.min.y + Labspace), display->black, ZP, mediumfont, buf);
  981. }
  982. maxx = screen->r.max.x;
  983. /* label right, if requested */
  984. if(ylabels && dy>Nlab*(mediumfont->height+1)){
  985. wid = labelwidth();
  986. if(wid < (maxx-startx)-30){
  987. /* else there's not enough room */
  988. maxx -= 1+Lx+wid;
  989. draw(screen, Rect(maxx, starty, maxx+1, screen->r.max.y), display->black, nil, ZP);
  990. y = starty;
  991. for(j=0; j<ngraph; j++, y+=dy){
  992. /* choose value for rightmost graph */
  993. g = &graph[ngraph*(nmach-1)+j];
  994. labelstrs(g, labs, &nlab);
  995. r = Rect(maxx+1, y, screen->r.max.x, y+dy-1);
  996. if(j == ngraph-1)
  997. r.max.y = screen->r.max.y;
  998. draw(screen, r, cols[g->colindex][0], nil, paritypt(r.min.x));
  999. for(k=0; k<nlab; k++){
  1000. ly = y + (dy*(nlab-k)/(nlab+1));
  1001. draw(screen, Rect(maxx+1, ly, maxx+1+Lx, ly+1), display->black, nil, ZP);
  1002. ly -= mediumfont->height/2;
  1003. string(screen, Pt(maxx+1+Lx, ly), display->black, ZP, mediumfont, labs[k]);
  1004. }
  1005. }
  1006. }
  1007. }
  1008. /* create graphs */
  1009. for(i=0; i<nmach; i++){
  1010. machr = Rect(startx+i*dx, starty, maxx, screen->r.max.y);
  1011. if(i < nmach-1)
  1012. machr.max.x = startx+(i+1)*dx - 1;
  1013. y = starty;
  1014. for(j=0; j<ngraph; j++, y+=dy){
  1015. g = &graph[i*ngraph+j];
  1016. /* allocate data */
  1017. ondata = g->ndata;
  1018. g->ndata = Dx(machr)+1; /* may be too many if label will be drawn here; so what? */
  1019. g->data = erealloc(g->data, g->ndata*sizeof(ulong));
  1020. if(g->ndata > ondata)
  1021. memset(g->data+ondata, 0, (g->ndata-ondata)*sizeof(ulong));
  1022. /* set geometry */
  1023. g->r = machr;
  1024. g->r.min.y = y;
  1025. g->r.max.y = y+dy - 1;
  1026. if(j == ngraph-1)
  1027. g->r.max.y = screen->r.max.y;
  1028. draw(screen, g->r, cols[g->colindex][0], nil, paritypt(g->r.min.x));
  1029. g->overflow = 0;
  1030. r = g->r;
  1031. r.max.y = r.min.y+mediumfont->height;
  1032. r.max.x = r.min.x+stringwidth(mediumfont, "999999999999");
  1033. freeimage(g->overtmp);
  1034. g->overtmp = nil;
  1035. if(r.max.x <= g->r.max.x)
  1036. g->overtmp = allocimage(display, r, screen->chan, 0, -1);
  1037. g->newvalue(g->mach, &v, &vmax, 0);
  1038. redraw(g, vmax);
  1039. }
  1040. }
  1041. flushimage(display, 1);
  1042. }
  1043. void
  1044. eresized(int new)
  1045. {
  1046. lockdisplay(display);
  1047. if(new && getwindow(display, Refnone) < 0) {
  1048. fprint(2, "stats: can't reattach to window\n");
  1049. killall("reattach");
  1050. }
  1051. resize();
  1052. unlockdisplay(display);
  1053. }
  1054. void
  1055. mouseproc(void)
  1056. {
  1057. Mouse mouse;
  1058. int i;
  1059. for(;;){
  1060. mouse = emouse();
  1061. if(mouse.buttons == 4){
  1062. lockdisplay(display);
  1063. for(i=0; i<Nmenu2; i++)
  1064. if(present[i])
  1065. memmove(menu2str[i], "drop ", Opwid);
  1066. else
  1067. memmove(menu2str[i], "add ", Opwid);
  1068. i = emenuhit(3, &mouse, &menu2);
  1069. if(i >= 0){
  1070. if(!present[i])
  1071. addgraph(i);
  1072. else if(ngraph > 1)
  1073. dropgraph(i);
  1074. resize();
  1075. }
  1076. unlockdisplay(display);
  1077. }
  1078. }
  1079. }
  1080. void
  1081. startproc(void (*f)(void), int index)
  1082. {
  1083. int pid;
  1084. switch(pid = rfork(RFPROC|RFMEM|RFNOWAIT)){
  1085. case -1:
  1086. fprint(2, "stats: fork failed: %r\n");
  1087. killall("fork failed");
  1088. case 0:
  1089. f();
  1090. fprint(2, "stats: %s process exits\n", procnames[index]);
  1091. if(index >= 0)
  1092. killall("process died");
  1093. exits(nil);
  1094. }
  1095. if(index >= 0)
  1096. pids[index] = pid;
  1097. }
  1098. void
  1099. main(int argc, char *argv[])
  1100. {
  1101. int i, j;
  1102. char *s;
  1103. ulong v, vmax, nargs;
  1104. char args[100];
  1105. nmach = 1;
  1106. mysysname = getenv("sysname");
  1107. if(mysysname == nil){
  1108. fprint(2, "stats: can't find $sysname: %r\n");
  1109. exits("sysname");
  1110. }
  1111. mysysname = estrdup(mysysname);
  1112. nargs = 0;
  1113. ARGBEGIN{
  1114. case 'T':
  1115. s = ARGF();
  1116. if(s == nil)
  1117. usage();
  1118. i = atoi(s);
  1119. if(i > 0)
  1120. sleeptime = 1000*i;
  1121. break;
  1122. case 'S':
  1123. s = ARGF();
  1124. if(s == nil)
  1125. usage();
  1126. scale = atof(s);
  1127. if(scale <= 0.)
  1128. usage();
  1129. break;
  1130. case 'L':
  1131. logscale++;
  1132. break;
  1133. case 'Y':
  1134. ylabels++;
  1135. break;
  1136. case 'O':
  1137. oldsystem = 1;
  1138. break;
  1139. default:
  1140. if(nargs>=sizeof args || strchr(argchars, ARGC())==nil)
  1141. usage();
  1142. args[nargs++] = ARGC();
  1143. }ARGEND
  1144. if(argc == 0){
  1145. mach = emalloc(nmach*sizeof(Machine));
  1146. initmach(&mach[0], mysysname);
  1147. readmach(&mach[0], 1);
  1148. }else{
  1149. for(i=j=0; i<argc; i++){
  1150. if (addmachine(argv[i]))
  1151. readmach(&mach[j++], 1);
  1152. }
  1153. if (j == 0)
  1154. exits("connect");
  1155. }
  1156. for(i=0; i<nargs; i++)
  1157. switch(args[i]){
  1158. default:
  1159. fprint(2, "stats: internal error: unknown arg %c\n", args[i]);
  1160. usage();
  1161. case 'b':
  1162. addgraph(Mbattery);
  1163. break;
  1164. case 'c':
  1165. addgraph(Mcontext);
  1166. break;
  1167. case 'e':
  1168. addgraph(Mether);
  1169. break;
  1170. case 'E':
  1171. addgraph(Metherin);
  1172. addgraph(Metherout);
  1173. break;
  1174. case 'f':
  1175. addgraph(Mfault);
  1176. break;
  1177. case 'i':
  1178. addgraph(Mintr);
  1179. break;
  1180. case 'I':
  1181. addgraph(Mload);
  1182. addgraph(Midle);
  1183. addgraph(Minintr);
  1184. break;
  1185. case 'l':
  1186. addgraph(Mload);
  1187. break;
  1188. case 'm':
  1189. addgraph(Mmem);
  1190. break;
  1191. case 'n':
  1192. addgraph(Metherin);
  1193. addgraph(Metherout);
  1194. addgraph(Methererr);
  1195. break;
  1196. case 'p':
  1197. addgraph(Mtlbpurge);
  1198. break;
  1199. case 's':
  1200. addgraph(Msyscall);
  1201. break;
  1202. case 't':
  1203. addgraph(Mtlbmiss);
  1204. addgraph(Mtlbpurge);
  1205. break;
  1206. case '8':
  1207. addgraph(Msignal);
  1208. break;
  1209. case 'w':
  1210. addgraph(Mswap);
  1211. break;
  1212. }
  1213. if(ngraph == 0)
  1214. addgraph(Mload);
  1215. for(i=0; i<nmach; i++)
  1216. for(j=0; j<ngraph; j++)
  1217. graph[i*ngraph+j].mach = &mach[i];
  1218. if(initdraw(nil, nil, "stats") < 0){
  1219. fprint(2, "stats: initdraw failed: %r\n");
  1220. exits("initdraw");
  1221. }
  1222. colinit();
  1223. einit(Emouse);
  1224. notify(nil);
  1225. startproc(mouseproc, Mouseproc);
  1226. pids[Mainproc] = getpid();
  1227. display->locking = 1; /* tell library we're using the display lock */
  1228. resize();
  1229. unlockdisplay(display); /* display is still locked from initdraw() */
  1230. for(;;){
  1231. for(i=0; i<nmach; i++)
  1232. readmach(&mach[i], 0);
  1233. lockdisplay(display);
  1234. parity = 1-parity;
  1235. for(i=0; i<nmach*ngraph; i++){
  1236. graph[i].newvalue(graph[i].mach, &v, &vmax, 0);
  1237. graph[i].update(&graph[i], v, vmax);
  1238. }
  1239. flushimage(display, 1);
  1240. unlockdisplay(display);
  1241. sleep(sleeptime);
  1242. }
  1243. }