#include <u.h>
#include <tos.h>
#include <libc.h>
#include <thread.h>
#include <ip.h>
#include <draw.h>
#include <mouse.h>
#include <cursor.h>
#include <keyboard.h>
#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] [pid ...]\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) == -1)
                       sysfatal("%s: Cannot mount %s under /mnt/wsys: %r",
                                               argv0, line);

               if(bind("/mnt/wsys", "/dev", MBEFORE) == -1)
                       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);
}