blob: 062f63fdd8e7ffe6e971f3ebd4bcb42678643a14 [file] [log] [blame]
Stephen M. Cameronff1f3282012-02-24 08:17:30 +01001/*
2 * gfio - gui front end for fio - the flexible io tester
3 *
4 * Copyright (C) 2012 Stephen M. Cameron <stephenmcameron@gmail.com>
5 *
6 * The license below covers all files distributed with fio unless otherwise
7 * noted in the file itself.
8 *
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License version 2 as
11 * published by the Free Software Foundation.
12 *
13 * This program is distributed in the hope that it will be useful,
14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
16 * GNU General Public License for more details.
17 *
18 * You should have received a copy of the GNU General Public License
19 * along with this program; if not, write to the Free Software
20 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
21 *
22 */
Stephen M. Cameron8232e282012-02-24 08:17:31 +010023#include <locale.h>
Stephen M. Cameron60f6b332012-02-24 08:17:32 +010024#include <malloc.h>
Stephen M. Cameron8232e282012-02-24 08:17:31 +010025
Stephen M. Cameron5b7573a2012-02-24 08:17:31 +010026#include <glib.h>
Stephen M. Cameronff1f3282012-02-24 08:17:30 +010027#include <gtk/gtk.h>
28
Stephen M. Cameron8232e282012-02-24 08:17:31 +010029#include "fio.h"
30
Jens Axboe3e47bd22012-02-29 13:45:02 +010031static void gfio_update_thread_status(char *status_message, double perc);
32
Stephen M. Cameronf3074002012-02-24 08:17:30 +010033#define ARRAYSIZE(x) (sizeof((x)) / (sizeof((x)[0])))
34
35typedef void (*clickfunction)(GtkWidget *widget, gpointer data);
36
Jens Axboe3e47bd22012-02-29 13:45:02 +010037static void connect_clicked(GtkWidget *widget, gpointer data);
Stephen M. Cameronf3074002012-02-24 08:17:30 +010038static void start_job_clicked(GtkWidget *widget, gpointer data);
39
40static struct button_spec {
41 const char *buttontext;
42 clickfunction f;
43 const char *tooltiptext;
Jens Axboe3e47bd22012-02-29 13:45:02 +010044 const int start_insensitive;
Stephen M. Cameronf3074002012-02-24 08:17:30 +010045} buttonspeclist[] = {
Jens Axboe3e47bd22012-02-29 13:45:02 +010046#define CONNECT_BUTTON 0
47#define START_JOB_BUTTON 1
48 { "Connect", connect_clicked, "Connect to host", 0 },
Stephen M. Cameronf3074002012-02-24 08:17:30 +010049 { "Start Job",
50 start_job_clicked,
Jens Axboe3e47bd22012-02-29 13:45:02 +010051 "Send current fio job to fio server to be executed", 1 },
Stephen M. Cameronf3074002012-02-24 08:17:30 +010052};
53
Jens Axboe843ad232012-02-29 11:44:53 +010054struct probe_widget {
55 GtkWidget *hostname;
56 GtkWidget *os;
57 GtkWidget *arch;
58 GtkWidget *fio_ver;
59};
60
Jens Axboe3e47bd22012-02-29 13:45:02 +010061struct eta_widget {
Jens Axboe807f9972012-03-02 10:25:24 +010062 GtkWidget *name;
63 GtkWidget *iotype;
64 GtkWidget *ioengine;
65 GtkWidget *iodepth;
Jens Axboe3e47bd22012-02-29 13:45:02 +010066 GtkWidget *jobs;
67 GtkWidget *files;
68 GtkWidget *read_bw;
69 GtkWidget *read_iops;
70 GtkWidget *cr_bw;
71 GtkWidget *cr_iops;
72 GtkWidget *write_bw;
73 GtkWidget *write_iops;
74 GtkWidget *cw_bw;
75 GtkWidget *cw_iops;
76};
77
Stephen M. Cameronff1f3282012-02-24 08:17:30 +010078struct gui {
79 GtkWidget *window;
Stephen M. Cameron5b7573a2012-02-24 08:17:31 +010080 GtkWidget *vbox;
Stephen M. Cameronc36f98d2012-02-24 08:17:32 +010081 GtkWidget *topvbox;
82 GtkWidget *topalign;
83 GtkWidget *bottomalign;
Stephen M. Cameron04cc6b72012-02-24 08:17:31 +010084 GtkWidget *thread_status_pb;
Stephen M. Cameronf3074002012-02-24 08:17:30 +010085 GtkWidget *buttonbox;
86 GtkWidget *button[ARRAYSIZE(buttonspeclist)];
Stephen M. Cameron736f2df2012-02-24 08:17:32 +010087 GtkWidget *scrolled_window;
88 GtkWidget *textview;
Jens Axboe0420ba62012-02-29 11:16:52 +010089 GtkWidget *error_info_bar;
90 GtkWidget *error_label;
Stephen M. Cameron736f2df2012-02-24 08:17:32 +010091 GtkTextBuffer *text;
Jens Axboe843ad232012-02-29 11:44:53 +010092 struct probe_widget probe;
Jens Axboe3e47bd22012-02-29 13:45:02 +010093 struct eta_widget eta;
Jens Axboe3ec62ec2012-03-01 12:01:29 +010094 int connected;
Stephen M. Cameron25927252012-02-24 08:17:31 +010095 pthread_t t;
Jens Axboe0420ba62012-02-29 11:16:52 +010096
Jens Axboe3ec62ec2012-03-01 12:01:29 +010097 struct fio_client *client;
Jens Axboe0420ba62012-02-29 11:16:52 +010098 int nr_job_files;
99 char **job_files;
Stephen M. Cameron5b7573a2012-02-24 08:17:31 +0100100} ui;
Stephen M. Cameronff1f3282012-02-24 08:17:30 +0100101
Jens Axboe8663ea62012-03-02 14:04:30 +0100102static void clear_ui_info(struct gui *ui)
103{
104 gtk_label_set_text(GTK_LABEL(ui->probe.hostname), "");
105 gtk_label_set_text(GTK_LABEL(ui->probe.os), "");
106 gtk_label_set_text(GTK_LABEL(ui->probe.arch), "");
107 gtk_label_set_text(GTK_LABEL(ui->probe.fio_ver), "");
Jens Axboeca850992012-03-05 20:04:43 +0100108 gtk_entry_set_text(GTK_ENTRY(ui->eta.name), "");
109 gtk_entry_set_text(GTK_ENTRY(ui->eta.iotype), "");
110 gtk_entry_set_text(GTK_ENTRY(ui->eta.ioengine), "");
111 gtk_entry_set_text(GTK_ENTRY(ui->eta.iodepth), "");
112 gtk_entry_set_text(GTK_ENTRY(ui->eta.jobs), "");
113 gtk_entry_set_text(GTK_ENTRY(ui->eta.files), "");
114 gtk_entry_set_text(GTK_ENTRY(ui->eta.read_bw), "");
115 gtk_entry_set_text(GTK_ENTRY(ui->eta.read_iops), "");
116 gtk_entry_set_text(GTK_ENTRY(ui->eta.write_bw), "");
117 gtk_entry_set_text(GTK_ENTRY(ui->eta.write_iops), "");
Jens Axboe8663ea62012-03-02 14:04:30 +0100118}
119
Jens Axboe3650a3c2012-03-05 14:09:03 +0100120static GtkWidget *new_info_entry_in_frame(GtkWidget *box, const char *label)
121{
122 GtkWidget *entry, *frame;
123
124 frame = gtk_frame_new(label);
125 entry = gtk_entry_new();
Jens Axboe1c1e4a52012-03-05 14:37:38 +0100126 gtk_entry_set_editable(GTK_ENTRY(entry), 0);
Jens Axboe3650a3c2012-03-05 14:09:03 +0100127 gtk_box_pack_start(GTK_BOX(box), frame, TRUE, TRUE, 3);
128 gtk_container_add(GTK_CONTAINER(frame), entry);
129
130 return entry;
131}
132
133static GtkWidget *new_info_label_in_frame(GtkWidget *box, const char *label)
134{
135 GtkWidget *label_widget;
136 GtkWidget *frame;
137
138 frame = gtk_frame_new(label);
139 label_widget = gtk_label_new(NULL);
140 gtk_box_pack_start(GTK_BOX(box), frame, TRUE, TRUE, 3);
141 gtk_container_add(GTK_CONTAINER(frame), label_widget);
142
143 return label_widget;
144}
145
146static GtkWidget *create_spinbutton(GtkWidget *hbox, double min, double max, double defval)
147{
148 GtkWidget *button, *box;
149
150 box = gtk_hbox_new(FALSE, 3);
151 gtk_container_add(GTK_CONTAINER(hbox), box);
152
153 button = gtk_spin_button_new_with_range(min, max, 1.0);
154 gtk_box_pack_start(GTK_BOX(box), button, TRUE, TRUE, 0);
155
156 gtk_spin_button_set_update_policy(GTK_SPIN_BUTTON(button), GTK_UPDATE_IF_VALID);
157 gtk_spin_button_set_value(GTK_SPIN_BUTTON(button), defval);
158
159 return button;
160}
161
Jens Axboe3ec62ec2012-03-01 12:01:29 +0100162static void gfio_set_connected(struct gui *ui, int connected)
163{
164 if (connected) {
165 gtk_widget_set_sensitive(ui->button[START_JOB_BUTTON], 1);
166 ui->connected = 1;
167 gtk_button_set_label(GTK_BUTTON(ui->button[CONNECT_BUTTON]), "Disconnect");
168 } else {
169 ui->connected = 0;
170 gtk_button_set_label(GTK_BUTTON(ui->button[CONNECT_BUTTON]), "Connect");
171 gtk_widget_set_sensitive(ui->button[START_JOB_BUTTON], 0);
172 }
173}
174
Jens Axboe3650a3c2012-03-05 14:09:03 +0100175static void label_set_int_value(GtkWidget *entry, unsigned int val)
176{
177 char tmp[80];
178
179 sprintf(tmp, "%u", val);
180 gtk_label_set_text(GTK_LABEL(entry), tmp);
181}
182
183static void entry_set_int_value(GtkWidget *entry, unsigned int val)
184{
185 char tmp[80];
186
187 sprintf(tmp, "%u", val);
188 gtk_entry_set_text(GTK_ENTRY(entry), tmp);
189}
190
Jens Axboea2697902012-03-05 16:43:49 +0100191#define ALIGN_LEFT 1
192#define ALIGN_RIGHT 2
193#define INVISIBLE 4
194#define UNSORTABLE 8
195
196GtkTreeViewColumn *tree_view_column(GtkWidget *tree_view, int index, const char *title, unsigned int flags)
197{
198 GtkCellRenderer *renderer;
199 GtkTreeViewColumn *col;
200 double xalign = 0.0; /* left as default */
201 PangoAlignment align;
202 gboolean visible;
203
204 align = (flags & ALIGN_LEFT) ? PANGO_ALIGN_LEFT :
205 (flags & ALIGN_RIGHT) ? PANGO_ALIGN_RIGHT :
206 PANGO_ALIGN_CENTER;
207 visible = !(flags & INVISIBLE);
208
209 renderer = gtk_cell_renderer_text_new();
210 col = gtk_tree_view_column_new();
211
212 gtk_tree_view_column_set_title(col, title);
213 if (!(flags & UNSORTABLE))
214 gtk_tree_view_column_set_sort_column_id(col, index);
215 gtk_tree_view_column_set_resizable(col, TRUE);
216 gtk_tree_view_column_pack_start(col, renderer, TRUE);
217 gtk_tree_view_column_add_attribute(col, renderer, "text", index);
218 gtk_object_set(GTK_OBJECT(renderer), "alignment", align, NULL);
219 switch (align) {
220 case PANGO_ALIGN_LEFT:
221 xalign = 0.0;
222 break;
223 case PANGO_ALIGN_CENTER:
224 xalign = 0.5;
225 break;
226 case PANGO_ALIGN_RIGHT:
227 xalign = 1.0;
228 break;
229 }
230 gtk_cell_renderer_set_alignment(GTK_CELL_RENDERER(renderer), xalign, 0.5);
231 gtk_tree_view_column_set_visible(col, visible);
232 gtk_tree_view_append_column(GTK_TREE_VIEW(tree_view), col);
233 return col;
234}
235
236static GtkWidget *gfio_output_clat_percentiles(unsigned int *ovals,
237 fio_fp64_t *plist,
238 unsigned int len,
239 const char *base,
240 unsigned int scale)
241{
242 GType types[FIO_IO_U_LIST_MAX_LEN];
243 GtkWidget *tree_view;
244 GtkTreeSelection *selection;
245 GtkListStore *model;
246 GtkTreeIter iter;
247 int i;
248
249 for (i = 0; i < len; i++)
250 types[i] = G_TYPE_INT;
251
252 model = gtk_list_store_newv(len, types);
253
254 tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
255 gtk_widget_set_can_focus(tree_view, FALSE);
256
257 selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
258 gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE);
259
260 for (i = 0; i < len; i++) {
261 char fbuf[8];
262
263 sprintf(fbuf, "%2.2f%%", plist[i].u.f);
264 tree_view_column(tree_view, i, fbuf, ALIGN_RIGHT | UNSORTABLE);
265 }
266
267 gtk_list_store_append(model, &iter);
268
269 for (i = 0; i < len; i++)
270 gtk_list_store_set(model, &iter, i, ovals[i], -1);
271
272 return tree_view;
273}
274
275static void gfio_show_clat_percentiles(GtkWidget *vbox, struct thread_stat *ts,
276 int ddir)
277{
278 unsigned int *io_u_plat = ts->io_u_plat[ddir];
279 unsigned long nr = ts->clat_stat[ddir].samples;
280 fio_fp64_t *plist = ts->percentile_list;
281 unsigned int *ovals, len, minv, maxv, scale_down;
282 const char *base;
283 GtkWidget *tree_view, *frame, *hbox;
284 char tmp[64];
285
286 len = calc_clat_percentiles(io_u_plat, nr, plist, &ovals, &maxv, &minv);
287 if (!len)
288 goto out;
289
290 /*
291 * We default to usecs, but if the value range is such that we
292 * should scale down to msecs, do that.
293 */
294 if (minv > 2000 && maxv > 99999) {
295 scale_down = 1;
296 base = "msec";
297 } else {
298 scale_down = 0;
299 base = "usec";
300 }
301
302 tree_view = gfio_output_clat_percentiles(ovals, plist, len, base, scale_down);
303
304 sprintf(tmp, "Completion percentiles (%s)", base);
305 frame = gtk_frame_new(tmp);
306 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
307
308 hbox = gtk_hbox_new(FALSE, 3);
309 gtk_container_add(GTK_CONTAINER(frame), hbox);
310
311 gtk_box_pack_start(GTK_BOX(hbox), tree_view, TRUE, FALSE, 3);
312out:
313 if (ovals)
314 free(ovals);
315}
316
Jens Axboe3650a3c2012-03-05 14:09:03 +0100317static void gfio_show_lat(GtkWidget *vbox, const char *name, unsigned long min,
318 unsigned long max, double mean, double dev)
319{
320 const char *base = "(usec)";
Jens Axboe1c1e4a52012-03-05 14:37:38 +0100321 GtkWidget *hbox, *label, *frame;
Jens Axboe3650a3c2012-03-05 14:09:03 +0100322 char *minp, *maxp;
323 char tmp[64];
324
325 if (!usec_to_msec(&min, &max, &mean, &dev))
326 base = "(msec)";
327
328 minp = num2str(min, 6, 1, 0);
329 maxp = num2str(max, 6, 1, 0);
330
Jens Axboe3650a3c2012-03-05 14:09:03 +0100331 sprintf(tmp, "%s %s", name, base);
332 frame = gtk_frame_new(tmp);
333 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
334
Jens Axboe3650a3c2012-03-05 14:09:03 +0100335 hbox = gtk_hbox_new(FALSE, 3);
Jens Axboe1c1e4a52012-03-05 14:37:38 +0100336 gtk_container_add(GTK_CONTAINER(frame), hbox);
Jens Axboe3650a3c2012-03-05 14:09:03 +0100337
338 label = new_info_label_in_frame(hbox, "Minimum");
339 gtk_label_set_text(GTK_LABEL(label), minp);
340 label = new_info_label_in_frame(hbox, "Maximum");
341 gtk_label_set_text(GTK_LABEL(label), maxp);
342 label = new_info_label_in_frame(hbox, "Average");
343 sprintf(tmp, "%5.02f", mean);
344 gtk_label_set_text(GTK_LABEL(label), tmp);
345 label = new_info_label_in_frame(hbox, "Standard deviation");
346 sprintf(tmp, "%5.02f", dev);
347 gtk_label_set_text(GTK_LABEL(label), tmp);
348
349 free(minp);
350 free(maxp);
351
352}
353
Jens Axboeca850992012-03-05 20:04:43 +0100354#define GFIO_CLAT 1
355#define GFIO_SLAT 2
356#define GFIO_LAT 4
357
Jens Axboe3650a3c2012-03-05 14:09:03 +0100358static void gfio_show_ddir_status(GtkWidget *mbox, struct group_run_stats *rs,
359 struct thread_stat *ts, int ddir)
360{
361 const char *ddir_label[2] = { "Read", "Write" };
362 GtkWidget *frame, *label, *box, *vbox;
363 unsigned long min, max, runt;
364 unsigned long long bw, iops;
Jens Axboeca850992012-03-05 20:04:43 +0100365 unsigned int flags = 0;
Jens Axboe3650a3c2012-03-05 14:09:03 +0100366 double mean, dev;
367 char *io_p, *bw_p, *iops_p;
368 int i2p;
369
370 if (!ts->runtime[ddir])
371 return;
372
373 i2p = is_power_of_2(rs->kb_base);
374 runt = ts->runtime[ddir];
375
376 bw = (1000 * ts->io_bytes[ddir]) / runt;
377 io_p = num2str(ts->io_bytes[ddir], 6, 1, i2p);
378 bw_p = num2str(bw, 6, 1, i2p);
379
380 iops = (1000 * (uint64_t)ts->total_io_u[ddir]) / runt;
381 iops_p = num2str(iops, 6, 1, 0);
382
383 box = gtk_hbox_new(FALSE, 3);
384 gtk_box_pack_start(GTK_BOX(mbox), box, TRUE, FALSE, 3);
385
386 frame = gtk_frame_new(ddir_label[ddir]);
387 gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 5);
388
389 vbox = gtk_vbox_new(FALSE, 3);
390 gtk_container_add(GTK_CONTAINER(frame), vbox);
391
392 box = gtk_hbox_new(FALSE, 3);
393 gtk_box_pack_start(GTK_BOX(vbox), box, TRUE, FALSE, 3);
394
395 label = new_info_label_in_frame(box, "IO");
396 gtk_label_set_text(GTK_LABEL(label), io_p);
397 label = new_info_label_in_frame(box, "Bandwidth");
398 gtk_label_set_text(GTK_LABEL(label), bw_p);
399 label = new_info_label_in_frame(box, "IOPS");
400 gtk_label_set_text(GTK_LABEL(label), iops_p);
401 label = new_info_label_in_frame(box, "Runtime (msec)");
402 label_set_int_value(label, ts->runtime[ddir]);
403
Jens Axboe3650a3c2012-03-05 14:09:03 +0100404 if (calc_lat(&ts->slat_stat[ddir], &min, &max, &mean, &dev))
Jens Axboeca850992012-03-05 20:04:43 +0100405 flags |= GFIO_SLAT;
Jens Axboe3650a3c2012-03-05 14:09:03 +0100406 if (calc_lat(&ts->clat_stat[ddir], &min, &max, &mean, &dev))
Jens Axboeca850992012-03-05 20:04:43 +0100407 flags |= GFIO_CLAT;
Jens Axboe3650a3c2012-03-05 14:09:03 +0100408 if (calc_lat(&ts->lat_stat[ddir], &min, &max, &mean, &dev))
Jens Axboeca850992012-03-05 20:04:43 +0100409 flags |= GFIO_LAT;
410
411 if (flags) {
412 frame = gtk_frame_new("Latency");
413 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
414
415 vbox = gtk_vbox_new(FALSE, 3);
416 gtk_container_add(GTK_CONTAINER(frame), vbox);
417
418 if (flags & GFIO_SLAT)
419 gfio_show_lat(vbox, "Submission latency", min, max, mean, dev);
420 if (flags & GFIO_CLAT)
421 gfio_show_lat(vbox, "Completion latency", min, max, mean, dev);
422 if (flags & GFIO_LAT)
423 gfio_show_lat(vbox, "Total latency", min, max, mean, dev);
424 }
425
Jens Axboea2697902012-03-05 16:43:49 +0100426 if (ts->clat_percentiles)
427 gfio_show_clat_percentiles(vbox, ts, ddir);
Jens Axboe3650a3c2012-03-05 14:09:03 +0100428
Jens Axboeca850992012-03-05 20:04:43 +0100429 if (calc_lat(&ts->bw_stat[ddir], &min, &max, &mean, &dev)) {
430 double p_of_agg = 100.0;
431 const char *bw_str = "KB";
432 char tmp[32];
433
434 if (rs->agg[ddir]) {
435 p_of_agg = mean * 100 / (double) rs->agg[ddir];
436 if (p_of_agg > 100.0)
437 p_of_agg = 100.0;
438 }
439
440 if (mean > 999999.9) {
441 min /= 1000.0;
442 max /= 1000.0;
443 mean /= 1000.0;
444 dev /= 1000.0;
445 bw_str = "MB";
446 }
447
448 frame = gtk_frame_new("Bandwidth");
449 gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 5);
450
451 vbox = gtk_vbox_new(FALSE, 3);
452 gtk_container_add(GTK_CONTAINER(frame), vbox);
453
454 label = new_info_label_in_frame(vbox, "Bandwidth");
455 gtk_label_set_text(GTK_LABEL(label), bw_str);
456 label = new_info_label_in_frame(vbox, "Minimum");
457 label_set_int_value(label, min);
458 label = new_info_label_in_frame(vbox, "Maximum");
459 label_set_int_value(label, max);
460 label = new_info_label_in_frame(vbox, "Percentage of jobs");
461 sprintf(tmp, "%3.2f%%", p_of_agg);
462 gtk_label_set_text(GTK_LABEL(label), tmp);
463 label = new_info_label_in_frame(vbox, "Average");
464 sprintf(tmp, "%5.02f", mean);
465 gtk_label_set_text(GTK_LABEL(label), tmp);
466 label = new_info_label_in_frame(vbox, "Standard deviation");
467 sprintf(tmp, "%5.02f", dev);
468 gtk_label_set_text(GTK_LABEL(label), tmp);
469 }
470
Jens Axboe3650a3c2012-03-05 14:09:03 +0100471 free(io_p);
472 free(bw_p);
473 free(iops_p);
474}
475
476static void gfio_display_ts(struct fio_client *client, struct thread_stat *ts,
477 struct group_run_stats *rs)
478{
479 GtkWidget *dialog, *box, *vbox, *entry, *content;
480 struct gui *ui = client->client_data;
481
482 gdk_threads_enter();
483
484 dialog = gtk_dialog_new_with_buttons("Results", GTK_WINDOW(ui->window),
485 GTK_DIALOG_DESTROY_WITH_PARENT,
486 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL);
487
488 g_signal_connect_swapped(dialog, "response",
489 G_CALLBACK(gtk_widget_destroy),
490 dialog);
491
492 content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
493
494 vbox = gtk_vbox_new(FALSE, 3);
495 gtk_container_add(GTK_CONTAINER(content), vbox);
496
497 box = gtk_hbox_new(TRUE, 3);
498 gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
499
500 entry = new_info_entry_in_frame(box, "Name");
501 gtk_entry_set_text(GTK_ENTRY(entry), ts->name);
502 if (strlen(ts->description)) {
503 entry = new_info_entry_in_frame(box, "Description");
504 gtk_entry_set_text(GTK_ENTRY(entry), ts->description);
505 }
506 entry = new_info_entry_in_frame(box, "Group ID");
507 entry_set_int_value(entry, ts->groupid);
508 entry = new_info_entry_in_frame(box, "Jobs");
509 entry_set_int_value(entry, ts->members);
510 entry = new_info_entry_in_frame(box, "Error");
511 entry_set_int_value(entry, ts->error);
512 entry = new_info_entry_in_frame(box, "PID");
513 entry_set_int_value(entry, ts->pid);
514
515 if (ts->io_bytes[DDIR_READ])
516 gfio_show_ddir_status(vbox, rs, ts, DDIR_READ);
517 if (ts->io_bytes[DDIR_WRITE])
518 gfio_show_ddir_status(vbox, rs, ts, DDIR_WRITE);
519
520 gtk_widget_show_all(dialog);
521
522 gdk_threads_leave();
523}
524
Jens Axboe084d1c62012-03-03 20:28:07 +0100525static void gfio_text_op(struct fio_client *client, struct fio_net_cmd *cmd)
Stephen M. Camerona1820202012-02-24 08:17:31 +0100526{
Jens Axboe807f9972012-03-02 10:25:24 +0100527#if 0
Stephen M. Cameron736f2df2012-02-24 08:17:32 +0100528 GtkTextBuffer *buffer;
529 GtkTextIter end;
530
531 buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(ui.textview));
532 gdk_threads_enter();
533 gtk_text_buffer_get_end_iter(buffer, &end);
534 gtk_text_buffer_insert(buffer, &end, buf, -1);
535 gdk_threads_leave();
536 gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(ui.textview),
537 &end, 0.0, FALSE, 0.0,0.0);
Jens Axboe807f9972012-03-02 10:25:24 +0100538#else
Jens Axboe084d1c62012-03-03 20:28:07 +0100539 fio_client_ops.text_op(client, cmd);
Jens Axboe807f9972012-03-02 10:25:24 +0100540#endif
Stephen M. Camerona1820202012-02-24 08:17:31 +0100541}
542
543static void gfio_disk_util_op(struct fio_client *client, struct fio_net_cmd *cmd)
544{
545 printf("gfio_disk_util_op called\n");
546 fio_client_ops.disk_util(client, cmd);
547}
548
Jens Axboe3650a3c2012-03-05 14:09:03 +0100549extern int sum_stat_clients;
550extern struct thread_stat client_ts;
551extern struct group_run_stats client_gs;
552
553static int sum_stat_nr;
554
Jens Axboe89e5fad2012-03-05 09:21:12 +0100555static void gfio_thread_status_op(struct fio_client *client,
556 struct fio_net_cmd *cmd)
Stephen M. Camerona1820202012-02-24 08:17:31 +0100557{
Jens Axboe3650a3c2012-03-05 14:09:03 +0100558 struct cmd_ts_pdu *p = (struct cmd_ts_pdu *) cmd->payload;
559
560 gfio_display_ts(client, &p->ts, &p->rs);
561
562 if (sum_stat_clients == 1)
563 return;
564
565 sum_thread_stats(&client_ts, &p->ts, sum_stat_nr);
566 sum_group_stats(&client_gs, &p->rs);
567
568 client_ts.members++;
569 client_ts.groupid = p->ts.groupid;
570
571 if (++sum_stat_nr == sum_stat_clients) {
572 strcpy(client_ts.name, "All clients");
573 gfio_display_ts(client, &client_ts, &client_gs);
574 }
Stephen M. Camerona1820202012-02-24 08:17:31 +0100575}
576
Jens Axboe89e5fad2012-03-05 09:21:12 +0100577static void gfio_group_stats_op(struct fio_client *client,
578 struct fio_net_cmd *cmd)
Stephen M. Camerona1820202012-02-24 08:17:31 +0100579{
580 printf("gfio_group_stats_op called\n");
Jens Axboe89e5fad2012-03-05 09:21:12 +0100581 fio_client_ops.group_stats(client, cmd);
Stephen M. Camerona1820202012-02-24 08:17:31 +0100582}
583
Jens Axboe3e47bd22012-02-29 13:45:02 +0100584static void gfio_update_eta(struct jobs_eta *je)
585{
586 static int eta_good;
587 char eta_str[128];
588 char output[256];
589 char tmp[32];
590 double perc = 0.0;
591 int i2p = 0;
592
593 eta_str[0] = '\0';
594 output[0] = '\0';
595
596 if (je->eta_sec != INT_MAX && je->elapsed_sec) {
597 perc = (double) je->elapsed_sec / (double) (je->elapsed_sec + je->eta_sec);
598 eta_to_str(eta_str, je->eta_sec);
599 }
600
601 sprintf(tmp, "%u", je->nr_running);
Jens Axboeca850992012-03-05 20:04:43 +0100602 gtk_entry_set_text(GTK_ENTRY(ui.eta.jobs), tmp);
Jens Axboe3e47bd22012-02-29 13:45:02 +0100603 sprintf(tmp, "%u", je->files_open);
Jens Axboeca850992012-03-05 20:04:43 +0100604 gtk_entry_set_text(GTK_ENTRY(ui.eta.files), tmp);
Jens Axboe3e47bd22012-02-29 13:45:02 +0100605
606#if 0
607 if (je->m_rate[0] || je->m_rate[1] || je->t_rate[0] || je->t_rate[1]) {
608 if (je->m_rate || je->t_rate) {
609 char *tr, *mr;
610
611 mr = num2str(je->m_rate, 4, 0, i2p);
612 tr = num2str(je->t_rate, 4, 0, i2p);
Jens Axboeca850992012-03-05 20:04:43 +0100613 gtk_entry_set_text(GTK_ENTRY(ui.eta);
Jens Axboe3e47bd22012-02-29 13:45:02 +0100614 p += sprintf(p, ", CR=%s/%s KB/s", tr, mr);
615 free(tr);
616 free(mr);
617 } else if (je->m_iops || je->t_iops)
618 p += sprintf(p, ", CR=%d/%d IOPS", je->t_iops, je->m_iops);
Jens Axboeebbd89c2012-03-02 11:21:13 +0100619
Jens Axboeca850992012-03-05 20:04:43 +0100620 gtk_entry_set_text(GTK_ENTRY(ui.eta.cr_bw), "---");
621 gtk_entry_set_text(GTK_ENTRY(ui.eta.cr_iops), "---");
622 gtk_entry_set_text(GTK_ENTRY(ui.eta.cw_bw), "---");
623 gtk_entry_set_text(GTK_ENTRY(ui.eta.cw_iops), "---");
Jens Axboe3e47bd22012-02-29 13:45:02 +0100624#endif
625
626 if (je->eta_sec != INT_MAX && je->nr_running) {
627 char *iops_str[2];
628 char *rate_str[2];
629
630 if ((!je->eta_sec && !eta_good) || je->nr_ramp == je->nr_running)
631 strcpy(output, "-.-% done");
632 else {
633 eta_good = 1;
634 perc *= 100.0;
635 sprintf(output, "%3.1f%% done", perc);
636 }
637
638 rate_str[0] = num2str(je->rate[0], 5, 10, i2p);
639 rate_str[1] = num2str(je->rate[1], 5, 10, i2p);
640
641 iops_str[0] = num2str(je->iops[0], 4, 1, 0);
642 iops_str[1] = num2str(je->iops[1], 4, 1, 0);
643
Jens Axboeca850992012-03-05 20:04:43 +0100644 gtk_entry_set_text(GTK_ENTRY(ui.eta.read_bw), rate_str[0]);
645 gtk_entry_set_text(GTK_ENTRY(ui.eta.read_iops), iops_str[0]);
646 gtk_entry_set_text(GTK_ENTRY(ui.eta.write_bw), rate_str[1]);
647 gtk_entry_set_text(GTK_ENTRY(ui.eta.write_iops), iops_str[1]);
Jens Axboe3e47bd22012-02-29 13:45:02 +0100648
649 free(rate_str[0]);
650 free(rate_str[1]);
651 free(iops_str[0]);
652 free(iops_str[1]);
653 }
654
655 if (eta_str[0]) {
656 char *dst = output + strlen(output);
657
658 sprintf(dst, " - %s", eta_str);
659 }
660
661 gfio_update_thread_status(output, perc);
662}
663
Stephen M. Camerona1820202012-02-24 08:17:31 +0100664static void gfio_probe_op(struct fio_client *client, struct fio_net_cmd *cmd)
665{
Jens Axboe843ad232012-02-29 11:44:53 +0100666 struct cmd_probe_pdu *probe = (struct cmd_probe_pdu *) cmd->payload;
667 const char *os, *arch;
668 char buf[64];
669
670 os = fio_get_os_string(probe->os);
671 if (!os)
672 os = "unknown";
673
674 arch = fio_get_arch_string(probe->arch);
675 if (!arch)
676 os = "unknown";
677
678 if (!client->name)
679 client->name = strdup((char *) probe->hostname);
680
681 gtk_label_set_text(GTK_LABEL(ui.probe.hostname), (char *) probe->hostname);
682 gtk_label_set_text(GTK_LABEL(ui.probe.os), os);
683 gtk_label_set_text(GTK_LABEL(ui.probe.arch), arch);
684 sprintf(buf, "%u.%u.%u", probe->fio_major, probe->fio_minor, probe->fio_patch);
685 gtk_label_set_text(GTK_LABEL(ui.probe.fio_ver), buf);
Stephen M. Camerona1820202012-02-24 08:17:31 +0100686}
687
Stephen M. Cameron04cc6b72012-02-24 08:17:31 +0100688static void gfio_update_thread_status(char *status_message, double perc)
Stephen M. Cameron5b7573a2012-02-24 08:17:31 +0100689{
690 static char message[100];
691 const char *m = message;
692
693 strncpy(message, status_message, sizeof(message) - 1);
Stephen M. Cameron04cc6b72012-02-24 08:17:31 +0100694 gtk_progress_bar_set_text(
695 GTK_PROGRESS_BAR(ui.thread_status_pb), m);
696 gtk_progress_bar_set_fraction(
697 GTK_PROGRESS_BAR(ui.thread_status_pb), perc / 100.0);
Stephen M. Cameron5b7573a2012-02-24 08:17:31 +0100698 gdk_threads_enter();
699 gtk_widget_queue_draw(ui.window);
700 gdk_threads_leave();
701}
702
Jens Axboe3ec62ec2012-03-01 12:01:29 +0100703static void gfio_quit_op(struct fio_client *client)
704{
705 struct gui *ui = client->client_data;
706
707 gfio_set_connected(ui, 0);
708}
709
Jens Axboe807f9972012-03-02 10:25:24 +0100710static void gfio_add_job_op(struct fio_client *client, struct fio_net_cmd *cmd)
711{
712 struct cmd_add_job_pdu *p = (struct cmd_add_job_pdu *) cmd->payload;
713 struct gui *ui = client->client_data;
714 char tmp[8];
715 int i;
716
717 p->iodepth = le32_to_cpu(p->iodepth);
718 p->rw = le32_to_cpu(p->rw);
719
720 for (i = 0; i < 2; i++) {
721 p->min_bs[i] = le32_to_cpu(p->min_bs[i]);
722 p->max_bs[i] = le32_to_cpu(p->max_bs[i]);
723 }
724
725 p->numjobs = le32_to_cpu(p->numjobs);
726 p->group_reporting = le32_to_cpu(p->group_reporting);
727
Jens Axboeca850992012-03-05 20:04:43 +0100728 gtk_entry_set_text(GTK_ENTRY(ui->eta.name), (gchar *) p->jobname);
729 gtk_entry_set_text(GTK_ENTRY(ui->eta.iotype), ddir_str(p->rw));
730 gtk_entry_set_text(GTK_ENTRY(ui->eta.ioengine), (gchar *) p->ioengine);
Jens Axboe807f9972012-03-02 10:25:24 +0100731
732 sprintf(tmp, "%u", p->iodepth);
Jens Axboeca850992012-03-05 20:04:43 +0100733 gtk_entry_set_text(GTK_ENTRY(ui->eta.iodepth), tmp);
Jens Axboe807f9972012-03-02 10:25:24 +0100734}
735
Jens Axboeed727a42012-03-02 12:14:40 +0100736static void gfio_client_timed_out(struct fio_client *client)
737{
738 struct gui *ui = client->client_data;
739 GtkWidget *dialog, *label, *content;
740 char buf[256];
741
742 gdk_threads_enter();
743
744 gfio_set_connected(ui, 0);
Jens Axboe88432652012-03-02 19:09:31 +0100745 clear_ui_info(ui);
Jens Axboeed727a42012-03-02 12:14:40 +0100746
747 sprintf(buf, "Client %s: timeout talking to server.\n", client->hostname);
748
749 dialog = gtk_dialog_new_with_buttons("Timed out!",
750 GTK_WINDOW(ui->window),
751 GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
752 GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
753
754 content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
755 label = gtk_label_new((const gchar *) buf);
756 gtk_container_add(GTK_CONTAINER(content), label);
757 gtk_widget_show_all(dialog);
758 gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
759
760 gtk_dialog_run(GTK_DIALOG(dialog));
761 gtk_widget_destroy(dialog);
762
763 gdk_threads_leave();
764}
765
Stephen M. Camerona1820202012-02-24 08:17:31 +0100766struct client_ops gfio_client_ops = {
Jens Axboe0420ba62012-02-29 11:16:52 +0100767 .text_op = gfio_text_op,
768 .disk_util = gfio_disk_util_op,
769 .thread_status = gfio_thread_status_op,
770 .group_stats = gfio_group_stats_op,
Jens Axboea5276612012-03-04 15:15:08 +0100771 .eta = gfio_update_eta,
Jens Axboe0420ba62012-02-29 11:16:52 +0100772 .probe = gfio_probe_op,
Jens Axboe3ec62ec2012-03-01 12:01:29 +0100773 .quit = gfio_quit_op,
Jens Axboe807f9972012-03-02 10:25:24 +0100774 .add_job = gfio_add_job_op,
Jens Axboeed727a42012-03-02 12:14:40 +0100775 .timed_out = gfio_client_timed_out,
Jens Axboe3ec62ec2012-03-01 12:01:29 +0100776 .stay_connected = 1,
Stephen M. Camerona1820202012-02-24 08:17:31 +0100777};
778
Stephen M. Cameronff1f3282012-02-24 08:17:30 +0100779static void quit_clicked(__attribute__((unused)) GtkWidget *widget,
780 __attribute__((unused)) gpointer data)
781{
782 gtk_main_quit();
783}
784
Stephen M. Cameron25927252012-02-24 08:17:31 +0100785static void *job_thread(void *arg)
Stephen M. Cameronf3074002012-02-24 08:17:30 +0100786{
Stephen M. Cameron25927252012-02-24 08:17:31 +0100787 fio_handle_clients(&gfio_client_ops);
Stephen M. Cameron25927252012-02-24 08:17:31 +0100788 return NULL;
789}
790
Jens Axboe0420ba62012-02-29 11:16:52 +0100791static int send_job_files(struct gui *ui)
Stephen M. Cameron60f6b332012-02-24 08:17:32 +0100792{
Jens Axboe441013b2012-03-01 08:01:52 +0100793 int i, ret = 0;
Stephen M. Cameron60f6b332012-02-24 08:17:32 +0100794
Jens Axboe0420ba62012-02-29 11:16:52 +0100795 for (i = 0; i < ui->nr_job_files; i++) {
796 ret = fio_clients_send_ini(ui->job_files[i]);
Jens Axboe441013b2012-03-01 08:01:52 +0100797 if (ret)
798 break;
799
Jens Axboe0420ba62012-02-29 11:16:52 +0100800 free(ui->job_files[i]);
801 ui->job_files[i] = NULL;
Jens Axboe441013b2012-03-01 08:01:52 +0100802 }
803 while (i < ui->nr_job_files) {
804 free(ui->job_files[i]);
805 ui->job_files[i] = NULL;
806 i++;
Jens Axboe0420ba62012-02-29 11:16:52 +0100807 }
808
Jens Axboe441013b2012-03-01 08:01:52 +0100809 return ret;
Stephen M. Cameron60f6b332012-02-24 08:17:32 +0100810}
811
Jens Axboe3ec62ec2012-03-01 12:01:29 +0100812static void start_job_thread(struct gui *ui)
Stephen M. Cameron25927252012-02-24 08:17:31 +0100813{
Jens Axboe0420ba62012-02-29 11:16:52 +0100814 if (send_job_files(ui)) {
Stephen M. Cameron60f6b332012-02-24 08:17:32 +0100815 printf("Yeah, I didn't really like those options too much.\n");
Stephen M. Cameron60f6b332012-02-24 08:17:32 +0100816 gtk_widget_set_sensitive(ui->button[START_JOB_BUTTON], 1);
817 return;
818 }
Stephen M. Cameron25927252012-02-24 08:17:31 +0100819}
820
821static void start_job_clicked(__attribute__((unused)) GtkWidget *widget,
822 gpointer data)
823{
824 struct gui *ui = data;
825
Stephen M. Cameron25927252012-02-24 08:17:31 +0100826 gtk_widget_set_sensitive(ui->button[START_JOB_BUTTON], 0);
Jens Axboe3ec62ec2012-03-01 12:01:29 +0100827 start_job_thread(ui);
Stephen M. Cameronf3074002012-02-24 08:17:30 +0100828}
829
Jens Axboedf06f222012-03-02 13:32:04 +0100830static void file_open(GtkWidget *w, gpointer data);
831
832static void connect_clicked(GtkWidget *widget, gpointer data)
Jens Axboe3e47bd22012-02-29 13:45:02 +0100833{
Jens Axboe3ec62ec2012-03-01 12:01:29 +0100834 struct gui *ui = data;
835
836 if (!ui->connected) {
Jens Axboedf06f222012-03-02 13:32:04 +0100837 if (!ui->nr_job_files)
838 file_open(widget, data);
Jens Axboe8663ea62012-03-02 14:04:30 +0100839 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No jobs running");
Jens Axboe3ec62ec2012-03-01 12:01:29 +0100840 fio_clients_connect();
841 pthread_create(&ui->t, NULL, job_thread, NULL);
842 gfio_set_connected(ui, 1);
Jens Axboedf06f222012-03-02 13:32:04 +0100843 } else {
844 fio_clients_terminate();
Jens Axboe3ec62ec2012-03-01 12:01:29 +0100845 gfio_set_connected(ui, 0);
Jens Axboe88432652012-03-02 19:09:31 +0100846 clear_ui_info(ui);
Jens Axboedf06f222012-03-02 13:32:04 +0100847 }
Jens Axboe3e47bd22012-02-29 13:45:02 +0100848}
849
Stephen M. Cameronf3074002012-02-24 08:17:30 +0100850static void add_button(struct gui *ui, int i, GtkWidget *buttonbox,
851 struct button_spec *buttonspec)
852{
853 ui->button[i] = gtk_button_new_with_label(buttonspec->buttontext);
854 g_signal_connect(ui->button[i], "clicked", G_CALLBACK (buttonspec->f), ui);
Jens Axboe3ec62ec2012-03-01 12:01:29 +0100855 gtk_box_pack_start(GTK_BOX (ui->buttonbox), ui->button[i], FALSE, FALSE, 3);
Stephen M. Cameronf3074002012-02-24 08:17:30 +0100856 gtk_widget_set_tooltip_text(ui->button[i], buttonspeclist[i].tooltiptext);
Jens Axboe3e47bd22012-02-29 13:45:02 +0100857 gtk_widget_set_sensitive(ui->button[i], !buttonspec->start_insensitive);
Stephen M. Cameronf3074002012-02-24 08:17:30 +0100858}
859
860static void add_buttons(struct gui *ui,
861 struct button_spec *buttonlist,
862 int nbuttons)
863{
864 int i;
865
Stephen M. Cameronf3074002012-02-24 08:17:30 +0100866 for (i = 0; i < nbuttons; i++)
867 add_button(ui, i, ui->buttonbox, &buttonlist[i]);
868}
869
Jens Axboe0420ba62012-02-29 11:16:52 +0100870static void on_info_bar_response(GtkWidget *widget, gint response,
871 gpointer data)
872{
873 if (response == GTK_RESPONSE_OK) {
874 gtk_widget_destroy(widget);
875 ui.error_info_bar = NULL;
876 }
877}
878
Jens Axboedf06f222012-03-02 13:32:04 +0100879void report_error(GError *error)
Jens Axboe0420ba62012-02-29 11:16:52 +0100880{
881 if (ui.error_info_bar == NULL) {
882 ui.error_info_bar = gtk_info_bar_new_with_buttons(GTK_STOCK_OK,
883 GTK_RESPONSE_OK,
884 NULL);
885 g_signal_connect(ui.error_info_bar, "response", G_CALLBACK(on_info_bar_response), NULL);
886 gtk_info_bar_set_message_type(GTK_INFO_BAR(ui.error_info_bar),
887 GTK_MESSAGE_ERROR);
888
889 ui.error_label = gtk_label_new(error->message);
890 GtkWidget *container = gtk_info_bar_get_content_area(GTK_INFO_BAR(ui.error_info_bar));
891 gtk_container_add(GTK_CONTAINER(container), ui.error_label);
892
893 gtk_box_pack_start(GTK_BOX(ui.vbox), ui.error_info_bar, FALSE, FALSE, 0);
894 gtk_widget_show_all(ui.vbox);
895 } else {
896 char buffer[256];
897 snprintf(buffer, sizeof(buffer), "Failed to open file.");
898 gtk_label_set(GTK_LABEL(ui.error_label), buffer);
899 }
900}
901
Jens Axboeb9f3c7e2012-03-02 14:27:17 +0100902static int get_connection_details(char **host, int *port, int *type,
903 int *server_start)
Jens Axboea7a42ce2012-03-02 13:12:04 +0100904{
905 GtkWidget *dialog, *box, *vbox, *hentry, *hbox, *frame, *pentry, *combo;
Jens Axboeb9f3c7e2012-03-02 14:27:17 +0100906 GtkWidget *button;
Jens Axboea7a42ce2012-03-02 13:12:04 +0100907 char *typeentry;
908
909 dialog = gtk_dialog_new_with_buttons("Connection details",
910 GTK_WINDOW(ui.window),
911 GTK_DIALOG_DESTROY_WITH_PARENT,
912 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
913 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL);
914
915 frame = gtk_frame_new("Hostname / socket name");
916 vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
917 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
918
919 box = gtk_vbox_new(FALSE, 6);
920 gtk_container_add(GTK_CONTAINER(frame), box);
921
922 hbox = gtk_hbox_new(TRUE, 10);
923 gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
924 hentry = gtk_entry_new();
925 gtk_entry_set_text(GTK_ENTRY(hentry), "localhost");
926 gtk_box_pack_start(GTK_BOX(hbox), hentry, TRUE, TRUE, 0);
927
928 frame = gtk_frame_new("Port");
929 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
930 box = gtk_vbox_new(FALSE, 10);
931 gtk_container_add(GTK_CONTAINER(frame), box);
932
933 hbox = gtk_hbox_new(TRUE, 4);
934 gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
935 pentry = create_spinbutton(hbox, 1, 65535, FIO_NET_PORT);
936
937 frame = gtk_frame_new("Type");
938 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
939 box = gtk_vbox_new(FALSE, 10);
940 gtk_container_add(GTK_CONTAINER(frame), box);
941
942 hbox = gtk_hbox_new(TRUE, 4);
943 gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
944
945 combo = gtk_combo_box_text_new();
946 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), "IPv4");
947 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), "IPv6");
948 gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), "local socket");
949 gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
950
951 gtk_container_add(GTK_CONTAINER(hbox), combo);
952
Jens Axboeb9f3c7e2012-03-02 14:27:17 +0100953 frame = gtk_frame_new("Options");
954 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
955 box = gtk_vbox_new(FALSE, 10);
956 gtk_container_add(GTK_CONTAINER(frame), box);
957
958 hbox = gtk_hbox_new(TRUE, 4);
959 gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
960
961 button = gtk_check_button_new_with_label("Auto-spawn fio backend");
962 gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), 1);
963 gtk_widget_set_tooltip_text(button, "When running fio locally, it is necessary to have the backend running on the same system. If this is checked, gfio will start the backend automatically for you if it isn't already running.");
964 gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 6);
965
Jens Axboea7a42ce2012-03-02 13:12:04 +0100966 gtk_widget_show_all(dialog);
967
968 if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
969 gtk_widget_destroy(dialog);
970 return 1;
971 }
972
973 *host = strdup(gtk_entry_get_text(GTK_ENTRY(hentry)));
974 *port = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(pentry));
975
976 typeentry = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(combo));
977 if (!typeentry || !strncmp(typeentry, "IPv4", 4))
978 *type = Fio_client_ipv4;
979 else if (!strncmp(typeentry, "IPv6", 4))
980 *type = Fio_client_ipv6;
981 else
982 *type = Fio_client_socket;
983 g_free(typeentry);
984
Jens Axboeb9f3c7e2012-03-02 14:27:17 +0100985 *server_start = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button));
986
Jens Axboea7a42ce2012-03-02 13:12:04 +0100987 gtk_widget_destroy(dialog);
988 return 0;
989}
990
Jens Axboe0420ba62012-02-29 11:16:52 +0100991static void file_open(GtkWidget *w, gpointer data)
992{
993 GtkWidget *dialog;
994 GSList *filenames, *fn_glist;
995 GtkFileFilter *filter;
Jens Axboea7a42ce2012-03-02 13:12:04 +0100996 char *host;
Jens Axboeb9f3c7e2012-03-02 14:27:17 +0100997 int port, type, server_start;
Jens Axboe0420ba62012-02-29 11:16:52 +0100998
999 dialog = gtk_file_chooser_dialog_new("Open File",
1000 GTK_WINDOW(ui.window),
1001 GTK_FILE_CHOOSER_ACTION_OPEN,
1002 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1003 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
1004 NULL);
1005 gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);
1006
1007 filter = gtk_file_filter_new();
1008 gtk_file_filter_add_pattern(filter, "*.fio");
1009 gtk_file_filter_add_pattern(filter, "*.job");
1010 gtk_file_filter_add_mime_type(filter, "text/fio");
1011 gtk_file_filter_set_name(filter, "Fio job file");
1012 gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter);
1013
1014 if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
1015 gtk_widget_destroy(dialog);
1016 return;
1017 }
1018
1019 fn_glist = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
Jens Axboea7a42ce2012-03-02 13:12:04 +01001020
1021 gtk_widget_destroy(dialog);
1022
Jens Axboeb9f3c7e2012-03-02 14:27:17 +01001023 if (get_connection_details(&host, &port, &type, &server_start))
Jens Axboea7a42ce2012-03-02 13:12:04 +01001024 goto err;
1025
Jens Axboe0420ba62012-02-29 11:16:52 +01001026 filenames = fn_glist;
1027 while (filenames != NULL) {
Jens Axboe0420ba62012-02-29 11:16:52 +01001028 ui.job_files = realloc(ui.job_files, (ui.nr_job_files + 1) * sizeof(char *));
1029 ui.job_files[ui.nr_job_files] = strdup(filenames->data);
1030 ui.nr_job_files++;
1031
Jens Axboea5276612012-03-04 15:15:08 +01001032 ui.client = fio_client_add_explicit(&gfio_client_ops, host, type, port);
Jens Axboedf06f222012-03-02 13:32:04 +01001033 if (!ui.client) {
1034 GError *error;
1035
1036 error = g_error_new(g_quark_from_string("fio"), 1,
1037 "Failed to add client %s", host);
Jens Axboe0420ba62012-02-29 11:16:52 +01001038 report_error(error);
1039 g_error_free(error);
Jens Axboe0420ba62012-02-29 11:16:52 +01001040 }
Jens Axboedf06f222012-03-02 13:32:04 +01001041 ui.client->client_data = &ui;
Jens Axboe0420ba62012-02-29 11:16:52 +01001042
1043 g_free(filenames->data);
1044 filenames = g_slist_next(filenames);
1045 }
Jens Axboea7a42ce2012-03-02 13:12:04 +01001046 free(host);
1047err:
Jens Axboe0420ba62012-02-29 11:16:52 +01001048 g_slist_free(fn_glist);
Jens Axboe0420ba62012-02-29 11:16:52 +01001049}
1050
1051static void file_save(GtkWidget *w, gpointer data)
1052{
1053 GtkWidget *dialog;
1054
1055 dialog = gtk_file_chooser_dialog_new("Save File",
1056 GTK_WINDOW(ui.window),
1057 GTK_FILE_CHOOSER_ACTION_SAVE,
1058 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1059 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
1060 NULL);
1061
1062 gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
1063 gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "Untitled document");
1064
1065 if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
1066 char *filename;
1067
1068 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
1069 // save_job_file(filename);
1070 g_free(filename);
1071 }
1072 gtk_widget_destroy(dialog);
1073}
1074
Jens Axboe46974a72012-03-02 19:34:13 +01001075static void preferences(GtkWidget *w, gpointer data)
1076{
1077 GtkWidget *dialog, *frame, *box, **buttons;
1078 int i;
1079
1080 dialog = gtk_dialog_new_with_buttons("Preferences",
1081 GTK_WINDOW(ui.window),
1082 GTK_DIALOG_DESTROY_WITH_PARENT,
1083 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
1084 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
1085 NULL);
1086
Jens Axboe0b8d11e2012-03-02 19:44:15 +01001087 frame = gtk_frame_new("Debug logging");
Jens Axboe46974a72012-03-02 19:34:13 +01001088 gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5);
1089 box = gtk_hbox_new(FALSE, 6);
1090 gtk_container_add(GTK_CONTAINER(frame), box);
1091
1092 buttons = malloc(sizeof(GtkWidget *) * FD_DEBUG_MAX);
1093
1094 for (i = 0; i < FD_DEBUG_MAX; i++) {
1095 buttons[i] = gtk_check_button_new_with_label(debug_levels[i].name);
Jens Axboe0b8d11e2012-03-02 19:44:15 +01001096 gtk_widget_set_tooltip_text(buttons[i], debug_levels[i].help);
Jens Axboe46974a72012-03-02 19:34:13 +01001097 gtk_box_pack_start(GTK_BOX(box), buttons[i], FALSE, FALSE, 6);
1098 }
1099
1100 gtk_widget_show_all(dialog);
1101
1102 if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
1103 gtk_widget_destroy(dialog);
1104 return;
1105 }
1106
1107 for (i = 0; i < FD_DEBUG_MAX; i++) {
1108 int set;
1109
1110 set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(buttons[i]));
1111 if (set)
1112 fio_debug |= (1UL << i);
1113 }
1114
1115 gtk_widget_destroy(dialog);
1116}
1117
Jens Axboe0420ba62012-02-29 11:16:52 +01001118static void about_dialog(GtkWidget *w, gpointer data)
1119{
1120 gtk_show_about_dialog(NULL,
1121 "program-name", "gfio",
1122 "comments", "Gtk2 UI for fio",
1123 "license", "GPLv2",
1124 "version", fio_version_string,
1125 "copyright", "Jens Axboe <axboe@kernel.dk> 2012",
1126 "logo-icon-name", "fio",
1127 /* Must be last: */
1128 NULL, NULL,
1129 NULL);
1130}
1131
1132static GtkActionEntry menu_items[] = {
Jens Axboe46974a72012-03-02 19:34:13 +01001133 { "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL},
1134 { "HelpMenuAction", GTK_STOCK_HELP, "Help", NULL, NULL, NULL},
1135 { "OpenFile", GTK_STOCK_OPEN, NULL, "<Control>O", NULL, G_CALLBACK(file_open) },
1136 { "SaveFile", GTK_STOCK_SAVE, NULL, "<Control>S", NULL, G_CALLBACK(file_save) },
1137 { "Preferences", GTK_STOCK_PREFERENCES, NULL, "<Control>p", NULL, G_CALLBACK(preferences) },
1138 { "Quit", GTK_STOCK_QUIT, NULL, "<Control>Q", NULL, G_CALLBACK(quit_clicked) },
1139 { "About", GTK_STOCK_ABOUT, NULL, NULL, NULL, G_CALLBACK(about_dialog) },
Jens Axboe0420ba62012-02-29 11:16:52 +01001140};
Jens Axboe3e47bd22012-02-29 13:45:02 +01001141static gint nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]);
Jens Axboe0420ba62012-02-29 11:16:52 +01001142
1143static const gchar *ui_string = " \
1144 <ui> \
1145 <menubar name=\"MainMenu\"> \
1146 <menu name=\"FileMenu\" action=\"FileMenuAction\"> \
1147 <menuitem name=\"Open\" action=\"OpenFile\" /> \
1148 <menuitem name=\"Save\" action=\"SaveFile\" /> \
1149 <separator name=\"Separator\"/> \
Jens Axboe46974a72012-03-02 19:34:13 +01001150 <menuitem name=\"Preferences\" action=\"Preferences\" /> \
1151 <separator name=\"Separator2\"/> \
Jens Axboe0420ba62012-02-29 11:16:52 +01001152 <menuitem name=\"Quit\" action=\"Quit\" /> \
1153 </menu> \
1154 <menu name=\"Help\" action=\"HelpMenuAction\"> \
1155 <menuitem name=\"About\" action=\"About\" /> \
1156 </menu> \
1157 </menubar> \
1158 </ui> \
1159";
1160
1161static GtkWidget *get_menubar_menu(GtkWidget *window, GtkUIManager *ui_manager)
1162{
1163 GtkActionGroup *action_group = gtk_action_group_new("Menu");
1164 GError *error = 0;
1165
1166 action_group = gtk_action_group_new("Menu");
1167 gtk_action_group_add_actions(action_group, menu_items, nmenu_items, 0);
1168
1169 gtk_ui_manager_insert_action_group(ui_manager, action_group, 0);
1170 gtk_ui_manager_add_ui_from_string(GTK_UI_MANAGER(ui_manager), ui_string, -1, &error);
1171
1172 gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(ui_manager));
1173 return gtk_ui_manager_get_widget(ui_manager, "/MainMenu");
1174}
1175
1176void gfio_ui_setup(GtkSettings *settings, GtkWidget *menubar,
1177 GtkWidget *vbox, GtkUIManager *ui_manager)
1178{
1179 gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);
1180}
1181
Stephen M. Cameronff1f3282012-02-24 08:17:30 +01001182static void init_ui(int *argc, char **argv[], struct gui *ui)
1183{
Jens Axboe0420ba62012-02-29 11:16:52 +01001184 GtkSettings *settings;
1185 GtkUIManager *uimanager;
Jens Axboe843ad232012-02-29 11:44:53 +01001186 GtkWidget *menu, *probe, *probe_frame, *probe_box;
Jens Axboe0420ba62012-02-29 11:16:52 +01001187
1188 memset(ui, 0, sizeof(*ui));
Stephen M. Cameron45032dd2012-02-24 08:17:31 +01001189
Stephen M. Cameron2839f0c2012-02-24 08:17:31 +01001190 /* Magical g*thread incantation, you just need this thread stuff.
Stephen M. Cameron04cc6b72012-02-24 08:17:31 +01001191 * Without it, the update that happens in gfio_update_thread_status
Stephen M. Cameron2839f0c2012-02-24 08:17:31 +01001192 * doesn't really happen in a timely fashion, you need expose events
1193 */
Jens Axboeed727a42012-03-02 12:14:40 +01001194 if (!g_thread_supported())
Stephen M. Cameron2839f0c2012-02-24 08:17:31 +01001195 g_thread_init(NULL);
1196 gdk_threads_init();
1197
Stephen M. Cameronff1f3282012-02-24 08:17:30 +01001198 gtk_init(argc, argv);
Jens Axboe0420ba62012-02-29 11:16:52 +01001199 settings = gtk_settings_get_default();
1200 gtk_settings_set_long_property(settings, "gtk_tooltip_timeout", 10, "gfio setting");
1201 g_type_init();
Stephen M. Cameronff1f3282012-02-24 08:17:30 +01001202
1203 ui->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1204 gtk_window_set_title(GTK_WINDOW(ui->window), "fio");
1205 gtk_window_set_default_size(GTK_WINDOW(ui->window), 700, 500);
1206
Jens Axboe0420ba62012-02-29 11:16:52 +01001207 g_signal_connect(ui->window, "delete-event", G_CALLBACK(quit_clicked), NULL);
1208 g_signal_connect(ui->window, "destroy", G_CALLBACK(quit_clicked), NULL);
Stephen M. Cameronff1f3282012-02-24 08:17:30 +01001209
Stephen M. Cameron5b7573a2012-02-24 08:17:31 +01001210 ui->vbox = gtk_vbox_new(FALSE, 0);
1211 gtk_container_add(GTK_CONTAINER (ui->window), ui->vbox);
Stephen M. Cameron04cc6b72012-02-24 08:17:31 +01001212
Jens Axboe0420ba62012-02-29 11:16:52 +01001213 uimanager = gtk_ui_manager_new();
1214 menu = get_menubar_menu(ui->window, uimanager);
1215 gfio_ui_setup(settings, menu, ui->vbox, uimanager);
1216
Stephen M. Cameron04cc6b72012-02-24 08:17:31 +01001217 /*
Stephen M. Cameronc36f98d2012-02-24 08:17:32 +01001218 * Set up alignments for widgets at the top of ui,
1219 * align top left, expand horizontally but not vertically
1220 */
1221 ui->topalign = gtk_alignment_new(0, 0, 1, 0);
Jens Axboe3ec62ec2012-03-01 12:01:29 +01001222 ui->topvbox = gtk_vbox_new(FALSE, 3);
Stephen M. Cameronc36f98d2012-02-24 08:17:32 +01001223 gtk_container_add(GTK_CONTAINER(ui->topalign), ui->topvbox);
Stephen M. Camerone1645342012-02-24 08:17:32 +01001224 gtk_box_pack_start(GTK_BOX(ui->vbox), ui->topalign, FALSE, FALSE, 0);
Stephen M. Cameronc36f98d2012-02-24 08:17:32 +01001225
Jens Axboe3e47bd22012-02-29 13:45:02 +01001226 probe = gtk_frame_new("Job");
Jens Axboe843ad232012-02-29 11:44:53 +01001227 gtk_box_pack_start(GTK_BOX(ui->topvbox), probe, TRUE, FALSE, 3);
1228 probe_frame = gtk_vbox_new(FALSE, 3);
1229 gtk_container_add(GTK_CONTAINER(probe), probe_frame);
1230
1231 probe_box = gtk_hbox_new(FALSE, 3);
1232 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
Jens Axboe843ad232012-02-29 11:44:53 +01001233 ui->probe.hostname = new_info_label_in_frame(probe_box, "Host");
1234 ui->probe.os = new_info_label_in_frame(probe_box, "OS");
1235 ui->probe.arch = new_info_label_in_frame(probe_box, "Architecture");
1236 ui->probe.fio_ver = new_info_label_in_frame(probe_box, "Fio version");
1237
Jens Axboe3e47bd22012-02-29 13:45:02 +01001238 probe_box = gtk_hbox_new(FALSE, 3);
1239 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
Jens Axboe807f9972012-03-02 10:25:24 +01001240
Jens Axboeca850992012-03-05 20:04:43 +01001241 ui->eta.name = new_info_entry_in_frame(probe_box, "Name");
1242 ui->eta.iotype = new_info_entry_in_frame(probe_box, "IO");
1243 ui->eta.ioengine = new_info_entry_in_frame(probe_box, "IO Engine");
1244 ui->eta.iodepth = new_info_entry_in_frame(probe_box, "IO Depth");
1245 ui->eta.jobs = new_info_entry_in_frame(probe_box, "Jobs");
1246 ui->eta.files = new_info_entry_in_frame(probe_box, "Open files");
Jens Axboe3e47bd22012-02-29 13:45:02 +01001247
1248 probe_box = gtk_hbox_new(FALSE, 3);
1249 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
Jens Axboeca850992012-03-05 20:04:43 +01001250 ui->eta.read_bw = new_info_entry_in_frame(probe_box, "Read BW");
1251 ui->eta.read_iops = new_info_entry_in_frame(probe_box, "IOPS");
1252 ui->eta.write_bw = new_info_entry_in_frame(probe_box, "Write BW");
1253 ui->eta.write_iops = new_info_entry_in_frame(probe_box, "IOPS");
Jens Axboe807f9972012-03-02 10:25:24 +01001254
1255 /*
1256 * Only add this if we have a commit rate
1257 */
1258#if 0
1259 probe_box = gtk_hbox_new(FALSE, 3);
1260 gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
1261
Jens Axboe3e47bd22012-02-29 13:45:02 +01001262 ui->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
1263 ui->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1264
Jens Axboe3e47bd22012-02-29 13:45:02 +01001265 ui->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
1266 ui->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
Jens Axboe807f9972012-03-02 10:25:24 +01001267#endif
Jens Axboe3e47bd22012-02-29 13:45:02 +01001268
Stephen M. Cameron45032dd2012-02-24 08:17:31 +01001269 /*
Stephen M. Cameron736f2df2012-02-24 08:17:32 +01001270 * Add a text box for text op messages
1271 */
1272 ui->textview = gtk_text_view_new();
1273 ui->text = gtk_text_view_get_buffer(GTK_TEXT_VIEW(ui->textview));
1274 gtk_text_buffer_set_text(ui->text, "", -1);
1275 gtk_text_view_set_editable(GTK_TEXT_VIEW(ui->textview), FALSE);
1276 gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(ui->textview), FALSE);
1277 ui->scrolled_window = gtk_scrolled_window_new(NULL, NULL);
1278 gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(ui->scrolled_window),
1279 GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1280 gtk_container_add(GTK_CONTAINER(ui->scrolled_window), ui->textview);
Stephen M. Camerone1645342012-02-24 08:17:32 +01001281 gtk_box_pack_start(GTK_BOX(ui->vbox), ui->scrolled_window,
1282 TRUE, TRUE, 0);
Stephen M. Cameron736f2df2012-02-24 08:17:32 +01001283
Stephen M. Cameronc36f98d2012-02-24 08:17:32 +01001284 /*
1285 * Set up alignments for widgets at the bottom of ui,
1286 * align bottom left, expand horizontally but not vertically
1287 */
1288 ui->bottomalign = gtk_alignment_new(0, 1, 1, 0);
1289 ui->buttonbox = gtk_hbox_new(FALSE, 0);
1290 gtk_container_add(GTK_CONTAINER(ui->bottomalign), ui->buttonbox);
Stephen M. Camerone1645342012-02-24 08:17:32 +01001291 gtk_box_pack_start(GTK_BOX(ui->vbox), ui->bottomalign,
1292 FALSE, FALSE, 0);
Stephen M. Cameronc36f98d2012-02-24 08:17:32 +01001293
Stephen M. Cameronf3074002012-02-24 08:17:30 +01001294 add_buttons(ui, buttonspeclist, ARRAYSIZE(buttonspeclist));
Jens Axboe3ec62ec2012-03-01 12:01:29 +01001295
1296 /*
1297 * Set up thread status progress bar
1298 */
1299 ui->thread_status_pb = gtk_progress_bar_new();
1300 gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->thread_status_pb), 0.0);
Jens Axboe8663ea62012-03-02 14:04:30 +01001301 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No connections");
Jens Axboe3ec62ec2012-03-01 12:01:29 +01001302 gtk_container_add(GTK_CONTAINER(ui->buttonbox), ui->thread_status_pb);
1303
1304
Stephen M. Cameronff1f3282012-02-24 08:17:30 +01001305 gtk_widget_show_all(ui->window);
1306}
1307
Stephen M. Cameron8232e282012-02-24 08:17:31 +01001308int main(int argc, char *argv[], char *envp[])
Stephen M. Cameronff1f3282012-02-24 08:17:30 +01001309{
Stephen M. Cameron8232e282012-02-24 08:17:31 +01001310 if (initialize_fio(envp))
1311 return 1;
Jens Axboe0420ba62012-02-29 11:16:52 +01001312 if (fio_init_options())
1313 return 1;
Stephen M. Camerona1820202012-02-24 08:17:31 +01001314
Stephen M. Cameronff1f3282012-02-24 08:17:30 +01001315 init_ui(&argc, &argv, &ui);
Stephen M. Cameron5b7573a2012-02-24 08:17:31 +01001316
Stephen M. Cameron2839f0c2012-02-24 08:17:31 +01001317 gdk_threads_enter();
Stephen M. Cameronff1f3282012-02-24 08:17:30 +01001318 gtk_main();
Stephen M. Cameron2839f0c2012-02-24 08:17:31 +01001319 gdk_threads_leave();
Stephen M. Cameronff1f3282012-02-24 08:17:30 +01001320 return 0;
1321}