Pass in client for thread/group status op
[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_label_set_text(GTK_LABEL(ui->eta.name), "");
109         gtk_label_set_text(GTK_LABEL(ui->eta.iotype), "");
110         gtk_label_set_text(GTK_LABEL(ui->eta.ioengine), "");
111         gtk_label_set_text(GTK_LABEL(ui->eta.iodepth), "");
112         gtk_label_set_text(GTK_LABEL(ui->eta.jobs), "");
113         gtk_label_set_text(GTK_LABEL(ui->eta.files), "");
114         gtk_label_set_text(GTK_LABEL(ui->eta.read_bw), "");
115         gtk_label_set_text(GTK_LABEL(ui->eta.read_iops), "");
116         gtk_label_set_text(GTK_LABEL(ui->eta.write_bw), "");
117         gtk_label_set_text(GTK_LABEL(ui->eta.write_iops), "");
118 }
119
120 static void gfio_set_connected(struct gui *ui, int connected)
121 {
122         if (connected) {
123                 gtk_widget_set_sensitive(ui->button[START_JOB_BUTTON], 1);
124                 ui->connected = 1;
125                 gtk_button_set_label(GTK_BUTTON(ui->button[CONNECT_BUTTON]), "Disconnect");
126         } else {
127                 ui->connected = 0;
128                 gtk_button_set_label(GTK_BUTTON(ui->button[CONNECT_BUTTON]), "Connect");
129                 gtk_widget_set_sensitive(ui->button[START_JOB_BUTTON], 0);
130         }
131 }
132
133 static void gfio_text_op(struct fio_client *client, struct fio_net_cmd *cmd)
134 {
135 #if 0
136         GtkTextBuffer *buffer;
137         GtkTextIter end;
138
139         buffer = gtk_text_view_get_buffer(GTK_TEXT_VIEW(ui.textview));
140         gdk_threads_enter();
141         gtk_text_buffer_get_end_iter(buffer, &end);
142         gtk_text_buffer_insert(buffer, &end, buf, -1);
143         gdk_threads_leave();
144         gtk_text_view_scroll_to_iter(GTK_TEXT_VIEW(ui.textview),
145                                         &end, 0.0, FALSE, 0.0,0.0);
146 #else
147         fio_client_ops.text_op(client, cmd);
148 #endif
149 }
150
151 static void gfio_disk_util_op(struct fio_client *client, struct fio_net_cmd *cmd)
152 {
153         printf("gfio_disk_util_op called\n");
154         fio_client_ops.disk_util(client, cmd);
155 }
156
157 static void gfio_thread_status_op(struct fio_client *client,
158                                   struct fio_net_cmd *cmd)
159 {
160         printf("gfio_thread_status_op called\n");
161         fio_client_ops.thread_status(client, cmd);
162 }
163
164 static void gfio_group_stats_op(struct fio_client *client,
165                                 struct fio_net_cmd *cmd)
166 {
167         printf("gfio_group_stats_op called\n");
168         fio_client_ops.group_stats(client, cmd);
169 }
170
171 static void gfio_update_eta(struct jobs_eta *je)
172 {
173         static int eta_good;
174         char eta_str[128];
175         char output[256];
176         char tmp[32];
177         double perc = 0.0;
178         int i2p = 0;
179
180         eta_str[0] = '\0';
181         output[0] = '\0';
182
183         if (je->eta_sec != INT_MAX && je->elapsed_sec) {
184                 perc = (double) je->elapsed_sec / (double) (je->elapsed_sec + je->eta_sec);
185                 eta_to_str(eta_str, je->eta_sec);
186         }
187
188         sprintf(tmp, "%u", je->nr_running);
189         gtk_label_set_text(GTK_LABEL(ui.eta.jobs), tmp);
190         sprintf(tmp, "%u", je->files_open);
191         gtk_label_set_text(GTK_LABEL(ui.eta.files), tmp);
192
193 #if 0
194         if (je->m_rate[0] || je->m_rate[1] || je->t_rate[0] || je->t_rate[1]) {
195         if (je->m_rate || je->t_rate) {
196                 char *tr, *mr;
197
198                 mr = num2str(je->m_rate, 4, 0, i2p);
199                 tr = num2str(je->t_rate, 4, 0, i2p);
200                 gtk_label_set_text(GTK_LABEL(ui.eta.
201                 p += sprintf(p, ", CR=%s/%s KB/s", tr, mr);
202                 free(tr);
203                 free(mr);
204         } else if (je->m_iops || je->t_iops)
205                 p += sprintf(p, ", CR=%d/%d IOPS", je->t_iops, je->m_iops);
206
207         gtk_label_set_text(GTK_LABEL(ui.eta.cr_bw), "---");
208         gtk_label_set_text(GTK_LABEL(ui.eta.cr_iops), "---");
209         gtk_label_set_text(GTK_LABEL(ui.eta.cw_bw), "---");
210         gtk_label_set_text(GTK_LABEL(ui.eta.cw_iops), "---");
211 #endif
212
213         if (je->eta_sec != INT_MAX && je->nr_running) {
214                 char *iops_str[2];
215                 char *rate_str[2];
216
217                 if ((!je->eta_sec && !eta_good) || je->nr_ramp == je->nr_running)
218                         strcpy(output, "-.-% done");
219                 else {
220                         eta_good = 1;
221                         perc *= 100.0;
222                         sprintf(output, "%3.1f%% done", perc);
223                 }
224
225                 rate_str[0] = num2str(je->rate[0], 5, 10, i2p);
226                 rate_str[1] = num2str(je->rate[1], 5, 10, i2p);
227
228                 iops_str[0] = num2str(je->iops[0], 4, 1, 0);
229                 iops_str[1] = num2str(je->iops[1], 4, 1, 0);
230
231                 gtk_label_set_text(GTK_LABEL(ui.eta.read_bw), rate_str[0]);
232                 gtk_label_set_text(GTK_LABEL(ui.eta.read_iops), iops_str[0]);
233                 gtk_label_set_text(GTK_LABEL(ui.eta.write_bw), rate_str[1]);
234                 gtk_label_set_text(GTK_LABEL(ui.eta.write_iops), iops_str[1]);
235
236                 free(rate_str[0]);
237                 free(rate_str[1]);
238                 free(iops_str[0]);
239                 free(iops_str[1]);
240         }
241
242         if (eta_str[0]) {
243                 char *dst = output + strlen(output);
244
245                 sprintf(dst, " - %s", eta_str);
246         }
247                 
248         gfio_update_thread_status(output, perc);
249 }
250
251 static void gfio_probe_op(struct fio_client *client, struct fio_net_cmd *cmd)
252 {
253         struct cmd_probe_pdu *probe = (struct cmd_probe_pdu *) cmd->payload;
254         const char *os, *arch;
255         char buf[64];
256
257         os = fio_get_os_string(probe->os);
258         if (!os)
259                 os = "unknown";
260
261         arch = fio_get_arch_string(probe->arch);
262         if (!arch)
263                 os = "unknown";
264
265         if (!client->name)
266                 client->name = strdup((char *) probe->hostname);
267
268         gtk_label_set_text(GTK_LABEL(ui.probe.hostname), (char *) probe->hostname);
269         gtk_label_set_text(GTK_LABEL(ui.probe.os), os);
270         gtk_label_set_text(GTK_LABEL(ui.probe.arch), arch);
271         sprintf(buf, "%u.%u.%u", probe->fio_major, probe->fio_minor, probe->fio_patch);
272         gtk_label_set_text(GTK_LABEL(ui.probe.fio_ver), buf);
273 }
274
275 static void gfio_update_thread_status(char *status_message, double perc)
276 {
277         static char message[100];
278         const char *m = message;
279
280         strncpy(message, status_message, sizeof(message) - 1);
281         gtk_progress_bar_set_text(
282                 GTK_PROGRESS_BAR(ui.thread_status_pb), m);
283         gtk_progress_bar_set_fraction(
284                 GTK_PROGRESS_BAR(ui.thread_status_pb), perc / 100.0);
285         gdk_threads_enter();
286         gtk_widget_queue_draw(ui.window);
287         gdk_threads_leave();
288 }
289
290 static void gfio_quit_op(struct fio_client *client)
291 {
292         struct gui *ui = client->client_data;
293
294         gfio_set_connected(ui, 0);
295 }
296
297 static void gfio_add_job_op(struct fio_client *client, struct fio_net_cmd *cmd)
298 {
299         struct cmd_add_job_pdu *p = (struct cmd_add_job_pdu *) cmd->payload;
300         struct gui *ui = client->client_data;
301         char tmp[8];
302         int i;
303
304         p->iodepth              = le32_to_cpu(p->iodepth);
305         p->rw                   = le32_to_cpu(p->rw);
306
307         for (i = 0; i < 2; i++) {
308                 p->min_bs[i]    = le32_to_cpu(p->min_bs[i]);
309                 p->max_bs[i]    = le32_to_cpu(p->max_bs[i]);
310         }
311
312         p->numjobs              = le32_to_cpu(p->numjobs);
313         p->group_reporting      = le32_to_cpu(p->group_reporting);
314
315         gtk_label_set_text(GTK_LABEL(ui->eta.name), (gchar *) p->jobname);
316         gtk_label_set_text(GTK_LABEL(ui->eta.iotype), ddir_str(p->rw));
317         gtk_label_set_text(GTK_LABEL(ui->eta.ioengine), (gchar *) p->ioengine);
318
319         sprintf(tmp, "%u", p->iodepth);
320         gtk_label_set_text(GTK_LABEL(ui->eta.iodepth), tmp);
321 }
322
323 static void gfio_client_timed_out(struct fio_client *client)
324 {
325         struct gui *ui = client->client_data;
326         GtkWidget *dialog, *label, *content;
327         char buf[256];
328
329         gdk_threads_enter();
330
331         gfio_set_connected(ui, 0);
332         clear_ui_info(ui);
333
334         sprintf(buf, "Client %s: timeout talking to server.\n", client->hostname);
335
336         dialog = gtk_dialog_new_with_buttons("Timed out!",
337                         GTK_WINDOW(ui->window),
338                         GTK_DIALOG_MODAL | GTK_DIALOG_DESTROY_WITH_PARENT,
339                         GTK_STOCK_OK, GTK_RESPONSE_OK, NULL);
340
341         content = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
342         label = gtk_label_new((const gchar *) buf);
343         gtk_container_add(GTK_CONTAINER(content), label);
344         gtk_widget_show_all(dialog);
345         gtk_dialog_set_default_response(GTK_DIALOG(dialog), GTK_RESPONSE_ACCEPT);
346
347         gtk_dialog_run(GTK_DIALOG(dialog));
348         gtk_widget_destroy(dialog);
349
350         gdk_threads_leave();
351 }
352
353 struct client_ops gfio_client_ops = {
354         .text_op                = gfio_text_op,
355         .disk_util              = gfio_disk_util_op,
356         .thread_status          = gfio_thread_status_op,
357         .group_stats            = gfio_group_stats_op,
358         .eta                    = gfio_update_eta,
359         .probe                  = gfio_probe_op,
360         .quit                   = gfio_quit_op,
361         .add_job                = gfio_add_job_op,
362         .timed_out              = gfio_client_timed_out,
363         .stay_connected         = 1,
364 };
365
366 static void quit_clicked(__attribute__((unused)) GtkWidget *widget,
367                 __attribute__((unused)) gpointer data)
368 {
369         gtk_main_quit();
370 }
371
372 static void *job_thread(void *arg)
373 {
374         fio_handle_clients(&gfio_client_ops);
375         return NULL;
376 }
377
378 static int send_job_files(struct gui *ui)
379 {
380         int i, ret = 0;
381
382         for (i = 0; i < ui->nr_job_files; i++) {
383                 ret = fio_clients_send_ini(ui->job_files[i]);
384                 if (ret)
385                         break;
386
387                 free(ui->job_files[i]);
388                 ui->job_files[i] = NULL;
389         }
390         while (i < ui->nr_job_files) {
391                 free(ui->job_files[i]);
392                 ui->job_files[i] = NULL;
393                 i++;
394         }
395
396         return ret;
397 }
398
399 static void start_job_thread(struct gui *ui)
400 {
401         if (send_job_files(ui)) {
402                 printf("Yeah, I didn't really like those options too much.\n");
403                 gtk_widget_set_sensitive(ui->button[START_JOB_BUTTON], 1);
404                 return;
405         }
406 }
407
408 static GtkWidget *new_info_label_in_frame(GtkWidget *box, const char *label)
409 {
410         GtkWidget *label_widget;
411         GtkWidget *frame;
412
413         frame = gtk_frame_new(label);
414         label_widget = gtk_label_new(NULL);
415         gtk_box_pack_start(GTK_BOX(box), frame, TRUE, TRUE, 3);
416         gtk_container_add(GTK_CONTAINER(frame), label_widget);
417
418         return label_widget;
419 }
420
421 static GtkWidget *create_spinbutton(GtkWidget *hbox, double min, double max, double defval)
422 {
423         GtkWidget *button, *box;
424
425         box = gtk_hbox_new(FALSE, 3);
426         gtk_container_add(GTK_CONTAINER(hbox), box);
427
428         button = gtk_spin_button_new_with_range(min, max, 1.0);
429         gtk_box_pack_start(GTK_BOX(box), button, TRUE, TRUE, 0);
430
431         gtk_spin_button_set_update_policy(GTK_SPIN_BUTTON(button), GTK_UPDATE_IF_VALID);
432         gtk_spin_button_set_value(GTK_SPIN_BUTTON(button), defval);
433
434         return button;
435 }
436
437 static void start_job_clicked(__attribute__((unused)) GtkWidget *widget,
438                 gpointer data)
439 {
440         struct gui *ui = data;
441
442         gtk_widget_set_sensitive(ui->button[START_JOB_BUTTON], 0);
443         start_job_thread(ui);
444 }
445
446 static void file_open(GtkWidget *w, gpointer data);
447
448 static void connect_clicked(GtkWidget *widget, gpointer data)
449 {
450         struct gui *ui = data;
451
452         if (!ui->connected) {
453                 if (!ui->nr_job_files)
454                         file_open(widget, data);
455                 gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No jobs running");
456                 fio_clients_connect();
457                 pthread_create(&ui->t, NULL, job_thread, NULL);
458                 gfio_set_connected(ui, 1);
459         } else {
460                 fio_clients_terminate();
461                 gfio_set_connected(ui, 0);
462                 clear_ui_info(ui);
463         }
464 }
465
466 static void add_button(struct gui *ui, int i, GtkWidget *buttonbox,
467                         struct button_spec *buttonspec)
468 {
469         ui->button[i] = gtk_button_new_with_label(buttonspec->buttontext);
470         g_signal_connect(ui->button[i], "clicked", G_CALLBACK (buttonspec->f), ui);
471         gtk_box_pack_start(GTK_BOX (ui->buttonbox), ui->button[i], FALSE, FALSE, 3);
472         gtk_widget_set_tooltip_text(ui->button[i], buttonspeclist[i].tooltiptext);
473         gtk_widget_set_sensitive(ui->button[i], !buttonspec->start_insensitive);
474 }
475
476 static void add_buttons(struct gui *ui,
477                                 struct button_spec *buttonlist,
478                                 int nbuttons)
479 {
480         int i;
481
482         for (i = 0; i < nbuttons; i++)
483                 add_button(ui, i, ui->buttonbox, &buttonlist[i]);
484 }
485
486 static void on_info_bar_response(GtkWidget *widget, gint response,
487                                  gpointer data)
488 {
489         if (response == GTK_RESPONSE_OK) {
490                 gtk_widget_destroy(widget);
491                 ui.error_info_bar = NULL;
492         }
493 }
494
495 void report_error(GError *error)
496 {
497         if (ui.error_info_bar == NULL) {
498                 ui.error_info_bar = gtk_info_bar_new_with_buttons(GTK_STOCK_OK,
499                                                                GTK_RESPONSE_OK,
500                                                                NULL);
501                 g_signal_connect(ui.error_info_bar, "response", G_CALLBACK(on_info_bar_response), NULL);
502                 gtk_info_bar_set_message_type(GTK_INFO_BAR(ui.error_info_bar),
503                                               GTK_MESSAGE_ERROR);
504                 
505                 ui.error_label = gtk_label_new(error->message);
506                 GtkWidget *container = gtk_info_bar_get_content_area(GTK_INFO_BAR(ui.error_info_bar));
507                 gtk_container_add(GTK_CONTAINER(container), ui.error_label);
508                 
509                 gtk_box_pack_start(GTK_BOX(ui.vbox), ui.error_info_bar, FALSE, FALSE, 0);
510                 gtk_widget_show_all(ui.vbox);
511         } else {
512                 char buffer[256];
513                 snprintf(buffer, sizeof(buffer), "Failed to open file.");
514                 gtk_label_set(GTK_LABEL(ui.error_label), buffer);
515         }
516 }
517
518 static int get_connection_details(char **host, int *port, int *type,
519                                   int *server_start)
520 {
521         GtkWidget *dialog, *box, *vbox, *hentry, *hbox, *frame, *pentry, *combo;
522         GtkWidget *button;
523         char *typeentry;
524
525         dialog = gtk_dialog_new_with_buttons("Connection details",
526                         GTK_WINDOW(ui.window),
527                         GTK_DIALOG_DESTROY_WITH_PARENT,
528                         GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
529                         GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT, NULL);
530
531         frame = gtk_frame_new("Hostname / socket name");
532         vbox = gtk_dialog_get_content_area(GTK_DIALOG(dialog));
533         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
534
535         box = gtk_vbox_new(FALSE, 6);
536         gtk_container_add(GTK_CONTAINER(frame), box);
537
538         hbox = gtk_hbox_new(TRUE, 10);
539         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
540         hentry = gtk_entry_new();
541         gtk_entry_set_text(GTK_ENTRY(hentry), "localhost");
542         gtk_box_pack_start(GTK_BOX(hbox), hentry, TRUE, TRUE, 0);
543
544         frame = gtk_frame_new("Port");
545         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
546         box = gtk_vbox_new(FALSE, 10);
547         gtk_container_add(GTK_CONTAINER(frame), box);
548
549         hbox = gtk_hbox_new(TRUE, 4);
550         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
551         pentry = create_spinbutton(hbox, 1, 65535, FIO_NET_PORT);
552
553         frame = gtk_frame_new("Type");
554         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
555         box = gtk_vbox_new(FALSE, 10);
556         gtk_container_add(GTK_CONTAINER(frame), box);
557
558         hbox = gtk_hbox_new(TRUE, 4);
559         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
560
561         combo = gtk_combo_box_text_new();
562         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), "IPv4");
563         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), "IPv6");
564         gtk_combo_box_text_append_text(GTK_COMBO_BOX_TEXT(combo), "local socket");
565         gtk_combo_box_set_active(GTK_COMBO_BOX(combo), 0);
566
567         gtk_container_add(GTK_CONTAINER(hbox), combo);
568
569         frame = gtk_frame_new("Options");
570         gtk_box_pack_start(GTK_BOX(vbox), frame, FALSE, FALSE, 5);
571         box = gtk_vbox_new(FALSE, 10);
572         gtk_container_add(GTK_CONTAINER(frame), box);
573
574         hbox = gtk_hbox_new(TRUE, 4);
575         gtk_box_pack_start(GTK_BOX(box), hbox, FALSE, FALSE, 0);
576
577         button = gtk_check_button_new_with_label("Auto-spawn fio backend");
578         gtk_toggle_button_set_active(GTK_TOGGLE_BUTTON(button), 1);
579         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.");
580         gtk_box_pack_start(GTK_BOX(hbox), button, FALSE, FALSE, 6);
581
582         gtk_widget_show_all(dialog);
583
584         if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
585                 gtk_widget_destroy(dialog);
586                 return 1;
587         }
588
589         *host = strdup(gtk_entry_get_text(GTK_ENTRY(hentry)));
590         *port = gtk_spin_button_get_value_as_int(GTK_SPIN_BUTTON(pentry));
591
592         typeentry = gtk_combo_box_text_get_active_text(GTK_COMBO_BOX_TEXT(combo));
593         if (!typeentry || !strncmp(typeentry, "IPv4", 4))
594                 *type = Fio_client_ipv4;
595         else if (!strncmp(typeentry, "IPv6", 4))
596                 *type = Fio_client_ipv6;
597         else
598                 *type = Fio_client_socket;
599         g_free(typeentry);
600
601         *server_start = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(button));
602
603         gtk_widget_destroy(dialog);
604         return 0;
605 }
606
607 static void file_open(GtkWidget *w, gpointer data)
608 {
609         GtkWidget *dialog;
610         GSList *filenames, *fn_glist;
611         GtkFileFilter *filter;
612         char *host;
613         int port, type, server_start;
614
615         dialog = gtk_file_chooser_dialog_new("Open File",
616                 GTK_WINDOW(ui.window),
617                 GTK_FILE_CHOOSER_ACTION_OPEN,
618                 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
619                 GTK_STOCK_OPEN, GTK_RESPONSE_ACCEPT,
620                 NULL);
621         gtk_file_chooser_set_select_multiple(GTK_FILE_CHOOSER(dialog), TRUE);
622
623         filter = gtk_file_filter_new();
624         gtk_file_filter_add_pattern(filter, "*.fio");
625         gtk_file_filter_add_pattern(filter, "*.job");
626         gtk_file_filter_add_mime_type(filter, "text/fio");
627         gtk_file_filter_set_name(filter, "Fio job file");
628         gtk_file_chooser_set_filter(GTK_FILE_CHOOSER(dialog), filter);
629
630         if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
631                 gtk_widget_destroy(dialog);
632                 return;
633         }
634
635         fn_glist = gtk_file_chooser_get_filenames(GTK_FILE_CHOOSER(dialog));
636
637         gtk_widget_destroy(dialog);
638
639         if (get_connection_details(&host, &port, &type, &server_start))
640                 goto err;
641
642         filenames = fn_glist;
643         while (filenames != NULL) {
644                 ui.job_files = realloc(ui.job_files, (ui.nr_job_files + 1) * sizeof(char *));
645                 ui.job_files[ui.nr_job_files] = strdup(filenames->data);
646                 ui.nr_job_files++;
647
648                 ui.client = fio_client_add_explicit(&gfio_client_ops, host, type, port);
649                 if (!ui.client) {
650                         GError *error;
651
652                         error = g_error_new(g_quark_from_string("fio"), 1,
653                                         "Failed to add client %s", host);
654                         report_error(error);
655                         g_error_free(error);
656                 }
657                 ui.client->client_data = &ui;
658                         
659                 g_free(filenames->data);
660                 filenames = g_slist_next(filenames);
661         }
662         free(host);
663 err:
664         g_slist_free(fn_glist);
665 }
666
667 static void file_save(GtkWidget *w, gpointer data)
668 {
669         GtkWidget *dialog;
670
671         dialog = gtk_file_chooser_dialog_new("Save File",
672                 GTK_WINDOW(ui.window),
673                 GTK_FILE_CHOOSER_ACTION_SAVE,
674                 GTK_STOCK_CANCEL, GTK_RESPONSE_CANCEL,
675                 GTK_STOCK_SAVE, GTK_RESPONSE_ACCEPT,
676                 NULL);
677
678         gtk_file_chooser_set_do_overwrite_confirmation(GTK_FILE_CHOOSER(dialog), TRUE);
679         gtk_file_chooser_set_current_name(GTK_FILE_CHOOSER(dialog), "Untitled document");
680
681         if (gtk_dialog_run(GTK_DIALOG(dialog)) == GTK_RESPONSE_ACCEPT) {
682                 char *filename;
683
684                 filename = gtk_file_chooser_get_filename(GTK_FILE_CHOOSER(dialog));
685                 // save_job_file(filename);
686                 g_free(filename);
687         }
688         gtk_widget_destroy(dialog);
689 }
690
691 static void preferences(GtkWidget *w, gpointer data)
692 {
693         GtkWidget *dialog, *frame, *box, **buttons;
694         int i;
695
696         dialog = gtk_dialog_new_with_buttons("Preferences",
697                 GTK_WINDOW(ui.window),
698                 GTK_DIALOG_DESTROY_WITH_PARENT,
699                 GTK_STOCK_OK, GTK_RESPONSE_ACCEPT,
700                 GTK_STOCK_CANCEL, GTK_RESPONSE_REJECT,
701                 NULL);
702
703         frame = gtk_frame_new("Debug logging");
704         gtk_box_pack_start(GTK_BOX(GTK_DIALOG(dialog)->vbox), frame, FALSE, FALSE, 5);
705         box = gtk_hbox_new(FALSE, 6);
706         gtk_container_add(GTK_CONTAINER(frame), box);
707
708         buttons = malloc(sizeof(GtkWidget *) * FD_DEBUG_MAX);
709
710         for (i = 0; i < FD_DEBUG_MAX; i++) {
711                 buttons[i] = gtk_check_button_new_with_label(debug_levels[i].name);
712                 gtk_widget_set_tooltip_text(buttons[i], debug_levels[i].help);
713                 gtk_box_pack_start(GTK_BOX(box), buttons[i], FALSE, FALSE, 6);
714         }
715
716         gtk_widget_show_all(dialog);
717
718         if (gtk_dialog_run(GTK_DIALOG(dialog)) != GTK_RESPONSE_ACCEPT) {
719                 gtk_widget_destroy(dialog);
720                 return;
721         }
722
723         for (i = 0; i < FD_DEBUG_MAX; i++) {
724                 int set;
725
726                 set = gtk_toggle_button_get_active(GTK_TOGGLE_BUTTON(buttons[i]));
727                 if (set)
728                         fio_debug |= (1UL << i);
729         }
730
731         gtk_widget_destroy(dialog);
732 }
733
734 static void about_dialog(GtkWidget *w, gpointer data)
735 {
736         gtk_show_about_dialog(NULL,
737                 "program-name", "gfio",
738                 "comments", "Gtk2 UI for fio",
739                 "license", "GPLv2",
740                 "version", fio_version_string,
741                 "copyright", "Jens Axboe <axboe@kernel.dk> 2012",
742                 "logo-icon-name", "fio",
743                 /* Must be last: */
744                 NULL, NULL,
745                 NULL);
746 }
747
748 static GtkActionEntry menu_items[] = {
749         { "FileMenuAction", GTK_STOCK_FILE, "File", NULL, NULL, NULL},
750         { "HelpMenuAction", GTK_STOCK_HELP, "Help", NULL, NULL, NULL},
751         { "OpenFile", GTK_STOCK_OPEN, NULL,   "<Control>O", NULL, G_CALLBACK(file_open) },
752         { "SaveFile", GTK_STOCK_SAVE, NULL,   "<Control>S", NULL, G_CALLBACK(file_save) },
753         { "Preferences", GTK_STOCK_PREFERENCES, NULL, "<Control>p", NULL, G_CALLBACK(preferences) },
754         { "Quit", GTK_STOCK_QUIT, NULL,   "<Control>Q", NULL, G_CALLBACK(quit_clicked) },
755         { "About", GTK_STOCK_ABOUT, NULL,  NULL, NULL, G_CALLBACK(about_dialog) },
756 };
757 static gint nmenu_items = sizeof(menu_items) / sizeof(menu_items[0]);
758
759 static const gchar *ui_string = " \
760         <ui> \
761                 <menubar name=\"MainMenu\"> \
762                         <menu name=\"FileMenu\" action=\"FileMenuAction\"> \
763                                 <menuitem name=\"Open\" action=\"OpenFile\" /> \
764                                 <menuitem name=\"Save\" action=\"SaveFile\" /> \
765                                 <separator name=\"Separator\"/> \
766                                 <menuitem name=\"Preferences\" action=\"Preferences\" /> \
767                                 <separator name=\"Separator2\"/> \
768                                 <menuitem name=\"Quit\" action=\"Quit\" /> \
769                         </menu> \
770                         <menu name=\"Help\" action=\"HelpMenuAction\"> \
771                                 <menuitem name=\"About\" action=\"About\" /> \
772                         </menu> \
773                 </menubar> \
774         </ui> \
775 ";
776
777 static GtkWidget *get_menubar_menu(GtkWidget *window, GtkUIManager *ui_manager)
778 {
779         GtkActionGroup *action_group = gtk_action_group_new("Menu");
780         GError *error = 0;
781
782         action_group = gtk_action_group_new("Menu");
783         gtk_action_group_add_actions(action_group, menu_items, nmenu_items, 0);
784
785         gtk_ui_manager_insert_action_group(ui_manager, action_group, 0);
786         gtk_ui_manager_add_ui_from_string(GTK_UI_MANAGER(ui_manager), ui_string, -1, &error);
787
788         gtk_window_add_accel_group(GTK_WINDOW(window), gtk_ui_manager_get_accel_group(ui_manager));
789         return gtk_ui_manager_get_widget(ui_manager, "/MainMenu");
790 }
791
792 void gfio_ui_setup(GtkSettings *settings, GtkWidget *menubar,
793                    GtkWidget *vbox, GtkUIManager *ui_manager)
794 {
795         gtk_box_pack_start(GTK_BOX(vbox), menubar, FALSE, FALSE, 0);
796 }
797
798 static void init_ui(int *argc, char **argv[], struct gui *ui)
799 {
800         GtkSettings *settings;
801         GtkUIManager *uimanager;
802         GtkWidget *menu, *probe, *probe_frame, *probe_box;
803
804         memset(ui, 0, sizeof(*ui));
805
806         /* Magical g*thread incantation, you just need this thread stuff.
807          * Without it, the update that happens in gfio_update_thread_status
808          * doesn't really happen in a timely fashion, you need expose events
809          */
810         if (!g_thread_supported())
811                 g_thread_init(NULL);
812         gdk_threads_init();
813
814         gtk_init(argc, argv);
815         settings = gtk_settings_get_default();
816         gtk_settings_set_long_property(settings, "gtk_tooltip_timeout", 10, "gfio setting");
817         g_type_init();
818         
819         ui->window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
820         gtk_window_set_title(GTK_WINDOW(ui->window), "fio");
821         gtk_window_set_default_size(GTK_WINDOW(ui->window), 700, 500);
822
823         g_signal_connect(ui->window, "delete-event", G_CALLBACK(quit_clicked), NULL);
824         g_signal_connect(ui->window, "destroy", G_CALLBACK(quit_clicked), NULL);
825
826         ui->vbox = gtk_vbox_new(FALSE, 0);
827         gtk_container_add(GTK_CONTAINER (ui->window), ui->vbox);
828
829         uimanager = gtk_ui_manager_new();
830         menu = get_menubar_menu(ui->window, uimanager);
831         gfio_ui_setup(settings, menu, ui->vbox, uimanager);
832
833         /*
834          * Set up alignments for widgets at the top of ui, 
835          * align top left, expand horizontally but not vertically
836          */
837         ui->topalign = gtk_alignment_new(0, 0, 1, 0);
838         ui->topvbox = gtk_vbox_new(FALSE, 3);
839         gtk_container_add(GTK_CONTAINER(ui->topalign), ui->topvbox);
840         gtk_box_pack_start(GTK_BOX(ui->vbox), ui->topalign, FALSE, FALSE, 0);
841
842         probe = gtk_frame_new("Job");
843         gtk_box_pack_start(GTK_BOX(ui->topvbox), probe, TRUE, FALSE, 3);
844         probe_frame = gtk_vbox_new(FALSE, 3);
845         gtk_container_add(GTK_CONTAINER(probe), probe_frame);
846
847         probe_box = gtk_hbox_new(FALSE, 3);
848         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
849         ui->probe.hostname = new_info_label_in_frame(probe_box, "Host");
850         ui->probe.os = new_info_label_in_frame(probe_box, "OS");
851         ui->probe.arch = new_info_label_in_frame(probe_box, "Architecture");
852         ui->probe.fio_ver = new_info_label_in_frame(probe_box, "Fio version");
853
854         probe_box = gtk_hbox_new(FALSE, 3);
855         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
856
857         ui->eta.name = new_info_label_in_frame(probe_box, "Name");
858         ui->eta.iotype = new_info_label_in_frame(probe_box, "IO");
859         ui->eta.ioengine = new_info_label_in_frame(probe_box, "IO Engine");
860         ui->eta.iodepth = new_info_label_in_frame(probe_box, "IO Depth");
861         ui->eta.jobs = new_info_label_in_frame(probe_box, "Jobs");
862         ui->eta.files = new_info_label_in_frame(probe_box, "Open files");
863
864         probe_box = gtk_hbox_new(FALSE, 3);
865         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
866         ui->eta.read_bw = new_info_label_in_frame(probe_box, "Read BW");
867         ui->eta.read_iops = new_info_label_in_frame(probe_box, "IOPS");
868         ui->eta.write_bw = new_info_label_in_frame(probe_box, "Write BW");
869         ui->eta.write_iops = new_info_label_in_frame(probe_box, "IOPS");
870
871         /*
872          * Only add this if we have a commit rate
873          */
874 #if 0
875         probe_box = gtk_hbox_new(FALSE, 3);
876         gtk_box_pack_start(GTK_BOX(probe_frame), probe_box, TRUE, FALSE, 3);
877
878         ui->eta.cr_bw = new_info_label_in_frame(probe_box, "Commit BW");
879         ui->eta.cr_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
880
881         ui->eta.cw_bw = new_info_label_in_frame(probe_box, "Commit BW");
882         ui->eta.cw_iops = new_info_label_in_frame(probe_box, "Commit IOPS");
883 #endif
884
885         /*
886          * Add a text box for text op messages 
887          */
888         ui->textview = gtk_text_view_new();
889         ui->text = gtk_text_view_get_buffer(GTK_TEXT_VIEW(ui->textview));
890         gtk_text_buffer_set_text(ui->text, "", -1);
891         gtk_text_view_set_editable(GTK_TEXT_VIEW(ui->textview), FALSE);
892         gtk_text_view_set_cursor_visible(GTK_TEXT_VIEW(ui->textview), FALSE);
893         ui->scrolled_window = gtk_scrolled_window_new(NULL, NULL);
894         gtk_scrolled_window_set_policy(GTK_SCROLLED_WINDOW(ui->scrolled_window),
895                                         GTK_POLICY_AUTOMATIC, GTK_POLICY_AUTOMATIC);
896         gtk_container_add(GTK_CONTAINER(ui->scrolled_window), ui->textview);
897         gtk_box_pack_start(GTK_BOX(ui->vbox), ui->scrolled_window,
898                         TRUE, TRUE, 0);
899
900         /*
901          * Set up alignments for widgets at the bottom of ui, 
902          * align bottom left, expand horizontally but not vertically
903          */
904         ui->bottomalign = gtk_alignment_new(0, 1, 1, 0);
905         ui->buttonbox = gtk_hbox_new(FALSE, 0);
906         gtk_container_add(GTK_CONTAINER(ui->bottomalign), ui->buttonbox);
907         gtk_box_pack_start(GTK_BOX(ui->vbox), ui->bottomalign,
908                                         FALSE, FALSE, 0);
909
910         add_buttons(ui, buttonspeclist, ARRAYSIZE(buttonspeclist));
911
912         /*
913          * Set up thread status progress bar
914          */
915         ui->thread_status_pb = gtk_progress_bar_new();
916         gtk_progress_bar_set_fraction(GTK_PROGRESS_BAR(ui->thread_status_pb), 0.0);
917         gtk_progress_bar_set_text(GTK_PROGRESS_BAR(ui->thread_status_pb), "No connections");
918         gtk_container_add(GTK_CONTAINER(ui->buttonbox), ui->thread_status_pb);
919
920
921         gtk_widget_show_all(ui->window);
922 }
923
924 int main(int argc, char *argv[], char *envp[])
925 {
926         if (initialize_fio(envp))
927                 return 1;
928         if (fio_init_options())
929                 return 1;
930
931         init_ui(&argc, &argv, &ui);
932
933         gdk_threads_enter();
934         gtk_main();
935         gdk_threads_leave();
936         return 0;
937 }