#include #include #include #include #include #include #include #include #include #include #include "trace.h" #pragma varargck type "t" vlong #pragma varargck type "U" uvlong #define NS(x) ((vlong)x) #define US(x) (NS(x) * 1000ULL) #define MS(x) (US(x) * 1000ULL) #define S(x) (MS(x) * 1000ULL) #define numblocks(a, b) (((a) + (b) - 1) / (b)) #define roundup(a, b) (numblocks((a), (b)) * (b)) enum { OneRound = MS(1)/2LL, MilliRound = US(1)/2LL, }; typedef struct Event Event; typedef struct Task Task; struct Event { Traceevent; vlong etime; /* length of block to draw */ }; struct Task { int pid; char *name; int nevents; Event *events; vlong tstart; vlong total; vlong runtime; vlong runmax; vlong runthis; long runs; ulong tevents[Nevent]; }; enum { Nevents = 1024, Ncolor = 6, K = 1024, }; vlong now, prevts; int newwin; int Width = 1000; int Height = 100; // Per task int topmargin = 8; int bottommargin = 4; int lineht = 12; int wctlfd; int nevents; Traceevent *eventbuf; Event *event; void drawtrace(void); int schedparse(char*, char*, char*); int timeconv(Fmt*); char *schedstatename[] = { [SAdmit] = "Admit", [SSleep] = "Sleep", [SDead] = "Dead", [SDeadline] = "Deadline", [SEdf] = "Edf", [SExpel] = "Expel", [SReady] = "Ready", [SRelease] = "Release", [SRun] = "Run", [SSlice] = "Slice", [SInts] = "Ints", [SInte] = "Inte", [SUser] = "User", [SYield] = "Yield", }; struct { vlong scale; vlong bigtics; vlong littletics; int sleep; } scales[] = { { US(500), US(100), US(50), 0}, { US(1000), US(500), US(100), 0}, { US(2000), US(1000), US(200), 0}, { US(5000), US(1000), US(500), 0}, { MS(10), MS(5), MS(1), 20}, { MS(20), MS(10), MS(2), 20}, { MS(50), MS(10), MS(5), 20}, { MS(100), MS(50), MS(10), 20}, /* starting scaleno */ { MS(200), MS(100), MS(20), 20}, { MS(500), MS(100), MS(50), 50}, { MS(1000), MS(500), MS(100), 100}, { MS(2000), MS(1000), MS(200), 100}, { MS(5000), MS(1000), MS(500), 100}, { S(10), S(50), S(1), 100}, { S(20), S(10), S(2), 100}, { S(50), S(10), S(5), 100}, { S(100), S(50), S(10), 100}, { S(200), S(100), S(20), 100}, { S(500), S(100), S(50), 100}, { S(1000), S(500), S(100), 100}, }; int ntasks, verbose, triggerproc, paused; Task *tasks; Image *cols[Ncolor][4]; Font *mediumfont, *tinyfont; Image *grey, *red, *green, *blue, *bg, *fg; char*profdev = "/proc/trace"; static void usage(void) { fprint(2, "Usage: %s [-d profdev] [-w] [-v] [-t triggerproc] [processes]\n", argv0); exits(nil); } void threadmain(int argc, char **argv) { int fd, i; char fname[80]; fmtinstall('t', timeconv); ARGBEGIN { case 'd': profdev = EARGF(usage()); break; case 'v': verbose = 1; break; case 'w': newwin++; break; case 't': triggerproc = (int)strtol(EARGF(usage()), nil, 0); break; default: usage(); } ARGEND; fname[sizeof fname - 1] = 0; for(i = 0; i < argc; i++){ snprint(fname, sizeof fname - 2, "/proc/%s/ctl", argv[i]); if((fd = open(fname, OWRITE)) < 0){ fprint(2, "%s: cannot open %s: %r\n", argv[0], fname); continue; } if(fprint(fd, "trace 1") < 0) fprint(2, "%s: cannot enable tracing on %s: %r\n", argv[0], fname); close(fd); } drawtrace(); } static void mkcol(int i, int c0, int c1, int c2) { cols[i][0] = allocimagemix(display, c0, DWhite); cols[i][1] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, c1); cols[i][2] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, c2); cols[i][3] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, c0); } static void colinit(void) { mediumfont = openfont(display, "/lib/font/bit/lucidasans/unicode.10.font"); if(mediumfont == nil) mediumfont = font; tinyfont = openfont(display, "/lib/font/bit/lucidasans/unicode.7.font"); if(tinyfont == nil) tinyfont = font; topmargin = mediumfont->height+2; bottommargin = tinyfont->height+2; /* Peach */ mkcol(0, 0xFFAAAAFF, 0xFFAAAAFF, 0xBB5D5DFF); /* Aqua */ mkcol(1, DPalebluegreen, DPalegreygreen, DPurpleblue); /* Yellow */ mkcol(2, DPaleyellow, DDarkyellow, DYellowgreen); /* Green */ mkcol(3, DPalegreen, DMedgreen, DDarkgreen); /* Blue */ mkcol(4, 0x00AAFFFF, 0x00AAFFFF, 0x0088CCFF); /* Grey */ cols[5][0] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xEEEEEEFF); cols[5][1] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xCCCCCCFF); cols[5][2] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x888888FF); cols[5][3] = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xAAAAAAFF); grey = cols[5][2]; red = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0xFF0000FF); green = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x00FF00FF); blue = allocimage(display, Rect(0,0,1,1), screen->chan, 1, 0x0000FFFF); bg = display->white; fg = display->black; } static void redraw(int scaleno) { int n, i, j, x; char buf[256]; Point p, q; Rectangle r, rtime; Task *t; vlong ts, oldestts, newestts, period, ppp, scale, s, ss; # define time2x(t) ((int)(((t) - oldestts) / ppp)) scale = scales[scaleno].scale; period = scale + scales[scaleno].littletics; ppp = period / Width; // period per pixel. /* Round `now' to a nice number */ newestts = now - (now % scales[scaleno].bigtics) + (scales[scaleno].littletics>>1); oldestts = newestts - period; //print("newestts %t, period %t, %d-%d\n", newestts, period, time2x(oldestts), time2x(newestts)); if (prevts < oldestts){ oldestts = newestts - period; prevts = oldestts; draw(screen, screen->r, bg, nil, ZP); }else{ /* just white out time */ rtime = screen->r; rtime.min.x = rtime.max.x - stringwidth(mediumfont, "00000000000.000s"); rtime.max.y = rtime.min.y + mediumfont->height; draw(screen, rtime, bg, nil, ZP); } p = screen->r.min; for (n = 0; n != ntasks; n++) { t = &tasks[n]; /* p is upper left corner for this task */ rtime = Rpt(p, addpt(p, Pt(500, mediumfont->height))); draw(screen, rtime, bg, nil, ZP); snprint(buf, sizeof(buf), "%d %s", t->pid, t->name); q = string(screen, p, fg, ZP, mediumfont, buf); s = now - t->tstart; if(t->tevents[SRelease]) snprint(buf, sizeof(buf), " per %t — avg: %t max: %t", (vlong)(s/t->tevents[SRelease]), (vlong)(t->runtime/t->tevents[SRelease]), t->runmax); else if((s /=1000000000LL) != 0) snprint(buf, sizeof(buf), " per 1s — avg: %t total: %t", t->total/s, t->total); else snprint(buf, sizeof(buf), " total: %t", t->total); string(screen, q, fg, ZP, tinyfont, buf); p.y += Height; } x = time2x(prevts); p = screen->r.min; for (n = 0; n != ntasks; n++) { t = &tasks[n]; /* p is upper left corner for this task */ /* Move part already drawn */ r = Rect(p.x, p.y + topmargin, p.x + x, p.y+Height); draw(screen, r, screen, nil, Pt(p.x + Width - x, p.y + topmargin)); r.max.x = screen->r.max.x; r.min.x += x; draw(screen, r, bg, nil, ZP); line(screen, addpt(p, Pt(x, Height - lineht)), Pt(screen->r.max.x, p.y + Height - lineht), Endsquare, Endsquare, 0, cols[n % Ncolor][1], ZP); for (i = 0; i < t->nevents-1; i++) if (prevts < t->events[i + 1].time) break; if (i > 0) { memmove(t->events, t->events + i, (t->nevents - i) * sizeof(Event)); t->nevents -= i; } for (i = 0; i != t->nevents; i++) { Event *e = &t->events[i], *_e; int sx, ex; switch (e->etype & 0xffff) { case SAdmit: if (e->time > prevts && e->time <= newestts) { sx = time2x(e->time); line(screen, addpt(p, Pt(sx, topmargin)), addpt(p, Pt(sx, Height - bottommargin)), Endarrow, Endsquare, 1, green, ZP); } break; case SExpel: if (e->time > prevts && e->time <= newestts) { sx = time2x(e->time); line(screen, addpt(p, Pt(sx, topmargin)), addpt(p, Pt(sx, Height - bottommargin)), Endsquare, Endarrow, 1, red, ZP); } break; case SRelease: if (e->time > prevts && e->time <= newestts) { sx = time2x(e->time); line(screen, addpt(p, Pt(sx, topmargin)), addpt(p, Pt(sx, Height - bottommargin)), Endarrow, Endsquare, 1, fg, ZP); } break; case SDeadline: if (e->time > prevts && e->time <= newestts) { sx = time2x(e->time); line(screen, addpt(p, Pt(sx, topmargin)), addpt(p, Pt(sx, Height - bottommargin)), Endsquare, Endarrow, 1, fg, ZP); } break; case SYield: case SUser: if (e->time > prevts && e->time <= newestts) { sx = time2x(e->time); line(screen, addpt(p, Pt(sx, topmargin)), addpt(p, Pt(sx, Height - bottommargin)), Endsquare, Endarrow, 0, (e->etype == SYield)? green: blue, ZP); } break; case SSlice: if (e->time > prevts && e->time <= newestts) { sx = time2x(e->time); line(screen, addpt(p, Pt(sx, topmargin)), addpt(p, Pt(sx, Height - bottommargin)), Endsquare, Endarrow, 0, red, ZP); } break; case SRun: case SEdf: sx = time2x(e->time); ex = time2x(e->etime); if(ex == sx) ex++; r = Rect(sx, topmargin + 8, ex, Height - lineht); r = rectaddpt(r, p); draw(screen, r, cols[n % Ncolor][e->etype==SRun?1:3], nil, ZP); if(t->pid == triggerproc && ex < Width) paused ^= 1; for(j = 0; j < t->nevents; j++){ _e = &t->events[j]; switch(_e->etype & 0xffff){ case SInts: if (_e->time > prevts && _e->time <= newestts){ sx = time2x(_e->time); line(screen, addpt(p, Pt(sx, topmargin)), addpt(p, Pt(sx, Height / 2 - bottommargin)), Endsquare, Endsquare, 0, green, ZP); } break; case SInte: if (_e->time > prevts && _e->time <= newestts) { sx = time2x(_e->time); line(screen, addpt(p, Pt(sx, Height / 2 - bottommargin)), addpt(p, Pt(sx, Height - bottommargin)), Endsquare, Endsquare, 0, blue, ZP); } break; } } break; } } p.y += Height; } ts = prevts + scales[scaleno].littletics - (prevts % scales[scaleno].littletics); x = time2x(ts); while(x < Width){ p = screen->r.min; for(n = 0; n < ntasks; n++){ int height, width; /* p is upper left corner for this task */ if ((ts % scales[scaleno].scale) == 0){ height = 10 * Height; width = 1; }else if ((ts % scales[scaleno].bigtics) == 0){ height = 12 * Height; width = 0; }else{ height = 13 * Height; width = 0; } height >>= 4; line(screen, addpt(p, Pt(x, height)), addpt(p, Pt(x, Height - lineht)), Endsquare, Endsquare, width, cols[n % Ncolor][2], ZP); p.y += Height; } ts += scales[scaleno].littletics; x = time2x(ts); } rtime = screen->r; rtime.min.y = rtime.max.y - tinyfont->height + 2; draw(screen, rtime, bg, nil, ZP); ts = oldestts + scales[scaleno].bigtics - (oldestts % scales[scaleno].bigtics); x = time2x(ts); ss = 0; while(x < Width){ snprint(buf, sizeof(buf), "%t", ss); string(screen, addpt(p, Pt(x - stringwidth(tinyfont, buf)/2, - tinyfont->height - 1)), fg, ZP, tinyfont, buf); ts += scales[scaleno].bigtics; ss += scales[scaleno].bigtics; x = time2x(ts); } snprint(buf, sizeof(buf), "%t", now); string(screen, Pt(screen->r.max.x - stringwidth(mediumfont, buf), screen->r.min.y), fg, ZP, mediumfont, buf); flushimage(display, 1); prevts = newestts; } Task* newtask(ulong pid) { Task *t; char buf[64], *p; int fd,n; tasks = realloc(tasks, (ntasks + 1) * sizeof(Task)); assert(tasks); t = &tasks[ntasks++]; memset(t, 0, sizeof(Task)); t->events = nil; snprint(buf, sizeof buf, "/proc/%ld/status", pid); t->name = nil; fd = open(buf, OREAD); if (fd >= 0){ n = read(fd, buf, sizeof buf); if(n > 0){ p = buf + sizeof buf - 1; *p = 0; p = strchr(buf, ' '); if (p) *p = 0; t->name = strdup(buf); }else print("%s: %r\n", buf); close(fd); }else print("%s: %r\n", buf); t->pid = pid; prevts = 0; if (newwin){ fprint(wctlfd, "resize -dx %d -dy %d\n", Width + 20, (ntasks * Height) + 5); }else Height = ntasks ? Dy(screen->r)/ntasks : Dy(screen->r); return t; } void doevent(Task *t, Traceevent *ep) { int i, n; Event *event; vlong runt; t->tevents[ep->etype & 0xffff]++; n = t->nevents++; t->events = realloc(t->events, t->nevents*sizeof(Event)); assert(t->events); event = &t->events[n]; memmove(event, ep, sizeof(Traceevent)); event->etime = 0; switch(event->etype & 0xffff){ case SRelease: if (t->runthis > t->runmax) t->runmax = t->runthis; t->runthis = 0; break; case SSleep: case SYield: case SReady: case SSlice: for(i = n-1; i >= 0; i--) if (t->events[i].etype == SRun || t->events[i].etype == SEdf) break; if(i < 0 || t->events[i].etime != 0) break; runt = event->time - t->events[i].time; if(runt > 0){ t->events[i].etime = event->time; t->runtime += runt; t->total += runt; t->runthis += runt; t->runs++; } break; case SDead: print("task died %ld %t %s\n", event->pid, event->time, schedstatename[event->etype & 0xffff]); free(t->events); free(t->name); ntasks--; memmove(t, t+1, sizeof(Task)*(&tasks[ntasks]-t)); if (newwin) fprint(wctlfd, "resize -dx %d -dy %d\n", Width + 20, (ntasks * Height) + 5); else Height = ntasks ? Dy(screen->r)/ntasks : Dy(screen->r); prevts = 0; } } void drawtrace(void) { char *wsys, line[256]; int wfd, logfd; Mousectl *mousectl; Keyboardctl *keyboardctl; int scaleno; Rune r; int i, n; Task *t; Traceevent *ep; eventbuf = malloc(Nevents*sizeof(Traceevent)); assert(eventbuf); if((logfd = open(profdev, OREAD)) < 0) sysfatal("%s: Cannot open %s: %r", argv0, profdev); if(newwin){ if((wsys = getenv("wsys")) == nil) sysfatal("%s: Cannot find windowing system: %r", argv0); if((wfd = open(wsys, ORDWR)) < 0) sysfatal("%s: Cannot open windowing system: %r", argv0); snprint(line, sizeof(line), "new -pid %d -dx %d -dy %d", getpid(), Width + 20, Height + 5); line[sizeof(line) - 1] = '\0'; rfork(RFNAMEG); if(mount(wfd, -1, "/mnt/wsys", MREPL, line) < 0) sysfatal("%s: Cannot mount %s under /mnt/wsys: %r", argv0, line); if(bind("/mnt/wsys", "/dev", MBEFORE) < 0) sysfatal("%s: Cannot bind /mnt/wsys in /dev: %r", argv0); } if((wctlfd = open("/dev/wctl", OWRITE)) < 0) sysfatal("%s: Cannot open /dev/wctl: %r", argv0); if(initdraw(nil, nil, "trace") < 0) sysfatal("%s: initdraw failure: %r", argv0); Width = Dx(screen->r); Height = Dy(screen->r); if((mousectl = initmouse(nil, screen)) == nil) sysfatal("%s: cannot initialize mouse: %r", argv0); if((keyboardctl = initkeyboard(nil)) == nil) sysfatal("%s: cannot initialize keyboard: %r", argv0); colinit(); paused = 0; scaleno = 7; /* 100 milliseconds */ now = nsec(); for(;;) { Alt a[] = { { mousectl->c, nil, CHANRCV }, { mousectl->resizec, nil, CHANRCV }, { keyboardctl->c, &r, CHANRCV }, { nil, nil, CHANNOBLK }, }; switch (alt(a)) { case 0: continue; case 1: if(getwindow(display, Refnone) < 0) sysfatal("drawrt: Cannot re-attach window"); if(newwin){ if(Dx(screen->r) != Width || Dy(screen->r) != (ntasks * Height)){ fprint(2, "resize: x: have %d, need %d; y: have %d, need %d\n", Dx(screen->r), Width + 8, Dy(screen->r), (ntasks * Height) + 8); fprint(wctlfd, "resize -dx %d -dy %d\n", Width + 8, (ntasks * Height) + 8); } } else{ Width = Dx(screen->r); Height = ntasks? Dy(screen->r)/ntasks: Dy(screen->r); } break; case 2: switch(r){ case 'r': for(i = 0; i < ntasks; i++){ tasks[i].tstart = now; tasks[i].total = 0; tasks[i].runtime = 0; tasks[i].runmax = 0; tasks[i].runthis = 0; tasks[i].runs = 0; memset(tasks[i].tevents, 0, Nevent*sizeof(ulong)); } break; case 'p': paused ^= 1; prevts = 0; break; case '-': if (scaleno < nelem(scales) - 1) scaleno++; prevts = 0; break; case '+': if (scaleno > 0) scaleno--; prevts = 0; break; case 'q': threadexitsall(nil); case 'v': verbose ^= 1; default: break; } break; case 3: now = nsec(); while((n = read(logfd, eventbuf, Nevents*sizeof(Traceevent))) > 0){ assert((n % sizeof(Traceevent)) == 0); nevents = n / sizeof(Traceevent); for (ep = eventbuf; ep < eventbuf + nevents; ep++){ if ((ep->etype & 0xffff) >= Nevent){ print("%ld %t Illegal event %ld\n", ep->pid, ep->time, ep->etype & 0xffff); continue; } if (verbose) print("%ld %t %s\n", ep->pid, ep->time, schedstatename[ep->etype & 0xffff]); for(i = 0; i < ntasks; i++) if(tasks[i].pid == ep->pid) break; if(i == ntasks){ t = newtask(ep->pid); t->tstart = ep->time; }else t = &tasks[i]; doevent(t, ep); } } if(!paused) redraw(scaleno); } sleep(scales[scaleno].sleep); } } int timeconv(Fmt *f) { char buf[128], *sign; vlong t; buf[0] = 0; switch(f->r) { case 'U': t = va_arg(f->args, vlong); break; case 't': // vlong in nanoseconds t = va_arg(f->args, vlong); break; default: return fmtstrcpy(f, "(timeconv)"); } if (t < 0) { sign = "-"; t = -t; }else sign = ""; if (t > S(1)){ t += OneRound; sprint(buf, "%s%d.%.3ds", sign, (int)(t / S(1)), (int)(t % S(1))/1000000); }else if (t > MS(1)){ t += MilliRound; sprint(buf, "%s%d.%.3dms", sign, (int)(t / MS(1)), (int)(t % MS(1))/1000); }else if (t > US(1)) sprint(buf, "%s%d.%.3dµs", sign, (int)(t / US(1)), (int)(t % US(1))); else sprint(buf, "%s%dns", sign, (int)t); return fmtstrcpy(f, buf); }