]> git.cworth.org Git - acre/blob - acre-x.c
Expand to add CPU/GPU load and frame latency graphs.
[acre] / acre-x.c
1 /* acre - A cairo-based library for creating plots and charts.
2  *
3  * Copyright © 2009 Carl Worth <cworth@cworth.org>
4  *
5  * This program is free software; you can redistribute it and/or
6  * modify it under the terms of the GNU General Public License as
7  * published by the Free Software Foundation; either version 2 of the
8  * License, or (at your option) any later version.
9  *
10  * This program is distributed in the hope that it will be useful, but
11  * WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13  * General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public License along
16  * with this program; if not, write to the Free Software Foundation, Inc.,
17  * 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA.
18  */
19
20 #include <stdlib.h>
21 #include <stdbool.h>
22 #include <string.h>
23 #include <errno.h>
24 #include <inttypes.h>
25
26 #include <X11/Xlib.h>
27
28 #include <cairo-svg.h>
29 #include <cairo-xlib.h>
30
31 #include "acre.h"
32 #include "math.h"
33
34 typedef struct
35 {
36         acre_t *cpu_gpu_load;
37         acre_t *frame_time;
38         acre_t *frame_latency;
39 } charts_t;
40
41 #define STRNCMP_LITERAL(var, literal) \
42     strncmp ((var), (literal), sizeof (literal) - 1)
43
44 static acre_t *
45 _create_chart (const char *title,
46                const char *x_axis_label,
47                const char *y_axis_label)
48 {
49         acre_t *acre;
50
51         acre = acre_create ();
52
53         acre_set_title (acre, title);
54         acre_set_x_axis_label (acre, x_axis_label);
55         acre_set_y_axis_label (acre, y_axis_label);
56
57         return acre;
58 }
59
60 static int
61 load_fips_charts (charts_t *charts, const char *filename)
62 {
63         FILE *file;
64         acre_data_t *cpu_load_data;
65         acre_data_t *gpu_load_data;
66         acre_data_t *cpu_bound_frame_time_data;
67         acre_data_t *gpu_bound_frame_time_data;
68         acre_data_t *frame_latency_data;
69         char *line = NULL, *s;
70         size_t line_size;
71         ssize_t bytes;
72
73         file = fopen (filename, "r");
74         if (file == NULL) {
75                 fprintf (stderr, "Failed to open %s: %s\n",
76                          filename, strerror (errno));
77                 return 1;
78         }
79
80         charts->cpu_gpu_load = _create_chart ("CPU/GPU load", "Frame #", "Load");
81         charts->frame_time = _create_chart ("Frame time", "Frame #", "Time (ms)");
82         charts->frame_latency = _create_chart ("Frame latency", "Frame #", "Latency (ms)");
83
84         cpu_load_data = acre_data_create ();
85         acre_data_set_name (cpu_load_data, "CPU");
86
87         gpu_load_data = acre_data_create ();
88         acre_data_set_name (gpu_load_data, "GPU");
89
90         cpu_bound_frame_time_data = acre_data_create ();
91         acre_data_set_style (cpu_bound_frame_time_data, ACRE_STYLE_BARS);
92         acre_data_set_name (cpu_bound_frame_time_data, "CPU Bound");
93
94         gpu_bound_frame_time_data = acre_data_create ();
95         acre_data_set_style (gpu_bound_frame_time_data, ACRE_STYLE_BARS);
96         acre_data_set_name (gpu_bound_frame_time_data, "GPU Bound");
97
98         frame_latency_data = acre_data_create ();
99
100         while (1) {
101                 int scanned;
102
103                 bytes = getline (&line, &line_size, file);
104                 if (bytes == -1)
105                         break;
106
107                 s = line;
108
109                 if (STRNCMP_LITERAL (s, "frame: ") == 0) {
110                         unsigned frame;
111                         double frame_time_ms, frame_latency_ms;
112                         double cpu_load, gpu_load;
113
114                         s += strlen("frame: ");
115
116                         scanned = sscanf (s, "%d %lg %lg %lg %lg",
117                                           &frame,
118                                           &frame_time_ms,
119                                           &frame_latency_ms,
120                                           &cpu_load,
121                                           &gpu_load);
122                         if (scanned != 5) {
123                                 fprintf (stderr, "Warning: Failed to parse line: %s\n", line);
124                                 continue;
125                         }
126
127                         acre_data_add_point_2d (cpu_load_data, frame, cpu_load);
128                         acre_data_add_point_2d (gpu_load_data, frame, gpu_load);
129                         if (cpu_load > gpu_load) {
130                                 acre_data_add_point_2d (
131                                         cpu_bound_frame_time_data,
132                                         frame, frame_time_ms);
133                         } else {
134                                 acre_data_add_point_2d (
135                                         gpu_bound_frame_time_data,
136                                         frame, frame_time_ms);
137                         }
138                         acre_data_add_point_2d (frame_latency_data, frame,
139                                                 frame_latency_ms);
140
141                 } else {
142                         /* Ignore all other lines. */
143                 }
144         }
145
146         free (line);
147
148         acre_add_data (charts->cpu_gpu_load, cpu_load_data);
149         acre_add_data (charts->cpu_gpu_load, gpu_load_data);
150         acre_add_data (charts->frame_time, cpu_bound_frame_time_data);
151         acre_add_data (charts->frame_time, gpu_bound_frame_time_data);
152         acre_add_data (charts->frame_latency, frame_latency_data);
153
154         return 0;
155 }
156
157 static void
158 draw_cairo (cairo_t *cr, charts_t *charts, int width, int height,
159             double x_min, double x_max)
160 {
161         int chart_height = height / 3;
162
163         /* Erase to white */
164         cairo_set_source_rgb (cr, 1, 1, 1);
165         cairo_paint (cr);
166
167         acre_set_x_axis_range (charts->cpu_gpu_load, x_min, x_max);
168         acre_draw (charts->cpu_gpu_load, cr, width, chart_height);
169
170         cairo_translate (cr, 0.0, chart_height);
171
172         acre_set_x_axis_range (charts->frame_time, x_min, x_max);
173         acre_draw (charts->frame_time, cr, width, chart_height);
174
175         cairo_translate (cr, 0.0, chart_height);
176
177         acre_set_x_axis_range (charts->frame_latency, x_min, x_max);
178         acre_draw (charts->frame_latency, cr, width, chart_height);
179 }
180
181 static void
182 draw_xlib (Display *dpy, Window window, Visual *visual, charts_t *charts,
183       int width, int height, double x_min, double x_max)
184 {
185         cairo_t *cr;
186         cairo_surface_t *surface;
187
188         surface = cairo_xlib_surface_create (dpy, window, visual,
189                                              width, height);
190         cr = cairo_create (surface);
191
192         draw_cairo (cr, charts, width, height, x_min, x_max);
193
194         cairo_destroy (cr);
195
196         cairo_surface_destroy (surface);
197 }
198
199 static void
200 draw_svg (const char *filename, charts_t *charts,
201           int width, int height, double x_min, double x_max)
202 {
203         cairo_t *cr;
204         cairo_surface_t *surface;
205
206         surface = cairo_svg_surface_create (filename, width, height);
207
208         cr = cairo_create (surface);
209
210         draw_cairo (cr, charts, width, height, x_min, x_max);
211
212         cairo_destroy (cr);
213
214         cairo_surface_destroy (surface);
215 }
216
217 static void
218 draw_png (const char *filename, charts_t *charts,
219           int width, int height, double x_min, double x_max)
220 {
221         cairo_t *cr;
222         cairo_surface_t *surface;
223
224         surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
225                                               width, height);
226
227         cr = cairo_create (surface);
228
229         draw_cairo (cr, charts, width, height, x_min, x_max);
230
231         cairo_destroy (cr);
232
233         cairo_surface_write_to_png (surface, filename);
234
235         cairo_surface_destroy (surface);
236 }
237
238 static void
239 handle_events(Display *dpy, Window window, Visual *visual,
240               charts_t *charts, int width, int height)
241 {
242         XEvent xev;
243         KeyCode quit_code = XKeysymToKeycode (dpy, XStringToKeysym("Q"));
244         KeyCode escape_code = XKeysymToKeycode (dpy, XStringToKeysym("Escape"));
245         KeyCode left_code = XKeysymToKeycode (dpy, XStringToKeysym("Left"));
246         KeyCode right_code = XKeysymToKeycode (dpy, XStringToKeysym("Right"));
247         KeyCode plus_code = XKeysymToKeycode (dpy, XStringToKeysym("plus"));
248         KeyCode equal_code = XKeysymToKeycode (dpy, XStringToKeysym("equal"));
249         KeyCode minus_code = XKeysymToKeycode (dpy, XStringToKeysym("minus"));
250         KeyCode home_code = XKeysymToKeycode (dpy, XStringToKeysym("Home"));
251         KeyCode png_code = XKeysymToKeycode (dpy, XStringToKeysym("P"));
252         KeyCode svg_code = XKeysymToKeycode (dpy, XStringToKeysym("S"));
253         KeyCode keycode;
254         bool need_redraw = false;
255         double x_min, x_max, shift;
256
257         acre_get_x_axis_data_range (charts->cpu_gpu_load, &x_min, &x_max);
258
259         while (1) {
260                 if (! XPending (dpy) && need_redraw)
261                         draw_xlib (dpy, window, visual, charts,
262                                    width, height, x_min, x_max);
263
264 #define PAN  0.05
265 #define ZOOM PAN
266                 XNextEvent (dpy, &xev);
267                 switch (xev.type) {
268                 case KeyPress:
269                         need_redraw = true;
270                         keycode = xev.xkey.keycode;
271                         if (keycode == quit_code ||
272                             keycode == escape_code)
273                         {
274                                 return;
275                         }
276                         else if (keycode == left_code)
277                         {
278                                 shift = PAN * (x_max - x_min);
279                                 x_min += shift;
280                                 x_max += shift;
281                         }
282                         else if (keycode == right_code)
283                         {
284                                 shift = PAN * (x_max - x_min);
285                                 x_min -= shift;
286                                 x_max -= shift;
287                         }
288                         else if (keycode == plus_code ||
289                                    keycode == equal_code)
290                         {
291                                 shift = ZOOM * (x_max - x_min);
292                                 x_min += shift;
293                                 x_max -= shift;
294                         }
295                         else if (keycode == minus_code)
296                         {
297                                 shift = (ZOOM/(1 - 2 * ZOOM)) * (x_max - x_min);
298                                 x_min -= shift;
299                                 x_max += shift;
300                         }
301                         else if (keycode == home_code)
302                         {
303                                 acre_get_x_axis_data_range (charts->cpu_gpu_load,
304                                                             &x_min, &x_max);
305                         }
306                         else if (keycode == svg_code)
307                         {
308                                 need_redraw = false;
309                                 draw_svg ("acre-fips.svg", charts,
310                                           width, height, x_min, x_max);
311                         }
312                         else if (keycode == png_code)
313                         {
314                                 need_redraw = false;
315                                 draw_png ("acre-fips.png", charts,
316                                           width, height, x_min, x_max);
317                         }
318                         else
319                         {
320                                 need_redraw = false;
321                         }
322                         break;
323                 case ConfigureNotify:
324                         width = xev.xconfigure.width;
325                         height = xev.xconfigure.height;
326                         break;
327                 case Expose:
328                         if (xev.xexpose.count == 0)
329                                 need_redraw = 1;
330                         break;
331                 }
332         }
333 }
334
335 int
336 main (int argc, char *argv[])
337 {
338         Display *dpy;
339         Window window, root;
340         Visual *visual;
341         XSetWindowAttributes window_attr;
342         Colormap colormap;
343         unsigned long window_mask, event_mask;
344         unsigned long white;
345         charts_t charts;
346         int err;
347
348         int width = 800;
349         int height = 600;
350
351         if (argc < 2) {
352                 fprintf (stderr, "Usage: acre-x data-file\n");
353                 fprintf (stderr, "Where the data file is the output from fips (with frame-timing feature.\n");
354                 exit (1);
355         }
356
357         err = load_fips_charts (&charts, argv[1]);
358         if (err)
359                 return err;
360
361         dpy = XOpenDisplay (NULL);
362
363         if (dpy == NULL) {
364                 fprintf(stderr, "Failed to open display %s\n",
365                         XDisplayName(NULL));
366                 return 1;
367         }
368
369         root = DefaultRootWindow (dpy);
370         white = WhitePixel (dpy, DefaultScreen (dpy));
371         visual = DefaultVisual (dpy, DefaultScreen (dpy));
372         colormap = XCreateColormap (dpy, root, visual, AllocNone);
373         event_mask = KeyPressMask | StructureNotifyMask | ExposureMask;
374
375         window_mask = 0;
376         window_mask |= CWBackPixel;     window_attr.background_pixel = white;
377         window_mask |= CWBorderPixel;   window_attr.border_pixel = white;
378         window_mask |= CWColormap;      window_attr.colormap = colormap;
379         window_mask |= CWEventMask;     window_attr.event_mask = event_mask;
380
381         window = XCreateWindow(dpy, root, 0, 0, width, height, 0,
382                                DefaultDepth (dpy, DefaultScreen (dpy)),
383                                InputOutput, visual,
384                                window_mask, &window_attr);
385
386         XMapWindow (dpy, window);
387
388         handle_events (dpy, window, visual, &charts, width, height);
389
390         acre_destroy (charts.cpu_gpu_load);
391         acre_destroy (charts.frame_time);
392         acre_destroy (charts.frame_latency);
393
394         XDestroyWindow (dpy, window);
395         XCloseDisplay (dpy);
396
397         return 0;
398 }