xref: /plan9/sys/src/cmd/trace.c (revision 37aecfafe7291d709d10f28aa9a9a7a22ac1305c)
1 #include <u.h>
2 #include <tos.h>
3 #include <libc.h>
4 #include <thread.h>
5 #include <ip.h>
6 #include <bio.h>
7 #include <stdio.h>
8 #include <draw.h>
9 #include <mouse.h>
10 #include <cursor.h>
11 #include <keyboard.h>
12 #include "trace.h"
13 
14 #pragma	varargck	type	"t"		vlong
15 #pragma	varargck	type	"U"		uvlong
16 
17 #define NS(x)	((vlong)x)
18 #define US(x)	(NS(x) * 1000ULL)
19 #define MS(x)	(US(x) * 1000ULL)
20 #define S(x)	(MS(x) * 1000ULL)
21 
22 #define numblocks(a, b)	(((a) + (b) - 1) / (b))
23 #define roundup(a, b)	(numblocks((a), (b)) * (b))
24 
25 enum {
26 	OneRound = MS(1)/2LL,
27 	MilliRound = US(1)/2LL,
28 };
29 
30 typedef struct Event	Event;
31 typedef struct Task	Task;
32 struct Event {
33 	Traceevent;
34 	vlong	etime;	/* length of block to draw */
35 };
36 
37 struct Task {
38 	int	pid;
39 	char	*name;
40 	int	nevents;
41 	Event	*events;
42 	vlong	tstart;
43 	vlong	total;
44 	vlong	runtime;
45 	vlong	runmax;
46 	vlong	runthis;
47 	long	runs;
48 	ulong	tevents[Nevent];
49 };
50 
51 enum {
52 	Nevents = 1024,
53 	Ncolor = 6,
54 	K = 1024,
55 };
56 
57 vlong	now, prevts;
58 
59 int	newwin;
60 int	Width = 1000;
61 int	Height = 100;		// Per task
62 int	topmargin = 8;
63 int	bottommargin = 4;
64 int	lineht = 12;
65 int	wctlfd;
66 int	nevents;
67 Traceevent *eventbuf;
68 Event	*event;
69 
70 void drawtrace(void);
71 int schedparse(char*, char*, char*);
72 int timeconv(Fmt*);
73 
74 char *schedstatename[] = {
75 	[SAdmit] =	"Admit",
76 	[SSleep] =	"Sleep",
77 	[SDead] =	"Dead",
78 	[SDeadline] =	"Deadline",
79 	[SEdf] =	"Edf",
80 	[SExpel] =	"Expel",
81 	[SReady] =	"Ready",
82 	[SRelease] =	"Release",
83 	[SRun] =	"Run",
84 	[SSlice] =	"Slice",
85 	[SInts] =	"Ints",
86 	[SInte] =	"Inte",
87 	[SUser] = 	"User",
88 	[SYield] =	"Yield",
89 };
90 
91 struct {
92 	vlong	scale;
93 	vlong	bigtics;
94 	vlong	littletics;
95 	int	sleep;
96 } scales[] = {
97 	{	US(500),	US(100),	US(50),		  0},
98 	{	US(1000),	US(500),	US(100),	  0},
99 	{	US(2000),	US(1000),	US(200),	  0},
100 	{	US(5000),	US(1000),	US(500),	  0},
101 	{	MS(10),		MS(5),		MS(1),		 20},
102 	{	MS(20),		MS(10),		MS(2),		 20},
103 	{	MS(50),		MS(10),		MS(5),		 20},
104 	{	MS(100),	MS(50),		MS(10),		 20},	/* starting scaleno */
105 	{	MS(200),	MS(100),	MS(20),		 20},
106 	{	MS(500),	MS(100),	MS(50),		 50},
107 	{	MS(1000),	MS(500),	MS(100),	100},
108 	{	MS(2000),	MS(1000),	MS(200),	100},
109 	{	MS(5000),	MS(1000),	MS(500),	100},
110 	{	S(10),		S(50),		S(1),		100},
111 	{	S(20),		S(10),		S(2),		100},
112 	{	S(50),		S(10),		S(5),		100},
113 	{	S(100),		S(50),		S(10),		100},
114 	{	S(200),		S(100),		S(20),		100},
115 	{	S(500),		S(100),		S(50),		100},
116 	{	S(1000),	S(500),		S(100),		100},
117 };
118 
119 int ntasks, verbose;
120 Task *tasks;
121 Image *cols[Ncolor][4];
122 Font *mediumfont, *tinyfont;
123 Image *grey, *red, *green, *blue, *bg, *fg;
124 char*profdev = "/proc/trace";
125 
126 static void
127 usage(void)
128 {
129 	fprint(2, "Usage: %s [-d profdev] [-w] [-b] [-v] [processes]\n", argv0);
130 	exits(nil);
131 }
132 
133 void
134 threadmain(int argc, char **argv)
135 {
136 	int fd, i;
137 	char fname[80];
138 
139 	fmtinstall('t', timeconv);
140 	ARGBEGIN {
141 	case 'd':
142 		profdev = EARGF(usage());
143 		break;
144 	case 'v':
145 		verbose = 1;
146 		break;
147 	case 'w':
148 		newwin++;
149 		break;
150 	default:
151 		usage();
152 	}
153 	ARGEND;
154 
155 	fname[sizeof fname - 1] = 0;
156 	for(i = 0; i < argc; i++){
157 		snprint(fname, sizeof fname - 2, "/proc/%s/ctl",
158 					argv[i]);
159 		if((fd = open(fname, OWRITE)) < 0){
160 			fprint(2, "%s: cannot open %s: %r\n",
161 						argv[0], fname);
162 			continue;
163 		}
164 
165 		if(fprint(fd, "trace") < 0)
166 			fprint(2, "%s: cannot enable tracing on %s: %r\n",
167 						argv[0], fname);
168 		close(fd);
169 	}
170 
171 	drawtrace();
172 }
173 
174 static void
175 mkcol(int i, int c0, int c1, int c2)
176 {
177 	cols[i][0] = allocimagemix(display, c0, DWhite);
178 	cols[i][1] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, c1);
179 	cols[i][2] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, c2);
180 	cols[i][3] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, c0);
181 }
182 
183 static void
184 colinit(void)
185 {
186 	mediumfont = openfont(display, "/lib/font/bit/lucidasans/unicode.10.font");
187 	if(mediumfont == nil)
188 		mediumfont = font;
189 	tinyfont = openfont(display, "/lib/font/bit/lucidasans/unicode.7.font");
190 	if(tinyfont == nil)
191 		tinyfont = font;
192 	topmargin = mediumfont->height+2;
193 	bottommargin = tinyfont->height+2;
194 
195 	/* Peach */
196 	mkcol(0, 0xFFAAAAFF, 0xFFAAAAFF, 0xBB5D5DFF);
197 	/* Aqua */
198 	mkcol(1, DPalebluegreen, DPalegreygreen, DPurpleblue);
199 	/* Yellow */
200 	mkcol(2, DPaleyellow, DDarkyellow, DYellowgreen);
201 	/* Green */
202 	mkcol(3, DPalegreen, DMedgreen, DDarkgreen);
203 	/* Blue */
204 	mkcol(4, 0x00AAFFFF, 0x00AAFFFF, 0x0088CCFF);
205 	/* Grey */
206 	cols[5][0] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xEEEEEEFF);
207 	cols[5][1] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xCCCCCCFF);
208 	cols[5][2] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x888888FF);
209 	cols[5][3] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xAAAAAAFF);
210 	grey = cols[5][2];
211 	red = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xFF0000FF);
212 	green = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x00FF00FF);
213 	blue = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x0000FFFF);
214 	bg = display->white;
215 	fg = display->black;
216 }
217 
218 static void
219 redraw(int scaleno)
220 {
221 	int n, i, j, x;
222 	char buf[256];
223 	Point p, q;
224 	Rectangle r, rtime;
225 	Task *t;
226 	vlong ts, oldestts, newestts, period, ppp, scale, s;
227 
228 #	define time2x(t)	((int)(((t) - oldestts) / ppp))
229 
230 	scale = scales[scaleno].scale;
231 	period = scale + scales[scaleno].littletics;
232 	ppp = period / Width;	// period per pixel.
233 
234 	/* Round `now' to a nice number */
235 	newestts = now - (now % scales[scaleno].bigtics) +
236 			(scales[scaleno].littletics>>1);
237 
238 	oldestts = newestts - period;
239 
240 //print("newestts %t, period %t, %d-%d\n", newestts, period, time2x(oldestts), time2x(newestts));
241 	if (prevts < oldestts){
242 		oldestts = newestts - period;
243 
244 		prevts = oldestts;
245 		draw(screen, screen->r, bg, nil, ZP);
246 	}else{
247 		/* just white out time */
248 		rtime = screen->r;
249 		rtime.min.x = rtime.max.x - stringwidth(mediumfont, "00000000000.000s");
250 		rtime.max.y = rtime.min.y + mediumfont->height;
251 		draw(screen, rtime, bg, nil, ZP);
252 	}
253 	p = screen->r.min;
254 	for (n = 0; n != ntasks; n++) {
255 		t = &tasks[n];
256 		/* p is upper left corner for this task */
257 		rtime = Rpt(p, addpt(p, Pt(500, mediumfont->height)));
258 		draw(screen, rtime, bg, nil, ZP);
259 		snprint(buf, sizeof(buf), "%d %s", t->pid, t->name);
260 		q = string(screen, p, fg, ZP, mediumfont, buf);
261 		s = now - t->tstart;
262 		if(t->tevents[SRelease])
263 			snprint(buf, sizeof(buf), " per %t — avg: %t max: %t",
264 				(vlong)(s/t->tevents[SRelease]),
265 				(vlong)(t->runtime/t->tevents[SRelease]),
266 				t->runmax);
267 		else if((s /=1000000000LL) != 0)
268 			snprint(buf, sizeof(buf), " per 1s — avg: %t total: %t",
269 				t->total/s,
270 				t->total);
271 		else
272 			snprint(buf, sizeof(buf), " total: %t", t->total);
273 		string(screen, q, fg, ZP, tinyfont, buf);
274 		p.y += Height;
275 	}
276 	x = time2x(prevts);
277 
278 	p = screen->r.min;
279 	for (n = 0; n != ntasks; n++) {
280 		t = &tasks[n];
281 
282 		/* p is upper left corner for this task */
283 
284 		/* Move part already drawn */
285 		r = Rect(p.x, p.y + topmargin, p.x + x, p.y+Height);
286 		draw(screen, r, screen, nil,
287 				Pt(p.x + Width - x, p.y + topmargin));
288 
289 		r.max.x = screen->r.max.x;
290 		r.min.x += x;
291 		draw(screen, r, bg, nil, ZP);
292 
293 		line(screen, addpt(p, Pt(x, Height - lineht)),
294 				Pt(screen->r.max.x, p.y + Height - lineht),
295 				Endsquare, Endsquare, 0,
296 				cols[n % Ncolor][1], ZP);
297 
298 		for (i = 0; i < t->nevents-1; i++)
299 			if (prevts < t->events[i + 1].time)
300 				break;
301 
302 		if (i > 0) {
303 			memmove(t->events, t->events + i,
304 					  (t->nevents - i) * sizeof(Event));
305 			t->nevents -= i;
306 		}
307 
308 		for (i = 0; i != t->nevents; i++) {
309 			Event *e = &t->events[i], *_e;
310 			int sx, ex;
311 
312 			switch (e->etype & 0xffff) {
313 			case SAdmit:
314 				if (e->time > prevts && e->time <= newestts) {
315 					sx = time2x(e->time);
316 					line(screen, addpt(p, Pt(sx, topmargin)),
317 						addpt(p, Pt(sx, Height - bottommargin)),
318 						Endarrow, Endsquare, 1, green, ZP);
319 				}
320 				break;
321 			case SExpel:
322 				if (e->time > prevts && e->time <= newestts) {
323 					sx = time2x(e->time);
324 					line(screen, addpt(p, Pt(sx, topmargin)),
325 						addpt(p, Pt(sx, Height - bottommargin)),
326 						Endsquare, Endarrow, 1, red, ZP);
327 				}
328 				break;
329 			case SRelease:
330 				if (e->time > prevts && e->time <= newestts) {
331 					sx = time2x(e->time);
332 					line(screen, addpt(p, Pt(sx, topmargin)),
333 						addpt(p, Pt(sx, Height - bottommargin)),
334 						Endarrow, Endsquare, 1, fg, ZP);
335 				}
336 				break;
337 			case SDeadline:
338 				if (e->time > prevts && e->time <= newestts) {
339 					sx = time2x(e->time);
340 					line(screen, addpt(p, Pt(sx, topmargin)),
341 						addpt(p, Pt(sx, Height - bottommargin)),
342 						Endsquare, Endarrow, 1, fg, ZP);
343 				}
344 				break;
345 
346 			case SYield:
347 			case SUser:
348 				if (e->time > prevts && e->time <= newestts) {
349 					sx = time2x(e->time);
350 					line(screen, addpt(p, Pt(sx, topmargin)),
351 						addpt(p, Pt(sx, Height - bottommargin)),
352 						Endsquare, Endarrow, 0,
353 						(e->etype == SYield)? green: blue, ZP);
354 				}
355 				break;
356 			case SSlice:
357 				if (e->time > prevts && e->time <= newestts) {
358 					sx = time2x(e->time);
359 					line(screen, addpt(p, Pt(sx, topmargin)),
360 						addpt(p, Pt(sx, Height - bottommargin)),
361 						Endsquare, Endarrow, 0, red, ZP);
362 				}
363 				break;
364 
365 			case SRun:
366 			case SEdf:
367 				sx = time2x(e->time);
368 				ex = time2x(e->etime);
369 
370 				r = Rect(sx, topmargin + 8, ex, Height - lineht);
371 				r = rectaddpt(r, p);
372 
373 				draw(screen, r, cols[n % Ncolor][e->etype==SRun?1:3], nil, ZP);
374 
375 				for(j = 0; j < t->nevents; j++){
376 					_e = &t->events[j];
377 					switch(_e->etype & 0xffff){
378 					case SInts:
379 						if (_e->time > prevts && _e->time <= newestts){
380 							sx = time2x(_e->time);
381 							line(screen, addpt(p, Pt(sx, topmargin)),
382 												addpt(p, Pt(sx, Height / 2 - bottommargin)),
383 												Endsquare, Endsquare, 0,
384 												green, ZP);
385 						}
386 						break;
387 					case SInte:
388 						if (_e->time > prevts && _e->time <= newestts) {
389 							sx = time2x(_e->time);
390 							line(screen, addpt(p, Pt(sx, Height / 2 - bottommargin)),
391 												addpt(p, Pt(sx, Height - bottommargin)),
392 												Endsquare, Endsquare, 0,
393 												blue, ZP);
394 						}
395 						break;
396 					}
397 				}
398 				break;
399 			}
400 		}
401 		p.y += Height;
402 	}
403 
404 	ts = prevts + scales[scaleno].littletics - (prevts % scales[scaleno].littletics);
405 	x = time2x(ts);
406 
407 	while(x < Width){
408 		p = screen->r.min;
409 		for(n = 0; n < ntasks; n++){
410 			int height, width;
411 
412 			/* p is upper left corner for this task */
413 			if ((ts % scales[scaleno].scale) == 0){
414 				height = 10 * Height;
415 				width = 1;
416 			}else if ((ts % scales[scaleno].bigtics) == 0){
417 				height = 12 * Height;
418 				width = 0;
419 			}else{
420 				height = 13 * Height;
421 				width = 0;
422 			}
423 			height >>= 4;
424 
425 			line(screen, addpt(p, Pt(x, height)), addpt(p, Pt(x, Height - lineht)),
426 				Endsquare, Endsquare, width, cols[n % Ncolor][2], ZP);
427 
428 			p.y += Height;
429 		}
430 		ts += scales[scaleno].littletics;
431 		x = time2x(ts);
432 	}
433 
434 	rtime = screen->r;
435 	rtime.min.y = rtime.max.y - tinyfont->height + 2;
436 	draw(screen, rtime, bg, nil, ZP);
437 	ts = oldestts + scales[scaleno].bigtics - (oldestts % scales[scaleno].bigtics);
438 	x = time2x(ts);
439 	while(x < Width){
440 		snprint(buf, sizeof(buf), "%t", ts);
441 		string(screen, addpt(p, Pt(x - stringwidth(tinyfont, buf)/2, - tinyfont->height - 1)),
442 			fg, ZP, tinyfont, buf);
443 		ts += scales[scaleno].bigtics;
444 		x = time2x(ts);
445 	}
446 
447 	snprint(buf, sizeof(buf), "%t", now);
448 	string(screen, Pt(screen->r.max.x - stringwidth(mediumfont, buf), screen->r.min.y),
449 		fg, ZP, mediumfont, buf);
450 
451 	flushimage(display, 1);
452 	prevts = newestts;
453 }
454 
455 Task*
456 newtask(ulong pid)
457 {
458 	Task *t;
459 	char buf[64], *p;
460 	int fd,n;
461 
462 	tasks = realloc(tasks, (ntasks + 1) * sizeof(Task));
463 	assert(tasks);
464 
465 	t = &tasks[ntasks++];
466 	memset(t, 0, sizeof(Task));
467 	t->events = nil;
468 	snprint(buf, sizeof buf, "/proc/%ld/status", pid);
469 	t->name = nil;
470 	fd = open(buf, OREAD);
471 	if (fd >= 0){
472 		n = read(fd, buf, sizeof buf);
473 		if(n > 0){
474 			p = buf + sizeof buf - 1;
475 			*p = 0;
476 			p = strchr(buf, ' ');
477 			if (p) *p = 0;
478 			t->name = strdup(buf);
479 		}else
480 			print("%s: %r\n", buf);
481 		close(fd);
482 	}else
483 		print("%s: %r\n", buf);
484 	t->pid = pid;
485 	prevts = 0;
486 	if (newwin){
487 		fprint(wctlfd, "resize -dx %d -dy %d\n",
488 			Width + 20, (ntasks * Height) + 5);
489 	}else
490 		Height = ntasks ? Dy(screen->r)/ntasks : Dy(screen->r);
491 	return t;
492 }
493 
494 void
495 doevent(Task *t, Traceevent *ep)
496 {
497 	int i, n;
498 	Event *event;
499 	vlong runt;
500 
501 	t->tevents[ep->etype & 0xffff]++;
502 	n = t->nevents++;
503 	t->events = realloc(t->events, t->nevents*sizeof(Event));
504 	assert(t->events);
505 	event = &t->events[n];
506 	memmove(event, ep, sizeof(Traceevent));
507 	event->etime = 0;
508 
509 	switch(event->etype & 0xffff){
510 	case SRelease:
511 		if (t->runthis > t->runmax)
512 			t->runmax = t->runthis;
513 		t->runthis = 0;
514 		break;
515 
516 	case SSleep:
517 	case SYield:
518 	case SReady:
519 	case SSlice:
520 		for(i = n-1; i >= 0; i--)
521 			if (t->events[i].etype == SRun ||
522 				t->events[i].etype == SEdf)
523 				break;
524 		if(i < 0 || t->events[i].etime != 0)
525 			break;
526 		runt = event->time - t->events[i].time;
527 		if(runt > 0){
528 			t->events[i].etime = event->time;
529 			t->runtime += runt;
530 			t->total += runt;
531 			t->runthis += runt;
532 			t->runs++;
533 		}
534 		break;
535 	case SDead:
536 print("task died %ld %t %s\n", event->pid, event->time, schedstatename[event->etype & 0xffff]);
537 		free(t->events);
538 		free(t->name);
539 		ntasks--;
540 		memmove(t, t+1, sizeof(Task)*(&tasks[ntasks]-t));
541 		if (newwin)
542 			fprint(wctlfd, "resize -dx %d -dy %d\n",
543 				Width + 20, (ntasks * Height) + 5);
544 		else
545 			Height = ntasks ? Dy(screen->r)/ntasks : Dy(screen->r);
546 		prevts = 0;
547 	}
548 }
549 
550 void
551 drawtrace(void)
552 {
553 	char *wsys, line[256];
554 	int wfd, logfd;
555 	Mousectl *mousectl;
556 	Keyboardctl *keyboardctl;
557 	int paused;
558 	int scaleno;
559 	Rune r;
560 	int i, n;
561 	Task *t;
562 	Traceevent *ep;
563 
564 	eventbuf = malloc(Nevents*sizeof(Traceevent));
565 	assert(eventbuf);
566 
567 	if((logfd = open(profdev, OREAD)) < 0)
568 		sysfatal("%s: Cannot open %s: %r\n", argv0, profdev);
569 
570 	if(newwin){
571 		if((wsys = getenv("wsys")) == nil)
572 			sysfatal("%s: Cannot find windowing system: %r\n",
573 						argv0);
574 
575 		if((wfd = open(wsys, ORDWR)) < 0)
576 			sysfatal("%s: Cannot open windowing system: %r\n",
577 						argv0);
578 
579 		snprint(line, sizeof(line), "new -pid %d -dx %d -dy %d",
580 				getpid(), Width + 20, Height + 5);
581 		line[sizeof(line) - 1] = '\0';
582 		rfork(RFNAMEG);
583 
584 		if(mount(wfd, -1, "/mnt/wsys", MREPL, line) < 0)
585 			sysfatal("%s: Cannot mount %s under /mnt/wsys: %r\n",
586 						argv0, line);
587 
588 		if(bind("/mnt/wsys", "/dev", MBEFORE) < 0)
589 			sysfatal("%s: Cannot bind /mnt/wsys in /dev: %r\n",
590 						argv0);
591 
592 	}
593 	if((wctlfd = open("/dev/wctl", OWRITE)) < 0)
594 		sysfatal("%s: Cannot open /dev/wctl: %r\n", argv0);
595 	if(initdraw(nil, nil, "trace") < 0)
596 		sysfatal("%s: initdraw failure: %r\n", argv0);
597 
598 	Width = Dx(screen->r);
599 	Height = Dy(screen->r);
600 
601 	if((mousectl = initmouse(nil, screen)) == nil)
602 		sysfatal("%s: cannot initialize mouse: %r\n", argv0);
603 
604 	if((keyboardctl = initkeyboard(nil)) == nil)
605 		sysfatal("%s: cannot initialize keyboard: %r\n", argv0);
606 
607 	colinit();
608 
609 	paused = 0;
610 	scaleno = 7;	/* 100 milliseconds */
611 	now = nsec();
612 	while(1) {
613 		Alt a[] = {
614 			{ mousectl->c,			nil,		CHANRCV		},
615 			{ mousectl->resizec,	nil,		CHANRCV		},
616 			{ keyboardctl->c,		&r,			CHANRCV		},
617 			{ nil,					nil,		CHANNOBLK	},
618 		};
619 
620 		switch (alt(a)) {
621 		case 0:
622 			continue;
623 
624 		case 1:
625 			if(getwindow(display, Refnone) < 0)
626 				sysfatal("drawrt: Cannot re-attach window\n");
627 			if(newwin){
628 				if(Dx(screen->r) != Width ||
629 					Dy(screen->r) != (ntasks * Height)){
630 					fprint(2, "resize: x: have %d, need %d; y: have %d, need %d\n",
631 							Dx(screen->r), Width + 8, Dy(screen->r), (ntasks * Height) + 8);
632 					fprint(wctlfd, "resize -dx %d -dy %d\n",
633 							Width + 8, (ntasks * Height) + 8);
634 				}
635 			}
636 			else{
637 				Width = Dx(screen->r);
638 				Height = ntasks? Dy(screen->r)/ntasks:
639 							Dy(screen->r);
640 			}
641 			break;
642 
643 		case 2:
644 
645 			switch(r){
646 			case 'r':
647 				for(i = 0; i < ntasks; i++){
648 					tasks[i].tstart = now;
649 					tasks[i].total = 0;
650 					tasks[i].runtime = 0;
651 					tasks[i].runmax = 0;
652 					tasks[i].runthis = 0;
653 					tasks[i].runs = 0;
654 					memset(tasks[i].tevents, 0, Nevent*sizeof(ulong));
655 
656 				}
657 				break;
658 
659 			case 'p':
660 				paused = (paused + 1) & 1;
661 				prevts = 0;
662 				break;
663 
664 			case '-':
665 				if (scaleno < nelem(scales) - 1)
666 					scaleno++;
667 				prevts = 0;
668 				break;
669 
670 			case '+':
671 				if (scaleno > 0)
672 					scaleno--;
673 				prevts = 0;
674 				break;
675 
676 			case 'q':
677 				threadexitsall(nil);
678 
679 			case 'v':
680 				verbose ^= 1;
681 
682 			default:
683 				break;
684 			}
685 			break;
686 
687 		case 3:
688 			now = nsec();
689 			while((n = read(logfd, eventbuf, Nevents*sizeof(Traceevent))) > 0){
690 				assert((n % sizeof(Traceevent)) == 0);
691 				nevents = n / sizeof(Traceevent);
692 				for (ep = eventbuf; ep < eventbuf + nevents; ep++){
693 					if ((ep->etype & 0xffff) >= Nevent){
694 						print("%ld %t Illegal event %ld\n",
695 							ep->pid, ep->time, ep->etype & 0xffff);
696 						continue;
697 					}
698 					if (verbose)
699 						print("%ld %t %s\n",
700 							ep->pid, ep->time, schedstatename[ep->etype & 0xffff]);
701 
702 					for(i = 0; i < ntasks; i++)
703 						if(tasks[i].pid == ep->pid)
704 							break;
705 
706 					if(i == ntasks){
707 						t = newtask(ep->pid);
708 						t->tstart = ep->time;
709 					}else
710 						t = &tasks[i];
711 
712 					doevent(t, ep);
713 				}
714 			}
715 			if(!paused)
716 				redraw(scaleno);
717 		}
718 		sleep(scales[scaleno].sleep);
719 	}
720 }
721 
722 int
723 timeconv(Fmt *f)
724 {
725 	char buf[128], *sign;
726 	vlong t;
727 
728 	buf[0] = 0;
729 	switch(f->r) {
730 	case 'U':
731 		t = va_arg(f->args, vlong);
732 		break;
733 	case 't':		// vlong in nanoseconds
734 		t = va_arg(f->args, vlong);
735 		break;
736 	default:
737 		return fmtstrcpy(f, "(timeconv)");
738 	}
739 	if (t < 0) {
740 		sign = "-";
741 		t = -t;
742 	}else
743 		sign = "";
744 	if (t > S(1)){
745 		t += OneRound;
746 		sprint(buf, "%s%d.%.3ds", sign, (int)(t / S(1)), (int)(t % S(1))/1000000);
747 	}else if (t > MS(1)){
748 		t += MilliRound;
749 		sprint(buf, "%s%d.%.3dms", sign, (int)(t / MS(1)), (int)(t % MS(1))/1000);
750 	}else if (t > US(1))
751 		sprint(buf, "%s%d.%.3dµs", sign, (int)(t / US(1)), (int)(t % US(1)));
752 	else
753 		sprint(buf, "%s%dns", sign, (int)t);
754 	return fmtstrcpy(f, buf);
755 }
756