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