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