gfio: put results from same job file into a notebook
[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         GtkWidget *results_notebook;
92         GtkWidget *results_window;
93         GtkTextBuffer *text;
94         struct probe_widget probe;
95         struct eta_widget eta;
96         int connected;
97         pthread_t t;
98
99         struct fio_client *client;
100         int nr_job_files;
101         char **job_files;
102 } ui;
103
104 static void clear_ui_info(struct gui *ui)
105 {
106         gtk_label_set_text(GTK_LABEL(ui->probe.hostname), "");
107         gtk_label_set_text(GTK_LABEL(ui->probe.os), "");
108         gtk_label_set_text(GTK_LABEL(ui->probe.arch), "");
109         gtk_label_set_text(GTK_LABEL(ui->probe.fio_ver), "");
110         gtk_entry_set_text(GTK_ENTRY(ui->eta.name), "");
111         gtk_entry_set_text(GTK_ENTRY(ui->eta.iotype), "");
112         gtk_entry_set_text(GTK_ENTRY(ui->eta.ioengine), "");
113         gtk_entry_set_text(GTK_ENTRY(ui->eta.iodepth), "");
114         gtk_entry_set_text(GTK_ENTRY(ui->eta.jobs), "");
115         gtk_entry_set_text(GTK_ENTRY(ui->eta.files), "");
116         gtk_entry_set_text(GTK_ENTRY(ui->eta.read_bw), "");
117         gtk_entry_set_text(GTK_ENTRY(ui->eta.read_iops), "");
118         gtk_entry_set_text(GTK_ENTRY(ui->eta.write_bw), "");
119         gtk_entry_set_text(GTK_ENTRY(ui->eta.write_iops), "");
120 }
121
122 static GtkWidget *new_info_entry_in_frame(GtkWidget *box, const char *label)
123 {
124         GtkWidget *entry, *frame;
125
126         frame = gtk_frame_new(label);
127         entry = gtk_entry_new();
128         gtk_entry_set_editable(GTK_ENTRY(entry), 0);
129         gtk_box_pack_start(GTK_BOX(box), frame, TRUE, TRUE, 3);
130         gtk_container_add(GTK_CONTAINER(frame), entry);
131
132         return entry;
133 }
134
135 static GtkWidget *new_info_label_in_frame(GtkWidget *box, const char *label)
136 {
137         GtkWidget *label_widget;
138         GtkWidget *frame;
139
140         frame = gtk_frame_new(label);
141         label_widget = gtk_label_new(NULL);
142         gtk_box_pack_start(GTK_BOX(box), frame, TRUE, TRUE, 3);
143         gtk_container_add(GTK_CONTAINER(frame), label_widget);
144
145         return label_widget;
146 }
147
148 static GtkWidget *create_spinbutton(GtkWidget *hbox, double min, double max, double defval)
149 {
150         GtkWidget *button, *box;
151
152         box = gtk_hbox_new(FALSE, 3);
153         gtk_container_add(GTK_CONTAINER(hbox), box);
154
155         button = gtk_spin_button_new_with_range(min, max, 1.0);
156         gtk_box_pack_start(GTK_BOX(box), button, TRUE, TRUE, 0);
157
158         gtk_spin_button_set_update_policy(GTK_SPIN_BUTTON(button), GTK_UPDATE_IF_VALID);
159         gtk_spin_button_set_value(GTK_SPIN_BUTTON(button), defval);
160
161         return button;
162 }
163
164 static void gfio_set_connected(struct gui *ui, int connected)
165 {
166         if (connected) {
167                 gtk_widget_set_sensitive(ui->button[START_JOB_BUTTON], 1);
168                 ui->connected = 1;
169                 gtk_button_set_label(GTK_BUTTON(ui->button[CONNECT_BUTTON]), "Disconnect");
170         } else {
171                 ui->connected = 0;
172                 gtk_button_set_label(GTK_BUTTON(ui->button[CONNECT_BUTTON]), "Connect");
173                 gtk_widget_set_sensitive(ui->button[START_JOB_BUTTON], 0);
174         }
175 }
176
177 static void label_set_int_value(GtkWidget *entry, unsigned int val)
178 {
179         char tmp[80];
180
181         sprintf(tmp, "%u", val);
182         gtk_label_set_text(GTK_LABEL(entry), tmp);
183 }
184
185 static void entry_set_int_value(GtkWidget *entry, unsigned int val)
186 {
187         char tmp[80];
188
189         sprintf(tmp, "%u", val);
190         gtk_entry_set_text(GTK_ENTRY(entry), tmp);
191 }
192
193 #define ALIGN_LEFT 1
194 #define ALIGN_RIGHT 2
195 #define INVISIBLE 4
196 #define UNSORTABLE 8
197
198 GtkTreeViewColumn *tree_view_column(GtkWidget *tree_view, int index, const char *title, unsigned int flags)
199 {
200         GtkCellRenderer *renderer;
201         GtkTreeViewColumn *col;
202         double xalign = 0.0; /* left as default */
203         PangoAlignment align;
204         gboolean visible;
205
206         align = (flags & ALIGN_LEFT) ? PANGO_ALIGN_LEFT :
207                 (flags & ALIGN_RIGHT) ? PANGO_ALIGN_RIGHT :
208                 PANGO_ALIGN_CENTER;
209         visible = !(flags & INVISIBLE);
210
211         renderer = gtk_cell_renderer_text_new();
212         col = gtk_tree_view_column_new();
213
214         gtk_tree_view_column_set_title(col, title);
215         if (!(flags & UNSORTABLE))
216                 gtk_tree_view_column_set_sort_column_id(col, index);
217         gtk_tree_view_column_set_resizable(col, TRUE);
218         gtk_tree_view_column_pack_start(col, renderer, TRUE);
219         gtk_tree_view_column_add_attribute(col, renderer, "text", index);
220         gtk_object_set(GTK_OBJECT(renderer), "alignment", align, NULL);
221         switch (align) {
222         case PANGO_ALIGN_LEFT:
223                 xalign = 0.0;
224                 break;
225         case PANGO_ALIGN_CENTER:
226                 xalign = 0.5;
227                 break;
228         case PANGO_ALIGN_RIGHT:
229                 xalign = 1.0;
230                 break;
231         }
232         gtk_cell_renderer_set_alignment(GTK_CELL_RENDERER(renderer), xalign, 0.5);
233         gtk_tree_view_column_set_visible(col, visible);
234         gtk_tree_view_append_column(GTK_TREE_VIEW(tree_view), col);
235         return col;
236 }
237
238 static GtkWidget *gfio_output_clat_percentiles(unsigned int *ovals,
239                                                fio_fp64_t *plist,
240                                                unsigned int len,
241                                                const char *base,
242                                                unsigned int scale)
243 {
244         GType types[FIO_IO_U_LIST_MAX_LEN];
245         GtkWidget *tree_view;
246         GtkTreeSelection *selection;
247         GtkListStore *model;
248         GtkTreeIter iter;
249         int i;
250
251         for (i = 0; i < len; i++)
252                 types[i] = G_TYPE_INT;
253
254         model = gtk_list_store_newv(len, types);
255
256         tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
257         gtk_widget_set_can_focus(tree_view, FALSE);
258
259         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
260         gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE);
261
262         for (i = 0; i < len; i++) {
263                 char fbuf[8];
264
265                 sprintf(fbuf, "%2.2f%%", plist[i].u.f);
266                 tree_view_column(tree_view, i, fbuf, ALIGN_RIGHT | UNSORTABLE);
267         }
268
269         gtk_list_store_append(model, &iter);
270
271         for (i = 0; i < len; i++)
272                 gtk_list_store_set(model, &iter, i, ovals[i], -1);
273
274         return tree_view;
275 }
276
277 static void gfio_show_clat_percentiles(GtkWidget *vbox, struct thread_stat *ts,
278                                        int ddir)
279 {
280         unsigned int *io_u_plat = ts->io_u_plat[ddir];
281         unsigned long nr = ts->clat_stat[ddir].samples;
282         fio_fp64_t *plist = ts->percentile_list;
283         unsigned int *ovals, len, minv, maxv, scale_down;
284         const char *base;
285         GtkWidget *tree_view, *frame, *hbox;
286         char tmp[64];
287
288         len = calc_clat_percentiles(io_u_plat, nr, plist, &ovals, &maxv, &minv);
289         if (!len)
290                 goto out;
291
292         /*
293          * We default to usecs, but if the value range is such that we
294          * should scale down to msecs, do that.
295          */
296         if (minv > 2000 && maxv > 99999) {
297                 scale_down = 1;
298                 base = "msec";
299         } else {
300                 scale_down = 0;
301                 base = "usec";
302         }
303
304         tree_view = gfio_output_clat_percentiles(ovals, plist, len, base, scale_down);
305
306         sprintf(tmp, "Completion percentiles (%s)", base);
307         frame = gtk_frame_new(tmp);
308         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
309
310         hbox = gtk_hbox_new(FALSE, 3);
311         gtk_container_add(GTK_CONTAINER(frame), hbox);
312
313         gtk_box_pack_start(GTK_BOX(hbox), tree_view, TRUE, FALSE, 3);
314 out:
315         if (ovals)
316                 free(ovals);
317 }
318
319 static void gfio_show_lat(GtkWidget *vbox, const char *name, unsigned long min,
320                           unsigned long max, double mean, double dev)
321 {
322         const char *base = "(usec)";
323         GtkWidget *hbox, *label, *frame;
324         char *minp, *maxp;
325         char tmp[64];
326
327         if (!usec_to_msec(&min, &max, &mean, &dev))
328                 base = "(msec)";
329
330         minp = num2str(min, 6, 1, 0);
331         maxp = num2str(max, 6, 1, 0);
332
333         sprintf(tmp, "%s %s", name, base);
334         frame = gtk_frame_new(tmp);
335         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
336
337         hbox = gtk_hbox_new(FALSE, 3);
338         gtk_container_add(GTK_CONTAINER(frame), hbox);
339
340         label = new_info_label_in_frame(hbox, "Minimum");
341         gtk_label_set_text(GTK_LABEL(label), minp);
342         label = new_info_label_in_frame(hbox, "Maximum");
343         gtk_label_set_text(GTK_LABEL(label), maxp);
344         label = new_info_label_in_frame(hbox, "Average");
345         sprintf(tmp, "%5.02f", mean);
346         gtk_label_set_text(GTK_LABEL(label), tmp);
347         label = new_info_label_in_frame(hbox, "Standard deviation");
348         sprintf(tmp, "%5.02f", dev);
349         gtk_label_set_text(GTK_LABEL(label), tmp);
350
351         free(minp);
352         free(maxp);
353
354 }
355
356 #define GFIO_CLAT       1
357 #define GFIO_SLAT       2
358 #define GFIO_LAT        4
359
360 static void gfio_show_ddir_status(GtkWidget *mbox, struct group_run_stats *rs,
361                                   struct thread_stat *ts, int ddir)
362 {
363         const char *ddir_label[2] = { "Read", "Write" };
364         GtkWidget *frame, *label, *box, *vbox, *main_vbox;
365         unsigned long min, max, runt;
366         unsigned long long bw, iops;
367         unsigned int flags = 0;
368         double mean, dev;
369         char *io_p, *bw_p, *iops_p;
370         int i2p;
371
372         if (!ts->runtime[ddir])
373                 return;
374
375         i2p = is_power_of_2(rs->kb_base);
376         runt = ts->runtime[ddir];
377
378         bw = (1000 * ts->io_bytes[ddir]) / runt;
379         io_p = num2str(ts->io_bytes[ddir], 6, 1, i2p);
380         bw_p = num2str(bw, 6, 1, i2p);
381
382         iops = (1000 * (uint64_t)ts->total_io_u[ddir]) / runt;
383         iops_p = num2str(iops, 6, 1, 0);
384
385         box = gtk_hbox_new(FALSE, 3);
386         gtk_box_pack_start(GTK_BOX(mbox), box, TRUE, FALSE, 3);
387
388         frame = gtk_frame_new(ddir_label[ddir]);
389         gtk_box_pack_start(GTK_BOX(box), frame, FALSE, FALSE, 5);
390
391         main_vbox = gtk_vbox_new(FALSE, 3);
392         gtk_container_add(GTK_CONTAINER(frame), main_vbox);
393
394         box = gtk_hbox_new(FALSE, 3);
395         gtk_box_pack_start(GTK_BOX(main_vbox), box, TRUE, FALSE, 3);
396
397         label = new_info_label_in_frame(box, "IO");
398         gtk_label_set_text(GTK_LABEL(label), io_p);
399         label = new_info_label_in_frame(box, "Bandwidth");
400         gtk_label_set_text(GTK_LABEL(label), bw_p);
401         label = new_info_label_in_frame(box, "IOPS");
402         gtk_label_set_text(GTK_LABEL(label), iops_p);
403         label = new_info_label_in_frame(box, "Runtime (msec)");
404         label_set_int_value(label, ts->runtime[ddir]);
405
406         if (calc_lat(&ts->bw_stat[ddir], &min, &max, &mean, &dev)) {
407                 double p_of_agg = 100.0;
408                 const char *bw_str = "KB";
409                 char tmp[32];
410
411                 if (rs->agg[ddir]) {
412                         p_of_agg = mean * 100 / (double) rs->agg[ddir];
413                         if (p_of_agg > 100.0)
414                                 p_of_agg = 100.0;
415                 }
416
417                 if (mean > 999999.9) {
418                         min /= 1000.0;
419                         max /= 1000.0;
420                         mean /= 1000.0;
421                         dev /= 1000.0;
422                         bw_str = "MB";
423                 }
424
425                 sprintf(tmp, "Bandwidth (%s)", bw_str);
426                 frame = gtk_frame_new(tmp);
427                 gtk_box_pack_start(GTK_BOX(main_vbox), frame, FALSE, FALSE, 5);
428
429                 box = gtk_hbox_new(FALSE, 3);
430                 gtk_container_add(GTK_CONTAINER(frame), box);
431
432                 label = new_info_label_in_frame(box, "Minimum");
433                 label_set_int_value(label, min);
434                 label = new_info_label_in_frame(box, "Maximum");
435                 label_set_int_value(label, max);
436                 label = new_info_label_in_frame(box, "Percentage of jobs");
437                 sprintf(tmp, "%3.2f%%", p_of_agg);
438                 gtk_label_set_text(GTK_LABEL(label), tmp);
439                 label = new_info_label_in_frame(box, "Average");
440                 sprintf(tmp, "%5.02f", mean);
441                 gtk_label_set_text(GTK_LABEL(label), tmp);
442                 label = new_info_label_in_frame(box, "Standard deviation");
443                 sprintf(tmp, "%5.02f", dev);
444                 gtk_label_set_text(GTK_LABEL(label), tmp);
445         }
446
447         if (calc_lat(&ts->slat_stat[ddir], &min, &max, &mean, &dev))
448                 flags |= GFIO_SLAT;
449         if (calc_lat(&ts->clat_stat[ddir], &min, &max, &mean, &dev))
450                 flags |= GFIO_CLAT;
451         if (calc_lat(&ts->lat_stat[ddir], &min, &max, &mean, &dev))
452                 flags |= GFIO_LAT;
453
454         if (flags) {
455                 frame = gtk_frame_new("Latency");
456                 gtk_box_pack_start(GTK_BOX(main_vbox), frame, FALSE, FALSE, 5);
457
458                 vbox = gtk_vbox_new(FALSE, 3);
459                 gtk_container_add(GTK_CONTAINER(frame), vbox);
460
461                 if (flags & GFIO_SLAT)
462                         gfio_show_lat(vbox, "Submission latency", min, max, mean, dev);
463                 if (flags & GFIO_CLAT)
464                         gfio_show_lat(vbox, "Completion latency", min, max, mean, dev);
465                 if (flags & GFIO_LAT)
466                         gfio_show_lat(vbox, "Total latency", min, max, mean, dev);
467         }
468
469         if (ts->clat_percentiles)
470                 gfio_show_clat_percentiles(main_vbox, ts, ddir);
471
472
473         free(io_p);
474         free(bw_p);
475         free(iops_p);
476 }
477
478 static GtkWidget *gfio_output_lat_buckets(double *lat, unsigned int num,
479                                           const char **labels)
480 {
481         GtkWidget *tree_view;
482         GtkTreeSelection *selection;
483         GtkListStore *model;
484         GtkTreeIter iter;
485         GType *types;
486         int i, skipped;
487
488         /*
489          * Check if all are empty, in which case don't bother
490          */
491         for (i = 0, skipped = 0; i < num; i++)
492                 if (lat[i] <= 0.0)
493                         skipped++;
494
495         if (skipped == num)
496                 return NULL;
497
498         types = malloc(num * sizeof(GType));
499
500         for (i = 0; i < num; i++)
501                 types[i] = G_TYPE_STRING;
502
503         model = gtk_list_store_newv(num, types);
504         free(types);
505         types = NULL;
506
507         tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
508         gtk_widget_set_can_focus(tree_view, FALSE);
509
510         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
511         gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE);
512
513         for (i = 0; i < num; i++)
514                 tree_view_column(tree_view, i, labels[i], ALIGN_RIGHT | UNSORTABLE);
515
516         gtk_list_store_append(model, &iter);
517
518         for (i = 0; i < num; i++) {
519                 char fbuf[32];
520
521                 if (lat[i] <= 0.0)
522                         sprintf(fbuf, "0.00");
523                 else
524                         sprintf(fbuf, "%3.2f%%", lat[i]);
525
526                 gtk_list_store_set(model, &iter, i, fbuf, -1);
527         }
528
529         return tree_view;
530 }
531
532 static void gfio_show_latency_buckets(GtkWidget *vbox, struct thread_stat *ts)
533 {
534         GtkWidget *box, *frame, *tree_view;
535         double io_u_lat_u[FIO_IO_U_LAT_U_NR];
536         double io_u_lat_m[FIO_IO_U_LAT_M_NR];
537         const char *uranges[] = { "2", "4", "10", "20", "50", "100",
538                                   "250", "500", "750", "1000", };
539         const char *mranges[] = { "2", "4", "10", "20", "50", "100",
540                                   "250", "500", "750", "1000", "2000",
541                                   ">= 2000", };
542
543         stat_calc_lat_u(ts, io_u_lat_u);
544         stat_calc_lat_m(ts, io_u_lat_m);
545
546         tree_view = gfio_output_lat_buckets(io_u_lat_u, FIO_IO_U_LAT_U_NR, uranges);
547         if (tree_view) {
548                 frame = gtk_frame_new("Latency buckets (usec)");
549                 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
550
551                 box = gtk_hbox_new(FALSE, 3);
552                 gtk_container_add(GTK_CONTAINER(frame), box);
553                 gtk_box_pack_start(GTK_BOX(box), tree_view, TRUE, FALSE, 3);
554         }
555
556         tree_view = gfio_output_lat_buckets(io_u_lat_m, FIO_IO_U_LAT_M_NR, mranges);
557         if (tree_view) {
558                 frame = gtk_frame_new("Latency buckets (msec)");
559                 gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
560
561                 box = gtk_hbox_new(FALSE, 3);
562                 gtk_container_add(GTK_CONTAINER(frame), box);
563                 gtk_box_pack_start(GTK_BOX(box), tree_view, TRUE, FALSE, 3);
564         }
565 }
566
567 static void gfio_show_cpu_usage(GtkWidget *vbox, struct thread_stat *ts)
568 {
569         GtkWidget *box, *frame, *entry;
570         double usr_cpu, sys_cpu;
571         unsigned long runtime;
572         char tmp[32];
573
574         runtime = ts->total_run_time;
575         if (runtime) {
576                 double runt = (double) runtime;
577
578                 usr_cpu = (double) ts->usr_time * 100 / runt;
579                 sys_cpu = (double) ts->sys_time * 100 / runt;
580         } else {
581                 usr_cpu = 0;
582                 sys_cpu = 0;
583         }
584
585         frame = gtk_frame_new("OS resources");
586         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
587
588         box = gtk_hbox_new(FALSE, 3);
589         gtk_container_add(GTK_CONTAINER(frame), box);
590
591         entry = new_info_entry_in_frame(box, "User CPU");
592         sprintf(tmp, "%3.2f%%", usr_cpu);
593         gtk_entry_set_text(GTK_ENTRY(entry), tmp);
594         entry = new_info_entry_in_frame(box, "System CPU");
595         sprintf(tmp, "%3.2f%%", sys_cpu);
596         gtk_entry_set_text(GTK_ENTRY(entry), tmp);
597         entry = new_info_entry_in_frame(box, "Context switches");
598         entry_set_int_value(entry, ts->ctx);
599         entry = new_info_entry_in_frame(box, "Major faults");
600         entry_set_int_value(entry, ts->majf);
601         entry = new_info_entry_in_frame(box, "Minor faults");
602         entry_set_int_value(entry, ts->minf);
603 }
604 static void gfio_add_sc_depths_tree(GtkListStore *model,
605                                     struct thread_stat *ts, unsigned int len,
606                                     int submit)
607 {
608         double io_u_dist[FIO_IO_U_MAP_NR];
609         GtkTreeIter iter;
610         /* Bits 0, and 3-8 */
611         const int add_mask = 0x1f9;
612         int i, j;
613
614         if (submit)
615                 stat_calc_dist(ts->io_u_submit, ts->total_submit, io_u_dist);
616         else
617                 stat_calc_dist(ts->io_u_complete, ts->total_complete, io_u_dist);
618
619         gtk_list_store_append(model, &iter);
620
621         gtk_list_store_set(model, &iter, 0, submit ? "Submit" : "Complete", -1);
622
623         for (i = 1, j = 0; i < len; i++) {
624                 char fbuf[32];
625
626                 if (!(add_mask & (1UL << (i - 1))))
627                         sprintf(fbuf, "0.0%%");
628                 else {
629                         sprintf(fbuf, "%3.1f%%", io_u_dist[j]);
630                         j++;
631                 }
632
633                 gtk_list_store_set(model, &iter, i, fbuf, -1);
634         }
635
636 }
637
638 static void gfio_add_total_depths_tree(GtkListStore *model,
639                                        struct thread_stat *ts, unsigned int len)
640 {
641         double io_u_dist[FIO_IO_U_MAP_NR];
642         GtkTreeIter iter;
643         /* Bits 1-6, and 8 */
644         const int add_mask = 0x17e;
645         int i, j;
646
647         stat_calc_dist(ts->io_u_map, ts_total_io_u(ts), io_u_dist);
648
649         gtk_list_store_append(model, &iter);
650
651         gtk_list_store_set(model, &iter, 0, "Total", -1);
652
653         for (i = 1, j = 0; i < len; i++) {
654                 char fbuf[32];
655
656                 if (!(add_mask & (1UL << (i - 1))))
657                         sprintf(fbuf, "0.0%%");
658                 else {
659                         sprintf(fbuf, "%3.1f%%", io_u_dist[j]);
660                         j++;
661                 }
662
663                 gtk_list_store_set(model, &iter, i, fbuf, -1);
664         }
665
666 }
667
668 static void gfio_show_io_depths(GtkWidget *vbox, struct thread_stat *ts)
669 {
670         GtkWidget *frame, *box, *tree_view;
671         GtkTreeSelection *selection;
672         GtkListStore *model;
673         GType types[FIO_IO_U_MAP_NR + 1];
674         int i;
675 #define NR_LABELS       10
676         const char *labels[NR_LABELS] = { "Depth", "0", "1", "2", "4", "8", "16", "32", "64", ">= 64" };
677
678         frame = gtk_frame_new("IO depths");
679         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
680
681         box = gtk_hbox_new(FALSE, 3);
682         gtk_container_add(GTK_CONTAINER(frame), box);
683
684         for (i = 0; i < NR_LABELS; i++)
685                 types[i] = G_TYPE_STRING;
686
687         model = gtk_list_store_newv(NR_LABELS, types);
688
689         tree_view = gtk_tree_view_new_with_model(GTK_TREE_MODEL(model));
690         gtk_widget_set_can_focus(tree_view, FALSE);
691
692         selection = gtk_tree_view_get_selection(GTK_TREE_VIEW(tree_view));
693         gtk_tree_selection_set_mode(GTK_TREE_SELECTION(selection), GTK_SELECTION_BROWSE);
694
695         for (i = 0; i < NR_LABELS; i++)
696                 tree_view_column(tree_view, i, labels[i], ALIGN_RIGHT | UNSORTABLE);
697
698         gfio_add_total_depths_tree(model, ts, NR_LABELS);
699         gfio_add_sc_depths_tree(model, ts, NR_LABELS, 1);
700         gfio_add_sc_depths_tree(model, ts, NR_LABELS, 0);
701
702         gtk_box_pack_start(GTK_BOX(box), tree_view, TRUE, FALSE, 3);
703 }
704
705 static gboolean results_window_delete(GtkWidget *w, gpointer data)
706 {
707         struct gui *ui = (struct gui *) data;
708
709         gtk_widget_destroy(w);
710         ui->results_window = NULL;
711         ui->results_notebook = NULL;
712         return TRUE;
713 }
714
715 static GtkWidget *get_results_window(struct gui *ui)
716 {
717         GtkWidget *win, *notebook;
718
719         if (ui->results_window)
720                 return ui->results_notebook;
721
722         win = gtk_window_new(GTK_WINDOW_TOPLEVEL);
723         gtk_window_set_title(GTK_WINDOW(win), "Results");
724         g_signal_connect(win, "delete-event", G_CALLBACK(results_window_delete), ui);
725         g_signal_connect(win, "destroy", G_CALLBACK(results_window_delete), ui);
726
727         notebook = gtk_notebook_new();
728         gtk_container_add(GTK_CONTAINER(win), notebook);
729
730         ui->results_window = win;
731         ui->results_notebook = notebook;
732         return ui->results_notebook;
733 }
734
735 static void gfio_display_ts(struct fio_client *client, struct thread_stat *ts,
736                             struct group_run_stats *rs)
737 {
738         GtkWidget *res_win, *box, *vbox, *entry;
739         struct gui *ui = client->client_data;
740
741         gdk_threads_enter();
742
743         res_win = get_results_window(ui);
744
745         vbox = gtk_vbox_new(FALSE, 3);
746
747         box = gtk_hbox_new(TRUE, 3);
748         gtk_box_pack_start(GTK_BOX(vbox), box, FALSE, FALSE, 5);
749
750         gtk_notebook_append_page(GTK_NOTEBOOK(res_win), vbox, gtk_label_new(ts->name));
751
752         entry = new_info_entry_in_frame(box, "Name");
753         gtk_entry_set_text(GTK_ENTRY(entry), ts->name);
754         if (strlen(ts->description)) {
755                 entry = new_info_entry_in_frame(box, "Description");
756                 gtk_entry_set_text(GTK_ENTRY(entry), ts->description);
757         }
758         entry = new_info_entry_in_frame(box, "Group ID");
759         entry_set_int_value(entry, ts->groupid);
760         entry = new_info_entry_in_frame(box, "Jobs");
761         entry_set_int_value(entry, ts->members);
762         entry = new_info_entry_in_frame(box, "Error");
763         entry_set_int_value(entry, ts->error);
764         entry = new_info_entry_in_frame(box, "PID");
765         entry_set_int_value(entry, ts->pid);
766
767         if (ts->io_bytes[DDIR_READ])
768                 gfio_show_ddir_status(vbox, rs, ts, DDIR_READ);
769         if (ts->io_bytes[DDIR_WRITE])
770                 gfio_show_ddir_status(vbox, rs, ts, DDIR_WRITE);
771
772         gfio_show_latency_buckets(vbox, ts);
773         gfio_show_cpu_usage(vbox, ts);
774         gfio_show_io_depths(vbox, ts);
775
776         gtk_widget_show_all(ui->results_window);
777         gdk_threads_leave();
778 }
779
780 static void gfio_text_op(struct fio_client *client, struct fio_net_cmd *cmd)
781 {
782 #if 0
783         GtkTextBuffer *buffer;
784         GtkTextIter end;
785
786         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(ui.textview));
787         gdk_threads_enter();
788         gtk_text_buffer_get_end_iter(buffer, &end);
789         gtk_text_buffer_insert(buffer, &end, buf, -1);
790         gdk_threads_leave();
791         gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(ui.textview),
792                                         &end, 0.0, FALSE, 0.0,0.0);
793 #else
794         fio_client_ops.text_op(client, cmd);
795 #endif
796 }
797
798 static void gfio_disk_util_op(struct fio_client *client, struct fio_net_cmd *cmd)
799 {
800         gdk_threads_enter();
801         printf("gfio_disk_util_op called\n");
802         fio_client_ops.disk_util(client, cmd);
803         gdk_threads_leave();
804 }
805
806 extern int sum_stat_clients;
807 extern struct thread_stat client_ts;
808 extern struct group_run_stats client_gs;
809
810 static int sum_stat_nr;
811
812 static void gfio_thread_status_op(struct fio_client *client,
813                                   struct fio_net_cmd *cmd)
814 {
815         struct cmd_ts_pdu *p = (struct cmd_ts_pdu *) cmd->payload;
816
817         gfio_display_ts(client, &p->ts, &p->rs);
818
819         if (sum_stat_clients == 1)
820                 return;
821
822         sum_thread_stats(&client_ts, &p->ts, sum_stat_nr);
823         sum_group_stats(&client_gs, &p->rs);
824
825         client_ts.members++;
826         client_ts.groupid = p->ts.groupid;
827
828         if (++sum_stat_nr == sum_stat_clients) {
829                 strcpy(client_ts.name, "All clients");
830                 gfio_display_ts(client, &client_ts, &client_gs);
831         }
832 }
833
834 static void gfio_group_stats_op(struct fio_client *client,
835                                 struct fio_net_cmd *cmd)
836 {
837         gdk_threads_enter();
838         printf("gfio_group_stats_op called\n");
839         fio_client_ops.group_stats(client, cmd);
840         gdk_threads_leave();
841 }
842
843 static void gfio_update_eta(struct jobs_eta *je)
844 {
845         static int eta_good;
846         char eta_str[128];
847         char output[256];
848         char tmp[32];
849         double perc = 0.0;
850         int i2p = 0;
851
852         gdk_threads_enter();
853
854         eta_str[0] = '\0';
855         output[0] = '\0';
856
857         if (je->eta_sec != INT_MAX && je->elapsed_sec) {
858                 perc = (double) je->elapsed_sec / (double) (je->elapsed_sec + je->eta_sec);
859                 eta_to_str(eta_str, je->eta_sec);
860         }
861
862         sprintf(tmp, "%u", je->nr_running);
863         gtk_entry_set_text(GTK_ENTRY(ui.eta.jobs), tmp);
864         sprintf(tmp, "%u", je->files_open);
865         gtk_entry_set_text(GTK_ENTRY(ui.eta.files), tmp);
866
867 #if 0
868         if (je->m_rate[0] || je->m_rate[1] || je->t_rate[0] || je->t_rate[1]) {
869         if (je->m_rate || je->t_rate) {
870                 char *tr, *mr;
871
872                 mr = num2str(je->m_rate, 4, 0, i2p);
873                 tr = num2str(je->t_rate, 4, 0, i2p);
874                 gtk_entry_set_text(GTK_ENTRY(ui.eta);
875                 p += sprintf(p, ", CR=%s/%s KB/s", tr, mr);
876                 free(tr);
877                 free(mr);
878         } else if (je->m_iops || je->t_iops)
879                 p += sprintf(p, ", CR=%d/%d IOPS", je->t_iops, je->m_iops);
880
881         gtk_entry_set_text(GTK_ENTRY(ui.eta.cr_bw), "---");
882         gtk_entry_set_text(GTK_ENTRY(ui.eta.cr_iops), "---");
883         gtk_entry_set_text(GTK_ENTRY(ui.eta.cw_bw), "---");
884         gtk_entry_set_text(GTK_ENTRY(ui.eta.cw_iops), "---");
885 #endif
886
887         if (je->eta_sec != INT_MAX && je->nr_running) {
888                 char *iops_str[2];
889                 char *rate_str[2];
890
891                 if ((!je->eta_sec && !eta_good) || je->nr_ramp == je->nr_running)
892                         strcpy(output, "-.-% done");
893                 else {
894                         eta_good = 1;
895                         perc *= 100.0;
896                         sprintf(output, "%3.1f%% done", perc);
897                 }
898
899                 rate_str[0] = num2str(je->rate[0], 5, 10, i2p);
900                 rate_str[1] = num2str(je->rate[1], 5, 10, i2p);
901
902                 iops_str[0] = num2str(je->iops[0], 4, 1, 0);
903                 iops_str[1] = num2str(je->iops[1], 4, 1, 0);
904
905                 gtk_entry_set_text(GTK_ENTRY(ui.eta.read_bw), rate_str[0]);
906                 gtk_entry_set_text(GTK_ENTRY(ui.eta.read_iops), iops_str[0]);
907                 gtk_entry_set_text(GTK_ENTRY(ui.eta.write_bw), rate_str[1]);
908                 gtk_entry_set_text(GTK_ENTRY(ui.eta.write_iops), iops_str[1]);
909
910                 free(rate_str[0]);
911                 free(rate_str[1]);
912                 free(iops_str[0]);
913                 free(iops_str[1]);
914         }
915
916         if (eta_str[0]) {
917                 char *dst = output + strlen(output);
918
919                 sprintf(dst, " - %s", eta_str);
920         }
921                 
922         gfio_update_thread_status(output, perc);
923         gdk_threads_leave();
924 }
925
926 static void gfio_probe_op(struct fio_client *client, struct fio_net_cmd *cmd)
927 {
928         struct cmd_probe_pdu *probe = (struct cmd_probe_pdu *) cmd->payload;
929         const char *os, *arch;
930         char buf[64];
931
932         os = fio_get_os_string(probe->os);
933         if (!os)
934                 os = "unknown";
935
936         arch = fio_get_arch_string(probe->arch);
937         if (!arch)
938                 os = "unknown";
939
940         if (!client->name)
941                 client->name = strdup((char *) probe->hostname);
942
943         gdk_threads_enter();
944
945         gtk_label_set_text(GTK_LABEL(ui.probe.hostname), (char *) probe->hostname);
946         gtk_label_set_text(GTK_LABEL(ui.probe.os), os);
947         gtk_label_set_text(GTK_LABEL(ui.probe.arch), arch);
948         sprintf(buf, "%u.%u.%u", probe->fio_major, probe->fio_minor, probe->fio_patch);
949         gtk_label_set_text(GTK_LABEL(ui.probe.fio_ver), buf);
950
951         gdk_threads_leave();
952 }
953
954 static void gfio_update_thread_status(char *status_message, double perc)
955 {
956         static char message[100];
957         const char *m = message;
958
959         strncpy(message, status_message, sizeof(message) - 1);
960         gtk_progress_bar_set_text(
961                 GTK_PROGRESS_BAR(ui.thread_status_pb), m);
962         gtk_progress_bar_set_fraction(
963                 GTK_PROGRESS_BAR(ui.thread_status_pb), perc / 100.0);
964         gtk_widget_queue_draw(ui.window);
965 }
966
967 static void gfio_quit_op(struct fio_client *client)
968 {
969         struct gui *ui = client->client_data;
970
971         gdk_threads_enter();
972         gfio_set_connected(ui, 0);
973         gdk_threads_leave();
974 }
975
976 static void gfio_add_job_op(struct fio_client *client, struct fio_net_cmd *cmd)
977 {
978         struct cmd_add_job_pdu *p = (struct cmd_add_job_pdu *) cmd->payload;
979         struct gui *ui = client->client_data;
980         char tmp[8];
981         int i;
982
983         p->iodepth              = le32_to_cpu(p->iodepth);
984         p->rw                   = le32_to_cpu(p->rw);
985
986         for (i = 0; i < 2; i++) {
987                 p->min_bs[i]    = le32_to_cpu(p->min_bs[i]);
988                 p->max_bs[i]    = le32_to_cpu(p->max_bs[i]);
989         }
990
991         p->numjobs              = le32_to_cpu(p->numjobs);
992         p->group_reporting      = le32_to_cpu(p->group_reporting);
993
994         gdk_threads_enter();
995
996         gtk_entry_set_text(GTK_ENTRY(ui->eta.name), (gchar *) p->jobname);
997         gtk_entry_set_text(GTK_ENTRY(ui->eta.iotype), ddir_str(p->rw));
998         gtk_entry_set_text(GTK_ENTRY(ui->eta.ioengine), (gchar *) p->ioengine);
999
1000         sprintf(tmp, "%u", p->iodepth);
1001         gtk_entry_set_text(GTK_ENTRY(ui->eta.iodepth), tmp);
1002
1003         gdk_threads_leave();
1004 }
1005
1006 static void gfio_client_timed_out(struct fio_client *client)
1007 {
1008         struct gui *ui = client->client_data;
1009         GtkWidget *dialog, *label, *content;
1010         char buf[256];
1011
1012         gdk_threads_enter();
1013
1014         gfio_set_connected(ui, 0);
1015         clear_ui_info(ui);
1016
1017         sprintf(buf, "Client %s: timeout talking to server.\n", client->hostname);
1018
1019         dialog = gtk_dialog_new_with_buttons("Timed out!",
1020                         GTK_WINDOW(ui->window),
1021                         GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
1022                         GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
1023
1024         content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
1025         label = gtk_label_new((const gchar *) buf);
1026         gtk_container_add(GTK_CONTAINER(content), label);
1027         gtk_widget_show_all(dialog);
1028         gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
1029
1030         gtk_dialog_run(GTK_DIALOG(dialog));
1031         gtk_widget_destroy(dialog);
1032
1033         gdk_threads_leave();
1034 }
1035
1036 struct client_ops gfio_client_ops = {
1037         .text_op                = gfio_text_op,
1038         .disk_util              = gfio_disk_util_op,
1039         .thread_status          = gfio_thread_status_op,
1040         .group_stats            = gfio_group_stats_op,
1041         .eta                    = gfio_update_eta,
1042         .probe                  = gfio_probe_op,
1043         .quit                   = gfio_quit_op,
1044         .add_job                = gfio_add_job_op,
1045         .timed_out              = gfio_client_timed_out,
1046         .stay_connected         = 1,
1047 };
1048
1049 static void quit_clicked(__attribute__((unused)) GtkWidget *widget,
1050                 __attribute__((unused)) gpointer data)
1051 {
1052         gtk_main_quit();
1053 }
1054
1055 static void *job_thread(void *arg)
1056 {
1057         fio_handle_clients(&gfio_client_ops);
1058         return NULL;
1059 }
1060
1061 static int send_job_files(struct gui *ui)
1062 {
1063         int i, ret = 0;
1064
1065         for (i = 0; i < ui->nr_job_files; i++) {
1066                 ret = fio_clients_send_ini(ui->job_files[i]);
1067                 if (ret)
1068                         break;
1069
1070                 free(ui->job_files[i]);
1071                 ui->job_files[i] = NULL;
1072         }
1073         while (i < ui->nr_job_files) {
1074                 free(ui->job_files[i]);
1075                 ui->job_files[i] = NULL;
1076                 i++;
1077         }
1078
1079         return ret;
1080 }
1081
1082 static void start_job_thread(struct gui *ui)
1083 {
1084         if (send_job_files(ui)) {
1085                 printf("Yeah, I didn't really like those options too much.\n");
1086                 gtk_widget_set_sensitive(ui->button[START_JOB_BUTTON], 1);
1087                 return;
1088         }
1089 }
1090
1091 static void start_job_clicked(__attribute__((unused)) GtkWidget *widget,
1092                 gpointer data)
1093 {
1094         struct gui *ui = data;
1095
1096         gtk_widget_set_sensitive(ui->button[START_JOB_BUTTON], 0);
1097         start_job_thread(ui);
1098 }
1099
1100 static void file_open(GtkWidget *w, gpointer data);
1101
1102 static void connect_clicked(GtkWidget *widget, gpointer data)
1103 {
1104         struct gui *ui = data;
1105
1106         if (!ui->connected) {
1107                 if (!ui->nr_job_files)
1108                         file_open(widget, data);
1109                 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No jobs running");
1110                 fio_clients_connect();
1111                 pthread_create(&ui->t, NULL, job_thread, NULL);
1112                 gfio_set_connected(ui, 1);
1113         } else {
1114                 fio_clients_terminate();
1115                 gfio_set_connected(ui, 0);
1116                 clear_ui_info(ui);
1117         }
1118 }
1119
1120 static void add_button(struct gui *ui, int i, GtkWidget *buttonbox,
1121                         struct button_spec *buttonspec)
1122 {
1123         ui->button[i] = gtk_button_new_with_label(buttonspec->buttontext);
1124         g_signal_connect(ui->button[i], "clicked", G_CALLBACK (buttonspec->f), ui);
1125         gtk_box_pack_start(GTK_BOX (ui->buttonbox), ui->button[i], FALSE, FALSE, 3);
1126         gtk_widget_set_tooltip_text(ui->button[i], buttonspeclist[i].tooltiptext);
1127         gtk_widget_set_sensitive(ui->button[i], !buttonspec->start_insensitive);
1128 }
1129
1130 static void add_buttons(struct gui *ui,
1131                                 struct button_spec *buttonlist,
1132                                 int nbuttons)
1133 {
1134         int i;
1135
1136         for (i = 0; i < nbuttons; i++)
1137                 add_button(ui, i, ui->buttonbox, &buttonlist[i]);
1138 }
1139
1140 static void on_info_bar_response(GtkWidget *widget, gint response,
1141                                  gpointer data)
1142 {
1143         if (response == GTK_RESPONSE_OK) {
1144                 gtk_widget_destroy(widget);
1145                 ui.error_info_bar = NULL;
1146         }
1147 }
1148
1149 void report_error(GError *error)
1150 {
1151         if (ui.error_info_bar == NULL) {
1152                 ui.error_info_bar = gtk_info_bar_new_with_buttons(GTK_STOCK_OK,
1153                                                                GTK_RESPONSE_OK,
1154                                                                NULL);
1155                 g_signal_connect(ui.error_info_bar, "response", G_CALLBACK(on_info_bar_response), NULL);
1156                 gtk_info_bar_set_message_type(GTK_INFO_BAR(ui.error_info_bar),
1157                                               GTK_MESSAGE_ERROR);
1158                 
1159                 ui.error_label = gtk_label_new(error->message);
1160                 GtkWidget *container = gtk_info_bar_get_content_area(GTK_INFO_BAR(ui.error_info_bar));
1161                 gtk_container_add(GTK_CONTAINER(container), ui.error_label);
1162                 
1163                 gtk_box_pack_start(GTK_BOX(ui.vbox), ui.error_info_bar, FALSE, FALSE, 0);
1164                 gtk_widget_show_all(ui.vbox);
1165         } else {
1166                 char buffer[256];
1167                 snprintf(buffer, sizeof(buffer), "Failed to open file.");
1168                 gtk_label_set(GTK_LABEL(ui.error_label), buffer);
1169         }
1170 }
1171
1172 static int get_connection_details(char **host, int *port, int *type,
1173                                   int *server_start)
1174 {
1175         GtkWidget *dialog, *box, *vbox, *hentry, *hbox, *frame, *pentry, *combo;
1176         GtkWidget *button;
1177         char *typeentry;
1178
1179         dialog = gtk_dialog_new_with_buttons("Connection details",
1180                         GTK_WINDOW(ui.window),
1181                         GTK_DIALOG_DESTROY_WITH_PARENT,
1182                         GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
1183                         GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL);
1184
1185         frame = gtk_frame_new("Hostname / socket name");
1186         vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
1187         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
1188
1189         box = gtk_vbox_new(FALSE, 6);
1190         gtk_container_add(GTK_CONTAINER(frame), box);
1191
1192         hbox = gtk_hbox_new(TRUE, 10);
1193         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
1194         hentry = gtk_entry_new();
1195         gtk_entry_set_text(GTK_ENTRY(hentry), "localhost");
1196         gtk_box_pack_start(GTK_BOX(hbox), hentry, TRUE, TRUE, 0);
1197
1198         frame = gtk_frame_new("Port");
1199         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
1200         box = gtk_vbox_new(FALSE, 10);
1201         gtk_container_add(GTK_CONTAINER(frame), box);
1202
1203         hbox = gtk_hbox_new(TRUE, 4);
1204         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
1205         pentry = create_spinbutton(hbox, 1, 65535, FIO_NET_PORT);
1206
1207         frame = gtk_frame_new("Type");
1208         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
1209         box = gtk_vbox_new(FALSE, 10);
1210         gtk_container_add(GTK_CONTAINER(frame), box);
1211
1212         hbox = gtk_hbox_new(TRUE, 4);
1213         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
1214
1215         combo = gtk_combo_box_text_new();
1216         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), "IPv4");
1217         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), "IPv6");
1218         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), "local socket");
1219         gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
1220
1221         gtk_container_add(GTK_CONTAINER(hbox), combo);
1222
1223         frame = gtk_frame_new("Options");
1224         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
1225         box = gtk_vbox_new(FALSE, 10);
1226         gtk_container_add(GTK_CONTAINER(frame), box);
1227
1228         hbox = gtk_hbox_new(TRUE, 4);
1229         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
1230
1231         button = gtk_check_button_new_with_label("Auto-spawn fio backend");
1232         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), 1);
1233         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.");
1234         gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 6);
1235
1236         gtk_widget_show_all(dialog);
1237
1238         if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
1239                 gtk_widget_destroy(dialog);
1240                 return 1;
1241         }
1242
1243         *host = strdup(gtk_entry_get_text(GTK_ENTRY(hentry)));
1244         *port = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(pentry));
1245
1246         typeentry = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(combo));
1247         if (!typeentry || !strncmp(typeentry, "IPv4", 4))
1248                 *type = Fio_client_ipv4;
1249         else if (!strncmp(typeentry, "IPv6", 4))
1250                 *type = Fio_client_ipv6;
1251         else
1252                 *type = Fio_client_socket;
1253         g_free(typeentry);
1254
1255         *server_start = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button));
1256
1257         gtk_widget_destroy(dialog);
1258         return 0;
1259 }
1260
1261 static void file_open(GtkWidget *w, gpointer data)
1262 {
1263         GtkWidget *dialog;
1264         GSList *filenames, *fn_glist;
1265         GtkFileFilter *filter;
1266         char *host;
1267         int port, type, server_start;
1268
1269         dialog = gtk_file_chooser_dialog_new("Open File",
1270                 GTK_WINDOW(ui.window),
1271                 GTK_FILE_CHOOSER_ACTION_OPEN,
1272                 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1273                 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
1274                 NULL);
1275         gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);
1276
1277         filter = gtk_file_filter_new();
1278         gtk_file_filter_add_pattern(filter, "*.fio");
1279         gtk_file_filter_add_pattern(filter, "*.job");
1280         gtk_file_filter_add_mime_type(filter, "text/fio");
1281         gtk_file_filter_set_name(filter, "Fio job file");
1282         gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter);
1283
1284         if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
1285                 gtk_widget_destroy(dialog);
1286                 return;
1287         }
1288
1289         fn_glist = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
1290
1291         gtk_widget_destroy(dialog);
1292
1293         if (get_connection_details(&host, &port, &type, &server_start))
1294                 goto err;
1295
1296         filenames = fn_glist;
1297         while (filenames != NULL) {
1298                 ui.job_files = realloc(ui.job_files, (ui.nr_job_files + 1) * sizeof(char *));
1299                 ui.job_files[ui.nr_job_files] = strdup(filenames->data);
1300                 ui.nr_job_files++;
1301
1302                 ui.client = fio_client_add_explicit(&gfio_client_ops, host, type, port);
1303                 if (!ui.client) {
1304                         GError *error;
1305
1306                         error = g_error_new(g_quark_from_string("fio"), 1,
1307                                         "Failed to add client %s", host);
1308                         report_error(error);
1309                         g_error_free(error);
1310                 }
1311                 ui.client->client_data = &ui;
1312                         
1313                 g_free(filenames->data);
1314                 filenames = g_slist_next(filenames);
1315         }
1316         free(host);
1317 err:
1318         g_slist_free(fn_glist);
1319 }
1320
1321 static void file_save(GtkWidget *w, gpointer data)
1322 {
1323         GtkWidget *dialog;
1324
1325         dialog = gtk_file_chooser_dialog_new("Save File",
1326                 GTK_WINDOW(ui.window),
1327                 GTK_FILE_CHOOSER_ACTION_SAVE,
1328                 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
1329                 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
1330                 NULL);
1331
1332         gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
1333         gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "Untitled document");
1334
1335         if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
1336                 char *filename;
1337
1338                 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
1339                 // save_job_file(filename);
1340                 g_free(filename);
1341         }
1342         gtk_widget_destroy(dialog);
1343 }
1344
1345 static void preferences(GtkWidget *w, gpointer data)
1346 {
1347         GtkWidget *dialog, *frame, *box, **buttons;
1348         int i;
1349
1350         dialog = gtk_dialog_new_with_buttons("Preferences",
1351                 GTK_WINDOW(ui.window),
1352                 GTK_DIALOG_DESTROY_WITH_PARENT,
1353                 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
1354                 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
1355                 NULL);
1356
1357         frame = gtk_frame_new("Debug logging");
1358         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5);
1359         box = gtk_hbox_new(FALSE, 6);
1360         gtk_container_add(GTK_CONTAINER(frame), box);
1361
1362         buttons = malloc(sizeof(GtkWidget *) * FD_DEBUG_MAX);
1363
1364         for (i = 0; i < FD_DEBUG_MAX; i++) {
1365                 buttons[i] = gtk_check_button_new_with_label(debug_levels[i].name);
1366                 gtk_widget_set_tooltip_text(buttons[i], debug_levels[i].help);
1367                 gtk_box_pack_start(GTK_BOX(box), buttons[i], FALSE, FALSE, 6);
1368         }
1369
1370         gtk_widget_show_all(dialog);
1371
1372         if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
1373                 gtk_widget_destroy(dialog);
1374                 return;
1375         }
1376
1377         for (i = 0; i < FD_DEBUG_MAX; i++) {
1378                 int set;
1379
1380                 set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(buttons[i]));
1381                 if (set)
1382                         fio_debug |= (1UL << i);
1383         }
1384
1385         gtk_widget_destroy(dialog);
1386 }
1387
1388 static void about_dialog(GtkWidget *w, gpointer data)
1389 {
1390         gtk_show_about_dialog(NULL,
1391                 "program-name", "gfio",
1392                 "comments", "Gtk2 UI for fio",
1393                 "license", "GPLv2",
1394                 "version", fio_version_string,
1395                 "copyright", "Jens Axboe <axboe@kernel.dk> 2012",
1396                 "logo-icon-name", "fio",
1397                 /* Must be last: */
1398                 NULL, NULL,
1399                 NULL);
1400 }
1401
1402 static GtkActionEntry menu_items[] = {
1403         { "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL},
1404         { "HelpMenuAction", GTK_STOCK_HELP, "Help", NULL, NULL, NULL},
1405         { "OpenFile", GTK_STOCK_OPEN, NULL,   "<Control>O", NULL, G_CALLBACK(file_open) },
1406         { "SaveFile", GTK_STOCK_SAVE, NULL,   "<Control>S", NULL, G_CALLBACK(file_save) },
1407         { "Preferences", GTK_STOCK_PREFERENCES, NULL, "<Control>p", NULL, G_CALLBACK(preferences) },
1408         { "Quit", GTK_STOCK_QUIT, NULL,   "<Control>Q", NULL, G_CALLBACK(quit_clicked) },
1409         { "About", GTK_STOCK_ABOUT, NULL,  NULL, NULL, G_CALLBACK(about_dialog) },
1410 };
1411 static gint nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]);
1412
1413 static const gchar *ui_string = " \
1414         <ui> \
1415                 <menubar name=\"MainMenu\"> \
1416                         <menu name=\"FileMenu\" action=\"FileMenuAction\"> \
1417                                 <menuitem name=\"Open\" action=\"OpenFile\" /> \
1418                                 <menuitem name=\"Save\" action=\"SaveFile\" /> \
1419                                 <separator name=\"Separator\"/> \
1420                                 <menuitem name=\"Preferences\" action=\"Preferences\" /> \
1421                                 <separator name=\"Separator2\"/> \
1422                                 <menuitem name=\"Quit\" action=\"Quit\" /> \
1423                         </menu> \
1424                         <menu name=\"Help\" action=\"HelpMenuAction\"> \
1425                                 <menuitem name=\"About\" action=\"About\" /> \
1426                         </menu> \
1427                 </menubar> \
1428         </ui> \
1429 ";
1430
1431 static GtkWidget *get_menubar_menu(GtkWidget *window, GtkUIManager *ui_manager)
1432 {
1433         GtkActionGroup *action_group = gtk_action_group_new("Menu");
1434         GError *error = 0;
1435
1436         action_group = gtk_action_group_new("Menu");
1437         gtk_action_group_add_actions(action_group, menu_items, nmenu_items, 0);
1438
1439         gtk_ui_manager_insert_action_group(ui_manager, action_group, 0);
1440         gtk_ui_manager_add_ui_from_string(GTK_UI_MANAGER(ui_manager), ui_string, -1, &error);
1441
1442         gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(ui_manager));
1443         return gtk_ui_manager_get_widget(ui_manager, "/MainMenu");
1444 }
1445
1446 void gfio_ui_setup(GtkSettings *settings, GtkWidget *menubar,
1447                    GtkWidget *vbox, GtkUIManager *ui_manager)
1448 {
1449         gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);
1450 }
1451
1452 static void init_ui(int *argc, char **argv[], struct gui *ui)
1453 {
1454         GtkSettings *settings;
1455         GtkUIManager *uimanager;
1456         GtkWidget *menu, *probe, *probe_frame, *probe_box;
1457
1458         memset(ui, 0, sizeof(*ui));
1459
1460         /* Magical g*thread incantation, you just need this thread stuff.
1461          * Without it, the update that happens in gfio_update_thread_status
1462          * doesn't really happen in a timely fashion, you need expose events
1463          */
1464         if (!g_thread_supported())
1465                 g_thread_init(NULL);
1466         gdk_threads_init();
1467
1468         gtk_init(argc, argv);
1469         settings = gtk_settings_get_default();
1470         gtk_settings_set_long_property(settings, "gtk_tooltip_timeout", 10, "gfio setting");
1471         g_type_init();
1472         
1473         ui->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
1474         gtk_window_set_title(GTK_WINDOW(ui->window), "fio");
1475         gtk_window_set_default_size(GTK_WINDOW(ui->window), 700, 500);
1476
1477         g_signal_connect(ui->window, "delete-event", G_CALLBACK(quit_clicked), NULL);
1478         g_signal_connect(ui->window, "destroy", G_CALLBACK(quit_clicked), NULL);
1479
1480         ui->vbox = gtk_vbox_new(FALSE, 0);
1481         gtk_container_add(GTK_CONTAINER (ui->window), ui->vbox);
1482
1483         uimanager = gtk_ui_manager_new();
1484         menu = get_menubar_menu(ui->window, uimanager);
1485         gfio_ui_setup(settings, menu, ui->vbox, uimanager);
1486
1487         /*
1488          * Set up alignments for widgets at the top of ui, 
1489          * align top left, expand horizontally but not vertically
1490          */
1491         ui->topalign = gtk_alignment_new(0, 0, 1, 0);
1492         ui->topvbox = gtk_vbox_new(FALSE, 3);
1493         gtk_container_add(GTK_CONTAINER(ui->topalign), ui->topvbox);
1494         gtk_box_pack_start(GTK_BOX(ui->vbox), ui->topalign, FALSE, FALSE, 0);
1495
1496         probe = gtk_frame_new("Job");
1497         gtk_box_pack_start(GTK_BOX(ui->topvbox), probe, TRUE, FALSE, 3);
1498         probe_frame = gtk_vbox_new(FALSE, 3);
1499         gtk_container_add(GTK_CONTAINER(probe), probe_frame);
1500
1501         probe_box = gtk_hbox_new(FALSE, 3);
1502         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
1503         ui->probe.hostname = new_info_label_in_frame(probe_box, "Host");
1504         ui->probe.os = new_info_label_in_frame(probe_box, "OS");
1505         ui->probe.arch = new_info_label_in_frame(probe_box, "Architecture");
1506         ui->probe.fio_ver = new_info_label_in_frame(probe_box, "Fio version");
1507
1508         probe_box = gtk_hbox_new(FALSE, 3);
1509         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
1510
1511         ui->eta.name = new_info_entry_in_frame(probe_box, "Name");
1512         ui->eta.iotype = new_info_entry_in_frame(probe_box, "IO");
1513         ui->eta.ioengine = new_info_entry_in_frame(probe_box, "IO Engine");
1514         ui->eta.iodepth = new_info_entry_in_frame(probe_box, "IO Depth");
1515         ui->eta.jobs = new_info_entry_in_frame(probe_box, "Jobs");
1516         ui->eta.files = new_info_entry_in_frame(probe_box, "Open files");
1517
1518         probe_box = gtk_hbox_new(FALSE, 3);
1519         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
1520         ui->eta.read_bw = new_info_entry_in_frame(probe_box, "Read BW");
1521         ui->eta.read_iops = new_info_entry_in_frame(probe_box, "IOPS");
1522         ui->eta.write_bw = new_info_entry_in_frame(probe_box, "Write BW");
1523         ui->eta.write_iops = new_info_entry_in_frame(probe_box, "IOPS");
1524
1525         /*
1526          * Only add this if we have a commit rate
1527          */
1528 #if 0
1529         probe_box = gtk_hbox_new(FALSE, 3);
1530         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
1531
1532         ui->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
1533         ui->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1534
1535         ui->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
1536         ui->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
1537 #endif
1538
1539         /*
1540          * Add a text box for text op messages 
1541          */
1542         ui->textview = gtk_text_view_new();
1543         ui->text = gtk_text_view_get_buffer(GTK_TEXT_VIEW(ui->textview));
1544         gtk_text_buffer_set_text(ui->text, "", -1);
1545         gtk_text_view_set_editable(GTK_TEXT_VIEW(ui->textview), FALSE);
1546         gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(ui->textview), FALSE);
1547         ui->scrolled_window = gtk_scrolled_window_new(NULL, NULL);
1548         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(ui->scrolled_window),
1549                                         GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
1550         gtk_container_add(GTK_CONTAINER(ui->scrolled_window), ui->textview);
1551         gtk_box_pack_start(GTK_BOX(ui->vbox), ui->scrolled_window,
1552                         TRUE, TRUE, 0);
1553
1554         /*
1555          * Set up alignments for widgets at the bottom of ui, 
1556          * align bottom left, expand horizontally but not vertically
1557          */
1558         ui->bottomalign = gtk_alignment_new(0, 1, 1, 0);
1559         ui->buttonbox = gtk_hbox_new(FALSE, 0);
1560         gtk_container_add(GTK_CONTAINER(ui->bottomalign), ui->buttonbox);
1561         gtk_box_pack_start(GTK_BOX(ui->vbox), ui->bottomalign,
1562                                         FALSE, FALSE, 0);
1563
1564         add_buttons(ui, buttonspeclist, ARRAYSIZE(buttonspeclist));
1565
1566         /*
1567          * Set up thread status progress bar
1568          */
1569         ui->thread_status_pb = gtk_progress_bar_new();
1570         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->thread_status_pb), 0.0);
1571         gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No connections");
1572         gtk_container_add(GTK_CONTAINER(ui->buttonbox), ui->thread_status_pb);
1573
1574
1575         gtk_widget_show_all(ui->window);
1576 }
1577
1578 int main(int argc, char *argv[], char *envp[])
1579 {
1580         if (initialize_fio(envp))
1581                 return 1;
1582         if (fio_init_options())
1583                 return 1;
1584
1585         init_ui(&argc, &argv, &ui);
1586
1587         gdk_threads_enter();
1588         gtk_main();
1589         gdk_threads_leave();
1590         return 0;
1591 }