gfio: fixup bw display
[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, *main_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         main_vbox = gtk_vbox_new(FALSE, 3);
390         gtk_container_add(GTK_CONTAINER(frame), main_vbox);
391
392         box = gtk_hbox_new(FALSE, 3);
393         gtk_box_pack_start(GTK_BOX(main_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(main_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(main_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                 sprintf(tmp, "Bandwidth (%s)", bw_str);
449                 frame = gtk_frame_new(tmp);
450                 gtk_box_pack_start(GTK_BOX(main_vbox), frame, FALSE, FALSE, 5);
451
452                 box = gtk_hbox_new(FALSE, 3);
453                 gtk_container_add(GTK_CONTAINER(frame), box);
454
455                 label = new_info_label_in_frame(box, "Minimum");
456                 label_set_int_value(label, min);
457                 label = new_info_label_in_frame(box, "Maximum");
458                 label_set_int_value(label, max);
459                 label = new_info_label_in_frame(box, "Percentage of jobs");
460                 sprintf(tmp, "%3.2f%%", p_of_agg);
461                 gtk_label_set_text(GTK_LABEL(label), tmp);
462                 label = new_info_label_in_frame(box, "Average");
463                 sprintf(tmp, "%5.02f", mean);
464                 gtk_label_set_text(GTK_LABEL(label), tmp);
465                 label = new_info_label_in_frame(box, "Standard deviation");
466                 sprintf(tmp, "%5.02f", dev);
467                 gtk_label_set_text(GTK_LABEL(label), tmp);
468         }
469
470         free(io_p);
471         free(bw_p);
472         free(iops_p);
473 }
474
475 static void gfio_display_ts(struct fio_client *client, struct thread_stat *ts,
476                             struct group_run_stats *rs)
477 {
478         GtkWidget *dialog, *box, *vbox, *entry, *content;
479         struct gui *ui = client->client_data;
480
481         gdk_threads_enter();
482
483         dialog = gtk_dialog_new_with_buttons("Results", GTK_WINDOW(ui->window),
484                         GTK_DIALOG_DESTROY_WITH_PARENT,
485                         GTK_STOCK_OK, GTK_RESPONSE_ACCEPT, NULL);
486
487         g_signal_connect_swapped(dialog, "response",
488                              G_CALLBACK(gtk_widget_destroy),
489                              dialog);
490
491         content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
492
493         vbox = gtk_vbox_new(FALSE, 3);
494         gtk_container_add(GTK_CONTAINER(content), vbox);
495
496         box = gtk_hbox_new(TRUE, 3);
497         gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
498
499         entry = new_info_entry_in_frame(box, "Name");
500         gtk_entry_set_text(GTK_ENTRY(entry), ts->name);
501         if (strlen(ts->description)) {
502                 entry = new_info_entry_in_frame(box, "Description");
503                 gtk_entry_set_text(GTK_ENTRY(entry), ts->description);
504         }
505         entry = new_info_entry_in_frame(box, "Group ID");
506         entry_set_int_value(entry, ts->groupid);
507         entry = new_info_entry_in_frame(box, "Jobs");
508         entry_set_int_value(entry, ts->members);
509         entry = new_info_entry_in_frame(box, "Error");
510         entry_set_int_value(entry, ts->error);
511         entry = new_info_entry_in_frame(box, "PID");
512         entry_set_int_value(entry, ts->pid);
513
514         if (ts->io_bytes[DDIR_READ])
515                 gfio_show_ddir_status(vbox, rs, ts, DDIR_READ);
516         if (ts->io_bytes[DDIR_WRITE])
517                 gfio_show_ddir_status(vbox, rs, ts, DDIR_WRITE);
518
519         gtk_widget_show_all(dialog);
520
521         gdk_threads_leave();
522 }
523
524 static void gfio_text_op(struct fio_client *client, struct fio_net_cmd *cmd)
525 {
526 #if 0
527         GtkTextBuffer *buffer;
528         GtkTextIter end;
529
530         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(ui.textview));
531         gdk_threads_enter();
532         gtk_text_buffer_get_end_iter(buffer, &end);
533         gtk_text_buffer_insert(buffer, &end, buf, -1);
534         gdk_threads_leave();
535         gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(ui.textview),
536                                         &end, 0.0, FALSE, 0.0,0.0);
537 #else
538         fio_client_ops.text_op(client, cmd);
539 #endif
540 }
541
542 static void gfio_disk_util_op(struct fio_client *client, struct fio_net_cmd *cmd)
543 {
544         printf("gfio_disk_util_op called\n");
545         fio_client_ops.disk_util(client, cmd);
546 }
547
548 extern int sum_stat_clients;
549 extern struct thread_stat client_ts;
550 extern struct group_run_stats client_gs;
551
552 static int sum_stat_nr;
553
554 static void gfio_thread_status_op(struct fio_client *client,
555                                   struct fio_net_cmd *cmd)
556 {
557         struct cmd_ts_pdu *p = (struct cmd_ts_pdu *) cmd->payload;
558
559         gfio_display_ts(client, &p->ts, &p->rs);
560
561         if (sum_stat_clients == 1)
562                 return;
563
564         sum_thread_stats(&client_ts, &p->ts, sum_stat_nr);
565         sum_group_stats(&client_gs, &p->rs);
566
567         client_ts.members++;
568         client_ts.groupid = p->ts.groupid;
569
570         if (++sum_stat_nr == sum_stat_clients) {
571                 strcpy(client_ts.name, "All clients");
572                 gfio_display_ts(client, &client_ts, &client_gs);
573         }
574 }
575
576 static void gfio_group_stats_op(struct fio_client *client,
577                                 struct fio_net_cmd *cmd)
578 {
579         printf("gfio_group_stats_op called\n");
580         fio_client_ops.group_stats(client, cmd);
581 }
582
583 static void gfio_update_eta(struct jobs_eta *je)
584 {
585         static int eta_good;
586         char eta_str[128];
587         char output[256];
588         char tmp[32];
589         double perc = 0.0;
590         int i2p = 0;
591
592         eta_str[0] = '\0';
593         output[0] = '\0';
594
595         if (je->eta_sec != INT_MAX && je->elapsed_sec) {
596                 perc = (double) je->elapsed_sec / (double) (je->elapsed_sec + je->eta_sec);
597                 eta_to_str(eta_str, je->eta_sec);
598         }
599
600         sprintf(tmp, "%u", je->nr_running);
601         gtk_entry_set_text(GTK_ENTRY(ui.eta.jobs), tmp);
602         sprintf(tmp, "%u", je->files_open);
603         gtk_entry_set_text(GTK_ENTRY(ui.eta.files), tmp);
604
605 #if 0
606         if (je->m_rate[0] || je->m_rate[1] || je->t_rate[0] || je->t_rate[1]) {
607         if (je->m_rate || je->t_rate) {
608                 char *tr, *mr;
609
610                 mr = num2str(je->m_rate, 4, 0, i2p);
611                 tr = num2str(je->t_rate, 4, 0, i2p);
612                 gtk_entry_set_text(GTK_ENTRY(ui.eta);
613                 p += sprintf(p, ", CR=%s/%s KB/s", tr, mr);
614                 free(tr);
615                 free(mr);
616         } else if (je->m_iops || je->t_iops)
617                 p += sprintf(p, ", CR=%d/%d IOPS", je->t_iops, je->m_iops);
618
619         gtk_entry_set_text(GTK_ENTRY(ui.eta.cr_bw), "---");
620         gtk_entry_set_text(GTK_ENTRY(ui.eta.cr_iops), "---");
621         gtk_entry_set_text(GTK_ENTRY(ui.eta.cw_bw), "---");
622         gtk_entry_set_text(GTK_ENTRY(ui.eta.cw_iops), "---");
623 #endif
624
625         if (je->eta_sec != INT_MAX && je->nr_running) {
626                 char *iops_str[2];
627                 char *rate_str[2];
628
629                 if ((!je->eta_sec && !eta_good) || je->nr_ramp == je->nr_running)
630                         strcpy(output, "-.-% done");
631                 else {
632                         eta_good = 1;
633                         perc *= 100.0;
634                         sprintf(output, "%3.1f%% done", perc);
635                 }
636
637                 rate_str[0] = num2str(je->rate[0], 5, 10, i2p);
638                 rate_str[1] = num2str(je->rate[1], 5, 10, i2p);
639
640                 iops_str[0] = num2str(je->iops[0], 4, 1, 0);
641                 iops_str[1] = num2str(je->iops[1], 4, 1, 0);
642
643                 gtk_entry_set_text(GTK_ENTRY(ui.eta.read_bw), rate_str[0]);
644                 gtk_entry_set_text(GTK_ENTRY(ui.eta.read_iops), iops_str[0]);
645                 gtk_entry_set_text(GTK_ENTRY(ui.eta.write_bw), rate_str[1]);
646                 gtk_entry_set_text(GTK_ENTRY(ui.eta.write_iops), iops_str[1]);
647
648                 free(rate_str[0]);
649                 free(rate_str[1]);
650                 free(iops_str[0]);
651                 free(iops_str[1]);
652         }
653
654         if (eta_str[0]) {
655                 char *dst = output + strlen(output);
656
657                 sprintf(dst, " - %s", eta_str);
658         }
659                 
660         gfio_update_thread_status(output, perc);
661 }
662
663 static void gfio_probe_op(struct fio_client *client, struct fio_net_cmd *cmd)
664 {
665         struct cmd_probe_pdu *probe = (struct cmd_probe_pdu *) cmd->payload;
666         const char *os, *arch;
667         char buf[64];
668
669         os = fio_get_os_string(probe->os);
670         if (!os)
671                 os = "unknown";
672
673         arch = fio_get_arch_string(probe->arch);
674         if (!arch)
675                 os = "unknown";
676
677         if (!client->name)
678                 client->name = strdup((char *) probe->hostname);
679
680         gtk_label_set_text(GTK_LABEL(ui.probe.hostname), (char *) probe->hostname);
681         gtk_label_set_text(GTK_LABEL(ui.probe.os), os);
682         gtk_label_set_text(GTK_LABEL(ui.probe.arch), arch);
683         sprintf(buf, "%u.%u.%u", probe->fio_major, probe->fio_minor, probe->fio_patch);
684         gtk_label_set_text(GTK_LABEL(ui.probe.fio_ver), buf);
685 }
686
687 static void gfio_update_thread_status(char *status_message, double perc)
688 {
689         static char message[100];
690         const char *m = message;
691
692         strncpy(message, status_message, sizeof(message) - 1);
693         gtk_progress_bar_set_text(
694                 GTK_PROGRESS_BAR(ui.thread_status_pb), m);
695         gtk_progress_bar_set_fraction(
696                 GTK_PROGRESS_BAR(ui.thread_status_pb), perc / 100.0);
697         gdk_threads_enter();
698         gtk_widget_queue_draw(ui.window);
699         gdk_threads_leave();
700 }
701
702 static void gfio_quit_op(struct fio_client *client)
703 {
704         struct gui *ui = client->client_data;
705
706         gfio_set_connected(ui, 0);
707 }
708
709 static void gfio_add_job_op(struct fio_client *client, struct fio_net_cmd *cmd)
710 {
711         struct cmd_add_job_pdu *p = (struct cmd_add_job_pdu *) cmd->payload;
712         struct gui *ui = client->client_data;
713         char tmp[8];
714         int i;
715
716         p->iodepth              = le32_to_cpu(p->iodepth);
717         p->rw                   = le32_to_cpu(p->rw);
718
719         for (i = 0; i < 2; i++) {
720                 p->min_bs[i]    = le32_to_cpu(p->min_bs[i]);
721                 p->max_bs[i]    = le32_to_cpu(p->max_bs[i]);
722         }
723
724         p->numjobs              = le32_to_cpu(p->numjobs);
725         p->group_reporting      = le32_to_cpu(p->group_reporting);
726
727         gtk_entry_set_text(GTK_ENTRY(ui->eta.name), (gchar *) p->jobname);
728         gtk_entry_set_text(GTK_ENTRY(ui->eta.iotype), ddir_str(p->rw));
729         gtk_entry_set_text(GTK_ENTRY(ui->eta.ioengine), (gchar *) p->ioengine);
730
731         sprintf(tmp, "%u", p->iodepth);
732         gtk_entry_set_text(GTK_ENTRY(ui->eta.iodepth), tmp);
733 }
734
735 static void gfio_client_timed_out(struct fio_client *client)
736 {
737         struct gui *ui = client->client_data;
738         GtkWidget *dialog, *label, *content;
739         char buf[256];
740
741         gdk_threads_enter();
742
743         gfio_set_connected(ui, 0);
744         clear_ui_info(ui);
745
746         sprintf(buf, "Client %s: timeout talking to server.\n", client->hostname);
747
748         dialog = gtk_dialog_new_with_buttons("Timed out!",
749                         GTK_WINDOW(ui->window),
750                         GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
751                         GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
752
753         content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
754         label = gtk_label_new((const gchar *) buf);
755         gtk_container_add(GTK_CONTAINER(content), label);
756         gtk_widget_show_all(dialog);
757         gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
758
759         gtk_dialog_run(GTK_DIALOG(dialog));
760         gtk_widget_destroy(dialog);
761
762         gdk_threads_leave();
763 }
764
765 struct client_ops gfio_client_ops = {
766         .text_op                = gfio_text_op,
767         .disk_util              = gfio_disk_util_op,
768         .thread_status          = gfio_thread_status_op,
769         .group_stats            = gfio_group_stats_op,
770         .eta                    = gfio_update_eta,
771         .probe                  = gfio_probe_op,
772         .quit                   = gfio_quit_op,
773         .add_job                = gfio_add_job_op,
774         .timed_out              = gfio_client_timed_out,
775         .stay_connected         = 1,
776 };
777
778 static void quit_clicked(__attribute__((unused)) GtkWidget *widget,
779                 __attribute__((unused)) gpointer data)
780 {
781         gtk_main_quit();
782 }
783
784 static void *job_thread(void *arg)
785 {
786         fio_handle_clients(&gfio_client_ops);
787         return NULL;
788 }
789
790 static int send_job_files(struct gui *ui)
791 {
792         int i, ret = 0;
793
794         for (i = 0; i < ui->nr_job_files; i++) {
795                 ret = fio_clients_send_ini(ui->job_files[i]);
796                 if (ret)
797                         break;
798
799                 free(ui->job_files[i]);
800                 ui->job_files[i] = NULL;
801         }
802         while (i < ui->nr_job_files) {
803                 free(ui->job_files[i]);
804                 ui->job_files[i] = NULL;
805                 i++;
806         }
807
808         return ret;
809 }
810
811 static void start_job_thread(struct gui *ui)
812 {
813         if (send_job_files(ui)) {
814                 printf("Yeah, I didn't really like those options too much.\n");
815                 gtk_widget_set_sensitive(ui->button[START_JOB_BUTTON], 1);
816                 return;
817         }
818 }
819
820 static void start_job_clicked(__attribute__((unused)) GtkWidget *widget,
821                 gpointer data)
822 {
823         struct gui *ui = data;
824
825         gtk_widget_set_sensitive(ui->button[START_JOB_BUTTON], 0);
826         start_job_thread(ui);
827 }
828
829 static void file_open(GtkWidget *w, gpointer data);
830
831 static void connect_clicked(GtkWidget *widget, gpointer data)
832 {
833         struct gui *ui = data;
834
835         if (!ui->connected) {
836                 if (!ui->nr_job_files)
837                         file_open(widget, data);
838                 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No jobs running");
839                 fio_clients_connect();
840                 pthread_create(&ui->t, NULL, job_thread, NULL);
841                 gfio_set_connected(ui, 1);
842         } else {
843                 fio_clients_terminate();
844                 gfio_set_connected(ui, 0);
845                 clear_ui_info(ui);
846         }
847 }
848
849 static void add_button(struct gui *ui, int i, GtkWidget *buttonbox,
850                         struct button_spec *buttonspec)
851 {
852         ui->button[i] = gtk_button_new_with_label(buttonspec->buttontext);
853         g_signal_connect(ui->button[i], "clicked", G_CALLBACK (buttonspec->f), ui);
854         gtk_box_pack_start(GTK_BOX (ui->buttonbox), ui->button[i], FALSE, FALSE, 3);
855         gtk_widget_set_tooltip_text(ui->button[i], buttonspeclist[i].tooltiptext);
856         gtk_widget_set_sensitive(ui->button[i], !buttonspec->start_insensitive);
857 }
858
859 static void add_buttons(struct gui *ui,
860                                 struct button_spec *buttonlist,
861                                 int nbuttons)
862 {
863         int i;
864
865         for (i = 0; i < nbuttons; i++)
866                 add_button(ui, i, ui->buttonbox, &buttonlist[i]);
867 }
868
869 static void on_info_bar_response(GtkWidget *widget, gint response,
870                                  gpointer data)
871 {
872         if (response == GTK_RESPONSE_OK) {
873                 gtk_widget_destroy(widget);
874                 ui.error_info_bar = NULL;
875         }
876 }
877
878 void report_error(GError *error)
879 {
880         if (ui.error_info_bar == NULL) {
881                 ui.error_info_bar = gtk_info_bar_new_with_buttons(GTK_STOCK_OK,
882                                                                GTK_RESPONSE_OK,
883                                                                NULL);
884                 g_signal_connect(ui.error_info_bar, "response", G_CALLBACK(on_info_bar_response), NULL);
885                 gtk_info_bar_set_message_type(GTK_INFO_BAR(ui.error_info_bar),
886                                               GTK_MESSAGE_ERROR);
887                 
888                 ui.error_label = gtk_label_new(error->message);
889                 GtkWidget *container = gtk_info_bar_get_content_area(GTK_INFO_BAR(ui.error_info_bar));
890                 gtk_container_add(GTK_CONTAINER(container), ui.error_label);
891                 
892                 gtk_box_pack_start(GTK_BOX(ui.vbox), ui.error_info_bar, FALSE, FALSE, 0);
893                 gtk_widget_show_all(ui.vbox);
894         } else {
895                 char buffer[256];
896                 snprintf(buffer, sizeof(buffer), "Failed to open file.");
897                 gtk_label_set(GTK_LABEL(ui.error_label), buffer);
898         }
899 }
900
901 static int get_connection_details(char **host, int *port, int *type,
902                                   int *server_start)
903 {
904         GtkWidget *dialog, *box, *vbox, *hentry, *hbox, *frame, *pentry, *combo;
905         GtkWidget *button;
906         char *typeentry;
907
908         dialog = gtk_dialog_new_with_buttons("Connection details",
909                         GTK_WINDOW(ui.window),
910                         GTK_DIALOG_DESTROY_WITH_PARENT,
911                         GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
912                         GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL);
913
914         frame = gtk_frame_new("Hostname / socket name");
915         vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
916         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
917
918         box = gtk_vbox_new(FALSE, 6);
919         gtk_container_add(GTK_CONTAINER(frame), box);
920
921         hbox = gtk_hbox_new(TRUE, 10);
922         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
923         hentry = gtk_entry_new();
924         gtk_entry_set_text(GTK_ENTRY(hentry), "localhost");
925         gtk_box_pack_start(GTK_BOX(hbox), hentry, TRUE, TRUE, 0);
926
927         frame = gtk_frame_new("Port");
928         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
929         box = gtk_vbox_new(FALSE, 10);
930         gtk_container_add(GTK_CONTAINER(frame), box);
931
932         hbox = gtk_hbox_new(TRUE, 4);
933         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
934         pentry = create_spinbutton(hbox, 1, 65535, FIO_NET_PORT);
935
936         frame = gtk_frame_new("Type");
937         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
938         box = gtk_vbox_new(FALSE, 10);
939         gtk_container_add(GTK_CONTAINER(frame), box);
940
941         hbox = gtk_hbox_new(TRUE, 4);
942         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
943
944         combo = gtk_combo_box_text_new();
945         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), "IPv4");
946         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), "IPv6");
947         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), "local socket");
948         gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
949
950         gtk_container_add(GTK_CONTAINER(hbox), combo);
951
952         frame = gtk_frame_new("Options");
953         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
954         box = gtk_vbox_new(FALSE, 10);
955         gtk_container_add(GTK_CONTAINER(frame), box);
956
957         hbox = gtk_hbox_new(TRUE, 4);
958         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
959
960         button = gtk_check_button_new_with_label("Auto-spawn fio backend");
961         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), 1);
962         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.");
963         gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 6);
964
965         gtk_widget_show_all(dialog);
966
967         if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
968                 gtk_widget_destroy(dialog);
969                 return 1;
970         }
971
972         *host = strdup(gtk_entry_get_text(GTK_ENTRY(hentry)));
973         *port = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(pentry));
974
975         typeentry = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(combo));
976         if (!typeentry || !strncmp(typeentry, "IPv4", 4))
977                 *type = Fio_client_ipv4;
978         else if (!strncmp(typeentry, "IPv6", 4))
979                 *type = Fio_client_ipv6;
980         else
981                 *type = Fio_client_socket;
982         g_free(typeentry);
983
984         *server_start = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button));
985
986         gtk_widget_destroy(dialog);
987         return 0;
988 }
989
990 static void file_open(GtkWidget *w, gpointer data)
991 {
992         GtkWidget *dialog;
993         GSList *filenames, *fn_glist;
994         GtkFileFilter *filter;
995         char *host;
996         int port, type, server_start;
997
998         dialog = gtk_file_chooser_dialog_new("Open File",
999                 GTK_WINDOW(ui.window),
1000                 GTK_FILE_CHOOSER_ACTION_OPEN,
1001                 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1002                 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
1003                 NULL);
1004         gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);
1005
1006         filter = gtk_file_filter_new();
1007         gtk_file_filter_add_pattern(filter, "*.fio");
1008         gtk_file_filter_add_pattern(filter, "*.job");
1009         gtk_file_filter_add_mime_type(filter, "text/fio");
1010         gtk_file_filter_set_name(filter, "Fio job file");
1011         gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter);
1012
1013         if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
1014                 gtk_widget_destroy(dialog);
1015                 return;
1016         }
1017
1018         fn_glist = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
1019
1020         gtk_widget_destroy(dialog);
1021
1022         if (get_connection_details(&host, &port, &type, &server_start))
1023                 goto err;
1024
1025         filenames = fn_glist;
1026         while (filenames != NULL) {
1027                 ui.job_files = realloc(ui.job_files, (ui.nr_job_files + 1) * sizeof(char *));
1028                 ui.job_files[ui.nr_job_files] = strdup(filenames->data);
1029                 ui.nr_job_files++;
1030
1031                 ui.client = fio_client_add_explicit(&gfio_client_ops, host, type, port);
1032                 if (!ui.client) {
1033                         GError *error;
1034
1035                         error = g_error_new(g_quark_from_string("fio"), 1,
1036                                         "Failed to add client %s", host);
1037                         report_error(error);
1038                         g_error_free(error);
1039                 }
1040                 ui.client->client_data = &ui;
1041                         
1042                 g_free(filenames->data);
1043                 filenames = g_slist_next(filenames);
1044         }
1045         free(host);
1046 err:
1047         g_slist_free(fn_glist);
1048 }
1049
1050 static void file_save(GtkWidget *w, gpointer data)
1051 {
1052         GtkWidget *dialog;
1053
1054         dialog = gtk_file_chooser_dialog_new("Save File",
1055                 GTK_WINDOW(ui.window),
1056                 GTK_FILE_CHOOSER_ACTION_SAVE,
1057                 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1058                 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
1059                 NULL);
1060
1061         gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
1062         gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "Untitled document");
1063
1064         if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
1065                 char *filename;
1066
1067                 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
1068                 // save_job_file(filename);
1069                 g_free(filename);
1070         }
1071         gtk_widget_destroy(dialog);
1072 }
1073
1074 static void preferences(GtkWidget *w, gpointer data)
1075 {
1076         GtkWidget *dialog, *frame, *box, **buttons;
1077         int i;
1078
1079         dialog = gtk_dialog_new_with_buttons("Preferences",
1080                 GTK_WINDOW(ui.window),
1081                 GTK_DIALOG_DESTROY_WITH_PARENT,
1082                 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
1083                 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
1084                 NULL);
1085
1086         frame = gtk_frame_new("Debug logging");
1087         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5);
1088         box = gtk_hbox_new(FALSE, 6);
1089         gtk_container_add(GTK_CONTAINER(frame), box);
1090
1091         buttons = malloc(sizeof(GtkWidget *) * FD_DEBUG_MAX);
1092
1093         for (i = 0; i < FD_DEBUG_MAX; i++) {
1094                 buttons[i] = gtk_check_button_new_with_label(debug_levels[i].name);
1095                 gtk_widget_set_tooltip_text(buttons[i], debug_levels[i].help);
1096                 gtk_box_pack_start(GTK_BOX(box), buttons[i], FALSE, FALSE, 6);
1097         }
1098
1099         gtk_widget_show_all(dialog);
1100
1101         if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
1102                 gtk_widget_destroy(dialog);
1103                 return;
1104         }
1105
1106         for (i = 0; i < FD_DEBUG_MAX; i++) {
1107                 int set;
1108
1109                 set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(buttons[i]));
1110                 if (set)
1111                         fio_debug |= (1UL << i);
1112         }
1113
1114         gtk_widget_destroy(dialog);
1115 }
1116
1117 static void about_dialog(GtkWidget *w, gpointer data)
1118 {
1119         gtk_show_about_dialog(NULL,
1120                 "program-name", "gfio",
1121                 "comments", "Gtk2 UI for fio",
1122                 "license", "GPLv2",
1123                 "version", fio_version_string,
1124                 "copyright", "Jens Axboe <axboe@kernel.dk> 2012",
1125                 "logo-icon-name", "fio",
1126                 /* Must be last: */
1127                 NULL, NULL,
1128                 NULL);
1129 }
1130
1131 static GtkActionEntry menu_items[] = {
1132         { "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL},
1133         { "HelpMenuAction", GTK_STOCK_HELP, "Help", NULL, NULL, NULL},
1134         { "OpenFile", GTK_STOCK_OPEN, NULL,   "<Control>O", NULL, G_CALLBACK(file_open) },
1135         { "SaveFile", GTK_STOCK_SAVE, NULL,   "<Control>S", NULL, G_CALLBACK(file_save) },
1136         { "Preferences", GTK_STOCK_PREFERENCES, NULL, "<Control>p", NULL, G_CALLBACK(preferences) },
1137         { "Quit", GTK_STOCK_QUIT, NULL,   "<Control>Q", NULL, G_CALLBACK(quit_clicked) },
1138         { "About", GTK_STOCK_ABOUT, NULL,  NULL, NULL, G_CALLBACK(about_dialog) },
1139 };
1140 static gint nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]);
1141
1142 static const gchar *ui_string = " \
1143         <ui> \
1144                 <menubar name=\"MainMenu\"> \
1145                         <menu name=\"FileMenu\" action=\"FileMenuAction\"> \
1146                                 <menuitem name=\"Open\" action=\"OpenFile\" /> \
1147                                 <menuitem name=\"Save\" action=\"SaveFile\" /> \
1148                                 <separator name=\"Separator\"/> \
1149                                 <menuitem name=\"Preferences\" action=\"Preferences\" /> \
1150                                 <separator name=\"Separator2\"/> \
1151                                 <menuitem name=\"Quit\" action=\"Quit\" /> \
1152                         </menu> \
1153                         <menu name=\"Help\" action=\"HelpMenuAction\"> \
1154                                 <menuitem name=\"About\" action=\"About\" /> \
1155                         </menu> \
1156                 </menubar> \
1157         </ui> \
1158 ";
1159
1160 static GtkWidget *get_menubar_menu(GtkWidget *window, GtkUIManager *ui_manager)
1161 {
1162         GtkActionGroup *action_group = gtk_action_group_new("Menu");
1163         GError *error = 0;
1164
1165         action_group = gtk_action_group_new("Menu");
1166         gtk_action_group_add_actions(action_group, menu_items, nmenu_items, 0);
1167
1168         gtk_ui_manager_insert_action_group(ui_manager, action_group, 0);
1169         gtk_ui_manager_add_ui_from_string(GTK_UI_MANAGER(ui_manager), ui_string, -1, &error);
1170
1171         gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(ui_manager));
1172         return gtk_ui_manager_get_widget(ui_manager, "/MainMenu");
1173 }
1174
1175 void gfio_ui_setup(GtkSettings *settings, GtkWidget *menubar,
1176                    GtkWidget *vbox, GtkUIManager *ui_manager)
1177 {
1178         gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);
1179 }
1180
1181 static void init_ui(int *argc, char **argv[], struct gui *ui)
1182 {
1183         GtkSettings *settings;
1184         GtkUIManager *uimanager;
1185         GtkWidget *menu, *probe, *probe_frame, *probe_box;
1186
1187         memset(ui, 0, sizeof(*ui));
1188
1189         /* Magical g*thread incantation, you just need this thread stuff.
1190          * Without it, the update that happens in gfio_update_thread_status
1191          * doesn't really happen in a timely fashion, you need expose events
1192          */
1193         if (!g_thread_supported())
1194                 g_thread_init(NULL);
1195         gdk_threads_init();
1196
1197         gtk_init(argc, argv);
1198         settings = gtk_settings_get_default();
1199         gtk_settings_set_long_property(settings, "gtk_tooltip_timeout", 10, "gfio setting");
1200         g_type_init();
1201         
1202         ui->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1203         gtk_window_set_title(GTK_WINDOW(ui->window), "fio");
1204         gtk_window_set_default_size(GTK_WINDOW(ui->window), 700, 500);
1205
1206         g_signal_connect(ui->window, "delete-event", G_CALLBACK(quit_clicked), NULL);
1207         g_signal_connect(ui->window, "destroy", G_CALLBACK(quit_clicked), NULL);
1208
1209         ui->vbox = gtk_vbox_new(FALSE, 0);
1210         gtk_container_add(GTK_CONTAINER (ui->window), ui->vbox);
1211
1212         uimanager = gtk_ui_manager_new();
1213         menu = get_menubar_menu(ui->window, uimanager);
1214         gfio_ui_setup(settings, menu, ui->vbox, uimanager);
1215
1216         /*
1217          * Set up alignments for widgets at the top of ui, 
1218          * align top left, expand horizontally but not vertically
1219          */
1220         ui->topalign = gtk_alignment_new(0, 0, 1, 0);
1221         ui->topvbox = gtk_vbox_new(FALSE, 3);
1222         gtk_container_add(GTK_CONTAINER(ui->topalign), ui->topvbox);
1223         gtk_box_pack_start(GTK_BOX(ui->vbox), ui->topalign, FALSE, FALSE, 0);
1224
1225         probe = gtk_frame_new("Job");
1226         gtk_box_pack_start(GTK_BOX(ui->topvbox), probe, TRUE, FALSE, 3);
1227         probe_frame = gtk_vbox_new(FALSE, 3);
1228         gtk_container_add(GTK_CONTAINER(probe), probe_frame);
1229
1230         probe_box = gtk_hbox_new(FALSE, 3);
1231         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
1232         ui->probe.hostname = new_info_label_in_frame(probe_box, "Host");
1233         ui->probe.os = new_info_label_in_frame(probe_box, "OS");
1234         ui->probe.arch = new_info_label_in_frame(probe_box, "Architecture");
1235         ui->probe.fio_ver = new_info_label_in_frame(probe_box, "Fio version");
1236
1237         probe_box = gtk_hbox_new(FALSE, 3);
1238         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
1239
1240         ui->eta.name = new_info_entry_in_frame(probe_box, "Name");
1241         ui->eta.iotype = new_info_entry_in_frame(probe_box, "IO");
1242         ui->eta.ioengine = new_info_entry_in_frame(probe_box, "IO Engine");
1243         ui->eta.iodepth = new_info_entry_in_frame(probe_box, "IO Depth");
1244         ui->eta.jobs = new_info_entry_in_frame(probe_box, "Jobs");
1245         ui->eta.files = new_info_entry_in_frame(probe_box, "Open files");
1246
1247         probe_box = gtk_hbox_new(FALSE, 3);
1248         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
1249         ui->eta.read_bw = new_info_entry_in_frame(probe_box, "Read BW");
1250         ui->eta.read_iops = new_info_entry_in_frame(probe_box, "IOPS");
1251         ui->eta.write_bw = new_info_entry_in_frame(probe_box, "Write BW");
1252         ui->eta.write_iops = new_info_entry_in_frame(probe_box, "IOPS");
1253
1254         /*
1255          * Only add this if we have a commit rate
1256          */
1257 #if 0
1258         probe_box = gtk_hbox_new(FALSE, 3);
1259         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
1260
1261         ui->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
1262         ui->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1263
1264         ui->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
1265         ui->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1266 #endif
1267
1268         /*
1269          * Add a text box for text op messages 
1270          */
1271         ui->textview = gtk_text_view_new();
1272         ui->text = gtk_text_view_get_buffer(GTK_TEXT_VIEW(ui->textview));
1273         gtk_text_buffer_set_text(ui->text, "", -1);
1274         gtk_text_view_set_editable(GTK_TEXT_VIEW(ui->textview), FALSE);
1275         gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(ui->textview), FALSE);
1276         ui->scrolled_window = gtk_scrolled_window_new(NULL, NULL);
1277         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(ui->scrolled_window),
1278                                         GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1279         gtk_container_add(GTK_CONTAINER(ui->scrolled_window), ui->textview);
1280         gtk_box_pack_start(GTK_BOX(ui->vbox), ui->scrolled_window,
1281                         TRUE, TRUE, 0);
1282
1283         /*
1284          * Set up alignments for widgets at the bottom of ui, 
1285          * align bottom left, expand horizontally but not vertically
1286          */
1287         ui->bottomalign = gtk_alignment_new(0, 1, 1, 0);
1288         ui->buttonbox = gtk_hbox_new(FALSE, 0);
1289         gtk_container_add(GTK_CONTAINER(ui->bottomalign), ui->buttonbox);
1290         gtk_box_pack_start(GTK_BOX(ui->vbox), ui->bottomalign,
1291                                         FALSE, FALSE, 0);
1292
1293         add_buttons(ui, buttonspeclist, ARRAYSIZE(buttonspeclist));
1294
1295         /*
1296          * Set up thread status progress bar
1297          */
1298         ui->thread_status_pb = gtk_progress_bar_new();
1299         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->thread_status_pb), 0.0);
1300         gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No connections");
1301         gtk_container_add(GTK_CONTAINER(ui->buttonbox), ui->thread_status_pb);
1302
1303
1304         gtk_widget_show_all(ui->window);
1305 }
1306
1307 int main(int argc, char *argv[], char *envp[])
1308 {
1309         if (initialize_fio(envp))
1310                 return 1;
1311         if (fio_init_options())
1312                 return 1;
1313
1314         init_ui(&argc, &argv, &ui);
1315
1316         gdk_threads_enter();
1317         gtk_main();
1318         gdk_threads_leave();
1319         return 0;
1320 }