gfio: Change some labels to entries and add bw stats to final output
[fio.git] / gfio.c
1 /*
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  */
23 #include <locale.h>
24 #include <malloc.h>
25
26 #include <glib.h>
27 #include <gtk/gtk.h>
28
29 #include "fio.h"
30
31 static void gfio_update_thread_status(char *status_message, double perc);
32
33 #define ARRAYSIZE(x) (sizeof((x)) / (sizeof((x)[0])))
34
35 typedef void (*clickfunction)(GtkWidget *widget, gpointer data);
36
37 static void connect_clicked(GtkWidget *widget, gpointer data);
38 static void start_job_clicked(GtkWidget *widget, gpointer data);
39
40 static struct button_spec {
41         const char *buttontext;
42         clickfunction f;
43         const char *tooltiptext;
44         const int start_insensitive;
45 } buttonspeclist[] = {
46 #define CONNECT_BUTTON 0
47 #define START_JOB_BUTTON 1
48         { "Connect", connect_clicked, "Connect to host", 0 },
49         { "Start Job",
50                 start_job_clicked,
51                 "Send current fio job to fio server to be executed", 1 },
52 };
53
54 struct probe_widget {
55         GtkWidget *hostname;
56         GtkWidget *os;
57         GtkWidget *arch;
58         GtkWidget *fio_ver;
59 };
60
61 struct eta_widget {
62         GtkWidget *name;
63         GtkWidget *iotype;
64         GtkWidget *ioengine;
65         GtkWidget *iodepth;
66         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
78 struct gui {
79         GtkWidget *window;
80         GtkWidget *vbox;
81         GtkWidget *topvbox;
82         GtkWidget *topalign;
83         GtkWidget *bottomalign;
84         GtkWidget *thread_status_pb;
85         GtkWidget *buttonbox;
86         GtkWidget *button[ARRAYSIZE(buttonspeclist)];
87         GtkWidget *scrolled_window;
88         GtkWidget *textview;
89         GtkWidget *error_info_bar;
90         GtkWidget *error_label;
91         GtkTextBuffer *text;
92         struct probe_widget probe;
93         struct eta_widget eta;
94         int connected;
95         pthread_t t;
96
97         struct fio_client *client;
98         int nr_job_files;
99         char **job_files;
100 } ui;
101
102 static 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), "");
108         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), "");
118 }
119
120 static 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();
126         gtk_entry_set_editable(GTK_ENTRY(entry), 0);
127         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
133 static 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
146 static 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
162 static 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
175 static 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
183 static 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
191 #define ALIGN_LEFT 1
192 #define ALIGN_RIGHT 2
193 #define INVISIBLE 4
194 #define UNSORTABLE 8
195
196 GtkTreeViewColumn *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
236 static 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
275 static 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);
312 out:
313         if (ovals)
314                 free(ovals);
315 }
316
317 static 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)";
321         GtkWidget *hbox, *label, *frame;
322         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
331         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
335         hbox = gtk_hbox_new(FALSE, 3);
336         gtk_container_add(GTK_CONTAINER(frame), hbox);
337
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
354 #define GFIO_CLAT       1
355 #define GFIO_SLAT       2
356 #define GFIO_LAT        4
357
358 static 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;
365         unsigned int flags = 0;
366         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
404         if (calc_lat(&ts->slat_stat[ddir], &min, &max, &mean, &dev))
405                 flags |= GFIO_SLAT;
406         if (calc_lat(&ts->clat_stat[ddir], &min, &max, &mean, &dev))
407                 flags |= GFIO_CLAT;
408         if (calc_lat(&ts->lat_stat[ddir], &min, &max, &mean, &dev))
409                 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
426         if (ts->clat_percentiles)
427                 gfio_show_clat_percentiles(vbox, ts, ddir);
428
429         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
471         free(io_p);
472         free(bw_p);
473         free(iops_p);
474 }
475
476 static 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
525 static void gfio_text_op(struct fio_client *client, struct fio_net_cmd *cmd)
526 {
527 #if 0
528         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);
538 #else
539         fio_client_ops.text_op(client, cmd);
540 #endif
541 }
542
543 static 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
549 extern int sum_stat_clients;
550 extern struct thread_stat client_ts;
551 extern struct group_run_stats client_gs;
552
553 static int sum_stat_nr;
554
555 static void gfio_thread_status_op(struct fio_client *client,
556                                   struct fio_net_cmd *cmd)
557 {
558         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         }
575 }
576
577 static void gfio_group_stats_op(struct fio_client *client,
578                                 struct fio_net_cmd *cmd)
579 {
580         printf("gfio_group_stats_op called\n");
581         fio_client_ops.group_stats(client, cmd);
582 }
583
584 static 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);
602         gtk_entry_set_text(GTK_ENTRY(ui.eta.jobs), tmp);
603         sprintf(tmp, "%u", je->files_open);
604         gtk_entry_set_text(GTK_ENTRY(ui.eta.files), tmp);
605
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);
613                 gtk_entry_set_text(GTK_ENTRY(ui.eta);
614                 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);
619
620         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), "---");
624 #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
644                 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]);
648
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
664 static void gfio_probe_op(struct fio_client *client, struct fio_net_cmd *cmd)
665 {
666         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);
686 }
687
688 static void gfio_update_thread_status(char *status_message, double perc)
689 {
690         static char message[100];
691         const char *m = message;
692
693         strncpy(message, status_message, sizeof(message) - 1);
694         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);
698         gdk_threads_enter();
699         gtk_widget_queue_draw(ui.window);
700         gdk_threads_leave();
701 }
702
703 static 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
710 static 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
728         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);
731
732         sprintf(tmp, "%u", p->iodepth);
733         gtk_entry_set_text(GTK_ENTRY(ui->eta.iodepth), tmp);
734 }
735
736 static 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);
745         clear_ui_info(ui);
746
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
766 struct client_ops gfio_client_ops = {
767         .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,
771         .eta                    = gfio_update_eta,
772         .probe                  = gfio_probe_op,
773         .quit                   = gfio_quit_op,
774         .add_job                = gfio_add_job_op,
775         .timed_out              = gfio_client_timed_out,
776         .stay_connected         = 1,
777 };
778
779 static void quit_clicked(__attribute__((unused)) GtkWidget *widget,
780                 __attribute__((unused)) gpointer data)
781 {
782         gtk_main_quit();
783 }
784
785 static void *job_thread(void *arg)
786 {
787         fio_handle_clients(&gfio_client_ops);
788         return NULL;
789 }
790
791 static int send_job_files(struct gui *ui)
792 {
793         int i, ret = 0;
794
795         for (i = 0; i < ui->nr_job_files; i++) {
796                 ret = fio_clients_send_ini(ui->job_files[i]);
797                 if (ret)
798                         break;
799
800                 free(ui->job_files[i]);
801                 ui->job_files[i] = NULL;
802         }
803         while (i < ui->nr_job_files) {
804                 free(ui->job_files[i]);
805                 ui->job_files[i] = NULL;
806                 i++;
807         }
808
809         return ret;
810 }
811
812 static void start_job_thread(struct gui *ui)
813 {
814         if (send_job_files(ui)) {
815                 printf("Yeah, I didn't really like those options too much.\n");
816                 gtk_widget_set_sensitive(ui->button[START_JOB_BUTTON], 1);
817                 return;
818         }
819 }
820
821 static void start_job_clicked(__attribute__((unused)) GtkWidget *widget,
822                 gpointer data)
823 {
824         struct gui *ui = data;
825
826         gtk_widget_set_sensitive(ui->button[START_JOB_BUTTON], 0);
827         start_job_thread(ui);
828 }
829
830 static void file_open(GtkWidget *w, gpointer data);
831
832 static void connect_clicked(GtkWidget *widget, gpointer data)
833 {
834         struct gui *ui = data;
835
836         if (!ui->connected) {
837                 if (!ui->nr_job_files)
838                         file_open(widget, data);
839                 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No jobs running");
840                 fio_clients_connect();
841                 pthread_create(&ui->t, NULL, job_thread, NULL);
842                 gfio_set_connected(ui, 1);
843         } else {
844                 fio_clients_terminate();
845                 gfio_set_connected(ui, 0);
846                 clear_ui_info(ui);
847         }
848 }
849
850 static 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);
855         gtk_box_pack_start(GTK_BOX (ui->buttonbox), ui->button[i], FALSE, FALSE, 3);
856         gtk_widget_set_tooltip_text(ui->button[i], buttonspeclist[i].tooltiptext);
857         gtk_widget_set_sensitive(ui->button[i], !buttonspec->start_insensitive);
858 }
859
860 static void add_buttons(struct gui *ui,
861                                 struct button_spec *buttonlist,
862                                 int nbuttons)
863 {
864         int i;
865
866         for (i = 0; i < nbuttons; i++)
867                 add_button(ui, i, ui->buttonbox, &buttonlist[i]);
868 }
869
870 static 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
879 void report_error(GError *error)
880 {
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
902 static int get_connection_details(char **host, int *port, int *type,
903                                   int *server_start)
904 {
905         GtkWidget *dialog, *box, *vbox, *hentry, *hbox, *frame, *pentry, *combo;
906         GtkWidget *button;
907         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
953         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
966         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
985         *server_start = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button));
986
987         gtk_widget_destroy(dialog);
988         return 0;
989 }
990
991 static void file_open(GtkWidget *w, gpointer data)
992 {
993         GtkWidget *dialog;
994         GSList *filenames, *fn_glist;
995         GtkFileFilter *filter;
996         char *host;
997         int port, type, server_start;
998
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));
1020
1021         gtk_widget_destroy(dialog);
1022
1023         if (get_connection_details(&host, &port, &type, &server_start))
1024                 goto err;
1025
1026         filenames = fn_glist;
1027         while (filenames != NULL) {
1028                 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
1032                 ui.client = fio_client_add_explicit(&gfio_client_ops, host, type, port);
1033                 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);
1038                         report_error(error);
1039                         g_error_free(error);
1040                 }
1041                 ui.client->client_data = &ui;
1042                         
1043                 g_free(filenames->data);
1044                 filenames = g_slist_next(filenames);
1045         }
1046         free(host);
1047 err:
1048         g_slist_free(fn_glist);
1049 }
1050
1051 static 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
1075 static 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
1087         frame = gtk_frame_new("Debug logging");
1088         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);
1096                 gtk_widget_set_tooltip_text(buttons[i], debug_levels[i].help);
1097                 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
1118 static 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
1132 static GtkActionEntry menu_items[] = {
1133         { "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) },
1140 };
1141 static gint nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]);
1142
1143 static 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\"/> \
1150                                 <menuitem name=\"Preferences\" action=\"Preferences\" /> \
1151                                 <separator name=\"Separator2\"/> \
1152                                 <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
1161 static 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
1176 void 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
1182 static void init_ui(int *argc, char **argv[], struct gui *ui)
1183 {
1184         GtkSettings *settings;
1185         GtkUIManager *uimanager;
1186         GtkWidget *menu, *probe, *probe_frame, *probe_box;
1187
1188         memset(ui, 0, sizeof(*ui));
1189
1190         /* Magical g*thread incantation, you just need this thread stuff.
1191          * Without it, the update that happens in gfio_update_thread_status
1192          * doesn't really happen in a timely fashion, you need expose events
1193          */
1194         if (!g_thread_supported())
1195                 g_thread_init(NULL);
1196         gdk_threads_init();
1197
1198         gtk_init(argc, argv);
1199         settings = gtk_settings_get_default();
1200         gtk_settings_set_long_property(settings, "gtk_tooltip_timeout", 10, "gfio setting");
1201         g_type_init();
1202         
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
1207         g_signal_connect(ui->window, "delete-event", G_CALLBACK(quit_clicked), NULL);
1208         g_signal_connect(ui->window, "destroy", G_CALLBACK(quit_clicked), NULL);
1209
1210         ui->vbox = gtk_vbox_new(FALSE, 0);
1211         gtk_container_add(GTK_CONTAINER (ui->window), ui->vbox);
1212
1213         uimanager = gtk_ui_manager_new();
1214         menu = get_menubar_menu(ui->window, uimanager);
1215         gfio_ui_setup(settings, menu, ui->vbox, uimanager);
1216
1217         /*
1218          * 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);
1222         ui->topvbox = gtk_vbox_new(FALSE, 3);
1223         gtk_container_add(GTK_CONTAINER(ui->topalign), ui->topvbox);
1224         gtk_box_pack_start(GTK_BOX(ui->vbox), ui->topalign, FALSE, FALSE, 0);
1225
1226         probe = gtk_frame_new("Job");
1227         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);
1233         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
1238         probe_box = gtk_hbox_new(FALSE, 3);
1239         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
1240
1241         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");
1247
1248         probe_box = gtk_hbox_new(FALSE, 3);
1249         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
1250         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");
1254
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
1262         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
1265         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");
1267 #endif
1268
1269         /*
1270          * 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);
1281         gtk_box_pack_start(GTK_BOX(ui->vbox), ui->scrolled_window,
1282                         TRUE, TRUE, 0);
1283
1284         /*
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);
1291         gtk_box_pack_start(GTK_BOX(ui->vbox), ui->bottomalign,
1292                                         FALSE, FALSE, 0);
1293
1294         add_buttons(ui, buttonspeclist, ARRAYSIZE(buttonspeclist));
1295
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);
1301         gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No connections");
1302         gtk_container_add(GTK_CONTAINER(ui->buttonbox), ui->thread_status_pb);
1303
1304
1305         gtk_widget_show_all(ui->window);
1306 }
1307
1308 int main(int argc, char *argv[], char *envp[])
1309 {
1310         if (initialize_fio(envp))
1311                 return 1;
1312         if (fio_init_options())
1313                 return 1;
1314
1315         init_ui(&argc, &argv, &ui);
1316
1317         gdk_threads_enter();
1318         gtk_main();
1319         gdk_threads_leave();
1320         return 0;
1321 }