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