From c2c12a1d664d2b1b56e19777f03c1acc60cc7b28 Mon Sep 17 00:00:00 2001
From: Carl Worth <cworth@cworth.org>
Date: Sat, 9 Nov 2013 18:48:11 -0800
Subject: [PATCH] Expand to add CPU/GPU load and frame latency graphs.

The load graph shows both CPU and GPU load as separate lines.

The loads are also used to choose which colors for the frame-time
bars, (whether CPU-bound or GPU-bound).
---
 acre-x.c | 202 ++++++++++++++++++++++++++++++++++++++++++-------------
 1 file changed, 155 insertions(+), 47 deletions(-)

diff --git a/acre-x.c b/acre-x.c
index e62c596..22cb063 100644
--- a/acre-x.c
+++ b/acre-x.c
@@ -31,15 +31,41 @@
 #include "acre.h"
 #include "math.h"
 
+typedef struct
+{
+	acre_t *cpu_gpu_load;
+	acre_t *frame_time;
+	acre_t *frame_latency;
+} charts_t;
+
 #define STRNCMP_LITERAL(var, literal) \
     strncmp ((var), (literal), sizeof (literal) - 1)
 
 static acre_t *
-load_fips_data (const char *filename)
+_create_chart (const char *title,
+	       const char *x_axis_label,
+	       const char *y_axis_label)
 {
-	FILE *file;
 	acre_t *acre;
-	acre_data_t *frame_time;
+
+	acre = acre_create ();
+
+	acre_set_title (acre, title);
+	acre_set_x_axis_label (acre, x_axis_label);
+	acre_set_y_axis_label (acre, y_axis_label);
+
+	return acre;
+}
+
+static int
+load_fips_charts (charts_t *charts, const char *filename)
+{
+	FILE *file;
+	acre_data_t *cpu_load_data;
+	acre_data_t *gpu_load_data;
+	acre_data_t *cpu_bound_frame_time_data;
+	acre_data_t *gpu_bound_frame_time_data;
+	acre_data_t *frame_latency_data;
 	char *line = NULL, *s;
 	size_t line_size;
 	ssize_t bytes;
@@ -48,17 +74,28 @@ load_fips_data (const char *filename)
 	if (file == NULL) {
 		fprintf (stderr, "Failed to open %s: %s\n",
 			 filename, strerror (errno));
-		return NULL;
+		return 1;
 	}
 
-	acre = acre_create ();
+	charts->cpu_gpu_load = _create_chart ("CPU/GPU load", "Frame #", "Load");
+	charts->frame_time = _create_chart ("Frame time", "Frame #", "Time (ms)");
+	charts->frame_latency = _create_chart ("Frame latency", "Frame #", "Latency (ms)");
+
+	cpu_load_data = acre_data_create ();
+	acre_data_set_name (cpu_load_data, "CPU");
 
-	acre_set_title (acre, filename);
-	acre_set_x_axis_label (acre, "Frame #");
-	acre_set_y_axis_label (acre, "Frame time (ms)");
+	gpu_load_data = acre_data_create ();
+	acre_data_set_name (gpu_load_data, "GPU");
 
-	frame_time = acre_data_create ();
-	acre_data_set_style (frame_time, ACRE_STYLE_BARS_OR_LINE);
+	cpu_bound_frame_time_data = acre_data_create ();
+	acre_data_set_style (cpu_bound_frame_time_data, ACRE_STYLE_BARS);
+	acre_data_set_name (cpu_bound_frame_time_data, "CPU Bound");
+
+	gpu_bound_frame_time_data = acre_data_create ();
+	acre_data_set_style (gpu_bound_frame_time_data, ACRE_STYLE_BARS);
+	acre_data_set_name (gpu_bound_frame_time_data, "GPU Bound");
+
+	frame_latency_data = acre_data_create ();
 
 	while (1) {
 		int scanned;
@@ -69,35 +106,80 @@ load_fips_data (const char *filename)
 
 		s = line;
 
-		if (STRNCMP_LITERAL (s, "frame-time: ") == 0) {
+		if (STRNCMP_LITERAL (s, "frame: ") == 0) {
 			unsigned frame;
-			int64_t frame_time_ns;
-
-			s += strlen("frame-time: ");
-
-			scanned = sscanf (s, "%d %" SCNu64,
-					  &frame, &frame_time_ns);
-			if (scanned != 2) {
+			double frame_time_ms, frame_latency_ms;
+			double cpu_load, gpu_load;
+
+			s += strlen("frame: ");
+
+			scanned = sscanf (s, "%d %lg %lg %lg %lg",
+					  &frame,
+					  &frame_time_ms,
+					  &frame_latency_ms,
+					  &cpu_load,
+					  &gpu_load);
+			if (scanned != 5) {
 				fprintf (stderr, "Warning: Failed to parse line: %s\n", line);
 				continue;
 			}
 
-			acre_data_add_point_2d (frame_time, frame,
-						frame_time_ns / 1e6);
+			acre_data_add_point_2d (cpu_load_data, frame, cpu_load);
+			acre_data_add_point_2d (gpu_load_data, frame, gpu_load);
+			if (cpu_load > gpu_load) {
+				acre_data_add_point_2d (
+					cpu_bound_frame_time_data,
+					frame, frame_time_ms);
+			} else {
+				acre_data_add_point_2d (
+					gpu_bound_frame_time_data,
+					frame, frame_time_ms);
+			}
+			acre_data_add_point_2d (frame_latency_data, frame,
+						frame_latency_ms);
+
 		} else {
-			/* Ignoring all other lines. */
+			/* Ignore all other lines. */
 		}
 	}
 
 	free (line);
 
-	acre_add_data (acre, frame_time);
+	acre_add_data (charts->cpu_gpu_load, cpu_load_data);
+	acre_add_data (charts->cpu_gpu_load, gpu_load_data);
+	acre_add_data (charts->frame_time, cpu_bound_frame_time_data);
+	acre_add_data (charts->frame_time, gpu_bound_frame_time_data);
+	acre_add_data (charts->frame_latency, frame_latency_data);
 
-	return acre;
+	return 0;
 }
 
 static void
-draw (Display *dpy, Window window, Visual *visual, acre_t *acre,
+draw_cairo (cairo_t *cr, charts_t *charts, int width, int height,
+	    double x_min, double x_max)
+{
+	int chart_height = height / 3;
+
+	/* Erase to white */
+	cairo_set_source_rgb (cr, 1, 1, 1);
+	cairo_paint (cr);
+
+	acre_set_x_axis_range (charts->cpu_gpu_load, x_min, x_max);
+	acre_draw (charts->cpu_gpu_load, cr, width, chart_height);
+
+	cairo_translate (cr, 0.0, chart_height);
+
+	acre_set_x_axis_range (charts->frame_time, x_min, x_max);
+	acre_draw (charts->frame_time, cr, width, chart_height);
+
+	cairo_translate (cr, 0.0, chart_height);
+
+	acre_set_x_axis_range (charts->frame_latency, x_min, x_max);
+	acre_draw (charts->frame_latency, cr, width, chart_height);
+}
+
+static void
+draw_xlib (Display *dpy, Window window, Visual *visual, charts_t *charts,
       int width, int height, double x_min, double x_max)
 {
 	cairo_t *cr;
@@ -107,12 +189,7 @@ draw (Display *dpy, Window window, Visual *visual, acre_t *acre,
 					     width, height);
 	cr = cairo_create (surface);
 
-	/* Erase to white */
-	cairo_set_source_rgb (cr, 1, 1, 1);
-	cairo_paint (cr);
-
-	acre_set_x_axis_range (acre, x_min, x_max);
-	acre_draw (acre, cr, width, height);
+	draw_cairo (cr, charts, width, height, x_min, x_max);
 
 	cairo_destroy (cr);
 
@@ -120,27 +197,47 @@ draw (Display *dpy, Window window, Visual *visual, acre_t *acre,
 }
 
 static void
-draw_svg (acre_t *acre, int width, int height, double x_min, double x_max)
+draw_svg (const char *filename, charts_t *charts,
+	  int width, int height, double x_min, double x_max)
 {
 	cairo_t *cr;
 	cairo_surface_t *surface;
 
-	surface = cairo_svg_surface_create ("acre-fips.svg", width, height);
+	surface = cairo_svg_surface_create (filename, width, height);
 
 	cr = cairo_create (surface);
 
-	acre_set_x_axis_range (acre, x_min, x_max);
-	acre_draw (acre, cr, width, height);
+	draw_cairo (cr, charts, width, height, x_min, x_max);
 
 	cairo_destroy (cr);
 
 	cairo_surface_destroy (surface);
 }
 
+static void
+draw_png (const char *filename, charts_t *charts,
+	  int width, int height, double x_min, double x_max)
+{
+	cairo_t *cr;
+	cairo_surface_t *surface;
+
+	surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
+					      width, height);
+
+	cr = cairo_create (surface);
+
+	draw_cairo (cr, charts, width, height, x_min, x_max);
+
+	cairo_destroy (cr);
+
+	cairo_surface_write_to_png (surface, filename);
+
+	cairo_surface_destroy (surface);
+}
 
 static void
 handle_events(Display *dpy, Window window, Visual *visual,
-	      acre_t *acre, int width, int height)
+	      charts_t *charts, int width, int height)
 {
         XEvent xev;
         KeyCode quit_code = XKeysymToKeycode (dpy, XStringToKeysym("Q"));
@@ -151,17 +248,18 @@ handle_events(Display *dpy, Window window, Visual *visual,
         KeyCode equal_code = XKeysymToKeycode (dpy, XStringToKeysym("equal"));
         KeyCode minus_code = XKeysymToKeycode (dpy, XStringToKeysym("minus"));
         KeyCode home_code = XKeysymToKeycode (dpy, XStringToKeysym("Home"));
+        KeyCode png_code = XKeysymToKeycode (dpy, XStringToKeysym("P"));
         KeyCode svg_code = XKeysymToKeycode (dpy, XStringToKeysym("S"));
 	KeyCode keycode;
 	bool need_redraw = false;
 	double x_min, x_max, shift;
 
-	acre_get_x_axis_data_range (acre, &x_min, &x_max);
+	acre_get_x_axis_data_range (charts->cpu_gpu_load, &x_min, &x_max);
 
         while (1) {
                 if (! XPending (dpy) && need_redraw)
-			draw (dpy, window, visual, acre,
-			      width, height, x_min, x_max);
+			draw_xlib (dpy, window, visual, charts,
+				   width, height, x_min, x_max);
 
 #define PAN  0.05
 #define ZOOM PAN
@@ -202,12 +300,20 @@ handle_events(Display *dpy, Window window, Visual *visual,
 			}
 			else if (keycode == home_code)
 			{
-				acre_get_x_axis_data_range (acre, &x_min, &x_max);
+				acre_get_x_axis_data_range (charts->cpu_gpu_load,
+							    &x_min, &x_max);
 			}
 			else if (keycode == svg_code)
 			{
 				need_redraw = false;
-				draw_svg (acre, width, height, x_min, x_max);
+				draw_svg ("acre-fips.svg", charts,
+					  width, height, x_min, x_max);
+			}
+			else if (keycode == png_code)
+			{
+				need_redraw = false;
+				draw_png ("acre-fips.png", charts,
+					  width, height, x_min, x_max);
 			}
 			else
 			{
@@ -232,11 +338,12 @@ main (int argc, char *argv[])
         Display *dpy;
         Window window, root;
 	Visual *visual;
-	acre_t *acre;
 	XSetWindowAttributes window_attr;
 	Colormap colormap;
 	unsigned long window_mask, event_mask;
 	unsigned long white;
+	charts_t charts;
+	int err;
 
 	int width = 800;
 	int height = 600;
@@ -247,10 +354,9 @@ main (int argc, char *argv[])
 		exit (1);
 	}
 
-	acre = load_fips_data (argv[1]);
-
-	if (acre == NULL)
-		return 1;
+	err = load_fips_charts (&charts, argv[1]);
+	if (err)
+		return err;
 
         dpy = XOpenDisplay (NULL);
 
@@ -279,9 +385,11 @@ main (int argc, char *argv[])
 
         XMapWindow (dpy, window);
 
-        handle_events (dpy, window, visual, acre, width, height);
+        handle_events (dpy, window, visual, &charts, width, height);
 
-	acre_destroy (acre);
+	acre_destroy (charts.cpu_gpu_load);
+	acre_destroy (charts.frame_time);
+	acre_destroy (charts.frame_latency);
 
         XDestroyWindow (dpy, window);
         XCloseDisplay (dpy);
-- 
2.45.2