shithub: riscv

Download patch

ref: 88faa807aaeae42ec058e6e09f29339df9773098
parent: c8544c91d9bd6d8094d80881d318cbc6dac899f1
author: Jacob Moody <moody@posixcafe.org>
date: Wed May 8 14:01:08 EDT 2024

flambe: flame graphs for prof(1) data

--- a/sys/man/1/prof
+++ b/sys/man/1/prof
@@ -9,6 +9,10 @@
 .I program
 .I profile
 .PP
+.B flambe
+.I program
+.I profile
+.PP
 .B tprof
 .I pid
 .PP
@@ -102,6 +106,17 @@
 using the cycle counter, or the time in user mode using the kernel's HZ clock.  The cycle counter
 is currently only available on modern PCs and on the PowerPC.  Default profiling measures user
 time, using the cycle counter if it is available.
+.PP
+.I Flambe
+presents an interactive flame graph using information gathered by
+.IR prof .
+The graph is presented as a series of rows, each row representing a level in the call stack.
+Each row is split up among all the siblings of the respective call stack level,
+their width representative of the portion of their parent's time they occupied.
+Hovering the mouse over any block shows its full name, the total time spent in the function, and
+the number of calls made to it in the top left hand side of the window.
+Clicking a block reroots the the graph with the selected block as the base.
+The escape key can be used to return to the real root of the graph.
 .PP
 .I Tprof
 is similar to
--- /dev/null
+++ b/sys/src/cmd/flambe.c
@@ -1,0 +1,301 @@
+#include <u.h>
+#include <libc.h>
+#include <thread.h>
+#include <draw.h>
+#include <mouse.h>
+#include <keyboard.h>
+#include <bio.h>
+#include <mach.h>
+
+typedef struct Data	Data;
+
+struct Data
+{
+	ushort	down;
+	ushort	right;
+	ulong	pc;
+	ulong	count;
+	uvlong	time;
+	uint 	rec;
+};
+enum { Datasz = 2+2+4+4+8 };
+
+Data*	data;
+long	ndata;
+uvlong	cyclefreq;
+
+void
+syms(char *cout)
+{
+	Fhdr f;
+	int fd;
+
+	if((fd = open(cout, 0)) < 0)
+		sysfatal("%r");
+	if(!crackhdr(fd, &f))
+		sysfatal("can't read text file header: %r");
+	if(f.type == FNONE)
+		sysfatal("text file not an a.out");
+	if(syminit(fd, &f) < 0)
+		sysfatal("syminit: %r");
+	close(fd);
+}
+
+#define GET2(p) (u16int)(p)[1] | (u16int)(p)[0]<<8
+#define GET4(p) (u32int)(p)[3] | (u32int)(p)[2]<<8 | (u32int)(p)[1]<<16 | (u32int)(p)[0]<<24
+#define GET8(p) (u64int)(p)[7] | (u64int)(p)[6]<<8 | (u64int)(p)[5]<<16 | (u64int)(p)[4]<<24 | \
+		(u64int)(p)[3]<<32 | (u64int)(p)[2]<<40 | (u64int)(p)[1]<<48 | (u64int)(p)[0]<<56
+
+void
+datas(char *dout)
+{
+	int fd;
+	Dir *d;
+	int i;
+	uchar hdr[3+1+8], *buf, *p;
+
+	if((fd = open(dout, 0)) < 0){
+		perror(dout);
+		exits("open");
+	}
+	d = dirfstat(fd);
+	if(d == nil){
+		perror(dout);
+		exits("stat");
+	}
+	d->length -= sizeof hdr;
+	ndata = d->length/Datasz;
+	data = malloc(ndata*sizeof(Data));
+	buf = malloc(d->length);
+	if(buf == 0 || data == 0)
+		sysfatal("malloc");
+	if(read(fd, hdr, sizeof hdr) != sizeof hdr)
+		sysfatal("read data header: %r");
+	if(memcmp(hdr, "pr\x0f", 3) != 0)
+		sysfatal("bad magic");
+	cyclefreq = GET8(hdr+4);
+	if(readn(fd, buf, d->length) != d->length)
+		sysfatal("data file read: %r");
+	for(p = buf, i = 0; i < ndata; i++){
+		data[i].down = GET2(p); p += 2;
+		data[i].right = GET2(p); p += 2;
+		data[i].pc = GET4(p); p += 4;
+		data[i].count = GET4(p); p += 4;
+		data[i].time = GET8(p); p += 8;
+	}
+	free(buf);
+	free(d);
+	close(fd);
+}
+
+char*
+name(ulong pc)
+{
+	Symbol s;
+	static char buf[16];
+
+	if (findsym(pc, CTEXT, &s))
+		return(s.name);
+	snprint(buf, sizeof(buf), "#%lux", pc);
+	return buf;
+}
+
+int rowh;
+Image **cols;
+int ncols;
+uvlong total;
+Rectangle *clicks;
+
+void
+gencols(void)
+{
+	int i, h;
+	ulong col, step;
+
+	h = Dy(screen->r) / rowh + 1;
+	if(ncols == h)
+		return;
+	for(i = 0; i < ncols; i++)
+		freeimage(cols[i]);
+	free(cols);
+
+	ncols = h;
+	cols = malloc(sizeof(Image*)*ncols);
+	col = DRed;
+	step = (0xFF/ncols)<<16;
+	for(i = 0; i < ncols; i++){
+		cols[i] = allocimagemix(display, DWhite, col);
+		col += step;
+	}
+}
+
+Image*
+colfor(int h)
+{
+	if(h % 2 == 0)
+		h += (ncols/3);
+	return cols[h % ncols];
+}
+
+void
+onhover(int i)
+{
+	Rectangle r;
+	char buf[128];
+
+	r = screen->r;
+	r.max.y = r.min.y + (font->height+2)*2;
+	draw(screen, r, display->white, nil, ZP);
+	string(screen, r.min, display->black, ZP, font, name(data[i].pc));
+
+	r.min.y += font->height + 1;
+	snprint(buf, sizeof buf, "Time: %.8f(s), Calls: %lud", (double)data[i].time/cyclefreq, data[i].count);
+	string(screen, r.min, display->black, ZP, font, buf);
+	flushimage(display, 1);
+}
+
+int
+getwidth(int i)
+{
+	return ((double)data[i].time * Dx(screen->r)) / total;
+}
+
+void
+graph(int i, int x, int h)
+{
+	Rectangle r, r2;
+
+	if(i >= ndata)
+		sysfatal("corrupted profile data");
+	r.min = (Point){x, screen->r.max.y - rowh*h};
+	r.max = (Point){x + getwidth(i), r.min.y + rowh};
+	clicks[i] = r;
+	if(Dx(r) > 6){
+		draw(screen, r, colfor(h), nil, ZP);
+		r2 = r;
+		r2.min.x  = r2.max.x - 2;
+		draw(screen, r2, display->black, nil, ZP);
+		screen->clipr = r;
+		r2.min = r.min;
+		r2.min.x += 4;
+		string(screen, r2.min, display->black, ZP, font, name(data[i].pc));
+		screen->clipr = screen->r;
+	}
+	if(data[i].right != 0xFFFF)
+		graph(data[i].right, r.max.x, h);
+	if(data[i].down != 0xFFFF)
+		graph(data[i].down, x, h + 1);
+}
+
+void
+redraw(int i)
+{
+	total = data[i].time;
+	memset(clicks, 0, ndata * sizeof(Rectangle));
+	gencols();
+	draw(screen, screen->r, display->white, nil, ZP);
+	graph(i, screen->r.min.x, 1);
+	flushimage(display, 1);
+}
+
+enum
+{
+	Ckey,
+	Cmouse,
+	Cresize,
+	Numchan,
+};
+
+void
+usage(void)
+{
+	fprint(2, "%s: $O.out prof\n", argv0);
+	exits("usage");
+}
+
+/* syminit has a Biobuf on the stack */
+mainstacksize = 64*1024;
+
+void
+threadmain(int argc, char **argv)
+{
+	Mousectl *mctl;
+	Keyboardctl *kctl;
+	Mouse m;
+	Rune r;
+	vlong t;
+	int i;
+	Alt a[Numchan+1] = {
+		[Ckey] = {nil, &r, CHANRCV},
+		[Cmouse] = {nil, &m, CHANRCV },
+		[Cresize] = {nil, nil, CHANRCV},
+		{nil, nil, CHANEND},
+	};
+
+	ARGBEGIN{
+	default:
+		usage();
+		break;
+	}ARGEND;
+	if(argc != 2)
+		usage();
+	syms(argv[0]);
+	datas(argv[1]);
+	for(int i = 1; i < ndata; i++){
+		if((t = data[i].time) < 0)
+			data[i].time =  t + data[0].time;
+	}
+
+	if(initdraw(nil, nil, "profflame") < 0)
+		sysfatal("initdraw: %r");
+	if((kctl = initkeyboard(nil)) == nil)
+		sysfatal("initkeyboard: %r");
+	a[Ckey].c = kctl->c;
+	if((mctl = initmouse(nil, screen)) == nil)
+		sysfatal("initmouse: %r");
+	a[Cmouse].c = mctl->c;
+	a[Cresize].c = mctl->resizec;
+
+	rowh = font->height + 2;
+	clicks = malloc(sizeof(Rectangle)*ndata);
+
+	redraw(1);
+	for(;;)
+	switch(alt(a)){
+		case Ckey:
+			switch(r){
+				case Kesc:
+					redraw(1);
+					break;
+				case Kdel:
+				case 'q':
+					threadexitsall(nil);
+				default:
+					break;
+			}
+			break;
+		case Cmouse:
+			if(m.buttons == 4 || m.buttons == 16){
+				redraw(1);
+				break;
+			}
+			for(i = 0; i < ndata; i++){
+				if(!ptinrect(m.xy, clicks[i]))
+					continue;
+				switch(m.buttons){
+				case 0:
+					onhover(i);
+					break;
+				case 1: case 8:
+					redraw(i);
+					break;
+				}
+				break;
+			}
+			break;
+		case Cresize:
+			getwindow(display, Refnone);
+			redraw(1);
+			break;
+	}
+}