* 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
*/
+#include <stdlib.h>
#include <stdbool.h>
+#include <string.h>
+#include <errno.h>
+#include <inttypes.h>
#include <X11/Xlib.h>
+
+#include <cairo-svg.h>
#include <cairo-xlib.h>
#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_chart (void)
+_create_chart (const char *title,
+ const char *x_axis_label,
+ const char *y_axis_label)
{
acre_t *acre;
- acre_data_t *data0, *data1, *data2;
- int i;
acre = acre_create ();
- acre_set_x_axis_label (acre, "X axis");
- acre_set_y_axis_label (acre, "Y axis");
- data0 = acre_data_create ();
- data1 = acre_data_create ();
- data2 = acre_data_create ();
+ acre_set_title (acre, title);
+ acre_set_x_axis_label (acre, x_axis_label);
+ acre_set_y_axis_label (acre, y_axis_label);
- acre_data_set_name (data0, "Data 0");
- acre_data_set_name (data1, "Data 1");
- acre_data_set_name (data2, "Data 2");
+ return acre;
+}
- for (i = 0; i <= 100; i++) {
- acre_data_add_point_2d (data0, i, 0 - (i/3.0)*(i/3.0));
+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;
+
+ file = fopen (filename, "r");
+ if (file == NULL) {
+ fprintf (stderr, "Failed to open %s: %s\n",
+ filename, strerror (errno));
+ return 1;
}
- for (i = 0; i < 100; i++) {
- double t = 1.0 - (i / 100.0);
- acre_data_add_point_2d (data1, i, -1000 * (1.0 - t*t*t));
- }
-
- for (i = 0; i <= 1000; i++) {
- double t, x, y;
- t = i/10.0 - 50;
- x = t + 50;
- if (t == 0.0)
- y = -200;
- else
- y = -1200 + 1000 * sin(t) / t;
- acre_data_add_point_2d (data2, x, y);
+ 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");
+
+ gpu_load_data = acre_data_create ();
+ acre_data_set_name (gpu_load_data, "GPU");
+
+ 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;
+
+ bytes = getline (&line, &line_size, file);
+ if (bytes == -1)
+ break;
+
+ s = line;
+
+ if (STRNCMP_LITERAL (s, "frame: ") == 0) {
+ unsigned frame;
+ 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 (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 {
+ /* Ignore all other lines. */
+ }
}
- acre_add_data (acre, data0);
- acre_add_data (acre, data1);
- acre_add_data (acre, data2);
+ free (line);
- acre_set_title (acre, "All the data");
+ 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;
width, height);
cr = cairo_create (surface);
- /* Erase to white */
- cairo_set_source_rgb (cr, 1, 1, 1);
- cairo_paint (cr);
+ draw_cairo (cr, charts, width, height, x_min, x_max);
- acre_set_x_axis_range (acre, x_min, x_max);
- acre_draw (acre, cr, width, height);
+ cairo_destroy (cr);
+
+ cairo_surface_destroy (surface);
+}
+
+static void
+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 (filename, width, height);
+
+ cr = cairo_create (surface);
+
+ 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"));
+ KeyCode escape_code = XKeysymToKeycode (dpy, XStringToKeysym("Escape"));
KeyCode left_code = XKeysymToKeycode (dpy, XStringToKeysym("Left"));
KeyCode right_code = XKeysymToKeycode (dpy, XStringToKeysym("Right"));
KeyCode plus_code = XKeysymToKeycode (dpy, XStringToKeysym("plus"));
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
XNextEvent (dpy, &xev);
switch (xev.type) {
case KeyPress:
+ need_redraw = true;
keycode = xev.xkey.keycode;
- if (keycode == quit_code)
+ if (keycode == quit_code ||
+ keycode == escape_code)
{
return;
}
else if (keycode == left_code)
{
- shift = 0.25 * (x_max - x_min);
+ shift = PAN * (x_max - x_min);
x_min += shift;
x_max += shift;
}
else if (keycode == right_code)
{
- shift = 0.25 * (x_max - x_min);
+ shift = PAN * (x_max - x_min);
x_min -= shift;
x_max -= shift;
}
else if (keycode == plus_code ||
keycode == equal_code)
{
- shift = 0.25 * (x_max - x_min);
+ shift = ZOOM * (x_max - x_min);
x_min += shift;
x_max -= shift;
}
else if (keycode == minus_code)
{
- shift = 0.5 * (x_max - x_min);
+ shift = (ZOOM/(1 - 2 * ZOOM)) * (x_max - x_min);
x_min -= shift;
x_max += shift;
}
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-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
+ {
+ need_redraw = false;
}
- need_redraw = 1;
break;
case ConfigureNotify:
width = xev.xconfigure.width;
}
int
-main (void)
+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;
- acre = load_chart ();
+ if (argc < 2) {
+ fprintf (stderr, "Usage: acre-x data-file\n");
+ fprintf (stderr, "Where the data file is the output from fips (with frame-timing feature.\n");
+ exit (1);
+ }
+
+ err = load_fips_charts (&charts, argv[1]);
+ if (err)
+ return err;
dpy = XOpenDisplay (NULL);
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);