Add json output for client/server mode

In client/server mode, this adds support for json mode.  Each job's
details are gradually accumulated and upon completion the client
dumps the full json tree.  The tree is annotated with the server
host and port.

Signed-off-by: Castor Fu <castor@alumni.caltech.edu>
Signed-off-by: Jens Axboe <axboe@kernel.dk>
diff --git a/client.c b/client.c
index 1fefda8..1557240 100644
--- a/client.c
+++ b/client.c
@@ -57,6 +57,9 @@
 int sum_stat_clients;
 
 static int sum_stat_nr;
+static struct json_object *root = NULL;
+static struct json_array *clients_array = NULL;
+static struct json_array *du_array = NULL;
 static int do_output_all_clients;
 
 #define FIO_CLIENT_HASH_BITS	7
@@ -86,6 +89,30 @@
 		INIT_FLIST_HEAD(&client_hash[i]);
 }
 
+static void fio_client_json_init(void)
+{
+	if (output_format != FIO_OUTPUT_JSON)
+		return;
+	root = json_create_object();
+	json_object_add_value_string(root, "fio version", fio_version_string);
+	clients_array = json_create_array();
+	json_object_add_value_array(root, "client_stats", clients_array);
+	du_array = json_create_array();
+	json_object_add_value_array(root, "disk_util", du_array);
+}
+
+static void fio_client_json_fini(void)
+{
+	if (output_format != FIO_OUTPUT_JSON)
+		return;
+	json_print_object(root);
+	log_info("\n");
+	json_free_object(root);
+	root = NULL;
+	clients_array = NULL;
+	du_array = NULL;
+}
+
 static struct fio_client *find_client_by_fd(int fd)
 {
 	int bucket = hash_long(fd, FIO_CLIENT_HASH_BITS) & FIO_CLIENT_HASH_MASK;
@@ -557,6 +584,8 @@
 
 	dprint(FD_NET, "client: start all\n");
 
+	fio_client_json_init();
+
 	flist_for_each_safe(entry, tmp, &client_list) {
 		client = flist_entry(entry, struct fio_client, list);
 
@@ -684,7 +713,7 @@
 	pdu.thread_number = cpu_to_le32(client->thread_number);
 	pdu.groupid = cpu_to_le32(client->groupid);
 	convert_thread_options_to_net(&pdu.top, o);
-	
+
 	return fio_net_send_cmd(client->fd, FIO_NET_CMD_UPDATE_JOB, &pdu, sizeof(pdu), tag, &client->cmd_list);
 }
 
@@ -788,12 +817,24 @@
 	dst->unified_rw_rep	= le32_to_cpu(src->unified_rw_rep);
 }
 
+static void json_object_add_client_info(struct json_object *obj,
+struct fio_client *client)
+{
+	json_object_add_value_string(obj, "hostname", client->hostname);
+	json_object_add_value_int(obj, "port", client->port);
+}
+
 static void handle_ts(struct fio_client *client, struct fio_net_cmd *cmd)
 {
 	struct cmd_ts_pdu *p = (struct cmd_ts_pdu *) cmd->payload;
+	struct json_object *tsobj;
 
-	show_thread_status(&p->ts, &p->rs);
+	tsobj = show_thread_status(&p->ts, &p->rs);
 	client->did_stat = 1;
+	if (tsobj) {
+		json_object_add_client_info(tsobj, client);
+		json_array_add_value_object(clients_array, tsobj);
+	}
 
 	if (!do_output_all_clients)
 		return;
@@ -808,7 +849,11 @@
 
 	if (++sum_stat_nr == sum_stat_clients) {
 		strcpy(client_ts.name, "All clients");
-		show_thread_status(&client_ts, &client_gs);
+		tsobj = show_thread_status(&client_ts, &client_gs);
+		if (tsobj) {
+			json_object_add_client_info(tsobj, client);
+			json_array_add_value_object(clients_array, tsobj);
+		}
 	}
 }
 
@@ -877,7 +922,13 @@
 		log_info("\nDisk stats (read/write):\n");
 	}
 
-	print_disk_util(&du->dus, &du->agg, output_format == FIO_OUTPUT_TERSE);
+	if (output_format == FIO_OUTPUT_JSON) {
+		struct json_object *duobj;
+		json_array_add_disk_util(&du->dus, &du->agg, du_array);
+		duobj = json_array_last_value_object(du_array);
+		json_object_add_client_info(duobj, client);
+	} else
+		print_disk_util(&du->dus, &du->agg, output_format == FIO_OUTPUT_TERSE);
 }
 
 static void convert_jobs_eta(struct jobs_eta *je)
@@ -1476,6 +1527,8 @@
 		}
 	}
 
+	fio_client_json_fini();
+
 	free(pfds);
 	return retval;
 }