Merge branch 'master' into client-server
Conflicts:
init.c
Signed-off-by: Jens Axboe <axboe@kernel.dk>
diff --git a/Makefile b/Makefile
index 85942a0..b361603 100644
--- a/Makefile
+++ b/Makefile
@@ -2,7 +2,7 @@
DEBUGFLAGS = -D_FORTIFY_SOURCE=2 -DFIO_INC_DEBUG
CPPFLAGS= -D_GNU_SOURCE -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 \
$(DEBUGFLAGS)
-OPTFLAGS= -O2 -fno-omit-frame-pointer -g $(EXTFLAGS)
+OPTFLAGS= -fno-omit-frame-pointer -g $(EXTFLAGS)
CFLAGS = -std=gnu99 -Wwrite-strings -Wall $(OPTFLAGS)
LIBS = -lm $(EXTLIBS)
PROGS = fio
@@ -14,7 +14,7 @@
rbtree.c smalloc.c filehash.c profile.c debug.c lib/rand.c \
lib/num2str.c $(wildcard crc/*.c) engines/cpu.c \
engines/mmap.c engines/sync.c engines/null.c engines/net.c \
- memalign.c
+ memalign.c server.c client.c iolog.c ieee754.c
ifeq ($(UNAME), Linux)
SOURCE += diskutil.c fifo.c blktrace.c helpers.c cgroup.c trim.c \
diff --git a/SERVER-TODO b/SERVER-TODO
new file mode 100644
index 0000000..929d482
--- /dev/null
+++ b/SERVER-TODO
@@ -0,0 +1,2 @@
+- Passing of float/doubles
+- Add -L listen option, to not bind to all devices
diff --git a/client.c b/client.c
new file mode 100644
index 0000000..d538e8a
--- /dev/null
+++ b/client.c
@@ -0,0 +1,587 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <unistd.h>
+#include <limits.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/poll.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <sys/wait.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <signal.h>
+
+#include "fio.h"
+#include "server.h"
+#include "crc/crc32.h"
+#include "flist.h"
+
+struct fio_client {
+ struct flist_head list;
+ struct sockaddr_in addr;
+ char *hostname;
+ int fd;
+
+ int state;
+ int skip_newline;
+
+ uint16_t argc;
+ char **argv;
+};
+
+enum {
+ Client_created = 0,
+ Client_connected = 1,
+ Client_started = 2,
+ Client_stopped = 3,
+ Client_exited = 4,
+};
+
+static FLIST_HEAD(client_list);
+
+static int handle_client(struct fio_client *client, int one);
+
+static struct fio_client *find_client_by_fd(int fd)
+{
+ struct fio_client *client;
+ struct flist_head *entry;
+
+ flist_for_each(entry, &client_list) {
+ client = flist_entry(entry, struct fio_client, list);
+
+ if (client->fd == fd)
+ return client;
+ }
+
+ return NULL;
+}
+
+static struct fio_client *find_client_by_name(const char *name)
+{
+ struct fio_client *client;
+ struct flist_head *entry;
+
+ flist_for_each(entry, &client_list) {
+ client = flist_entry(entry, struct fio_client, list);
+
+ if (!strcmp(name, client->hostname))
+ return client;
+ }
+
+ return NULL;
+}
+
+static void remove_client(struct fio_client *client)
+{
+ dprint(FD_NET, "client: removed <%s>\n", client->hostname);
+ flist_del(&client->list);
+ nr_clients--;
+
+ free(client->hostname);
+ if (client->argv)
+ free(client->argv);
+
+ free(client);
+}
+
+static void __fio_client_add_cmd_option(struct fio_client *client,
+ const char *opt)
+{
+ int index;
+
+ index = client->argc++;
+ client->argv = realloc(client->argv, sizeof(char *) * client->argc);
+ client->argv[index] = strdup(opt);
+ dprint(FD_NET, "client: add cmd %d: %s\n", index, opt);
+}
+
+void fio_client_add_cmd_option(const char *hostname, const char *opt)
+{
+ struct fio_client *client;
+
+ if (!hostname || !opt)
+ return;
+
+ client = find_client_by_name(hostname);
+ if (!client) {
+ log_err("fio: unknown client %s\n", hostname);
+ return;
+ }
+
+ __fio_client_add_cmd_option(client, opt);
+}
+
+void fio_client_add(const char *hostname)
+{
+ struct fio_client *client;
+
+ dprint(FD_NET, "client: added <%s>\n", hostname);
+ client = malloc(sizeof(*client));
+ memset(client, 0, sizeof(*client));
+
+ client->hostname = strdup(hostname);
+ client->fd = -1;
+
+ __fio_client_add_cmd_option(client, "fio");
+
+ flist_add(&client->list, &client_list);
+ nr_clients++;
+}
+
+static int fio_client_connect(struct fio_client *client)
+{
+ int fd;
+
+ dprint(FD_NET, "client: connect to host %s\n", client->hostname);
+
+ memset(&client->addr, 0, sizeof(client->addr));
+ client->addr.sin_family = AF_INET;
+ client->addr.sin_port = htons(fio_net_port);
+
+ if (inet_aton(client->hostname, &client->addr.sin_addr) != 1) {
+ struct hostent *hent;
+
+ hent = gethostbyname(client->hostname);
+ if (!hent) {
+ log_err("fio: gethostbyname: %s\n", strerror(errno));
+ return 1;
+ }
+
+ memcpy(&client->addr.sin_addr, hent->h_addr, 4);
+ }
+
+ fd = socket(AF_INET, SOCK_STREAM, 0);
+ if (fd < 0) {
+ log_err("fio: socket: %s\n", strerror(errno));
+ return 1;
+ }
+
+ if (connect(fd, (struct sockaddr *) &client->addr, sizeof(client->addr)) < 0) {
+ log_err("fio: connect: %s\n", strerror(errno));
+ log_err("fio: failed to connect to %s\n", client->hostname);
+ return 1;
+ }
+
+ client->fd = fd;
+ client->state = Client_connected;
+ return 0;
+}
+
+void fio_clients_terminate(void)
+{
+ struct flist_head *entry;
+ struct fio_client *client;
+
+ dprint(FD_NET, "client: terminate clients\n");
+
+ flist_for_each(entry, &client_list) {
+ client = flist_entry(entry, struct fio_client, list);
+
+ fio_net_send_simple_cmd(client->fd, FIO_NET_CMD_QUIT, 0);
+ }
+}
+
+static void sig_int(int sig)
+{
+ dprint(FD_NET, "client: got sign %d\n", sig);
+ fio_clients_terminate();
+}
+
+static void client_signal_handler(void)
+{
+ struct sigaction act;
+
+ memset(&act, 0, sizeof(act));
+ act.sa_handler = sig_int;
+ act.sa_flags = SA_RESTART;
+ sigaction(SIGINT, &act, NULL);
+
+ memset(&act, 0, sizeof(act));
+ act.sa_handler = sig_int;
+ act.sa_flags = SA_RESTART;
+ sigaction(SIGTERM, &act, NULL);
+}
+
+static void probe_client(struct fio_client *client)
+{
+ dprint(FD_NET, "client: send probe\n");
+
+ fio_net_send_simple_cmd(client->fd, FIO_NET_CMD_PROBE, 0);
+ handle_client(client, 1);
+}
+
+static int send_client_cmd_line(struct fio_client *client)
+{
+ struct cmd_line_pdu *pdu;
+ int i, ret;
+
+ dprint(FD_NET, "client: send cmdline %d\n", client->argc);
+
+ pdu = malloc(sizeof(*pdu));
+ for (i = 0; i < client->argc; i++)
+ strcpy((char *) pdu->argv[i], client->argv[i]);
+
+ pdu->argc = cpu_to_le16(client->argc);
+ ret = fio_net_send_cmd(client->fd, FIO_NET_CMD_JOBLINE, pdu, sizeof(*pdu));
+ free(pdu);
+ return ret;
+}
+
+int fio_clients_connect(void)
+{
+ struct fio_client *client;
+ struct flist_head *entry, *tmp;
+ int ret;
+
+ dprint(FD_NET, "client: connect all\n");
+
+ client_signal_handler();
+
+ flist_for_each_safe(entry, tmp, &client_list) {
+ client = flist_entry(entry, struct fio_client, list);
+
+ ret = fio_client_connect(client);
+ if (ret) {
+ remove_client(client);
+ continue;
+ }
+
+ probe_client(client);
+
+ if (client->argc > 1)
+ send_client_cmd_line(client);
+ }
+
+ return !nr_clients;
+}
+
+/*
+ * Send file contents to server backend. We could use sendfile(), but to remain
+ * more portable lets just read/write the darn thing.
+ */
+static int fio_client_send_ini(struct fio_client *client, const char *filename)
+{
+ struct stat sb;
+ char *p, *buf;
+ off_t len;
+ int fd, ret;
+
+ dprint(FD_NET, "send ini %s to %s\n", filename, client->hostname);
+
+ fd = open(filename, O_RDONLY);
+ if (fd < 0) {
+ log_err("fio: job file open: %s\n", strerror(errno));
+ return 1;
+ }
+
+ if (fstat(fd, &sb) < 0) {
+ log_err("fio: job file stat: %s\n", strerror(errno));
+ return 1;
+ }
+
+ buf = malloc(sb.st_size);
+
+ len = sb.st_size;
+ p = buf;
+ do {
+ ret = read(fd, p, len);
+ if (ret > 0) {
+ len -= ret;
+ if (!len)
+ break;
+ p += ret;
+ continue;
+ } else if (!ret)
+ break;
+ else if (errno == EAGAIN || errno == EINTR)
+ continue;
+ } while (1);
+
+ if (len) {
+ log_err("fio: failed reading job file %s\n", filename);
+ return 1;
+ }
+
+ ret = fio_net_send_cmd(client->fd, FIO_NET_CMD_JOB, buf, sb.st_size);
+ free(buf);
+ return ret;
+}
+
+int fio_clients_send_ini(const char *filename)
+{
+ struct fio_client *client;
+ struct flist_head *entry, *tmp;
+
+ flist_for_each_safe(entry, tmp, &client_list) {
+ client = flist_entry(entry, struct fio_client, list);
+
+ if (fio_client_send_ini(client, filename))
+ remove_client(client);
+ }
+
+ return !nr_clients;
+}
+
+static void convert_io_stat(struct io_stat *dst, struct io_stat *src)
+{
+ dst->max_val = le64_to_cpu(src->max_val);
+ dst->min_val = le64_to_cpu(src->min_val);
+ dst->samples = le64_to_cpu(src->samples);
+
+ /*
+ * Floats arrive as IEEE 754 encoded uint64_t, convert back to double
+ */
+ dst->mean.u.f = fio_uint64_to_double(le64_to_cpu(dst->mean.u.i));
+ dst->S.u.f = fio_uint64_to_double(le64_to_cpu(dst->S.u.i));
+}
+
+static void convert_ts(struct thread_stat *dst, struct thread_stat *src)
+{
+ int i, j;
+
+ dst->error = le32_to_cpu(src->error);
+ dst->groupid = le32_to_cpu(src->groupid);
+ dst->pid = le32_to_cpu(src->pid);
+ dst->members = le32_to_cpu(src->members);
+
+ for (i = 0; i < 2; i++) {
+ convert_io_stat(&dst->clat_stat[i], &src->clat_stat[i]);
+ convert_io_stat(&dst->slat_stat[i], &src->slat_stat[i]);
+ convert_io_stat(&dst->lat_stat[i], &src->lat_stat[i]);
+ convert_io_stat(&dst->bw_stat[i], &src->bw_stat[i]);
+ }
+
+ dst->usr_time = le64_to_cpu(src->usr_time);
+ dst->sys_time = le64_to_cpu(src->sys_time);
+ dst->ctx = le64_to_cpu(src->ctx);
+ dst->minf = le64_to_cpu(src->minf);
+ dst->majf = le64_to_cpu(src->majf);
+ dst->clat_percentiles = le64_to_cpu(src->clat_percentiles);
+
+ for (i = 0; i < FIO_IO_U_LIST_MAX_LEN; i++) {
+ fio_fp64_t *fps = &src->percentile_list[i];
+ fio_fp64_t *fpd = &dst->percentile_list[i];
+
+ fpd->u.f = fio_uint64_to_double(le64_to_cpu(fps->u.i));
+ }
+
+ for (i = 0; i < FIO_IO_U_MAP_NR; i++) {
+ dst->io_u_map[i] = le32_to_cpu(src->io_u_map[i]);
+ dst->io_u_submit[i] = le32_to_cpu(src->io_u_submit[i]);
+ dst->io_u_complete[i] = le32_to_cpu(src->io_u_complete[i]);
+ }
+
+ for (i = 0; i < FIO_IO_U_LAT_U_NR; i++) {
+ dst->io_u_lat_u[i] = le32_to_cpu(src->io_u_lat_u[i]);
+ dst->io_u_lat_m[i] = le32_to_cpu(src->io_u_lat_m[i]);
+ }
+
+ for (i = 0; i < 2; i++)
+ for (j = 0; j < FIO_IO_U_PLAT_NR; j++)
+ dst->io_u_plat[i][j] = le32_to_cpu(src->io_u_plat[i][j]);
+
+ for (i = 0; i < 3; i++) {
+ dst->total_io_u[i] = le64_to_cpu(src->total_io_u[i]);
+ dst->short_io_u[i] = le64_to_cpu(src->short_io_u[i]);
+ }
+
+ dst->total_submit = le64_to_cpu(src->total_submit);
+ dst->total_complete = le64_to_cpu(src->total_complete);
+
+ for (i = 0; i < 2; i++) {
+ dst->io_bytes[i] = le64_to_cpu(src->io_bytes[i]);
+ dst->runtime[i] = le64_to_cpu(src->runtime[i]);
+ }
+
+ dst->total_run_time = le64_to_cpu(src->total_run_time);
+ dst->continue_on_error = le16_to_cpu(src->continue_on_error);
+ dst->total_err_count = le64_to_cpu(src->total_err_count);
+ dst->first_error = le32_to_cpu(src->first_error);
+ dst->kb_base = le32_to_cpu(src->kb_base);
+}
+
+static void convert_gs(struct group_run_stats *dst, struct group_run_stats *src)
+{
+ int i;
+
+ for (i = 0; i < 2; i++) {
+ dst->max_run[i] = le64_to_cpu(src->max_run[i]);
+ dst->min_run[i] = le64_to_cpu(src->min_run[i]);
+ dst->max_bw[i] = le64_to_cpu(src->max_bw[i]);
+ dst->min_bw[i] = le64_to_cpu(src->min_bw[i]);
+ dst->io_kb[i] = le64_to_cpu(src->io_kb[i]);
+ dst->agg[i] = le64_to_cpu(src->agg[i]);
+ }
+
+ dst->kb_base = le32_to_cpu(src->kb_base);
+ dst->groupid = le32_to_cpu(src->groupid);
+}
+
+static void handle_ts(struct fio_net_cmd *cmd)
+{
+ struct cmd_ts_pdu *p = (struct cmd_ts_pdu *) cmd->payload;
+
+ convert_ts(&p->ts, &p->ts);
+ convert_gs(&p->rs, &p->rs);
+
+ show_thread_status(&p->ts, &p->rs);
+}
+
+static void handle_gs(struct fio_net_cmd *cmd)
+{
+ struct group_run_stats *gs = (struct group_run_stats *) cmd->payload;
+
+ convert_gs(gs, gs);
+ show_group_stats(gs);
+}
+
+static void handle_eta(struct fio_net_cmd *cmd)
+{
+ struct jobs_eta *je = (struct jobs_eta *) cmd->payload;
+ int i;
+
+ je->nr_running = le32_to_cpu(je->nr_running);
+ je->nr_ramp = le32_to_cpu(je->nr_ramp);
+ je->nr_pending = le32_to_cpu(je->nr_pending);
+ je->files_open = le32_to_cpu(je->files_open);
+ je->m_rate = le32_to_cpu(je->m_rate);
+ je->t_rate = le32_to_cpu(je->t_rate);
+ je->m_iops = le32_to_cpu(je->m_iops);
+ je->t_iops = le32_to_cpu(je->t_iops);
+
+ for (i = 0; i < 2; i++) {
+ je->rate[i] = le32_to_cpu(je->rate[i]);
+ je->iops[i] = le32_to_cpu(je->iops[i]);
+ }
+
+ je->elapsed_sec = le32_to_cpu(je->nr_running);
+ je->eta_sec = le64_to_cpu(je->eta_sec);
+
+ display_thread_status(je);
+}
+
+static void handle_probe(struct fio_net_cmd *cmd)
+{
+ struct cmd_probe_pdu *probe = (struct cmd_probe_pdu *) cmd->payload;
+
+ log_info("Probe: hostname=%s, be=%u, fio ver %u.%u.%u\n",
+ probe->hostname, probe->bigendian, probe->fio_major,
+ probe->fio_minor, probe->fio_patch);
+}
+
+static int handle_client(struct fio_client *client, int one)
+{
+ struct fio_net_cmd *cmd;
+ int done = 0;
+
+ dprint(FD_NET, "client: handle %s\n", client->hostname);
+
+ while ((cmd = fio_net_recv_cmd(client->fd, 1)) != NULL) {
+ dprint(FD_NET, "%s: got cmd op %d\n", client->hostname,
+ cmd->opcode);
+
+ switch (cmd->opcode) {
+ case FIO_NET_CMD_QUIT:
+ remove_client(client);
+ free(cmd);
+ done = 1;
+ break;
+ case FIO_NET_CMD_TEXT: {
+ const char *buf = (const char *) cmd->payload;
+
+ if (!client->skip_newline)
+ fprintf(f_out, "Client <%s>: ", client->hostname);
+ fwrite(buf, cmd->pdu_len, 1, f_out);
+ fflush(f_out);
+ client->skip_newline = strchr(buf, '\n') == NULL;
+ free(cmd);
+ break;
+ }
+ case FIO_NET_CMD_TS:
+ handle_ts(cmd);
+ free(cmd);
+ break;
+ case FIO_NET_CMD_GS:
+ handle_gs(cmd);
+ free(cmd);
+ break;
+ case FIO_NET_CMD_ETA:
+ handle_eta(cmd);
+ free(cmd);
+ break;
+ case FIO_NET_CMD_PROBE:
+ handle_probe(cmd);
+ free(cmd);
+ break;
+ case FIO_NET_CMD_START:
+ client->state = Client_started;
+ free(cmd);
+ break;
+ case FIO_NET_CMD_STOP:
+ client->state = Client_stopped;
+ free(cmd);
+ break;
+ default:
+ log_err("fio: unknown client op: %d\n", cmd->opcode);
+ free(cmd);
+ break;
+ }
+
+ if (done || one)
+ break;
+ }
+
+ return 0;
+}
+
+int fio_handle_clients(void)
+{
+ struct fio_client *client;
+ struct flist_head *entry;
+ struct pollfd *pfds;
+ int i, ret = 0;
+
+ pfds = malloc(nr_clients * sizeof(struct pollfd));
+
+ while (!exit_backend && nr_clients) {
+ i = 0;
+ flist_for_each(entry, &client_list) {
+ client = flist_entry(entry, struct fio_client, list);
+
+ pfds[i].fd = client->fd;
+ pfds[i].events = POLLIN;
+ i++;
+ }
+
+ assert(i == nr_clients);
+
+ do {
+ ret = poll(pfds, nr_clients, 100);
+ if (ret < 0) {
+ if (errno == EINTR)
+ continue;
+ log_err("fio: poll clients: %s\n", strerror(errno));
+ break;
+ } else if (!ret)
+ continue;
+ } while (ret <= 0);
+
+ for (i = 0; i < nr_clients; i++) {
+ if (!(pfds[i].revents & POLLIN))
+ continue;
+
+ client = find_client_by_fd(pfds[i].fd);
+ if (!client) {
+ log_err("fio: unknown client\n");
+ continue;
+ }
+ handle_client(client, 0);
+ }
+ }
+
+ free(pfds);
+ return 0;
+}
diff --git a/crc/crc16.c b/crc/crc16.c
index ac7983a..d9c4e49 100644
--- a/crc/crc16.c
+++ b/crc/crc16.c
@@ -43,11 +43,12 @@
0x8201, 0x42C0, 0x4380, 0x8341, 0x4100, 0x81C1, 0x8081, 0x4040
};
-unsigned short crc16(unsigned char const *buffer, unsigned int len)
+unsigned short crc16(const void *buffer, unsigned int len)
{
+ const unsigned char *cp = (const unsigned char *) buffer;
unsigned short crc = 0;
while (len--)
- crc = crc16_byte(crc, *buffer++);
+ crc = crc16_byte(crc, *cp++);
return crc;
}
diff --git a/crc/crc16.h b/crc/crc16.h
index 841378d..6c078a4 100644
--- a/crc/crc16.h
+++ b/crc/crc16.h
@@ -17,7 +17,7 @@
extern unsigned short const crc16_table[256];
-extern unsigned short crc16(const unsigned char *buffer, unsigned int len);
+extern unsigned short crc16(const void *buffer, unsigned int len);
static inline unsigned short crc16_byte(unsigned short crc,
const unsigned char data)
diff --git a/debug.c b/debug.c
index 013cd53..5e98063 100644
--- a/debug.c
+++ b/debug.c
@@ -16,8 +16,8 @@
&& pid != *fio_debug_jobp)
return;
- log_info("%-8s ", debug_levels[type].name);
- log_info("%-5u ", (int) pid);
+ log_local("%-8s ", debug_levels[type].name);
+ log_local("%-5u ", (int) pid);
va_start(args, str);
log_valist(str, args);
diff --git a/debug.h b/debug.h
index 3473d6f..af71d62 100644
--- a/debug.h
+++ b/debug.h
@@ -18,6 +18,7 @@
FD_MUTEX,
FD_PROFILE,
FD_TIME,
+ FD_NET,
FD_DEBUG_MAX,
};
diff --git a/engines/net.c b/engines/net.c
index 6866ba2..781dd10 100644
--- a/engines/net.c
+++ b/engines/net.c
@@ -280,11 +280,7 @@
do {
if (nd->net_protocol == IPPROTO_UDP) {
-#ifdef __hpux
- int len = sizeof(nd->addr);
-#else
- socklen_t len = sizeof(nd->addr);
-#endif
+ fio_socklen_t len = sizeof(nd->addr);
struct sockaddr *from = (struct sockaddr *) &nd->addr;
ret = recvfrom(io_u->file->fd, io_u->xfer_buf,
@@ -381,11 +377,7 @@
static int fio_netio_accept(struct thread_data *td, struct fio_file *f)
{
struct netio_data *nd = td->io_ops->data;
-#ifdef __hpux
- int socklen = sizeof(nd->addr);
-#else
- socklen_t socklen = sizeof(nd->addr);
-#endif
+ fio_socklen_t socklen = sizeof(nd->addr);
if (nd->net_protocol == IPPROTO_UDP) {
f->fd = nd->listenfd;
diff --git a/eta.c b/eta.c
index d93bf1a..5b912eb 100644
--- a/eta.c
+++ b/eta.c
@@ -230,32 +230,26 @@
* Print status of the jobs we know about. This includes rate estimates,
* ETA, thread state, etc.
*/
-void print_thread_status(void)
+int calc_thread_status(struct jobs_eta *je)
{
- unsigned long elapsed = (mtime_since_genesis() + 999) / 1000;
- int i, nr_ramp, nr_running, nr_pending, t_rate, m_rate;
- int t_iops, m_iops, files_open;
struct thread_data *td;
- char eta_str[128];
- double perc = 0.0;
- unsigned long long io_bytes[2], io_iops[2];
- unsigned long rate_time, disp_time, bw_avg_time, *eta_secs, eta_sec;
+ int i;
+ unsigned long rate_time, disp_time, bw_avg_time, *eta_secs;
+ unsigned long long io_bytes[2];
+ unsigned long long io_iops[2];
struct timeval now;
static unsigned long long rate_io_bytes[2];
static unsigned long long disp_io_bytes[2];
static unsigned long long disp_io_iops[2];
static struct timeval rate_prev_time, disp_prev_time;
- static unsigned int rate[2], iops[2];
- static int linelen_last;
- static int eta_good;
int i2p = 0;
if (temp_stall_ts || terse_output || eta_print == FIO_ETA_NEVER)
- return;
+ return 0;
if (!isatty(STDOUT_FILENO) && (eta_print != FIO_ETA_ALWAYS))
- return;
+ return 0;
if (!rate_io_bytes[0] && !rate_io_bytes[1])
fill_start_time(&rate_prev_time);
@@ -265,32 +259,31 @@
eta_secs = malloc(thread_number * sizeof(unsigned long));
memset(eta_secs, 0, thread_number * sizeof(unsigned long));
+ je->elapsed_sec = (mtime_since_genesis() + 999) / 1000;
+
io_bytes[0] = io_bytes[1] = 0;
io_iops[0] = io_iops[1] = 0;
- nr_pending = nr_running = t_rate = m_rate = t_iops = m_iops = 0;
- nr_ramp = 0;
bw_avg_time = ULONG_MAX;
- files_open = 0;
for_each_td(td, i) {
if (td->o.bw_avg_time < bw_avg_time)
bw_avg_time = td->o.bw_avg_time;
if (td->runstate == TD_RUNNING || td->runstate == TD_VERIFYING
|| td->runstate == TD_FSYNCING
|| td->runstate == TD_PRE_READING) {
- nr_running++;
- t_rate += td->o.rate[0] + td->o.rate[1];
- m_rate += td->o.ratemin[0] + td->o.ratemin[1];
- t_iops += td->o.rate_iops[0] + td->o.rate_iops[1];
- m_iops += td->o.rate_iops_min[0] +
+ je->nr_running++;
+ je->t_rate += td->o.rate[0] + td->o.rate[1];
+ je->m_rate += td->o.ratemin[0] + td->o.ratemin[1];
+ je->t_iops += td->o.rate_iops[0] + td->o.rate_iops[1];
+ je->m_iops += td->o.rate_iops_min[0] +
td->o.rate_iops_min[1];
- files_open += td->nr_open_files;
+ je->files_open += td->nr_open_files;
} else if (td->runstate == TD_RAMP) {
- nr_running++;
- nr_ramp++;
+ je->nr_running++;
+ je->nr_ramp++;
} else if (td->runstate < TD_RUNNING)
- nr_pending++;
+ je->nr_pending++;
- if (elapsed >= 3)
+ if (je->elapsed_sec >= 3)
eta_secs[i] = thread_eta(td);
else
eta_secs[i] = INT_MAX;
@@ -306,37 +299,32 @@
}
if (exitall_on_terminate)
- eta_sec = INT_MAX;
+ je->eta_sec = INT_MAX;
else
- eta_sec = 0;
+ je->eta_sec = 0;
for_each_td(td, i) {
if (!i2p && is_power_of_2(td->o.kb_base))
i2p = 1;
if (exitall_on_terminate) {
- if (eta_secs[i] < eta_sec)
- eta_sec = eta_secs[i];
+ if (eta_secs[i] < je->eta_sec)
+ je->eta_sec = eta_secs[i];
} else {
- if (eta_secs[i] > eta_sec)
- eta_sec = eta_secs[i];
+ if (eta_secs[i] > je->eta_sec)
+ je->eta_sec = eta_secs[i];
}
}
free(eta_secs);
- if (eta_sec != INT_MAX && elapsed) {
- perc = (double) elapsed / (double) (elapsed + eta_sec);
- eta_to_str(eta_str, eta_sec);
- }
-
fio_gettime(&now, NULL);
rate_time = mtime_since(&rate_prev_time, &now);
if (write_bw_log && rate_time > bw_avg_time && !in_ramp_time(td)) {
- calc_rate(rate_time, io_bytes, rate_io_bytes, rate);
+ calc_rate(rate_time, io_bytes, rate_io_bytes, je->rate);
memcpy(&rate_prev_time, &now, sizeof(now));
- add_agg_sample(rate[DDIR_READ], DDIR_READ, 0);
- add_agg_sample(rate[DDIR_WRITE], DDIR_WRITE, 0);
+ add_agg_sample(je->rate[DDIR_READ], DDIR_READ, 0);
+ add_agg_sample(je->rate[DDIR_WRITE], DDIR_WRITE, 0);
}
disp_time = mtime_since(&disp_prev_time, &now);
@@ -345,34 +333,54 @@
* Allow a little slack, the target is to print it every 1000 msecs
*/
if (disp_time < 900)
- return;
+ return 0;
- calc_rate(disp_time, io_bytes, disp_io_bytes, rate);
- calc_iops(disp_time, io_iops, disp_io_iops, iops);
+ calc_rate(disp_time, io_bytes, disp_io_bytes, je->rate);
+ calc_iops(disp_time, io_iops, disp_io_iops, je->iops);
memcpy(&disp_prev_time, &now, sizeof(now));
- if (!nr_running && !nr_pending)
- return;
+ if (!je->nr_running && !je->nr_pending)
+ return 0;
- printf("Jobs: %d (f=%d)", nr_running, files_open);
- if (m_rate || t_rate) {
+ je->nr_threads = thread_number;
+ memcpy(je->run_str, run_str, thread_number * sizeof(char));
+
+ return 1;
+}
+
+void display_thread_status(struct jobs_eta *je)
+{
+ static int linelen_last;
+ static int eta_good;
+ char output[512], *p = output;
+ char eta_str[128];
+ double perc = 0.0;
+ int i2p = 0;
+
+ if (je->eta_sec != INT_MAX && je->elapsed_sec) {
+ perc = (double) je->elapsed_sec / (double) (je->elapsed_sec + je->eta_sec);
+ eta_to_str(eta_str, je->eta_sec);
+ }
+
+ p += sprintf(p, "Jobs: %d (f=%d)", je->nr_running, je->files_open);
+ if (je->m_rate || je->t_rate) {
char *tr, *mr;
- mr = num2str(m_rate, 4, 0, i2p);
- tr = num2str(t_rate, 4, 0, i2p);
- printf(", CR=%s/%s KB/s", tr, mr);
+ mr = num2str(je->m_rate, 4, 0, i2p);
+ tr = num2str(je->t_rate, 4, 0, i2p);
+ p += sprintf(p, ", CR=%s/%s KB/s", tr, mr);
free(tr);
free(mr);
- } else if (m_iops || t_iops)
- printf(", CR=%d/%d IOPS", t_iops, m_iops);
- if (eta_sec != INT_MAX && nr_running) {
+ } else if (je->m_iops || je->t_iops)
+ p += sprintf(p, ", CR=%d/%d IOPS", je->t_iops, je->m_iops);
+ if (je->eta_sec != INT_MAX && je->nr_running) {
char perc_str[32];
char *iops_str[2];
char *rate_str[2];
int l;
- if ((!eta_sec && !eta_good) || nr_ramp == nr_running)
+ if ((!je->eta_sec && !eta_good) || je->nr_ramp == je->nr_running)
strcpy(perc_str, "-.-% done");
else {
eta_good = 1;
@@ -380,17 +388,18 @@
sprintf(perc_str, "%3.1f%% done", perc);
}
- rate_str[0] = num2str(rate[0], 5, 10, i2p);
- rate_str[1] = num2str(rate[1], 5, 10, i2p);
+ rate_str[0] = num2str(je->rate[0], 5, 10, i2p);
+ rate_str[1] = num2str(je->rate[1], 5, 10, i2p);
- iops_str[0] = num2str(iops[0], 4, 1, 0);
- iops_str[1] = num2str(iops[1], 4, 1, 0);
+ iops_str[0] = num2str(je->iops[0], 4, 1, 0);
+ iops_str[1] = num2str(je->iops[1], 4, 1, 0);
- l = printf(": [%s] [%s] [%s/%s /s] [%s/%s iops] [eta %s]",
- run_str, perc_str, rate_str[0], rate_str[1],
- iops_str[0], iops_str[1], eta_str);
+ l = sprintf(p, ": [%s] [%s] [%s/%s /s] [%s/%s iops] [eta %s]",
+ je->run_str, perc_str, rate_str[0],
+ rate_str[1], iops_str[0], iops_str[1], eta_str);
+ p += l;
if (l >= 0 && l < linelen_last)
- printf("%*s", linelen_last - l, "");
+ p += sprintf(p, "%*s", linelen_last - l, "");
linelen_last = l;
free(rate_str[0]);
@@ -398,10 +407,26 @@
free(iops_str[0]);
free(iops_str[1]);
}
- printf("\r");
+ p += sprintf(p, "\r");
+
+ printf("%s", output);
fflush(stdout);
}
+void print_thread_status(void)
+{
+ struct jobs_eta *je;
+
+ je = malloc(sizeof(*je) + thread_number * sizeof(char));
+
+ memset(je, 0, sizeof(*je) + thread_number * sizeof(char));
+
+ if (calc_thread_status(je))
+ display_thread_status(je);
+
+ free(je);
+}
+
void print_status_init(int thr_number)
{
run_str[thr_number] = 'P';
diff --git a/fio.c b/fio.c
index b492889..3e4dbb7 100644
--- a/fio.c
+++ b/fio.c
@@ -46,6 +46,7 @@
#include "profile.h"
#include "lib/rand.h"
#include "memalign.h"
+#include "server.h"
unsigned long page_mask;
unsigned long page_size;
@@ -74,7 +75,6 @@
struct io_log *agg_io_log[2];
-#define TERMINATE_ALL (-1)
#define JOB_START_TIMEOUT (5 * 1000)
void td_set_runstate(struct thread_data *td, int runstate)
@@ -87,7 +87,7 @@
td->runstate = runstate;
}
-static void terminate_threads(int group_id)
+void fio_terminate_threads(int group_id)
{
struct thread_data *td;
int i;
@@ -122,9 +122,10 @@
{
if (threads) {
log_info("\nfio: terminating on signal %d\n", sig);
+ exit_backend = 1;
fflush(stdout);
exit_value = 128;
- terminate_threads(TERMINATE_ALL);
+ fio_terminate_threads(TERMINATE_ALL);
}
}
@@ -137,7 +138,11 @@
if (!threads)
break;
update_io_ticks();
- print_thread_status();
+
+ if (is_backend)
+ fio_server_send_status();
+ else
+ print_thread_status();
}
return NULL;
@@ -178,6 +183,13 @@
act.sa_handler = sig_int;
act.sa_flags = SA_RESTART;
sigaction(SIGTERM, &act, NULL);
+
+ if (is_backend) {
+ memset(&act, 0, sizeof(act));
+ act.sa_handler = sig_int;
+ act.sa_flags = SA_RESTART;
+ sigaction(SIGPIPE, &act, NULL);
+ }
}
/*
@@ -744,7 +756,7 @@
if (!in_ramp_time(td) && should_check_rate(td, bytes_done)) {
if (check_min_rate(td, &comp_time, bytes_done)) {
if (exitall_on_terminate)
- terminate_threads(td->groupid);
+ fio_terminate_threads(td->groupid);
td_verror(td, EIO, "check_min_rate");
break;
}
@@ -972,7 +984,7 @@
static void reset_io_counters(struct thread_data *td)
{
- td->ts.stat_io_bytes[0] = td->ts.stat_io_bytes[1] = 0;
+ td->stat_io_bytes[0] = td->stat_io_bytes[1] = 0;
td->this_io_bytes[0] = td->this_io_bytes[1] = 0;
td->zone_bytes = 0;
td->rate_bytes[0] = td->rate_bytes[1] = 0;
@@ -1167,19 +1179,17 @@
}
fio_gettime(&td->epoch, NULL);
- getrusage(RUSAGE_SELF, &td->ts.ru_start);
+ getrusage(RUSAGE_SELF, &td->ru_start);
clear_state = 0;
while (keep_running(td)) {
fio_gettime(&td->start, NULL);
- memcpy(&td->ts.stat_sample_time[0], &td->start,
- sizeof(td->start));
- memcpy(&td->ts.stat_sample_time[1], &td->start,
- sizeof(td->start));
+ memcpy(&td->stat_sample_time[0], &td->start, sizeof(td->start));
+ memcpy(&td->stat_sample_time[1], &td->start, sizeof(td->start));
memcpy(&td->tv_cache, &td->start, sizeof(td->start));
if (td->o.ratemin[0] || td->o.ratemin[1])
- memcpy(&td->lastrate, &td->ts.stat_sample_time,
+ memcpy(&td->lastrate, &td->stat_sample_time,
sizeof(td->lastrate));
if (clear_state)
@@ -1228,40 +1238,40 @@
td->ts.io_bytes[1] = td->io_bytes[1];
fio_mutex_down(writeout_mutex);
- if (td->ts.bw_log) {
+ if (td->bw_log) {
if (td->o.bw_log_file) {
- finish_log_named(td, td->ts.bw_log,
+ finish_log_named(td, td->bw_log,
td->o.bw_log_file, "bw");
} else
- finish_log(td, td->ts.bw_log, "bw");
+ finish_log(td, td->bw_log, "bw");
}
- if (td->ts.lat_log) {
+ if (td->lat_log) {
if (td->o.lat_log_file) {
- finish_log_named(td, td->ts.lat_log,
+ finish_log_named(td, td->lat_log,
td->o.lat_log_file, "lat");
} else
- finish_log(td, td->ts.lat_log, "lat");
+ finish_log(td, td->lat_log, "lat");
}
- if (td->ts.slat_log) {
+ if (td->slat_log) {
if (td->o.lat_log_file) {
- finish_log_named(td, td->ts.slat_log,
+ finish_log_named(td, td->slat_log,
td->o.lat_log_file, "slat");
} else
- finish_log(td, td->ts.slat_log, "slat");
+ finish_log(td, td->slat_log, "slat");
}
- if (td->ts.clat_log) {
+ if (td->clat_log) {
if (td->o.lat_log_file) {
- finish_log_named(td, td->ts.clat_log,
+ finish_log_named(td, td->clat_log,
td->o.lat_log_file, "clat");
} else
- finish_log(td, td->ts.clat_log, "clat");
+ finish_log(td, td->clat_log, "clat");
}
fio_mutex_up(writeout_mutex);
if (td->o.exec_postrun)
exec_string(td->o.exec_postrun);
if (exitall_on_terminate)
- terminate_threads(td->groupid);
+ fio_terminate_threads(td->groupid);
err:
if (td->error)
@@ -1415,7 +1425,7 @@
}
if (*nr_running == cputhreads && !pending && realthreads)
- terminate_threads(TERMINATE_ALL);
+ fio_terminate_threads(TERMINATE_ALL);
}
static void *gtod_thread_main(void *data)
@@ -1477,6 +1487,8 @@
if (fio_gtod_offload && fio_start_gtod_thread())
return;
+ set_sig_handlers();
+
if (!terse_output) {
log_info("Starting ");
if (nr_thread)
@@ -1492,8 +1504,6 @@
fflush(stdout);
}
- set_sig_handlers();
-
todo = thread_number;
nr_running = 0;
nr_started = 0;
@@ -1609,7 +1619,7 @@
dprint(FD_MUTEX, "wait on startup_mutex\n");
if (fio_mutex_down_timeout(startup_mutex, 10)) {
log_err("fio: job startup hung? exiting.\n");
- terminate_threads(TERMINATE_ALL);
+ fio_terminate_threads(TERMINATE_ALL);
fio_abort = 1;
nr_started--;
break;
@@ -1683,42 +1693,21 @@
while (nr_running) {
reap_threads(&nr_running, &t_rate, &m_rate);
- usleep(10000);
+
+ if (is_backend)
+ fio_server_idle_loop();
+ else
+ usleep(10000);
}
update_io_ticks();
fio_unpin_memory();
}
-int main(int argc, char *argv[], char *envp[])
+int exec_run(void)
{
- long ps;
-
- arch_init(envp);
-
- sinit();
-
- /*
- * We need locale for number printing, if it isn't set then just
- * go with the US format.
- */
- if (!getenv("LC_NUMERIC"))
- setlocale(LC_NUMERIC, "en_US");
-
- ps = sysconf(_SC_PAGESIZE);
- if (ps < 0) {
- log_err("Failed to get page size\n");
- return 1;
- }
-
- page_size = ps;
- page_mask = ps - 1;
-
- fio_keywords_init();
-
- if (parse_options(argc, argv))
- return 1;
-
+ if (nr_clients)
+ return fio_handle_clients();
if (exec_profile && load_profile(exec_profile))
return 1;
@@ -1762,3 +1751,44 @@
fio_mutex_remove(writeout_mutex);
return exit_value;
}
+
+void reset_fio_state(void)
+{
+ groupid = 0;
+ thread_number = 0;
+ nr_process = 0;
+ nr_thread = 0;
+ done_secs = 0;
+}
+
+int main(int argc, char *argv[], char *envp[])
+{
+ long ps;
+
+ arch_init(envp);
+
+ sinit();
+
+ /*
+ * We need locale for number printing, if it isn't set then just
+ * go with the US format.
+ */
+ if (!getenv("LC_NUMERIC"))
+ setlocale(LC_NUMERIC, "en_US");
+
+ ps = sysconf(_SC_PAGESIZE);
+ if (ps < 0) {
+ log_err("Failed to get page size\n");
+ return 1;
+ }
+
+ page_size = ps;
+ page_mask = ps - 1;
+
+ fio_keywords_init();
+
+ if (parse_options(argc, argv))
+ return 1;
+
+ return exec_run();
+}
diff --git a/fio.h b/fio.h
index 022ba57..1fb2118 100644
--- a/fio.h
+++ b/fio.h
@@ -16,6 +16,10 @@
struct thread_data;
+#define FIO_MAJOR 1
+#define FIO_MINOR 58
+#define FIO_PATCH 0
+
#include "compiler/compiler.h"
#include "flist.h"
#include "fifo.h"
@@ -35,6 +39,8 @@
#include "time.h"
#include "lib/getopt.h"
#include "lib/rand.h"
+#include "server.h"
+#include "stat.h"
#ifdef FIO_HAVE_GUASI
#include <guasi.h>
@@ -44,14 +50,6 @@
#include <sys/asynch.h>
#endif
-struct group_run_stats {
- unsigned long long max_run[2], min_run[2];
- unsigned long long max_bw[2], min_bw[2];
- unsigned long long io_kb[2];
- unsigned long long agg[2];
- unsigned int kb_base;
-};
-
/*
* What type of allocation to use for io buffers
*/
@@ -71,172 +69,6 @@
RW_SEQ_IDENT,
};
-/*
- * How many depth levels to log
- */
-#define FIO_IO_U_MAP_NR 7
-#define FIO_IO_U_LAT_U_NR 10
-#define FIO_IO_U_LAT_M_NR 12
-
-/*
- * Aggregate clat samples to report percentile(s) of them.
- *
- * EXECUTIVE SUMMARY
- *
- * FIO_IO_U_PLAT_BITS determines the maximum statistical error on the
- * value of resulting percentiles. The error will be approximately
- * 1/2^(FIO_IO_U_PLAT_BITS+1) of the value.
- *
- * FIO_IO_U_PLAT_GROUP_NR and FIO_IO_U_PLAT_BITS determine the maximum
- * range being tracked for latency samples. The maximum value tracked
- * accurately will be 2^(GROUP_NR + PLAT_BITS -1) microseconds.
- *
- * FIO_IO_U_PLAT_GROUP_NR and FIO_IO_U_PLAT_BITS determine the memory
- * requirement of storing those aggregate counts. The memory used will
- * be (FIO_IO_U_PLAT_GROUP_NR * 2^FIO_IO_U_PLAT_BITS) * sizeof(int)
- * bytes.
- *
- * FIO_IO_U_PLAT_NR is the total number of buckets.
- *
- * DETAILS
- *
- * Suppose the clat varies from 0 to 999 (usec), the straightforward
- * method is to keep an array of (999 + 1) buckets, in which a counter
- * keeps the count of samples which fall in the bucket, e.g.,
- * {[0],[1],...,[999]}. However this consumes a huge amount of space,
- * and can be avoided if an approximation is acceptable.
- *
- * One such method is to let the range of the bucket to be greater
- * than one. This method has low accuracy when the value is small. For
- * example, let the buckets be {[0,99],[100,199],...,[900,999]}, and
- * the represented value of each bucket be the mean of the range. Then
- * a value 0 has an round-off error of 49.5. To improve on this, we
- * use buckets with non-uniform ranges, while bounding the error of
- * each bucket within a ratio of the sample value. A simple example
- * would be when error_bound = 0.005, buckets are {
- * {[0],[1],...,[99]}, {[100,101],[102,103],...,[198,199]},..,
- * {[900,909],[910,919]...} }. The total range is partitioned into
- * groups with different ranges, then buckets with uniform ranges. An
- * upper bound of the error is (range_of_bucket/2)/value_of_bucket
- *
- * For better efficiency, we implement this using base two. We group
- * samples by their Most Significant Bit (MSB), extract the next M bit
- * of them as an index within the group, and discard the rest of the
- * bits.
- *
- * E.g., assume a sample 'x' whose MSB is bit n (starting from bit 0),
- * and use M bit for indexing
- *
- * | n | M bits | bit (n-M-1) ... bit 0 |
- *
- * Because x is at least 2^n, and bit 0 to bit (n-M-1) is at most
- * (2^(n-M) - 1), discarding bit 0 to (n-M-1) makes the round-off
- * error
- *
- * 2^(n-M)-1 2^(n-M) 1
- * e <= --------- <= ------- = ---
- * 2^n 2^n 2^M
- *
- * Furthermore, we use "mean" of the range to represent the bucket,
- * the error e can be lowered by half to 1 / 2^(M+1). By using M bits
- * as the index, each group must contains 2^M buckets.
- *
- * E.g. Let M (FIO_IO_U_PLAT_BITS) be 6
- * Error bound is 1/2^(6+1) = 0.0078125 (< 1%)
- *
- * Group MSB #discarded range of #buckets
- * error_bits value
- * ----------------------------------------------------------------
- * 0* 0~5 0 [0,63] 64
- * 1* 6 0 [64,127] 64
- * 2 7 1 [128,255] 64
- * 3 8 2 [256,511] 64
- * 4 9 3 [512,1023] 64
- * ... ... ... [...,...] ...
- * 18 23 17 [8838608,+inf]** 64
- *
- * * Special cases: when n < (M-1) or when n == (M-1), in both cases,
- * the value cannot be rounded off. Use all bits of the sample as
- * index.
- *
- * ** If a sample's MSB is greater than 23, it will be counted as 23.
- */
-
-#define FIO_IO_U_PLAT_BITS 6
-#define FIO_IO_U_PLAT_VAL (1 << FIO_IO_U_PLAT_BITS)
-#define FIO_IO_U_PLAT_GROUP_NR 19
-#define FIO_IO_U_PLAT_NR (FIO_IO_U_PLAT_GROUP_NR * FIO_IO_U_PLAT_VAL)
-#define FIO_IO_U_LIST_MAX_LEN 20 /* The size of the default and user-specified
- list of percentiles */
-
-#define MAX_PATTERN_SIZE 512
-
-struct thread_stat {
- char *name;
- char *verror;
- int error;
- int groupid;
- pid_t pid;
- char *description;
- int members;
-
- struct io_log *slat_log;
- struct io_log *clat_log;
- struct io_log *lat_log;
- struct io_log *bw_log;
-
- /*
- * bandwidth and latency stats
- */
- struct io_stat clat_stat[2]; /* completion latency */
- struct io_stat slat_stat[2]; /* submission latency */
- struct io_stat lat_stat[2]; /* total latency */
- struct io_stat bw_stat[2]; /* bandwidth stats */
-
- unsigned long long stat_io_bytes[2];
- struct timeval stat_sample_time[2];
-
- /*
- * fio system usage accounting
- */
- struct rusage ru_start;
- struct rusage ru_end;
- unsigned long usr_time;
- unsigned long sys_time;
- unsigned long ctx;
- unsigned long minf, majf;
-
- /*
- * IO depth and latency stats
- */
- unsigned int clat_percentiles;
- double* percentile_list;
-
- unsigned int io_u_map[FIO_IO_U_MAP_NR];
- unsigned int io_u_submit[FIO_IO_U_MAP_NR];
- unsigned int io_u_complete[FIO_IO_U_MAP_NR];
- unsigned int io_u_lat_u[FIO_IO_U_LAT_U_NR];
- unsigned int io_u_lat_m[FIO_IO_U_LAT_M_NR];
- unsigned int io_u_plat[2][FIO_IO_U_PLAT_NR];
- unsigned long total_io_u[3];
- unsigned long short_io_u[3];
- unsigned long total_submit;
- unsigned long total_complete;
-
- unsigned long long io_bytes[2];
- unsigned long long runtime[2];
- unsigned long total_run_time;
-
- /*
- * IO Error related stats
- */
- unsigned continue_on_error;
- unsigned long total_err_count;
- int first_error;
-
- unsigned int kb_base;
-};
-
struct bssplit {
unsigned int bs;
unsigned char perc;
@@ -365,7 +197,7 @@
unsigned long long trim_backlog;
unsigned int clat_percentiles;
unsigned int overwrite_plist;
- double percentile_list[FIO_IO_U_LIST_MAX_LEN];
+ fio_fp64_t percentile_list[FIO_IO_U_LIST_MAX_LEN];
char *read_iolog_file;
char *write_iolog_file;
@@ -418,8 +250,6 @@
unsigned int userspace_libaio_reap;
};
-#define FIO_VERROR_SIZE 128
-
/*
* This describes a single thread/process executing a fio job.
*/
@@ -430,6 +260,18 @@
int thread_number;
int groupid;
struct thread_stat ts;
+
+ struct io_log *slat_log;
+ struct io_log *clat_log;
+ struct io_log *lat_log;
+ struct io_log *bw_log;
+
+ uint64_t stat_io_bytes[2];
+ struct timeval stat_sample_time[2];
+
+ struct rusage ru_start;
+ struct rusage ru_end;
+
struct fio_file **files;
unsigned int files_size;
unsigned int files_index;
@@ -632,6 +474,9 @@
#define td_vmsg(td, err, msg, func) \
__td_verror((td), (err), (msg), (func))
+#define __fio_stringify_1(x) #x
+#define __fio_stringify(x) __fio_stringify_1(x)
+
extern int exitall_on_terminate;
extern int thread_number;
extern int nr_process, nr_thread;
@@ -650,6 +495,10 @@
extern enum fio_cs fio_clock_source;
extern int warnings_fatal;
extern int terse_version;
+extern int is_backend;
+extern int nr_clients;
+extern int log_syslog;
+extern const fio_fp64_t def_percentile_list[FIO_IO_U_LIST_MAX_LEN];
extern struct thread_data *threads;
@@ -690,6 +539,10 @@
* Init/option functions
*/
extern int __must_check parse_options(int, char **);
+extern int parse_jobs_ini(char *, int, int);
+extern int parse_cmd_line(int, char **);
+extern int exec_run(void);
+extern void reset_fio_state(void);
extern int fio_options_parse(struct thread_data *, char **, int);
extern void fio_keywords_init(void);
extern int fio_cmd_option_parse(struct thread_data *, const char *, char *);
@@ -731,6 +584,8 @@
};
extern void td_set_runstate(struct thread_data *, int);
+#define TERMINATE_ALL (-1)
+extern void fio_terminate_threads(int);
/*
* Memory helpers
diff --git a/ieee754.c b/ieee754.c
new file mode 100644
index 0000000..c7742a2
--- /dev/null
+++ b/ieee754.c
@@ -0,0 +1,84 @@
+/*
+ * Shamelessly lifted from Beej's Guide to Network Programming, found here:
+ *
+ * http://beej.us/guide/bgnet/output/html/singlepage/bgnet.html#serialization
+ *
+ * Below code was granted to the public domain.
+ */
+#include <inttypes.h>
+#include "ieee754.h"
+
+uint64_t pack754(long double f, unsigned bits, unsigned expbits)
+{
+ long double fnorm;
+ int shift;
+ long long sign, exp, significand;
+ unsigned significandbits = bits - expbits - 1; // -1 for sign bit
+
+ // get this special case out of the way
+ if (f == 0.0)
+ return 0;
+
+ // check sign and begin normalization
+ if (f < 0) {
+ sign = 1;
+ fnorm = -f;
+ } else {
+ sign = 0;
+ fnorm = f;
+ }
+
+ // get the normalized form of f and track the exponent
+ shift = 0;
+ while (fnorm >= 2.0) {
+ fnorm /= 2.0;
+ shift++;
+ }
+ while (fnorm < 1.0) {
+ fnorm *= 2.0;
+ shift--;
+ }
+ fnorm = fnorm - 1.0;
+
+ // calculate the binary form (non-float) of the significand data
+ significand = fnorm * ((1LL << significandbits) + 0.5f);
+
+ // get the biased exponent
+ exp = shift + ((1 << (expbits - 1)) - 1); // shift + bias
+
+ // return the final answer
+ return (sign << (bits - 1)) | (exp << (bits-expbits - 1)) | significand;
+}
+
+long double unpack754(uint64_t i, unsigned bits, unsigned expbits)
+{
+ long double result;
+ long long shift;
+ unsigned bias;
+ unsigned significandbits = bits - expbits - 1; // -1 for sign bit
+
+ if (i == 0)
+ return 0.0;
+
+ // pull the significand
+ result = (i & ((1LL << significandbits) - 1)); // mask
+ result /= (1LL << significandbits); // convert back to float
+ result += 1.0f; // add the one back on
+
+ // deal with the exponent
+ bias = (1 << (expbits - 1)) - 1;
+ shift = ((i >> significandbits) & ((1LL << expbits) - 1)) - bias;
+ while (shift > 0) {
+ result *= 2.0;
+ shift--;
+ }
+ while (shift < 0) {
+ result /= 2.0;
+ shift++;
+ }
+
+ // sign it
+ result *= (i >> (bits - 1)) & 1 ? -1.0 : 1.0;
+
+ return result;
+}
diff --git a/ieee754.h b/ieee754.h
new file mode 100644
index 0000000..5af9518
--- /dev/null
+++ b/ieee754.h
@@ -0,0 +1,20 @@
+#ifndef FIO_IEEE754_H
+#define FIO_IEEE754_H
+
+#include <inttypes.h>
+
+extern uint64_t pack754(long double f, unsigned bits, unsigned expbits);
+extern long double unpack754(uint64_t i, unsigned bits, unsigned expbits);
+
+#define fio_double_to_uint64(val) pack754((val), 64, 11)
+#define fio_uint64_to_double(val) unpack754((val), 64, 11)
+
+typedef struct fio_fp64 {
+ union {
+ uint64_t i;
+ double f;
+ uint8_t filler[16];
+ } u;
+} fio_fp64_t;
+
+#endif
diff --git a/init.c b/init.c
index 4eb0df2..142a163 100644
--- a/init.c
+++ b/init.c
@@ -19,10 +19,18 @@
#include "filehash.h"
#include "verify.h"
#include "profile.h"
+#include "server.h"
#include "lib/getopt.h"
-static char fio_version_string[] = "fio 1.58";
+#if FIO_PATCH > 0
+static char fio_version_string[] = __fio_stringify(FIO_MAJOR) "." \
+ __fio_stringify(FIO_MINOR) "." \
+ __fio_stringify(FIO_PATCH);
+#else
+static char fio_version_string[] = __fio_stringify(FIO_MAJOR) "." \
+ __fio_stringify(FIO_MINOR);
+#endif
#define FIO_RANDSEED (0xb1899bedUL)
@@ -45,6 +53,9 @@
char *exec_profile = NULL;
int warnings_fatal = 0;
int terse_version = 2;
+int is_backend = 0;
+int nr_clients = 0;
+int log_syslog = 0;
int write_bw_log = 0;
int read_only = 0;
@@ -59,6 +70,24 @@
static char cmd_optstr[256];
+const fio_fp64_t def_percentile_list[FIO_IO_U_LIST_MAX_LEN] = {
+ { .u.f = 1.0 },
+ { .u.f = 5.0 },
+ { .u.f = 10.0 },
+ { .u.f = 20.0 },
+ { .u.f = 30.0 },
+ { .u.f = 40.0 },
+ { .u.f = 50.0 },
+ { .u.f = 60.0 },
+ { .u.f = 70.0 },
+ { .u.f = 80.0 },
+ { .u.f = 90.0 },
+ { .u.f = 95.0 },
+ { .u.f = 99.0 },
+ { .u.f = 99.5 },
+ { .u.f = 99.9 },
+};
+
/*
* Command line options. These will contain the above, plus a few
* extra that only pertain to fio itself and not jobs.
@@ -155,18 +184,95 @@
.val = 'V',
},
{
+ .name = (char *) "server",
+ .has_arg = no_argument,
+ .val = 'S',
+ },
+ { .name = (char *) "daemonize",
+ .has_arg = no_argument,
+ .val = 'D',
+ },
+ {
+ .name = (char *) "net-port",
+ .has_arg = required_argument,
+ .val = 'P',
+ },
+ {
+ .name = (char *) "client",
+ .has_arg = required_argument,
+ .val = 'C',
+ },
+ {
.name = NULL,
},
};
-FILE *get_f_out()
+static void free_shm(void)
{
- return f_out;
+ struct shmid_ds sbuf;
+
+ if (threads) {
+ void *tp = threads;
+
+ threads = NULL;
+ file_hash_exit();
+ fio_debug_jobp = NULL;
+ shmdt(tp);
+ shmctl(shm_id, IPC_RMID, &sbuf);
+ }
+
+ scleanup();
}
-FILE *get_f_err()
+/*
+ * The thread area is shared between the main process and the job
+ * threads/processes. So setup a shared memory segment that will hold
+ * all the job info. We use the end of the region for keeping track of
+ * open files across jobs, for file sharing.
+ */
+static int setup_thread_area(void)
{
- return f_err;
+ void *hash;
+
+ if (threads)
+ return 0;
+
+ /*
+ * 1024 is too much on some machines, scale max_jobs if
+ * we get a failure that looks like too large a shm segment
+ */
+ do {
+ size_t size = max_jobs * sizeof(struct thread_data);
+
+ size += file_hash_size;
+ size += sizeof(unsigned int);
+
+ shm_id = shmget(0, size, IPC_CREAT | 0600);
+ if (shm_id != -1)
+ break;
+ if (errno != EINVAL) {
+ perror("shmget");
+ break;
+ }
+
+ max_jobs >>= 1;
+ } while (max_jobs);
+
+ if (shm_id == -1)
+ return 1;
+
+ threads = shmat(shm_id, NULL, 0);
+ if (threads == (void *) -1) {
+ perror("shmat");
+ return 1;
+ }
+
+ memset(threads, 0, max_jobs * sizeof(struct thread_data));
+ hash = (void *) threads + max_jobs * sizeof(struct thread_data);
+ fio_debug_jobp = (void *) hash + file_hash_size;
+ *fio_debug_jobp = -1;
+ file_hash_init(hash);
+ return 0;
}
/*
@@ -178,6 +284,10 @@
if (global)
return &def_thread;
+ if (setup_thread_area()) {
+ log_err("error: failed to setup shm segment\n");
+ return NULL;
+ }
if (thread_number >= max_jobs) {
log_err("error: maximum number of jobs (%d) reached.\n",
max_jobs);
@@ -627,9 +737,9 @@
td->ts.clat_percentiles = td->o.clat_percentiles;
if (td->o.overwrite_plist)
- td->ts.percentile_list = td->o.percentile_list;
+ memcpy(td->ts.percentile_list, td->o.percentile_list, sizeof(td->o.percentile_list));
else
- td->ts.percentile_list = NULL;
+ memcpy(td->ts.percentile_list, def_percentile_list, sizeof(def_percentile_list));
td->ts.clat_stat[0].min_val = td->ts.clat_stat[1].min_val = ULONG_MAX;
td->ts.slat_stat[0].min_val = td->ts.slat_stat[1].min_val = ULONG_MAX;
@@ -652,12 +762,12 @@
goto err;
if (td->o.write_lat_log) {
- setup_log(&td->ts.lat_log);
- setup_log(&td->ts.slat_log);
- setup_log(&td->ts.clat_log);
+ setup_log(&td->lat_log);
+ setup_log(&td->slat_log);
+ setup_log(&td->clat_log);
}
if (td->o.write_bw_log)
- setup_log(&td->ts.bw_log);
+ setup_log(&td->bw_log);
if (!td->o.name)
td->o.name = strdup(jobname);
@@ -800,7 +910,7 @@
/*
* This is our [ini] type file parser.
*/
-static int parse_jobs_ini(char *file, int stonewall_flag)
+int parse_jobs_ini(char *file, int is_buf, int stonewall_flag)
{
unsigned int global;
struct thread_data *td;
@@ -814,14 +924,18 @@
char **opts;
int i, alloc_opts, num_opts;
- if (!strcmp(file, "-"))
- f = stdin;
- else
- f = fopen(file, "r");
+ if (is_buf)
+ f = NULL;
+ else {
+ if (!strcmp(file, "-"))
+ f = stdin;
+ else
+ f = fopen(file, "r");
- if (!f) {
- perror("fopen job file");
- return 1;
+ if (!f) {
+ perror("fopen job file");
+ return 1;
+ }
}
string = malloc(4096);
@@ -843,7 +957,10 @@
* haven't handled.
*/
if (!skip_fgets) {
- p = fgets(string, 4095, f);
+ if (is_buf)
+ p = strsep(&file, "\n");
+ else
+ p = fgets(string, 4095, f);
if (!p)
break;
}
@@ -897,7 +1014,14 @@
num_opts = 0;
memset(opts, 0, alloc_opts * sizeof(char *));
- while ((p = fgets(string, 4096, f)) != NULL) {
+ while (1) {
+ if (is_buf)
+ p = strsep(&file, "\n");
+ else
+ p = fgets(string, 4096, f);
+ if (!p)
+ break;
+
if (is_empty_or_comment(p))
continue;
@@ -950,7 +1074,7 @@
free(string);
free(name);
free(opts);
- if (f != stdin)
+ if (!is_buf && f != stdin)
fclose(f);
return ret;
}
@@ -969,72 +1093,6 @@
return 0;
}
-static void free_shm(void)
-{
- struct shmid_ds sbuf;
-
- if (threads) {
- void *tp = threads;
-
- threads = NULL;
- file_hash_exit();
- fio_debug_jobp = NULL;
- shmdt(tp);
- shmctl(shm_id, IPC_RMID, &sbuf);
- }
-
- scleanup();
-}
-
-/*
- * The thread area is shared between the main process and the job
- * threads/processes. So setup a shared memory segment that will hold
- * all the job info. We use the end of the region for keeping track of
- * open files across jobs, for file sharing.
- */
-static int setup_thread_area(void)
-{
- void *hash;
-
- /*
- * 1024 is too much on some machines, scale max_jobs if
- * we get a failure that looks like too large a shm segment
- */
- do {
- size_t size = max_jobs * sizeof(struct thread_data);
-
- size += file_hash_size;
- size += sizeof(unsigned int);
-
- shm_id = shmget(0, size, IPC_CREAT | 0600);
- if (shm_id != -1)
- break;
- if (errno != EINVAL) {
- perror("shmget");
- break;
- }
-
- max_jobs >>= 1;
- } while (max_jobs);
-
- if (shm_id == -1)
- return 1;
-
- threads = shmat(shm_id, NULL, 0);
- if (threads == (void *) -1) {
- perror("shmat");
- return 1;
- }
-
- memset(threads, 0, max_jobs * sizeof(struct thread_data));
- hash = (void *) threads + max_jobs * sizeof(struct thread_data);
- fio_debug_jobp = (void *) hash + file_hash_size;
- *fio_debug_jobp = -1;
- file_hash_init(hash);
- atexit(free_shm);
- return 0;
-}
-
static void usage(const char *name)
{
printf("%s\n", fio_version_string);
@@ -1060,6 +1118,9 @@
" (def 1024)\n");
printf("\t--warnings-fatal Fio parser warnings are fatal\n");
printf("\t--max-jobs\tMaximum number of threads/processes to support\n");
+ printf("\t--server\tStart a backend fio server\n");
+ printf("\t--client=hostname Talk to remove backend fio server at hostname\n");
+ printf("\t--net-port=port\tUse specified port for client/server connection\n");
printf("\nFio was written by Jens Axboe <jens.axboe@oracle.com>");
printf("\n Jens Axboe <jaxboe@fusionio.com>\n");
}
@@ -1079,6 +1140,7 @@
{ .name = "mutex", .shift = FD_MUTEX },
{ .name = "profile", .shift = FD_PROFILE },
{ .name = "time", .shift = FD_TIME },
+ { .name = "net", .shift = FD_NET },
{ .name = NULL, },
};
@@ -1163,27 +1225,35 @@
ostr[c] = '\0';
}
-static int parse_cmd_line(int argc, char *argv[])
+int parse_cmd_line(int argc, char *argv[])
{
struct thread_data *td = NULL;
int c, ini_idx = 0, lidx, ret = 0, do_exit = 0, exit_val = 0;
char *ostr = cmd_optstr;
+ int daemonize_server = 0;
+ char *cur_client = NULL;
+ int backend = 0;
while ((c = getopt_long_only(argc, argv, ostr, l_opts, &lidx)) != -1) {
switch (c) {
case 'a':
+ fio_client_add_cmd_option(cur_client, argv[optind-1]);
smalloc_pool_size = atoi(optarg);
break;
case 't':
+ fio_client_add_cmd_option(cur_client, argv[optind-1]);
def_timeout = atoi(optarg);
break;
case 'l':
+ fio_client_add_cmd_option(cur_client, argv[optind-1]);
write_lat_log = 1;
break;
case 'b':
+ fio_client_add_cmd_option(cur_client, argv[optind-1]);
write_bw_log = 1;
break;
case 'o':
+ fio_client_add_cmd_option(cur_client, argv[optind-1]);
f_out = fopen(optarg, "w+");
if (!f_out) {
perror("fopen output");
@@ -1192,6 +1262,7 @@
f_err = f_out;
break;
case 'm':
+ fio_client_add_cmd_option(cur_client, argv[optind-1]);
terse_output = 1;
break;
case 'h':
@@ -1200,15 +1271,18 @@
case 'c':
exit(fio_show_option_help(optarg));
case 's':
+ fio_client_add_cmd_option(cur_client, argv[optind-1]);
dump_cmdline = 1;
break;
case 'r':
+ fio_client_add_cmd_option(cur_client, argv[optind-1]);
read_only = 1;
break;
case 'v':
log_info("%s\n", fio_version_string);
exit(0);
case 'V':
+ fio_client_add_cmd_option(cur_client, argv[optind-1]);
terse_version = atoi(optarg);
if (terse_version != 2) {
log_err("fio: bad terse version format\n");
@@ -1217,18 +1291,22 @@
}
break;
case 'e':
+ fio_client_add_cmd_option(cur_client, argv[optind-1]);
if (!strcmp("always", optarg))
eta_print = FIO_ETA_ALWAYS;
else if (!strcmp("never", optarg))
eta_print = FIO_ETA_NEVER;
break;
case 'd':
+ fio_client_add_cmd_option(cur_client, argv[optind-1]);
if (set_debug(optarg))
do_exit++;
break;
case 'x': {
size_t new_size;
+ fio_client_add_cmd_option(cur_client, argv[optind-1]);
+
if (!strcmp(optarg, "global")) {
log_err("fio: can't use global as only "
"section\n");
@@ -1243,12 +1321,15 @@
break;
}
case 'p':
+ fio_client_add_cmd_option(cur_client, argv[optind-1]);
exec_profile = strdup(optarg);
break;
case FIO_GETOPT_JOB: {
const char *opt = l_opts[lidx].name;
char *val = optarg;
+ fio_client_add_cmd_option(cur_client, argv[optind-1]);
+
if (!strncmp(opt, "name", 4) && td) {
ret = add_job(td, td->o.name ?: "fio", 0);
if (ret)
@@ -1274,9 +1355,11 @@
break;
}
case 'w':
+ fio_client_add_cmd_option(cur_client, argv[optind-1]);
warnings_fatal = 1;
break;
case 'j':
+ fio_client_add_cmd_option(cur_client, argv[optind-1]);
max_jobs = atoi(optarg);
if (!max_jobs || max_jobs > REAL_MAX_JOBS) {
log_err("fio: invalid max jobs: %d\n", max_jobs);
@@ -1284,16 +1367,53 @@
exit_val = 1;
}
break;
+ case 'S':
+ if (nr_clients) {
+ log_err("fio: can't be both client and server\n");
+ do_exit++;
+ exit_val = 1;
+ break;
+ }
+ is_backend = 1;
+ backend = 1;
+ break;
+ case 'D':
+ daemonize_server = 1;
+ break;
+ case 'P':
+ fio_net_port = atoi(optarg);
+ break;
+ case 'C':
+ if (is_backend) {
+ log_err("fio: can't be both client and server\n");
+ do_exit++;
+ exit_val = 1;
+ break;
+ }
+ fio_client_add(optarg);
+ cur_client = optarg;
+ break;
default:
do_exit++;
exit_val = 1;
break;
}
+ if (do_exit)
+ break;
}
if (do_exit)
exit(exit_val);
+ if (nr_clients && fio_clients_connect()) {
+ do_exit++;
+ exit_val = 1;
+ return -1;
+ }
+
+ if (is_backend && backend)
+ return fio_start_server(daemonize_server);
+
if (td) {
if (!ret)
ret = add_job(td, td->o.name ?: "fio", 0);
@@ -1319,19 +1439,27 @@
fio_options_fill_optstring();
fio_options_dup_and_init(l_opts);
- if (setup_thread_area())
- return 1;
+ atexit(free_shm);
+
if (fill_def_thread())
return 1;
job_files = parse_cmd_line(argc, argv);
- for (i = 0; i < job_files; i++) {
- if (fill_def_thread())
- return 1;
- if (parse_jobs_ini(ini_file[i], i))
- return 1;
- free(ini_file[i]);
+ if (job_files > 0) {
+ for (i = 0; i < job_files; i++) {
+ if (fill_def_thread())
+ return 1;
+ if (nr_clients) {
+ if (fio_clients_send_ini(ini_file[i]))
+ return 1;
+ free(ini_file[i]);
+ } else if (!is_backend) {
+ if (parse_jobs_ini(ini_file[i], 0, i))
+ return 1;
+ free(ini_file[i]);
+ }
+ }
}
free(ini_file);
@@ -1342,9 +1470,13 @@
return 0;
if (exec_profile)
return 0;
+ if (is_backend || nr_clients)
+ return 0;
- log_err("No jobs(s) defined\n\n");
- usage(argv[0]);
+ if (job_files > 0) {
+ log_err("No jobs(s) defined\n\n");
+ usage(argv[0]);
+ }
return 1;
}
diff --git a/iolog.c b/iolog.c
new file mode 100644
index 0000000..f962864
--- /dev/null
+++ b/iolog.c
@@ -0,0 +1,541 @@
+/*
+ * Code related to writing an iolog of what a thread is doing, and to
+ * later read that back and replay
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <libgen.h>
+#include <assert.h>
+#include "flist.h"
+#include "fio.h"
+#include "verify.h"
+#include "trim.h"
+
+static const char iolog_ver2[] = "fio version 2 iolog";
+
+void queue_io_piece(struct thread_data *td, struct io_piece *ipo)
+{
+ flist_add_tail(&ipo->list, &td->io_log_list);
+ td->total_io_size += ipo->len;
+}
+
+void log_io_u(struct thread_data *td, struct io_u *io_u)
+{
+ const char *act[] = { "read", "write", "sync", "datasync",
+ "sync_file_range", "wait", "trim" };
+
+ assert(io_u->ddir <= 6);
+
+ if (!td->o.write_iolog_file)
+ return;
+
+ fprintf(td->iolog_f, "%s %s %llu %lu\n", io_u->file->file_name,
+ act[io_u->ddir], io_u->offset,
+ io_u->buflen);
+}
+
+void log_file(struct thread_data *td, struct fio_file *f,
+ enum file_log_act what)
+{
+ const char *act[] = { "add", "open", "close" };
+
+ assert(what < 3);
+
+ if (!td->o.write_iolog_file)
+ return;
+
+
+ /*
+ * this happens on the pre-open/close done before the job starts
+ */
+ if (!td->iolog_f)
+ return;
+
+ fprintf(td->iolog_f, "%s %s\n", f->file_name, act[what]);
+}
+
+static void iolog_delay(struct thread_data *td, unsigned long delay)
+{
+ unsigned long usec = utime_since_now(&td->last_issue);
+
+ if (delay < usec)
+ return;
+
+ delay -= usec;
+
+ /*
+ * less than 100 usec delay, just regard it as noise
+ */
+ if (delay < 100)
+ return;
+
+ usec_sleep(td, delay);
+}
+
+static int ipo_special(struct thread_data *td, struct io_piece *ipo)
+{
+ struct fio_file *f;
+ int ret;
+
+ /*
+ * Not a special ipo
+ */
+ if (ipo->ddir != DDIR_INVAL)
+ return 0;
+
+ f = td->files[ipo->fileno];
+
+ switch (ipo->file_action) {
+ case FIO_LOG_OPEN_FILE:
+ ret = td_io_open_file(td, f);
+ if (!ret)
+ break;
+ td_verror(td, ret, "iolog open file");
+ return -1;
+ case FIO_LOG_CLOSE_FILE:
+ td_io_close_file(td, f);
+ break;
+ case FIO_LOG_UNLINK_FILE:
+ unlink(f->file_name);
+ break;
+ default:
+ log_err("fio: bad file action %d\n", ipo->file_action);
+ break;
+ }
+
+ return 1;
+}
+
+int read_iolog_get(struct thread_data *td, struct io_u *io_u)
+{
+ struct io_piece *ipo;
+ unsigned long elapsed;
+
+ while (!flist_empty(&td->io_log_list)) {
+ int ret;
+
+ ipo = flist_entry(td->io_log_list.next, struct io_piece, list);
+ flist_del(&ipo->list);
+ remove_trim_entry(td, ipo);
+
+ ret = ipo_special(td, ipo);
+ if (ret < 0) {
+ free(ipo);
+ break;
+ } else if (ret > 0) {
+ free(ipo);
+ continue;
+ }
+
+ io_u->ddir = ipo->ddir;
+ if (ipo->ddir != DDIR_WAIT) {
+ io_u->offset = ipo->offset;
+ io_u->buflen = ipo->len;
+ io_u->file = td->files[ipo->fileno];
+ get_file(io_u->file);
+ dprint(FD_IO, "iolog: get %llu/%lu/%s\n", io_u->offset,
+ io_u->buflen, io_u->file->file_name);
+ if (ipo->delay)
+ iolog_delay(td, ipo->delay);
+ } else {
+ elapsed = mtime_since_genesis();
+ if (ipo->delay > elapsed)
+ usec_sleep(td, (ipo->delay - elapsed) * 1000);
+
+ }
+
+ free(ipo);
+
+ if (io_u->ddir != DDIR_WAIT)
+ return 0;
+ }
+
+ td->done = 1;
+ return 1;
+}
+
+void prune_io_piece_log(struct thread_data *td)
+{
+ struct io_piece *ipo;
+ struct rb_node *n;
+
+ while ((n = rb_first(&td->io_hist_tree)) != NULL) {
+ ipo = rb_entry(n, struct io_piece, rb_node);
+ rb_erase(n, &td->io_hist_tree);
+ remove_trim_entry(td, ipo);
+ td->io_hist_len--;
+ free(ipo);
+ }
+
+ while (!flist_empty(&td->io_hist_list)) {
+ ipo = flist_entry(td->io_hist_list.next, struct io_piece, list);
+ flist_del(&ipo->list);
+ remove_trim_entry(td, ipo);
+ td->io_hist_len--;
+ free(ipo);
+ }
+}
+
+/*
+ * log a successful write, so we can unwind the log for verify
+ */
+void log_io_piece(struct thread_data *td, struct io_u *io_u)
+{
+ struct rb_node **p, *parent;
+ struct io_piece *ipo, *__ipo;
+
+ ipo = malloc(sizeof(struct io_piece));
+ init_ipo(ipo);
+ ipo->file = io_u->file;
+ ipo->offset = io_u->offset;
+ ipo->len = io_u->buflen;
+
+ if (io_u_should_trim(td, io_u)) {
+ flist_add_tail(&ipo->trim_list, &td->trim_list);
+ td->trim_entries++;
+ }
+
+ /*
+ * We don't need to sort the entries, if:
+ *
+ * Sequential writes, or
+ * Random writes that lay out the file as it goes along
+ *
+ * For both these cases, just reading back data in the order we
+ * wrote it out is the fastest.
+ *
+ * One exception is if we don't have a random map AND we are doing
+ * verifies, in that case we need to check for duplicate blocks and
+ * drop the old one, which we rely on the rb insert/lookup for
+ * handling.
+ */
+ if ((!td_random(td) || !td->o.overwrite) &&
+ (file_randommap(td, ipo->file) || td->o.verify == VERIFY_NONE)) {
+ INIT_FLIST_HEAD(&ipo->list);
+ flist_add_tail(&ipo->list, &td->io_hist_list);
+ ipo->flags |= IP_F_ONLIST;
+ td->io_hist_len++;
+ return;
+ }
+
+ RB_CLEAR_NODE(&ipo->rb_node);
+
+ /*
+ * Sort the entry into the verification list
+ */
+restart:
+ p = &td->io_hist_tree.rb_node;
+ parent = NULL;
+ while (*p) {
+ parent = *p;
+
+ __ipo = rb_entry(parent, struct io_piece, rb_node);
+ if (ipo->file < __ipo->file)
+ p = &(*p)->rb_left;
+ else if (ipo->file > __ipo->file)
+ p = &(*p)->rb_right;
+ else if (ipo->offset < __ipo->offset)
+ p = &(*p)->rb_left;
+ else if (ipo->offset > __ipo->offset)
+ p = &(*p)->rb_right;
+ else {
+ assert(ipo->len == __ipo->len);
+ td->io_hist_len--;
+ rb_erase(parent, &td->io_hist_tree);
+ remove_trim_entry(td, __ipo);
+ free(__ipo);
+ goto restart;
+ }
+ }
+
+ rb_link_node(&ipo->rb_node, parent, p);
+ rb_insert_color(&ipo->rb_node, &td->io_hist_tree);
+ ipo->flags |= IP_F_ONRB;
+ td->io_hist_len++;
+}
+
+void write_iolog_close(struct thread_data *td)
+{
+ fflush(td->iolog_f);
+ fclose(td->iolog_f);
+ free(td->iolog_buf);
+ td->iolog_f = NULL;
+ td->iolog_buf = NULL;
+}
+
+/*
+ * Read version 2 iolog data. It is enhanced to include per-file logging,
+ * syncs, etc.
+ */
+static int read_iolog2(struct thread_data *td, FILE *f)
+{
+ unsigned long long offset;
+ unsigned int bytes;
+ int reads, writes, waits, fileno = 0, file_action = 0; /* stupid gcc */
+ char *fname, *act;
+ char *str, *p;
+ enum fio_ddir rw;
+
+ free_release_files(td);
+
+ /*
+ * Read in the read iolog and store it, reuse the infrastructure
+ * for doing verifications.
+ */
+ str = malloc(4096);
+ fname = malloc(256+16);
+ act = malloc(256+16);
+
+ reads = writes = waits = 0;
+ while ((p = fgets(str, 4096, f)) != NULL) {
+ struct io_piece *ipo;
+ int r;
+
+ r = sscanf(p, "%256s %256s %llu %u", fname, act, &offset,
+ &bytes);
+ if (r == 4) {
+ /*
+ * Check action first
+ */
+ if (!strcmp(act, "wait"))
+ rw = DDIR_WAIT;
+ else if (!strcmp(act, "read"))
+ rw = DDIR_READ;
+ else if (!strcmp(act, "write"))
+ rw = DDIR_WRITE;
+ else if (!strcmp(act, "sync"))
+ rw = DDIR_SYNC;
+ else if (!strcmp(act, "datasync"))
+ rw = DDIR_DATASYNC;
+ else if (!strcmp(act, "trim"))
+ rw = DDIR_TRIM;
+ else {
+ log_err("fio: bad iolog file action: %s\n",
+ act);
+ continue;
+ }
+ } else if (r == 2) {
+ rw = DDIR_INVAL;
+ if (!strcmp(act, "add")) {
+ td->o.nr_files++;
+ fileno = add_file(td, fname);
+ file_action = FIO_LOG_ADD_FILE;
+ continue;
+ } else if (!strcmp(act, "open")) {
+ fileno = get_fileno(td, fname);
+ file_action = FIO_LOG_OPEN_FILE;
+ } else if (!strcmp(act, "close")) {
+ fileno = get_fileno(td, fname);
+ file_action = FIO_LOG_CLOSE_FILE;
+ } else {
+ log_err("fio: bad iolog file action: %s\n",
+ act);
+ continue;
+ }
+ } else {
+ log_err("bad iolog2: %s", p);
+ continue;
+ }
+
+ if (rw == DDIR_READ)
+ reads++;
+ else if (rw == DDIR_WRITE) {
+ /*
+ * Don't add a write for ro mode
+ */
+ if (read_only)
+ continue;
+ writes++;
+ } else if (rw == DDIR_WAIT) {
+ waits++;
+ } else if (rw == DDIR_INVAL) {
+ } else if (!ddir_sync(rw)) {
+ log_err("bad ddir: %d\n", rw);
+ continue;
+ }
+
+ /*
+ * Make note of file
+ */
+ ipo = malloc(sizeof(*ipo));
+ init_ipo(ipo);
+ ipo->ddir = rw;
+ if (rw == DDIR_WAIT) {
+ ipo->delay = offset;
+ } else {
+ ipo->offset = offset;
+ ipo->len = bytes;
+ if (bytes > td->o.max_bs[rw])
+ td->o.max_bs[rw] = bytes;
+ ipo->fileno = fileno;
+ ipo->file_action = file_action;
+ }
+
+ queue_io_piece(td, ipo);
+ }
+
+ free(str);
+ free(act);
+ free(fname);
+
+ if (writes && read_only) {
+ log_err("fio: <%s> skips replay of %d writes due to"
+ " read-only\n", td->o.name, writes);
+ writes = 0;
+ }
+
+ if (!reads && !writes && !waits)
+ return 1;
+ else if (reads && !writes)
+ td->o.td_ddir = TD_DDIR_READ;
+ else if (!reads && writes)
+ td->o.td_ddir = TD_DDIR_WRITE;
+ else
+ td->o.td_ddir = TD_DDIR_RW;
+
+ return 0;
+}
+
+/*
+ * open iolog, check version, and call appropriate parser
+ */
+static int init_iolog_read(struct thread_data *td)
+{
+ char buffer[256], *p;
+ FILE *f;
+ int ret;
+
+ f = fopen(td->o.read_iolog_file, "r");
+ if (!f) {
+ perror("fopen read iolog");
+ return 1;
+ }
+
+ p = fgets(buffer, sizeof(buffer), f);
+ if (!p) {
+ td_verror(td, errno, "iolog read");
+ log_err("fio: unable to read iolog\n");
+ fclose(f);
+ return 1;
+ }
+
+ /*
+ * version 2 of the iolog stores a specific string as the
+ * first line, check for that
+ */
+ if (!strncmp(iolog_ver2, buffer, strlen(iolog_ver2)))
+ ret = read_iolog2(td, f);
+ else {
+ log_err("fio: iolog version 1 is no longer supported\n");
+ ret = 1;
+ }
+
+ fclose(f);
+ return ret;
+}
+
+/*
+ * Set up a log for storing io patterns.
+ */
+static int init_iolog_write(struct thread_data *td)
+{
+ struct fio_file *ff;
+ FILE *f;
+ unsigned int i;
+
+ f = fopen(td->o.write_iolog_file, "a");
+ if (!f) {
+ perror("fopen write iolog");
+ return 1;
+ }
+
+ /*
+ * That's it for writing, setup a log buffer and we're done.
+ */
+ td->iolog_f = f;
+ td->iolog_buf = malloc(8192);
+ setvbuf(f, td->iolog_buf, _IOFBF, 8192);
+
+ /*
+ * write our version line
+ */
+ if (fprintf(f, "%s\n", iolog_ver2) < 0) {
+ perror("iolog init\n");
+ return 1;
+ }
+
+ /*
+ * add all known files
+ */
+ for_each_file(td, ff, i)
+ log_file(td, ff, FIO_LOG_ADD_FILE);
+
+ return 0;
+}
+
+int init_iolog(struct thread_data *td)
+{
+ int ret = 0;
+
+ if (td->o.read_iolog_file) {
+ /*
+ * Check if it's a blktrace file and load that if possible.
+ * Otherwise assume it's a normal log file and load that.
+ */
+ if (is_blktrace(td->o.read_iolog_file))
+ ret = load_blktrace(td, td->o.read_iolog_file);
+ else
+ ret = init_iolog_read(td);
+ } else if (td->o.write_iolog_file)
+ ret = init_iolog_write(td);
+
+ return ret;
+}
+
+void setup_log(struct io_log **log)
+{
+ struct io_log *l = malloc(sizeof(*l));
+
+ l->nr_samples = 0;
+ l->max_samples = 1024;
+ l->log = malloc(l->max_samples * sizeof(struct io_sample));
+ *log = l;
+}
+
+void __finish_log(struct io_log *log, const char *name)
+{
+ unsigned int i;
+ FILE *f;
+
+ f = fopen(name, "a");
+ if (!f) {
+ perror("fopen log");
+ return;
+ }
+
+ for (i = 0; i < log->nr_samples; i++) {
+ fprintf(f, "%lu, %lu, %u, %u\n", log->log[i].time,
+ log->log[i].val,
+ log->log[i].ddir,
+ log->log[i].bs);
+ }
+
+ fclose(f);
+ free(log->log);
+ free(log);
+}
+
+void finish_log_named(struct thread_data *td, struct io_log *log,
+ const char *prefix, const char *postfix)
+{
+ char file_name[256], *p;
+
+ snprintf(file_name, 200, "%s_%s.log", prefix, postfix);
+ p = basename(file_name);
+ __finish_log(log, p);
+}
+
+void finish_log(struct thread_data *td, struct io_log *log, const char *name)
+{
+ finish_log_named(td, log, td->o.name, name);
+}
diff --git a/iolog.h b/iolog.h
index c35ce1e..dd4d647 100644
--- a/iolog.h
+++ b/iolog.h
@@ -1,16 +1,18 @@
#ifndef FIO_IOLOG_H
#define FIO_IOLOG_H
+#include "ieee754.h"
+
/*
* Use for maintaining statistics
*/
struct io_stat {
- unsigned long max_val;
- unsigned long min_val;
- unsigned long samples;
+ uint64_t max_val;
+ uint64_t min_val;
+ uint64_t samples;
- double mean;
- double S;
+ fio_fp64_t mean;
+ fio_fp64_t S;
};
/*
diff --git a/log.c b/log.c
index f962864..ec3ebce 100644
--- a/log.c
+++ b/log.c
@@ -1,541 +1,76 @@
-/*
- * Code related to writing an iolog of what a thread is doing, and to
- * later read that back and replay
- */
-#include <stdio.h>
-#include <stdlib.h>
-#include <libgen.h>
-#include <assert.h>
-#include "flist.h"
+#include <unistd.h>
+#include <fcntl.h>
+#include <string.h>
+#include <stdarg.h>
+#include <syslog.h>
+
#include "fio.h"
-#include "verify.h"
-#include "trim.h"
-static const char iolog_ver2[] = "fio version 2 iolog";
-
-void queue_io_piece(struct thread_data *td, struct io_piece *ipo)
+int log_valist(const char *str, va_list args)
{
- flist_add_tail(&ipo->list, &td->io_log_list);
- td->total_io_size += ipo->len;
-}
+ char buffer[1024];
+ size_t len;
-void log_io_u(struct thread_data *td, struct io_u *io_u)
-{
- const char *act[] = { "read", "write", "sync", "datasync",
- "sync_file_range", "wait", "trim" };
+ len = vsnprintf(buffer, sizeof(buffer), str, args);
- assert(io_u->ddir <= 6);
-
- if (!td->o.write_iolog_file)
- return;
-
- fprintf(td->iolog_f, "%s %s %llu %lu\n", io_u->file->file_name,
- act[io_u->ddir], io_u->offset,
- io_u->buflen);
-}
-
-void log_file(struct thread_data *td, struct fio_file *f,
- enum file_log_act what)
-{
- const char *act[] = { "add", "open", "close" };
-
- assert(what < 3);
-
- if (!td->o.write_iolog_file)
- return;
-
-
- /*
- * this happens on the pre-open/close done before the job starts
- */
- if (!td->iolog_f)
- return;
-
- fprintf(td->iolog_f, "%s %s\n", f->file_name, act[what]);
-}
-
-static void iolog_delay(struct thread_data *td, unsigned long delay)
-{
- unsigned long usec = utime_since_now(&td->last_issue);
-
- if (delay < usec)
- return;
-
- delay -= usec;
-
- /*
- * less than 100 usec delay, just regard it as noise
- */
- if (delay < 100)
- return;
-
- usec_sleep(td, delay);
-}
-
-static int ipo_special(struct thread_data *td, struct io_piece *ipo)
-{
- struct fio_file *f;
- int ret;
-
- /*
- * Not a special ipo
- */
- if (ipo->ddir != DDIR_INVAL)
- return 0;
-
- f = td->files[ipo->fileno];
-
- switch (ipo->file_action) {
- case FIO_LOG_OPEN_FILE:
- ret = td_io_open_file(td, f);
- if (!ret)
- break;
- td_verror(td, ret, "iolog open file");
- return -1;
- case FIO_LOG_CLOSE_FILE:
- td_io_close_file(td, f);
- break;
- case FIO_LOG_UNLINK_FILE:
- unlink(f->file_name);
- break;
- default:
- log_err("fio: bad file action %d\n", ipo->file_action);
- break;
- }
-
- return 1;
-}
-
-int read_iolog_get(struct thread_data *td, struct io_u *io_u)
-{
- struct io_piece *ipo;
- unsigned long elapsed;
-
- while (!flist_empty(&td->io_log_list)) {
- int ret;
-
- ipo = flist_entry(td->io_log_list.next, struct io_piece, list);
- flist_del(&ipo->list);
- remove_trim_entry(td, ipo);
-
- ret = ipo_special(td, ipo);
- if (ret < 0) {
- free(ipo);
- break;
- } else if (ret > 0) {
- free(ipo);
- continue;
- }
-
- io_u->ddir = ipo->ddir;
- if (ipo->ddir != DDIR_WAIT) {
- io_u->offset = ipo->offset;
- io_u->buflen = ipo->len;
- io_u->file = td->files[ipo->fileno];
- get_file(io_u->file);
- dprint(FD_IO, "iolog: get %llu/%lu/%s\n", io_u->offset,
- io_u->buflen, io_u->file->file_name);
- if (ipo->delay)
- iolog_delay(td, ipo->delay);
- } else {
- elapsed = mtime_since_genesis();
- if (ipo->delay > elapsed)
- usec_sleep(td, (ipo->delay - elapsed) * 1000);
-
- }
-
- free(ipo);
-
- if (io_u->ddir != DDIR_WAIT)
- return 0;
- }
-
- td->done = 1;
- return 1;
-}
-
-void prune_io_piece_log(struct thread_data *td)
-{
- struct io_piece *ipo;
- struct rb_node *n;
-
- while ((n = rb_first(&td->io_hist_tree)) != NULL) {
- ipo = rb_entry(n, struct io_piece, rb_node);
- rb_erase(n, &td->io_hist_tree);
- remove_trim_entry(td, ipo);
- td->io_hist_len--;
- free(ipo);
- }
-
- while (!flist_empty(&td->io_hist_list)) {
- ipo = flist_entry(td->io_hist_list.next, struct io_piece, list);
- flist_del(&ipo->list);
- remove_trim_entry(td, ipo);
- td->io_hist_len--;
- free(ipo);
- }
-}
-
-/*
- * log a successful write, so we can unwind the log for verify
- */
-void log_io_piece(struct thread_data *td, struct io_u *io_u)
-{
- struct rb_node **p, *parent;
- struct io_piece *ipo, *__ipo;
-
- ipo = malloc(sizeof(struct io_piece));
- init_ipo(ipo);
- ipo->file = io_u->file;
- ipo->offset = io_u->offset;
- ipo->len = io_u->buflen;
-
- if (io_u_should_trim(td, io_u)) {
- flist_add_tail(&ipo->trim_list, &td->trim_list);
- td->trim_entries++;
- }
-
- /*
- * We don't need to sort the entries, if:
- *
- * Sequential writes, or
- * Random writes that lay out the file as it goes along
- *
- * For both these cases, just reading back data in the order we
- * wrote it out is the fastest.
- *
- * One exception is if we don't have a random map AND we are doing
- * verifies, in that case we need to check for duplicate blocks and
- * drop the old one, which we rely on the rb insert/lookup for
- * handling.
- */
- if ((!td_random(td) || !td->o.overwrite) &&
- (file_randommap(td, ipo->file) || td->o.verify == VERIFY_NONE)) {
- INIT_FLIST_HEAD(&ipo->list);
- flist_add_tail(&ipo->list, &td->io_hist_list);
- ipo->flags |= IP_F_ONLIST;
- td->io_hist_len++;
- return;
- }
-
- RB_CLEAR_NODE(&ipo->rb_node);
-
- /*
- * Sort the entry into the verification list
- */
-restart:
- p = &td->io_hist_tree.rb_node;
- parent = NULL;
- while (*p) {
- parent = *p;
-
- __ipo = rb_entry(parent, struct io_piece, rb_node);
- if (ipo->file < __ipo->file)
- p = &(*p)->rb_left;
- else if (ipo->file > __ipo->file)
- p = &(*p)->rb_right;
- else if (ipo->offset < __ipo->offset)
- p = &(*p)->rb_left;
- else if (ipo->offset > __ipo->offset)
- p = &(*p)->rb_right;
- else {
- assert(ipo->len == __ipo->len);
- td->io_hist_len--;
- rb_erase(parent, &td->io_hist_tree);
- remove_trim_entry(td, __ipo);
- free(__ipo);
- goto restart;
- }
- }
-
- rb_link_node(&ipo->rb_node, parent, p);
- rb_insert_color(&ipo->rb_node, &td->io_hist_tree);
- ipo->flags |= IP_F_ONRB;
- td->io_hist_len++;
-}
-
-void write_iolog_close(struct thread_data *td)
-{
- fflush(td->iolog_f);
- fclose(td->iolog_f);
- free(td->iolog_buf);
- td->iolog_f = NULL;
- td->iolog_buf = NULL;
-}
-
-/*
- * Read version 2 iolog data. It is enhanced to include per-file logging,
- * syncs, etc.
- */
-static int read_iolog2(struct thread_data *td, FILE *f)
-{
- unsigned long long offset;
- unsigned int bytes;
- int reads, writes, waits, fileno = 0, file_action = 0; /* stupid gcc */
- char *fname, *act;
- char *str, *p;
- enum fio_ddir rw;
-
- free_release_files(td);
-
- /*
- * Read in the read iolog and store it, reuse the infrastructure
- * for doing verifications.
- */
- str = malloc(4096);
- fname = malloc(256+16);
- act = malloc(256+16);
-
- reads = writes = waits = 0;
- while ((p = fgets(str, 4096, f)) != NULL) {
- struct io_piece *ipo;
- int r;
-
- r = sscanf(p, "%256s %256s %llu %u", fname, act, &offset,
- &bytes);
- if (r == 4) {
- /*
- * Check action first
- */
- if (!strcmp(act, "wait"))
- rw = DDIR_WAIT;
- else if (!strcmp(act, "read"))
- rw = DDIR_READ;
- else if (!strcmp(act, "write"))
- rw = DDIR_WRITE;
- else if (!strcmp(act, "sync"))
- rw = DDIR_SYNC;
- else if (!strcmp(act, "datasync"))
- rw = DDIR_DATASYNC;
- else if (!strcmp(act, "trim"))
- rw = DDIR_TRIM;
- else {
- log_err("fio: bad iolog file action: %s\n",
- act);
- continue;
- }
- } else if (r == 2) {
- rw = DDIR_INVAL;
- if (!strcmp(act, "add")) {
- td->o.nr_files++;
- fileno = add_file(td, fname);
- file_action = FIO_LOG_ADD_FILE;
- continue;
- } else if (!strcmp(act, "open")) {
- fileno = get_fileno(td, fname);
- file_action = FIO_LOG_OPEN_FILE;
- } else if (!strcmp(act, "close")) {
- fileno = get_fileno(td, fname);
- file_action = FIO_LOG_CLOSE_FILE;
- } else {
- log_err("fio: bad iolog file action: %s\n",
- act);
- continue;
- }
- } else {
- log_err("bad iolog2: %s", p);
- continue;
- }
-
- if (rw == DDIR_READ)
- reads++;
- else if (rw == DDIR_WRITE) {
- /*
- * Don't add a write for ro mode
- */
- if (read_only)
- continue;
- writes++;
- } else if (rw == DDIR_WAIT) {
- waits++;
- } else if (rw == DDIR_INVAL) {
- } else if (!ddir_sync(rw)) {
- log_err("bad ddir: %d\n", rw);
- continue;
- }
-
- /*
- * Make note of file
- */
- ipo = malloc(sizeof(*ipo));
- init_ipo(ipo);
- ipo->ddir = rw;
- if (rw == DDIR_WAIT) {
- ipo->delay = offset;
- } else {
- ipo->offset = offset;
- ipo->len = bytes;
- if (bytes > td->o.max_bs[rw])
- td->o.max_bs[rw] = bytes;
- ipo->fileno = fileno;
- ipo->file_action = file_action;
- }
-
- queue_io_piece(td, ipo);
- }
-
- free(str);
- free(act);
- free(fname);
-
- if (writes && read_only) {
- log_err("fio: <%s> skips replay of %d writes due to"
- " read-only\n", td->o.name, writes);
- writes = 0;
- }
-
- if (!reads && !writes && !waits)
- return 1;
- else if (reads && !writes)
- td->o.td_ddir = TD_DDIR_READ;
- else if (!reads && writes)
- td->o.td_ddir = TD_DDIR_WRITE;
+ if (log_syslog)
+ syslog(LOG_INFO, "%s", buffer);
else
- td->o.td_ddir = TD_DDIR_RW;
+ len = fwrite(buffer, len, 1, f_out);
- return 0;
+ return len;
}
-/*
- * open iolog, check version, and call appropriate parser
- */
-static int init_iolog_read(struct thread_data *td)
+int log_local(const char *format, ...)
{
- char buffer[256], *p;
- FILE *f;
- int ret;
+ char buffer[1024];
+ va_list args;
+ size_t len;
- f = fopen(td->o.read_iolog_file, "r");
- if (!f) {
- perror("fopen read iolog");
- return 1;
- }
+ va_start(args, format);
+ len = vsnprintf(buffer, sizeof(buffer), format, args);
+ va_end(args);
- p = fgets(buffer, sizeof(buffer), f);
- if (!p) {
- td_verror(td, errno, "iolog read");
- log_err("fio: unable to read iolog\n");
- fclose(f);
- return 1;
- }
+ if (log_syslog)
+ syslog(LOG_INFO, "%s", buffer);
+ else
+ len = fwrite(buffer, len, 1, f_out);
- /*
- * version 2 of the iolog stores a specific string as the
- * first line, check for that
- */
- if (!strncmp(iolog_ver2, buffer, strlen(iolog_ver2)))
- ret = read_iolog2(td, f);
+ return len;
+}
+
+int log_info(const char *format, ...)
+{
+ char buffer[1024];
+ va_list args;
+ size_t len;
+
+ va_start(args, format);
+ len = vsnprintf(buffer, sizeof(buffer), format, args);
+ va_end(args);
+
+ if (is_backend)
+ return fio_server_text_output(buffer, len);
+ else
+ return fwrite(buffer, len, 1, f_out);
+}
+
+int log_err(const char *format, ...)
+{
+ char buffer[1024];
+ va_list args;
+ size_t len;
+
+ va_start(args, format);
+ len = vsnprintf(buffer, sizeof(buffer), format, args);
+ va_end(args);
+
+ if (is_backend)
+ return fio_server_text_output(buffer, len);
else {
- log_err("fio: iolog version 1 is no longer supported\n");
- ret = 1;
+ if (f_err != stderr)
+ fwrite(buffer, len, 1, stderr);
+
+ return fwrite(buffer, len, 1, f_err);
}
-
- fclose(f);
- return ret;
-}
-
-/*
- * Set up a log for storing io patterns.
- */
-static int init_iolog_write(struct thread_data *td)
-{
- struct fio_file *ff;
- FILE *f;
- unsigned int i;
-
- f = fopen(td->o.write_iolog_file, "a");
- if (!f) {
- perror("fopen write iolog");
- return 1;
- }
-
- /*
- * That's it for writing, setup a log buffer and we're done.
- */
- td->iolog_f = f;
- td->iolog_buf = malloc(8192);
- setvbuf(f, td->iolog_buf, _IOFBF, 8192);
-
- /*
- * write our version line
- */
- if (fprintf(f, "%s\n", iolog_ver2) < 0) {
- perror("iolog init\n");
- return 1;
- }
-
- /*
- * add all known files
- */
- for_each_file(td, ff, i)
- log_file(td, ff, FIO_LOG_ADD_FILE);
-
- return 0;
-}
-
-int init_iolog(struct thread_data *td)
-{
- int ret = 0;
-
- if (td->o.read_iolog_file) {
- /*
- * Check if it's a blktrace file and load that if possible.
- * Otherwise assume it's a normal log file and load that.
- */
- if (is_blktrace(td->o.read_iolog_file))
- ret = load_blktrace(td, td->o.read_iolog_file);
- else
- ret = init_iolog_read(td);
- } else if (td->o.write_iolog_file)
- ret = init_iolog_write(td);
-
- return ret;
-}
-
-void setup_log(struct io_log **log)
-{
- struct io_log *l = malloc(sizeof(*l));
-
- l->nr_samples = 0;
- l->max_samples = 1024;
- l->log = malloc(l->max_samples * sizeof(struct io_sample));
- *log = l;
-}
-
-void __finish_log(struct io_log *log, const char *name)
-{
- unsigned int i;
- FILE *f;
-
- f = fopen(name, "a");
- if (!f) {
- perror("fopen log");
- return;
- }
-
- for (i = 0; i < log->nr_samples; i++) {
- fprintf(f, "%lu, %lu, %u, %u\n", log->log[i].time,
- log->log[i].val,
- log->log[i].ddir,
- log->log[i].bs);
- }
-
- fclose(f);
- free(log->log);
- free(log);
-}
-
-void finish_log_named(struct thread_data *td, struct io_log *log,
- const char *prefix, const char *postfix)
-{
- char file_name[256], *p;
-
- snprintf(file_name, 200, "%s_%s.log", prefix, postfix);
- p = basename(file_name);
- __finish_log(log, p);
-}
-
-void finish_log(struct thread_data *td, struct io_log *log, const char *name)
-{
- finish_log_named(td, log, td->o.name, name);
}
diff --git a/log.h b/log.h
index eea1129..a69846e 100644
--- a/log.h
+++ b/log.h
@@ -2,23 +2,14 @@
#define FIO_LOG_H
#include <stdio.h>
+#include <stdarg.h>
extern FILE *f_out;
extern FILE *f_err;
-/*
- * If logging output to a file, stderr should go to both stderr and f_err
- */
-#define log_err(args, ...) do { \
- fprintf(f_err, args, ##__VA_ARGS__); \
- if (f_err != stderr) \
- fprintf(stderr, args, ##__VA_ARGS__); \
- } while (0)
-
-#define log_info(args, ...) fprintf(f_out, args, ##__VA_ARGS__)
-#define log_valist(str, args) vfprintf(f_out, (str), (args))
-
-FILE *get_f_out(void);
-FILE *get_f_err(void);
+extern int log_err(const char *format, ...);
+extern int log_info(const char *format, ...);
+extern int log_local(const char *format, ...);
+extern int log_valist(const char *str, va_list);
#endif
diff --git a/options.c b/options.c
index 5252477..1914037 100644
--- a/options.c
+++ b/options.c
@@ -829,9 +829,6 @@
return 0;
}
-#define __stringify_1(x) #x
-#define __stringify(x) __stringify_1(x)
-
/*
* Map of job/command line options
*/
@@ -1946,7 +1943,7 @@
.type = FIO_OPT_INT,
.off1 = td_var_offset(hugepage_size),
.help = "When using hugepages, specify size of each page",
- .def = __stringify(FIO_HUGE_PAGE),
+ .def = __fio_stringify(FIO_HUGE_PAGE),
},
{
.name = "group_reporting",
diff --git a/os/os-aix.h b/os/os-aix.h
index 91c8bcd..d21e573 100644
--- a/os/os-aix.h
+++ b/os/os-aix.h
@@ -25,6 +25,17 @@
#define OS_MAP_ANON MAP_ANON
#define OS_MSG_DONTWAIT 0
+#if BYTE_ORDER == BIG_ENDIAN
+#define FIO_BIG_ENDIAN
+#else
+#define FIO_LITTLE_ENDIAN
+#endif
+
+#define FIO_USE_GENERIC_SWAP
+
+#define FIO_OS_HAVE_SOCKLEN_T
+#define fio_socklen_t socklen_t
+
static inline int blockdev_invalidate_cache(struct fio_file *f)
{
return EINVAL;
diff --git a/os/os-freebsd.h b/os/os-freebsd.h
index 317d403..1a4f113 100644
--- a/os/os-freebsd.h
+++ b/os/os-freebsd.h
@@ -5,6 +5,7 @@
#include <sys/sysctl.h>
#include <sys/disk.h>
#include <sys/thr.h>
+#include <sys/endian.h>
#include "../file.h"
@@ -18,6 +19,16 @@
#define OS_MAP_ANON MAP_ANON
+#if BYTE_ORDER == LITTLE_ENDIAN
+#define FIO_LITTLE_ENDIAN
+#else
+#define FIO_BIG_ENDIAN
+#endif
+
+#define fio_swap16(x) bswap16(x)
+#define fio_swap32(x) bswap32(x)
+#define fio_swap64(x) bswap64(x)
+
typedef off_t off64_t;
static inline int blockdev_size(struct fio_file *f, unsigned long long *bytes)
diff --git a/os/os-hpux.h b/os/os-hpux.h
index 4353a01..93240b3 100644
--- a/os/os-hpux.h
+++ b/os/os-hpux.h
@@ -13,6 +13,7 @@
#include <sys/pstat.h>
#include <time.h>
#include <aio.h>
+#include <arm.h>
#include "../file.h"
@@ -43,9 +44,20 @@
#define MSG_WAITALL 0x40
#endif
+#ifdef LITTLE_ENDIAN
+#define FIO_LITTLE_ENDIAN
+#else
+#define FIO_BIG_ENDIAN
+#endif
+
+#define FIO_USE_GENERIC_SWAP
+
#define FIO_OS_HAVE_AIOCB_TYPEDEF
typedef struct aiocb64 os_aiocb_t;
+#define FIO_OS_HAVE_SOCKLEN_T
+typedef int fio_socklen_t;
+
static inline int blockdev_invalidate_cache(struct fio_file *f)
{
return EINVAL;
diff --git a/os/os-linux.h b/os/os-linux.h
index a36552b..2599091 100644
--- a/os/os-linux.h
+++ b/os/os-linux.h
@@ -12,6 +12,7 @@
#include <linux/unistd.h>
#include <linux/raw.h>
#include <linux/major.h>
+#include <endian.h>
#include "indirect.h"
#include "binject.h"
@@ -286,6 +287,18 @@
#define FIO_MADV_FREE MADV_REMOVE
#endif
+#if __BYTE_ORDER == __LITTLE_ENDIAN
+#define FIO_LITTLE_ENDIAN
+#elif __BYTE_ORDER == __BIG_ENDIAN
+#define FIO_BIG_ENDIAN
+#else
+#error "Unknown endianness"
+#endif
+
+#define fio_swap16(x) __bswap_16(x)
+#define fio_swap32(x) __bswap_32(x)
+#define fio_swap64(x) __bswap_64(x)
+
#define CACHE_LINE_FILE \
"/sys/devices/system/cpu/cpu0/cache/index0/coherency_line_size"
diff --git a/os/os-mac.h b/os/os-mac.h
index eb55cd7..24e0eb5 100644
--- a/os/os-mac.h
+++ b/os/os-mac.h
@@ -9,6 +9,8 @@
#include <unistd.h>
#include <signal.h>
#include <mach/mach_init.h>
+#include <machine/endian.h>
+#include <libkern/OSByteOrder.h>
#include "../file.h"
@@ -28,6 +30,18 @@
#define OS_MAP_ANON MAP_ANON
+#if defined(__LITTLE_ENDIAN__)
+#define FIO_LITTLE_ENDIAN
+#elif defined(__BIG_ENDIAN__)
+#define FIO_BIG_ENDIAN
+#else
+#error "Undefined byte order"
+#endif
+
+#define fio_swap16(x) OSSwapInt16(x)
+#define fio_swap32(x) OSSwapInt32(x)
+#define fio_swap64(x) OSSwapInt64(x)
+
/*
* OSX has a pitifully small shared memory segment by default,
* so default to a lower number of max jobs supported
diff --git a/os/os-netbsd.h b/os/os-netbsd.h
index e03866d..5d8258b 100644
--- a/os/os-netbsd.h
+++ b/os/os-netbsd.h
@@ -4,6 +4,7 @@
#include <errno.h>
#include <sys/param.h>
#include <sys/thr.h>
+#include <sys/endian.h>
/* XXX hack to avoid confilcts between rbtree.h and <sys/rb.h> */
#define rb_node _rb_node
#include <sys/sysctl.h>
@@ -30,6 +31,16 @@
#define PTHREAD_STACK_MIN 4096
#endif
+#if BYTE_ORDER == LITTLE_ENDIAN
+#define FIO_LITTLE_ENDIAN
+#else
+#define FIO_BIG_ENDIAN
+#endif
+
+#define fio_swap16(x) bswap16(x)
+#define fio_swap32(x) bswap32(x)
+#define fio_swap64(x) bswap64(x)
+
typedef off_t off64_t;
static inline int blockdev_invalidate_cache(struct fio_file *f)
diff --git a/os/os-solaris.h b/os/os-solaris.h
index c0d3c30..ef37e49 100644
--- a/os/os-solaris.h
+++ b/os/os-solaris.h
@@ -8,6 +8,7 @@
#include <sys/pset.h>
#include <sys/mman.h>
#include <sys/dkio.h>
+#include <sys/byteorder.h>
#include "../file.h"
@@ -24,6 +25,16 @@
#define OS_MAP_ANON MAP_ANON
#define OS_RAND_MAX 2147483648UL
+#if defined(_BIG_ENDIAN)
+#define FIO_BIG_ENDIAN
+#else
+#define FIO_LITTLE_ENDIAN
+#endif
+
+#define fio_swap16(x) BSWAP_16(x)
+#define fio_swap32(x) BSWAP_32(x)
+#define fio_swap64(x) BSWAP_64(x)
+
struct solaris_rand_seed {
unsigned short r[3];
};
diff --git a/os/os-windows.h b/os/os-windows.h
index db4127b..4af212d 100644
--- a/os/os-windows.h
+++ b/os/os-windows.h
@@ -5,6 +5,7 @@
#include <errno.h>
#include <windows.h>
#include <psapi.h>
+#include <stdlib.h>
#include "../smalloc.h"
#include "../file.h"
@@ -26,6 +27,11 @@
#define FIO_PREFERRED_ENGINE "windowsaio"
+#define FIO_LITTLE_ENDIAN
+#define fio_swap16(x) _byteswap_ushort(x)
+#define fio_swap32(x) _byteswap_ulong(x)
+#define fio_swap64(x) _byteswap_uint64(x)
+
typedef off_t off64_t;
typedef struct {
diff --git a/os/os.h b/os/os.h
index 2eb38e8..189041f 100644
--- a/os/os.h
+++ b/os/os.h
@@ -115,6 +115,34 @@
#define FIO_MAX_JOBS 2048
#endif
+#ifndef FIO_OS_HAVE_SOCKLEN_T
+typedef socklen_t fio_socklen_t;
+#endif
+
+#ifdef FIO_USE_GENERIC_SWAP
+static inline uint16_t fio_swap16(uint16_t val)
+{
+ return (val << 8) | (val >> 8);
+}
+
+static inline uint32_t fio_swap32(uint32_t val)
+{
+ val = ((val & 0xff00ff00UL) >> 8) | ((val & 0x00ff00ffUL) << 8);
+
+ return (val >> 16) | (val << 16);
+}
+
+static inline uint64_t fio_swap64(uint64_t val)
+{
+ val = ((val & 0xff00ff00ff00ff00ULL) >> 8) |
+ ((val & 0x00ff00ff00ff00ffULL) << 8);
+ val = ((val & 0xffff0000ffff0000ULL) >> 16) |
+ ((val & 0x0000ffff0000ffffULL) << 16);
+
+ return (val >> 32) | (val << 32);
+}
+#endif
+
#ifndef FIO_HAVE_BLKTRACE
static inline int is_blktrace(const char *fname)
{
diff --git a/parse.c b/parse.c
index 239e371..70cfa0b 100644
--- a/parse.c
+++ b/parse.c
@@ -44,24 +44,25 @@
qsort(vpmap, entries, sizeof(struct value_pair), vp_cmp);
}
-static void show_option_range(struct fio_option *o, FILE *out)
+static void show_option_range(struct fio_option *o,
+ int (*logger)(const char *format, ...))
{
if (o->type == FIO_OPT_FLOAT_LIST){
if (isnan(o->minfp) && isnan(o->maxfp))
return;
- fprintf(out, "%20s: min=%f", "range", o->minfp);
+ logger("%20s: min=%f", "range", o->minfp);
if (!isnan(o->maxfp))
- fprintf(out, ", max=%f", o->maxfp);
- fprintf(out, "\n");
+ logger(", max=%f", o->maxfp);
+ logger("\n");
} else {
if (!o->minval && !o->maxval)
return;
- fprintf(out, "%20s: min=%d", "range", o->minval);
+ logger("%20s: min=%d", "range", o->minval);
if (o->maxval)
- fprintf(out, ", max=%d", o->maxval);
- fprintf(out, "\n");
+ logger(", max=%d", o->maxval);
+ logger("\n");
}
}
@@ -75,17 +76,17 @@
if (!vp->ival)
continue;
- printf("%20s: %-10s", i == 0 ? "valid values" : "", vp->ival);
+ log_info("%20s: %-10s", i == 0 ? "valid values" : "", vp->ival);
if (vp->help)
- printf(" %s", vp->help);
- printf("\n");
+ log_info(" %s", vp->help);
+ log_info("\n");
}
if (i)
- printf("\n");
+ log_info("\n");
}
-static void show_option_help(struct fio_option *o, FILE *out)
+static void show_option_help(struct fio_option *o, int is_err)
{
const char *typehelp[] = {
"invalid",
@@ -101,15 +102,21 @@
"no argument (opt)",
"deprecated",
};
+ int (*logger)(const char *format, ...);
+
+ if (is_err)
+ logger = log_err;
+ else
+ logger = log_info;
if (o->alias)
- fprintf(out, "%20s: %s\n", "alias", o->alias);
+ logger("%20s: %s\n", "alias", o->alias);
- fprintf(out, "%20s: %s\n", "type", typehelp[o->type]);
- fprintf(out, "%20s: %s\n", "default", o->def ? o->def : "no default");
+ logger("%20s: %s\n", "type", typehelp[o->type]);
+ logger("%20s: %s\n", "default", o->def ? o->def : "no default");
if (o->prof_name)
- fprintf(out, "%20s: only for profile '%s'\n", "valid", o->prof_name);
- show_option_range(o, stdout);
+ logger("%20s: only for profile '%s'\n", "valid", o->prof_name);
+ show_option_range(o, logger);
show_option_values(o);
}
@@ -635,10 +642,10 @@
break;
}
case FIO_OPT_DEPRECATED:
- fprintf(stdout, "Option %s is deprecated\n", o->name);
+ log_info("Option %s is deprecated\n", o->name);
break;
default:
- fprintf(stderr, "Bad option type %u\n", o->type);
+ log_err("Bad option type %u\n", o->type);
ret = 1;
}
@@ -650,7 +657,7 @@
if (ret) {
fprintf(stderr,"Correct format for offending option\n");
fprintf(stderr, "%20s: %s\n", o->name, o->help);
- show_option_help(o, stderr);
+ show_option_help(o, 1);
}
}
@@ -776,14 +783,14 @@
o = find_option(options, opt);
if (!o) {
- fprintf(stderr, "Bad option <%s>\n", opt);
+ log_err("Bad option <%s>\n", opt);
return 1;
}
if (!handle_option(o, val, data))
return 0;
- fprintf(stderr, "fio: failed parsing %s=%s\n", opt, val);
+ log_err("fio: failed parsing %s=%s\n", opt, val);
return 1;
}
@@ -852,7 +859,7 @@
o = get_option(tmp, options, &post);
if (!o) {
- fprintf(stderr, "Bad option <%s>\n", tmp);
+ log_err("Bad option <%s>\n", tmp);
free(tmp);
return 1;
}
@@ -862,7 +869,7 @@
return 0;
}
- fprintf(stderr, "fio: failed parsing %s\n", opt);
+ log_err("fio: failed parsing %s\n", opt);
free(tmp);
return 1;
}
@@ -1012,7 +1019,7 @@
if (!match)
continue;
- show_option_help(o, stdout);
+ show_option_help(o, 0);
}
if (found)
@@ -1027,7 +1034,7 @@
if (closest && best_dist < 3) {
printf(" - showing closest match\n");
printf("%20s: %s\n", closest->name, closest->help);
- show_option_help(closest, stdout);
+ show_option_help(closest, 0);
} else
printf("\n");
@@ -1061,20 +1068,17 @@
o->maxfp = NAN;
}
if (o->type == FIO_OPT_STR_SET && o->def) {
- fprintf(stderr, "Option %s: string set option with"
+ log_err("Option %s: string set option with"
" default will always be true\n", o->name);
}
- if (!o->cb && (!o->off1 && !o->roff1)) {
- fprintf(stderr, "Option %s: neither cb nor offset given\n",
- o->name);
- }
+ if (!o->cb && (!o->off1 && !o->roff1))
+ log_err("Option %s: neither cb nor offset given\n", o->name);
if (o->type == FIO_OPT_STR || o->type == FIO_OPT_STR_STORE ||
o->type == FIO_OPT_STR_MULTI)
return;
if (o->cb && ((o->off1 || o->off2 || o->off3 || o->off4) ||
(o->roff1 || o->roff2 || o->roff3 || o->roff4))) {
- fprintf(stderr, "Option %s: both cb and offset given\n",
- o->name);
+ log_err("Option %s: both cb and offset given\n", o->name);
}
}
diff --git a/server.c b/server.c
new file mode 100644
index 0000000..0f64c23
--- /dev/null
+++ b/server.c
@@ -0,0 +1,737 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <limits.h>
+#include <errno.h>
+#include <fcntl.h>
+#include <sys/poll.h>
+#include <sys/types.h>
+#include <sys/wait.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <arpa/inet.h>
+#include <netdb.h>
+#include <syslog.h>
+#include <signal.h>
+
+#include "fio.h"
+#include "server.h"
+#include "crc/crc16.h"
+#include "ieee754.h"
+
+int fio_net_port = 8765;
+
+int exit_backend = 0;
+
+static int server_fd = -1;
+
+int fio_send_data(int sk, const void *p, unsigned int len)
+{
+ assert(len <= sizeof(struct fio_net_cmd) + FIO_SERVER_MAX_PDU);
+
+ do {
+ int ret = send(sk, p, len, 0);
+
+ if (ret > 0) {
+ len -= ret;
+ if (!len)
+ break;
+ p += ret;
+ continue;
+ } else if (!ret)
+ break;
+ else if (errno == EAGAIN || errno == EINTR)
+ continue;
+ } while (!exit_backend);
+
+ if (!len)
+ return 0;
+
+ return 1;
+}
+
+int fio_recv_data(int sk, void *p, unsigned int len)
+{
+ do {
+ int ret = recv(sk, p, len, MSG_WAITALL);
+
+ if (ret > 0) {
+ len -= ret;
+ if (!len)
+ break;
+ p += ret;
+ continue;
+ } else if (!ret)
+ break;
+ else if (errno == EAGAIN || errno == EINTR)
+ continue;
+ } while (!exit_backend);
+
+ if (!len)
+ return 0;
+
+ return -1;
+}
+
+static int verify_convert_cmd(struct fio_net_cmd *cmd)
+{
+ uint16_t crc;
+
+ cmd->cmd_crc16 = le16_to_cpu(cmd->cmd_crc16);
+ cmd->pdu_crc16 = le16_to_cpu(cmd->pdu_crc16);
+
+ crc = crc16(cmd, FIO_NET_CMD_CRC_SZ);
+ if (crc != cmd->cmd_crc16) {
+ log_err("fio: server bad crc on command (got %x, wanted %x)\n",
+ cmd->cmd_crc16, crc);
+ return 1;
+ }
+
+ cmd->version = le16_to_cpu(cmd->version);
+ cmd->opcode = le16_to_cpu(cmd->opcode);
+ cmd->flags = le32_to_cpu(cmd->flags);
+ cmd->serial = le64_to_cpu(cmd->serial);
+ cmd->pdu_len = le32_to_cpu(cmd->pdu_len);
+
+ switch (cmd->version) {
+ case FIO_SERVER_VER1:
+ break;
+ default:
+ log_err("fio: bad server cmd version %d\n", cmd->version);
+ return 1;
+ }
+
+ if (cmd->pdu_len > FIO_SERVER_MAX_PDU) {
+ log_err("fio: command payload too large: %u\n", cmd->pdu_len);
+ return 1;
+ }
+
+ return 0;
+}
+
+/*
+ * Read (and defragment, if necessary) incoming commands
+ */
+struct fio_net_cmd *fio_net_recv_cmd(int sk, int block)
+{
+ struct fio_net_cmd cmd, *cmdret = NULL;
+ size_t cmd_size = 0, pdu_offset = 0;
+ uint16_t crc;
+ int ret, first = 1;
+ void *pdu = NULL;
+
+ do {
+ struct pollfd pfd;
+
+ pfd.fd = sk;
+ pfd.events = POLLIN;
+ ret = 0;
+ do {
+ int timeo = block ? 100 : 10;
+
+ ret = poll(&pfd, 1, timeo);
+ if (ret < 0) {
+ if (errno == EINTR)
+ break;
+ log_err("fio: poll: %s\n", strerror(errno));
+ break;
+ } else if (!ret) {
+ if (!block)
+ return NULL;
+ continue;
+ }
+
+ if (pfd.revents & POLLIN)
+ break;
+ if (pfd.revents & (POLLERR|POLLHUP)) {
+ ret = 1;
+ break;
+ }
+ } while (ret >= 0 && block);
+
+ if (ret < 0)
+ break;
+
+ ret = fio_recv_data(sk, &cmd, sizeof(cmd));
+ if (ret)
+ break;
+
+ /* We have a command, verify it and swap if need be */
+ ret = verify_convert_cmd(&cmd);
+ if (ret)
+ break;
+
+ if (first) {
+ /* if this is text, add room for \0 at the end */
+ cmd_size = sizeof(cmd) + cmd.pdu_len + 1;
+ assert(!cmdret);
+ } else
+ cmd_size += cmd.pdu_len;
+
+ cmdret = realloc(cmdret, cmd_size);
+
+ if (first)
+ memcpy(cmdret, &cmd, sizeof(cmd));
+ else
+ assert(cmdret->opcode == cmd.opcode);
+
+ if (!cmd.pdu_len)
+ break;
+
+ /* There's payload, get it */
+ pdu = (void *) cmdret->payload + pdu_offset;
+ ret = fio_recv_data(sk, pdu, cmd.pdu_len);
+ if (ret)
+ break;
+
+ /* Verify payload crc */
+ crc = crc16(pdu, cmd.pdu_len);
+ if (crc != cmd.pdu_crc16) {
+ log_err("fio: server bad crc on payload ");
+ log_err("(got %x, wanted %x)\n", cmd.pdu_crc16, crc);
+ ret = 1;
+ break;
+ }
+
+ pdu_offset += cmd.pdu_len;
+ if (!first)
+ cmdret->pdu_len += cmd.pdu_len;
+ first = 0;
+ } while (cmd.flags & FIO_NET_CMD_F_MORE);
+
+ if (ret) {
+ free(cmdret);
+ cmdret = NULL;
+ } else if (cmdret) {
+ /* zero-terminate text input */
+ if (cmdret->pdu_len && (cmdret->opcode == FIO_NET_CMD_TEXT ||
+ cmdret->opcode == FIO_NET_CMD_JOB)) {
+ char *buf = (char *) cmdret->payload;
+
+ buf[cmdret->pdu_len ] = '\0';
+ }
+ /* frag flag is internal */
+ cmdret->flags &= ~FIO_NET_CMD_F_MORE;
+ }
+
+ return cmdret;
+}
+
+void fio_net_cmd_crc(struct fio_net_cmd *cmd)
+{
+ uint32_t pdu_len;
+
+ cmd->cmd_crc16 = __cpu_to_le16(crc16(cmd, FIO_NET_CMD_CRC_SZ));
+
+ pdu_len = le32_to_cpu(cmd->pdu_len);
+ if (pdu_len)
+ cmd->pdu_crc16 = __cpu_to_le16(crc16(cmd->payload, pdu_len));
+}
+
+int fio_net_send_cmd(int fd, uint16_t opcode, const void *buf, off_t size)
+{
+ struct fio_net_cmd *cmd;
+ size_t this_len;
+ int ret;
+
+ do {
+ this_len = size;
+ if (this_len > FIO_SERVER_MAX_PDU)
+ this_len = FIO_SERVER_MAX_PDU;
+
+ cmd = malloc(sizeof(*cmd) + this_len);
+
+ fio_init_net_cmd(cmd, opcode, buf, this_len);
+
+ if (this_len < size)
+ cmd->flags = __cpu_to_le32(FIO_NET_CMD_F_MORE);
+
+ fio_net_cmd_crc(cmd);
+
+ ret = fio_send_data(fd, cmd, sizeof(*cmd) + this_len);
+ free(cmd);
+ size -= this_len;
+ buf += this_len;
+ } while (!ret && size);
+
+ return ret;
+}
+
+int fio_net_send_simple_cmd(int sk, uint16_t opcode, uint64_t serial)
+{
+ struct fio_net_cmd cmd = {
+ .version = __cpu_to_le16(FIO_SERVER_VER1),
+ .opcode = cpu_to_le16(opcode),
+ .serial = cpu_to_le64(serial),
+ };
+
+ fio_net_cmd_crc(&cmd);
+
+ return fio_send_data(sk, &cmd, sizeof(cmd));
+}
+
+static int fio_server_send_quit_cmd(void)
+{
+ dprint(FD_NET, "server: sending quit\n");
+ return fio_net_send_simple_cmd(server_fd, FIO_NET_CMD_QUIT, 0);
+}
+
+static int handle_job_cmd(struct fio_net_cmd *cmd)
+{
+ char *buf = (char *) cmd->payload;
+ int ret;
+
+ if (parse_jobs_ini(buf, 1, 0))
+ return -1;
+
+ fio_net_send_simple_cmd(server_fd, FIO_NET_CMD_START, 0);
+
+ ret = exec_run();
+ fio_server_send_quit_cmd();
+ reset_fio_state();
+ return ret;
+}
+
+static int handle_jobline_cmd(struct fio_net_cmd *cmd)
+{
+ struct cmd_line_pdu *pdu = (struct cmd_line_pdu *) cmd->payload;
+ char *argv[FIO_NET_CMD_JOBLINE_ARGV];
+ int ret, i;
+
+ pdu->argc = le16_to_cpu(pdu->argc);
+
+ dprint(FD_NET, "server: %d command line args\n", pdu->argc);
+
+ for (i = 0; i < pdu->argc; i++) {
+ argv[i] = (char *) pdu->argv[i];
+ dprint(FD_NET, "server: %d: %s\n", i, argv[i]);
+ }
+
+ if (parse_cmd_line(pdu->argc, argv))
+ return -1;
+
+ fio_net_send_simple_cmd(server_fd, FIO_NET_CMD_START, 0);
+
+ ret = exec_run();
+ fio_server_send_quit_cmd();
+ reset_fio_state();
+ return ret;
+}
+
+static int handle_probe_cmd(struct fio_net_cmd *cmd)
+{
+ struct cmd_probe_pdu probe;
+
+ memset(&probe, 0, sizeof(probe));
+ gethostname((char *) probe.hostname, sizeof(probe.hostname));
+#ifdef FIO_BIG_ENDIAN
+ probe.bigendian = 1;
+#endif
+ probe.fio_major = FIO_MAJOR;
+ probe.fio_minor = FIO_MINOR;
+ probe.fio_patch = FIO_PATCH;
+
+ return fio_net_send_cmd(server_fd, FIO_NET_CMD_PROBE, &probe, sizeof(probe));
+}
+
+static int handle_command(struct fio_net_cmd *cmd)
+{
+ int ret;
+
+ dprint(FD_NET, "server: got opcode %d\n", cmd->opcode);
+
+ switch (cmd->opcode) {
+ case FIO_NET_CMD_QUIT:
+ fio_terminate_threads(TERMINATE_ALL);
+ return -1;
+ case FIO_NET_CMD_EXIT:
+ exit_backend = 1;
+ return -1;
+ case FIO_NET_CMD_JOB:
+ ret = handle_job_cmd(cmd);
+ break;
+ case FIO_NET_CMD_JOBLINE:
+ ret = handle_jobline_cmd(cmd);
+ break;
+ case FIO_NET_CMD_PROBE:
+ ret = handle_probe_cmd(cmd);
+ break;
+ default:
+ log_err("fio: unknown opcode: %d\n", cmd->opcode);
+ ret = 1;
+ }
+
+ return ret;
+}
+
+static int handle_connection(int sk, int block)
+{
+ struct fio_net_cmd *cmd = NULL;
+ int ret = 0;
+
+ /* read forever */
+ while (!exit_backend) {
+ cmd = fio_net_recv_cmd(sk, block);
+ if (!cmd) {
+ ret = -1;
+ break;
+ }
+
+ ret = handle_command(cmd);
+ if (ret)
+ break;
+
+ free(cmd);
+ cmd = NULL;
+ }
+
+ if (cmd)
+ free(cmd);
+
+ return ret;
+}
+
+void fio_server_idle_loop(void)
+{
+ if (server_fd != -1)
+ handle_connection(server_fd, 0);
+}
+
+static int accept_loop(int listen_sk)
+{
+ struct sockaddr_in addr;
+ fio_socklen_t len = sizeof(addr);
+ struct pollfd pfd;
+ int ret, sk, flags, exitval = 0;
+
+ dprint(FD_NET, "server enter accept loop\n");
+
+ flags = fcntl(listen_sk, F_GETFL);
+ flags |= O_NONBLOCK;
+ fcntl(listen_sk, F_SETFL, flags);
+again:
+ pfd.fd = listen_sk;
+ pfd.events = POLLIN;
+ do {
+ ret = poll(&pfd, 1, 100);
+ if (ret < 0) {
+ if (errno == EINTR)
+ break;
+ log_err("fio: poll: %s\n", strerror(errno));
+ goto out;
+ } else if (!ret)
+ continue;
+
+ if (pfd.revents & POLLIN)
+ break;
+ } while (!exit_backend);
+
+ if (exit_backend)
+ goto out;
+
+ sk = accept(listen_sk, (struct sockaddr *) &addr, &len);
+ if (sk < 0) {
+ log_err("fio: accept: %s\n", strerror(errno));
+ return -1;
+ }
+
+ dprint(FD_NET, "server: connect from %s\n", inet_ntoa(addr.sin_addr));
+
+ server_fd = sk;
+
+ exitval = handle_connection(sk, 1);
+
+ server_fd = -1;
+ close(sk);
+
+ if (!exit_backend)
+ goto again;
+
+out:
+ return exitval;
+}
+
+int fio_server_text_output(const char *buf, unsigned int len)
+{
+ if (server_fd != -1)
+ return fio_net_send_cmd(server_fd, FIO_NET_CMD_TEXT, buf, len);
+
+ return fwrite(buf, len, 1, f_err);
+}
+
+static void convert_io_stat(struct io_stat *dst, struct io_stat *src)
+{
+ dst->max_val = cpu_to_le64(src->max_val);
+ dst->min_val = cpu_to_le64(src->min_val);
+ dst->samples = cpu_to_le64(src->samples);
+
+ /*
+ * Encode to IEEE 754 for network transfer
+ */
+ dst->mean.u.i = __cpu_to_le64(fio_double_to_uint64(src->mean.u.f));
+ dst->S.u.i = __cpu_to_le64(fio_double_to_uint64(src->S.u.f));
+}
+
+static void convert_gs(struct group_run_stats *dst, struct group_run_stats *src)
+{
+ int i;
+
+ for (i = 0; i < 2; i++) {
+ dst->max_run[i] = cpu_to_le64(src->max_run[i]);
+ dst->min_run[i] = cpu_to_le64(src->min_run[i]);
+ dst->max_bw[i] = cpu_to_le64(src->max_bw[i]);
+ dst->min_bw[i] = cpu_to_le64(src->min_bw[i]);
+ dst->io_kb[i] = cpu_to_le64(src->io_kb[i]);
+ dst->agg[i] = cpu_to_le64(src->agg[i]);
+ }
+
+ dst->kb_base = cpu_to_le32(src->kb_base);
+ dst->groupid = cpu_to_le32(src->groupid);
+}
+
+/*
+ * Send a CMD_TS, which packs struct thread_stat and group_run_stats
+ * into a single payload.
+ */
+void fio_server_send_ts(struct thread_stat *ts, struct group_run_stats *rs)
+{
+ struct cmd_ts_pdu p;
+ int i, j;
+
+ dprint(FD_NET, "server sending end stats\n");
+
+ memset(&p, 0, sizeof(p));
+
+ strcpy(p.ts.name, ts->name);
+ strcpy(p.ts.verror, ts->verror);
+ strcpy(p.ts.description, ts->description);
+
+ p.ts.error = cpu_to_le32(ts->error);
+ p.ts.groupid = cpu_to_le32(ts->groupid);
+ p.ts.pid = cpu_to_le32(ts->pid);
+ p.ts.members = cpu_to_le32(ts->members);
+
+ for (i = 0; i < 2; i++) {
+ convert_io_stat(&p.ts.clat_stat[i], &ts->clat_stat[i]);
+ convert_io_stat(&p.ts.slat_stat[i], &ts->slat_stat[i]);
+ convert_io_stat(&p.ts.lat_stat[i], &ts->lat_stat[i]);
+ convert_io_stat(&p.ts.bw_stat[i], &ts->bw_stat[i]);
+ }
+
+ p.ts.usr_time = cpu_to_le64(ts->usr_time);
+ p.ts.sys_time = cpu_to_le64(ts->sys_time);
+ p.ts.ctx = cpu_to_le64(ts->ctx);
+ p.ts.minf = cpu_to_le64(ts->minf);
+ p.ts.majf = cpu_to_le64(ts->majf);
+ p.ts.clat_percentiles = cpu_to_le64(ts->clat_percentiles);
+
+ for (i = 0; i < FIO_IO_U_LIST_MAX_LEN; i++) {
+ fio_fp64_t *fp = &p.ts.percentile_list[i];
+
+ fp->u.i = __cpu_to_le64(fio_double_to_uint64(fp->u.f));
+ }
+
+ for (i = 0; i < FIO_IO_U_MAP_NR; i++) {
+ p.ts.io_u_map[i] = cpu_to_le32(ts->io_u_map[i]);
+ p.ts.io_u_submit[i] = cpu_to_le32(ts->io_u_submit[i]);
+ p.ts.io_u_complete[i] = cpu_to_le32(ts->io_u_complete[i]);
+ }
+
+ for (i = 0; i < FIO_IO_U_LAT_U_NR; i++) {
+ p.ts.io_u_lat_u[i] = cpu_to_le32(ts->io_u_lat_u[i]);
+ p.ts.io_u_lat_m[i] = cpu_to_le32(ts->io_u_lat_m[i]);
+ }
+
+ for (i = 0; i < 2; i++)
+ for (j = 0; j < FIO_IO_U_PLAT_NR; j++)
+ p.ts.io_u_plat[i][j] = cpu_to_le32(ts->io_u_plat[i][j]);
+
+ for (i = 0; i < 3; i++) {
+ p.ts.total_io_u[i] = cpu_to_le64(ts->total_io_u[i]);
+ p.ts.short_io_u[i] = cpu_to_le64(ts->short_io_u[i]);
+ }
+
+ p.ts.total_submit = cpu_to_le64(ts->total_submit);
+ p.ts.total_complete = cpu_to_le64(ts->total_complete);
+
+ for (i = 0; i < 2; i++) {
+ p.ts.io_bytes[i] = cpu_to_le64(ts->io_bytes[i]);
+ p.ts.runtime[i] = cpu_to_le64(ts->runtime[i]);
+ }
+
+ p.ts.total_run_time = cpu_to_le64(ts->total_run_time);
+ p.ts.continue_on_error = cpu_to_le16(ts->continue_on_error);
+ p.ts.total_err_count = cpu_to_le64(ts->total_err_count);
+ p.ts.first_error = cpu_to_le32(ts->first_error);
+ p.ts.kb_base = cpu_to_le32(ts->kb_base);
+
+ convert_gs(&p.rs, rs);
+
+ fio_net_send_cmd(server_fd, FIO_NET_CMD_TS, &p, sizeof(p));
+}
+
+void fio_server_send_gs(struct group_run_stats *rs)
+{
+ struct group_run_stats gs;
+
+ dprint(FD_NET, "server sending group run stats\n");
+
+ convert_gs(&gs, rs);
+ fio_net_send_cmd(server_fd, FIO_NET_CMD_GS, &gs, sizeof(gs));
+}
+
+void fio_server_send_status(void)
+{
+ struct jobs_eta *je;
+ size_t size;
+ void *buf;
+ int i;
+
+ size = sizeof(*je) + thread_number * sizeof(char);
+ buf = malloc(size);
+ memset(buf, 0, size);
+ je = buf;
+
+ if (!calc_thread_status(je)) {
+ free(je);
+ return;
+ }
+
+ dprint(FD_NET, "server sending status\n");
+
+ je->nr_running = cpu_to_le32(je->nr_running);
+ je->nr_ramp = cpu_to_le32(je->nr_ramp);
+ je->nr_pending = cpu_to_le32(je->nr_pending);
+ je->files_open = cpu_to_le32(je->files_open);
+ je->m_rate = cpu_to_le32(je->m_rate);
+ je->t_rate = cpu_to_le32(je->t_rate);
+ je->m_iops = cpu_to_le32(je->m_iops);
+ je->t_iops = cpu_to_le32(je->t_iops);
+
+ for (i = 0; i < 2; i++) {
+ je->rate[i] = cpu_to_le32(je->rate[i]);
+ je->iops[i] = cpu_to_le32(je->iops[i]);
+ }
+
+ je->elapsed_sec = cpu_to_le32(je->nr_running);
+ je->eta_sec = cpu_to_le64(je->eta_sec);
+
+ fio_net_send_cmd(server_fd, FIO_NET_CMD_ETA, buf, size);
+ free(je);
+}
+
+int fio_server_log(const char *format, ...)
+{
+ char buffer[1024];
+ va_list args;
+ size_t len;
+
+ dprint(FD_NET, "server log\n");
+
+ va_start(args, format);
+ len = vsnprintf(buffer, sizeof(buffer), format, args);
+ va_end(args);
+
+ return fio_server_text_output(buffer, len);
+}
+
+static int fio_server(void)
+{
+ struct sockaddr_in saddr_in;
+ struct sockaddr addr;
+ fio_socklen_t len;
+ int sk, opt, ret;
+
+ dprint(FD_NET, "starting server\n");
+
+ sk = socket(AF_INET, SOCK_STREAM, 0);
+ if (sk < 0) {
+ log_err("fio: socket: %s\n", strerror(errno));
+ return -1;
+ }
+
+ opt = 1;
+ if (setsockopt(sk, SOL_SOCKET, SO_REUSEADDR, &opt, sizeof(opt)) < 0) {
+ log_err("fio: setsockopt: %s\n", strerror(errno));
+ return -1;
+ }
+#ifdef SO_REUSEPORT
+ if (setsockopt(sk, SOL_SOCKET, SO_REUSEPORT, &opt, sizeof(opt)) < 0) {
+ log_err("fio: setsockopt: %s\n", strerror(errno));
+ return -1;
+ }
+#endif
+
+ saddr_in.sin_family = AF_INET;
+ saddr_in.sin_addr.s_addr = htonl(INADDR_ANY);
+ saddr_in.sin_port = htons(fio_net_port);
+
+ if (bind(sk, (struct sockaddr *) &saddr_in, sizeof(saddr_in)) < 0) {
+ log_err("fio: bind: %s\n", strerror(errno));
+ return -1;
+ }
+
+ if (listen(sk, 1) < 0) {
+ log_err("fio: listen: %s\n", strerror(errno));
+ return -1;
+ }
+
+ len = sizeof(addr);
+ if (getsockname(sk, &addr, &len) < 0) {
+ log_err("fio: getsockname: %s\n", strerror(errno));
+ return -1;
+ }
+
+ ret = accept_loop(sk);
+ close(sk);
+ return ret;
+}
+
+static void sig_int(int sig)
+{
+ fio_terminate_threads(TERMINATE_ALL);
+ exit_backend = 1;
+}
+
+static void server_signal_handler(void)
+{
+ struct sigaction act;
+
+ memset(&act, 0, sizeof(act));
+ act.sa_handler = sig_int;
+ act.sa_flags = SA_RESTART;
+ sigaction(SIGINT, &act, NULL);
+
+ memset(&act, 0, sizeof(act));
+ act.sa_handler = sig_int;
+ act.sa_flags = SA_RESTART;
+ sigaction(SIGTERM, &act, NULL);
+}
+
+int fio_start_server(int daemonize)
+{
+ pid_t pid;
+
+ server_signal_handler();
+
+ if (!daemonize)
+ return fio_server();
+
+ openlog("fio", LOG_NDELAY|LOG_NOWAIT|LOG_PID, LOG_USER);
+ pid = fork();
+ if (pid < 0) {
+ syslog(LOG_ERR, "failed server fork");
+ return -1;
+ } else if (pid)
+ exit(0);
+
+ setsid();
+ close(STDIN_FILENO);
+ close(STDOUT_FILENO);
+ close(STDERR_FILENO);
+ f_out = NULL;
+ f_err = NULL;
+ log_syslog = 1;
+ return fio_server();
+}
diff --git a/server.h b/server.h
new file mode 100644
index 0000000..b397617
--- /dev/null
+++ b/server.h
@@ -0,0 +1,155 @@
+#ifndef FIO_SERVER_H
+#define FIO_SERVER_H
+
+#include <inttypes.h>
+#include <string.h>
+
+#include "stat.h"
+#include "os/os.h"
+
+/*
+ * On-wire encoding is little endian
+ */
+struct fio_net_cmd {
+ uint16_t version; /* protocol version */
+ uint16_t opcode; /* command opcode */
+ uint32_t flags; /* modifier flags */
+ uint64_t serial; /* serial number */
+ uint32_t pdu_len; /* length of post-cmd layload */
+ /*
+ * These must be immediately before the payload, anything before
+ * these fields are checksummed.
+ */
+ uint16_t cmd_crc16; /* cmd checksum */
+ uint16_t pdu_crc16; /* payload checksum */
+ uint8_t payload[0]; /* payload */
+};
+
+enum {
+ FIO_SERVER_VER = 1,
+ FIO_SERVER_VER1 = 1,
+
+ FIO_SERVER_MAX_PDU = 64,
+
+ FIO_NET_CMD_QUIT = 1,
+ FIO_NET_CMD_EXIT = 2,
+ FIO_NET_CMD_JOB = 3,
+ FIO_NET_CMD_JOBLINE = 4,
+ FIO_NET_CMD_TEXT = 5,
+ FIO_NET_CMD_TS = 6,
+ FIO_NET_CMD_GS = 7,
+ FIO_NET_CMD_ETA = 8,
+ FIO_NET_CMD_PROBE = 9,
+ FIO_NET_CMD_START = 10,
+ FIO_NET_CMD_STOP = 11,
+
+ FIO_NET_CMD_F_MORE = 1UL << 0,
+
+ /* crc does not include the crc fields */
+ FIO_NET_CMD_CRC_SZ = sizeof(struct fio_net_cmd) -
+ 2 * sizeof(uint16_t),
+
+ FIO_NET_CMD_JOBLINE_ARGV = 128,
+};
+
+struct cmd_ts_pdu {
+ struct thread_stat ts;
+ struct group_run_stats rs;
+};
+
+struct cmd_probe_pdu {
+ uint8_t hostname[64];
+ uint8_t bigendian;
+ uint8_t fio_major;
+ uint8_t fio_minor;
+ uint8_t fio_patch;
+};
+
+struct cmd_line_pdu {
+ uint16_t argc;
+ uint8_t argv[FIO_NET_CMD_JOBLINE_ARGV][64];
+};
+
+extern int fio_start_server(int);
+extern int fio_server_text_output(const char *, unsigned int len);
+extern int fio_server_log(const char *format, ...);
+extern int fio_net_send_cmd(int, uint16_t, const void *, off_t);
+extern int fio_net_send_simple_cmd(int sk, uint16_t opcode, uint64_t serial);
+
+struct thread_stat;
+struct group_run_stats;
+extern void fio_server_send_ts(struct thread_stat *, struct group_run_stats *);
+extern void fio_server_send_gs(struct group_run_stats *);
+extern void fio_server_send_status(void);
+extern void fio_server_idle_loop(void);
+
+extern int fio_clients_connect(void);
+extern int fio_clients_send_ini(const char *);
+extern int fio_handle_clients(void);
+extern void fio_client_add(const char *);
+extern void fio_client_add_cmd_option(const char *, const char *);
+
+extern int fio_recv_data(int sk, void *p, unsigned int len);
+extern int fio_send_data(int sk, const void *p, unsigned int len);
+extern void fio_net_cmd_crc(struct fio_net_cmd *);
+extern struct fio_net_cmd *fio_net_recv_cmd(int sk, int block);
+
+extern int exit_backend;
+extern int fio_net_port;
+
+#ifdef FIO_LITTLE_ENDIAN
+#define __le16_to_cpu(x) (x)
+#define __le32_to_cpu(x) (x)
+#define __le64_to_cpu(x) (x)
+#define __cpu_to_le16(x) (x)
+#define __cpu_to_le32(x) (x)
+#define __cpu_to_le64(x) (x)
+#else
+#define __le16_to_cpu(x) fio_swap16(x)
+#define __le32_to_cpu(x) fio_swap32(x)
+#define __le64_to_cpu(x) fio_swap64(x)
+#define __cpu_to_le16(x) fio_swap16(x)
+#define __cpu_to_le32(x) fio_swap32(x)
+#define __cpu_to_le64(x) fio_swap64(x)
+#endif
+
+#define le16_to_cpu(val) ({ \
+ uint16_t *__val = &(val); \
+ __le16_to_cpu(*__val); \
+})
+#define le32_to_cpu(val) ({ \
+ uint32_t *__val = &(val); \
+ __le32_to_cpu(*__val); \
+})
+#define le64_to_cpu(val) ({ \
+ uint64_t *__val = &(val); \
+ __le64_to_cpu(*__val); \
+})
+#define cpu_to_le16(val) ({ \
+ uint16_t *__val = &(val); \
+ __cpu_to_le16(*__val); \
+})
+#define cpu_to_le32(val) ({ \
+ uint32_t *__val = &(val); \
+ __cpu_to_le32(*__val); \
+})
+#define cpu_to_le64(val) ({ \
+ uint64_t *__val = &(val); \
+ __cpu_to_le64(*__val); \
+})
+
+static inline void fio_init_net_cmd(struct fio_net_cmd *cmd, uint16_t opcode,
+ const void *pdu, uint32_t pdu_len)
+{
+ memset(cmd, 0, sizeof(*cmd));
+
+ cmd->version = __cpu_to_le16(FIO_SERVER_VER1);
+ cmd->opcode = cpu_to_le16(opcode);
+
+ if (pdu) {
+ cmd->pdu_len = cpu_to_le32(pdu_len);
+ memcpy(&cmd->payload, pdu, pdu_len);
+ }
+}
+
+#endif
diff --git a/stat.c b/stat.c
index 3662fd9..7bcae31 100644
--- a/stat.c
+++ b/stat.c
@@ -9,23 +9,24 @@
#include "fio.h"
#include "diskutil.h"
+#include "ieee754.h"
void update_rusage_stat(struct thread_data *td)
{
struct thread_stat *ts = &td->ts;
- getrusage(RUSAGE_SELF, &ts->ru_end);
+ getrusage(RUSAGE_SELF, &td->ru_end);
- ts->usr_time += mtime_since(&ts->ru_start.ru_utime,
- &ts->ru_end.ru_utime);
- ts->sys_time += mtime_since(&ts->ru_start.ru_stime,
- &ts->ru_end.ru_stime);
- ts->ctx += ts->ru_end.ru_nvcsw + ts->ru_end.ru_nivcsw
- - (ts->ru_start.ru_nvcsw + ts->ru_start.ru_nivcsw);
- ts->minf += ts->ru_end.ru_minflt - ts->ru_start.ru_minflt;
- ts->majf += ts->ru_end.ru_majflt - ts->ru_start.ru_majflt;
+ ts->usr_time += mtime_since(&td->ru_start.ru_utime,
+ &td->ru_end.ru_utime);
+ ts->sys_time += mtime_since(&td->ru_start.ru_stime,
+ &td->ru_end.ru_stime);
+ ts->ctx += td->ru_end.ru_nvcsw + td->ru_end.ru_nivcsw
+ - (td->ru_start.ru_nvcsw + td->ru_start.ru_nivcsw);
+ ts->minf += td->ru_end.ru_minflt - td->ru_start.ru_minflt;
+ ts->majf += td->ru_end.ru_majflt - td->ru_start.ru_majflt;
- memcpy(&ts->ru_start, &ts->ru_end, sizeof(ts->ru_end));
+ memcpy(&td->ru_start, &td->ru_end, sizeof(td->ru_end));
}
/*
@@ -101,13 +102,13 @@
static int double_cmp(const void *a, const void *b)
{
- const double fa = *(const double *)a;
- const double fb = *(const double *)b;
+ const fio_fp64_t fa = *(const fio_fp64_t *) a;
+ const fio_fp64_t fb = *(const fio_fp64_t *) b;
int cmp = 0;
- if (fa > fb)
+ if (fa.u.f > fb.u.f)
cmp = 1;
- else if (fa < fb)
+ else if (fa.u.f < fb.u.f)
cmp = -1;
return cmp;
@@ -116,38 +117,31 @@
/*
* Find and display the p-th percentile of clat
*/
-static void show_clat_percentiles(unsigned int* io_u_plat, unsigned long nr,
- double* user_list)
+static void show_clat_percentiles(unsigned int *io_u_plat, unsigned long nr,
+ fio_fp64_t *plist)
{
unsigned long sum = 0;
unsigned int len, i, j = 0;
- const double *plist;
int is_last = 0;
- static const double def_list[FIO_IO_U_LIST_MAX_LEN] = {
- 1.0, 5.0, 10.0, 20.0, 30.0,
- 40.0, 50.0, 60.0, 70.0, 80.0,
- 90.0, 95.0, 99.0, 99.5, 99.9};
- plist = user_list;
- if (!plist)
- plist = def_list;
-
- for (len = 0; len <FIO_IO_U_LIST_MAX_LEN && plist[len] != 0; len++)
- ;
+ len = 0;
+ while (len < FIO_IO_U_LIST_MAX_LEN && plist[len].u.f != 0.0)
+ len++;
/*
- * Sort the user-specified list. Note that this does not work
- * for NaN values
+ * Sort the percentile list. Note that it may already be sorted if
+ * we are using the default values, but since it's a short list this
+ * isn't a worry. Also note that this does not work for NaN values.
*/
- if (user_list && len > 1)
- qsort((void*)user_list, len, sizeof(user_list[0]), double_cmp);
+ if (len > 1)
+ qsort((void*)plist, len, sizeof(plist[0]), double_cmp);
log_info(" clat percentiles (usec) :");
for (i = 0; i < FIO_IO_U_PLAT_NR && !is_last; i++) {
sum += io_u_plat[i];
- while (sum >= (plist[j] / 100 * nr)) {
- assert(plist[j] <= 100.0);
+ while (sum >= (plist[j].u.f / 100.0 * nr)) {
+ assert(plist[j].u.f <= 100.0);
/* for formatting */
if (j != 0 && (j % 4) == 0)
@@ -181,23 +175,23 @@
*max = is->max_val;
n = (double) is->samples;
- *mean = is->mean;
+ *mean = is->mean.u.f;
if (n > 1.0)
- *dev = sqrt(is->S / (n - 1.0));
+ *dev = sqrt(is->S.u.f / (n - 1.0));
else
*dev = 0;
return 1;
}
-static void show_group_stats(struct group_run_stats *rs, int id)
+void show_group_stats(struct group_run_stats *rs)
{
char *p1, *p2, *p3, *p4;
const char *ddir_str[] = { " READ", " WRITE" };
int i;
- log_info("\nRun status group %d (all jobs):\n", id);
+ log_info("\nRun status group %d (all jobs):\n", rs->groupid);
for (i = 0; i <= DDIR_WRITE; i++) {
const int i2p = is_power_of_2(rs->kb_base);
@@ -433,8 +427,7 @@
log_info("\n");
}
-static void show_thread_status(struct thread_stat *ts,
- struct group_run_stats *rs)
+void show_thread_status(struct thread_stat *ts, struct group_run_stats *rs)
{
double usr_cpu, sys_cpu;
unsigned long runtime;
@@ -630,23 +623,23 @@
* #Parallel_algorithm>
*/
if (nr == 1) {
- mean = src->mean;
- S = src->S;
+ mean = src->mean.u.f;
+ S = src->S.u.f;
} else {
- double delta = src->mean - dst->mean;
+ double delta = src->mean.u.f - dst->mean.u.f;
- mean = ((src->mean * src->samples) +
- (dst->mean * dst->samples)) /
+ mean = ((src->mean.u.f * src->samples) +
+ (dst->mean.u.f * dst->samples)) /
(dst->samples + src->samples);
- S = src->S + dst->S + pow(delta, 2.0) *
+ S = src->S.u.f + dst->S.u.f + pow(delta, 2.0) *
(dst->samples * src->samples) /
(dst->samples + src->samples);
}
dst->samples += src->samples;
- dst->mean = mean;
- dst->S = S;
+ dst->mean.u.f = mean;
+ dst->S.u.f = S;
}
void show_run_stats(void)
@@ -716,9 +709,9 @@
ts->clat_percentiles = td->o.clat_percentiles;
if (td->o.overwrite_plist)
- ts->percentile_list = td->o.percentile_list;
+ memcpy(ts->percentile_list, td->o.percentile_list, sizeof(td->o.percentile_list));
else
- ts->percentile_list = NULL;
+ memcpy(ts->percentile_list, def_percentile_list, sizeof(def_percentile_list));
idx++;
ts->members++;
@@ -727,8 +720,13 @@
/*
* These are per-group shared already
*/
- ts->name = td->o.name;
- ts->description = td->o.description;
+ strncpy(ts->name, td->o.name, FIO_JOBNAME_SIZE);
+ if (td->o.description)
+ strncpy(ts->description, td->o.description,
+ FIO_JOBNAME_SIZE);
+ else
+ memset(ts->description, 0, FIO_JOBNAME_SIZE);
+
ts->groupid = td->groupid;
/*
@@ -750,10 +748,10 @@
if (!td->error && td->o.continue_on_error &&
td->first_error) {
ts->error = td->first_error;
- ts->verror = td->verror;
+ strcpy(ts->verror, td->verror);
} else if (td->error) {
ts->error = td->error;
- ts->verror = td->verror;
+ strcpy(ts->verror, td->verror);
}
}
@@ -763,7 +761,6 @@
sum_stat(&ts->lat_stat[l], &td->ts.lat_stat[l], idx);
sum_stat(&ts->bw_stat[l], &td->ts.bw_stat[l], idx);
- ts->stat_io_bytes[l] += td->ts.stat_io_bytes[l];
ts->io_bytes[l] += td->ts.io_bytes[l];
if (ts->runtime[l] < td->ts.runtime[l])
@@ -858,15 +855,24 @@
ts = &threadstats[i];
rs = &runstats[ts->groupid];
- if (terse_output)
+ if (is_backend)
+ fio_server_send_ts(ts, rs);
+ else if (terse_output)
show_thread_status_terse(ts, rs);
else
show_thread_status(ts, rs);
}
if (!terse_output) {
- for (i = 0; i < groupid + 1; i++)
- show_group_stats(&runstats[i], i);
+ for (i = 0; i < groupid + 1; i++) {
+ rs = &runstats[i];
+
+ rs->groupid = i;
+ if (is_backend)
+ fio_server_send_gs(rs);
+ else
+ show_group_stats(rs);
+ }
show_disk_util();
}
@@ -885,10 +891,10 @@
if (data < is->min_val)
is->min_val = data;
- delta = val - is->mean;
+ delta = val - is->mean.u.f;
if (delta) {
- is->mean += delta / (is->samples + 1.0);
- is->S += delta * (val - is->mean);
+ is->mean.u.f += delta / (is->samples + 1.0);
+ is->S.u.f += delta * (val - is->mean.u.f);
}
is->samples++;
@@ -954,8 +960,8 @@
add_stat_sample(&ts->clat_stat[ddir], usec);
- if (ts->clat_log)
- add_log_sample(td, ts->clat_log, usec, ddir, bs);
+ if (td->clat_log)
+ add_log_sample(td, td->clat_log, usec, ddir, bs);
if (ts->clat_percentiles)
add_clat_percentile_sample(ts, usec, ddir);
@@ -971,8 +977,8 @@
add_stat_sample(&ts->slat_stat[ddir], usec);
- if (ts->slat_log)
- add_log_sample(td, ts->slat_log, usec, ddir, bs);
+ if (td->slat_log)
+ add_log_sample(td, td->slat_log, usec, ddir, bs);
}
void add_lat_sample(struct thread_data *td, enum fio_ddir ddir,
@@ -985,8 +991,8 @@
add_stat_sample(&ts->lat_stat[ddir], usec);
- if (ts->lat_log)
- add_log_sample(td, ts->lat_log, usec, ddir, bs);
+ if (td->lat_log)
+ add_log_sample(td, td->lat_log, usec, ddir, bs);
}
void add_bw_sample(struct thread_data *td, enum fio_ddir ddir, unsigned int bs,
@@ -998,17 +1004,17 @@
if (!ddir_rw(ddir))
return;
- spent = mtime_since(&ts->stat_sample_time[ddir], t);
+ spent = mtime_since(&td->stat_sample_time[ddir], t);
if (spent < td->o.bw_avg_time)
return;
- rate = (td->this_io_bytes[ddir] - ts->stat_io_bytes[ddir]) *
+ rate = (td->this_io_bytes[ddir] - td->stat_io_bytes[ddir]) *
1000 / spent / 1024;
add_stat_sample(&ts->bw_stat[ddir], rate);
- if (ts->bw_log)
- add_log_sample(td, ts->bw_log, rate, ddir, bs);
+ if (td->bw_log)
+ add_log_sample(td, td->bw_log, rate, ddir, bs);
- fio_gettime(&ts->stat_sample_time[ddir], NULL);
- ts->stat_io_bytes[ddir] = td->this_io_bytes[ddir];
+ fio_gettime(&td->stat_sample_time[ddir], NULL);
+ td->stat_io_bytes[ddir] = td->this_io_bytes[ddir];
}
diff --git a/stat.h b/stat.h
new file mode 100644
index 0000000..89766fc
--- /dev/null
+++ b/stat.h
@@ -0,0 +1,195 @@
+#ifndef FIO_STAT_H
+#define FIO_STAT_H
+
+struct group_run_stats {
+ uint64_t max_run[2], min_run[2];
+ uint64_t max_bw[2], min_bw[2];
+ uint64_t io_kb[2];
+ uint64_t agg[2];
+ uint32_t kb_base;
+ uint32_t groupid;
+};
+
+/*
+ * How many depth levels to log
+ */
+#define FIO_IO_U_MAP_NR 7
+#define FIO_IO_U_LAT_U_NR 10
+#define FIO_IO_U_LAT_M_NR 12
+
+/*
+ * Aggregate clat samples to report percentile(s) of them.
+ *
+ * EXECUTIVE SUMMARY
+ *
+ * FIO_IO_U_PLAT_BITS determines the maximum statistical error on the
+ * value of resulting percentiles. The error will be approximately
+ * 1/2^(FIO_IO_U_PLAT_BITS+1) of the value.
+ *
+ * FIO_IO_U_PLAT_GROUP_NR and FIO_IO_U_PLAT_BITS determine the maximum
+ * range being tracked for latency samples. The maximum value tracked
+ * accurately will be 2^(GROUP_NR + PLAT_BITS -1) microseconds.
+ *
+ * FIO_IO_U_PLAT_GROUP_NR and FIO_IO_U_PLAT_BITS determine the memory
+ * requirement of storing those aggregate counts. The memory used will
+ * be (FIO_IO_U_PLAT_GROUP_NR * 2^FIO_IO_U_PLAT_BITS) * sizeof(int)
+ * bytes.
+ *
+ * FIO_IO_U_PLAT_NR is the total number of buckets.
+ *
+ * DETAILS
+ *
+ * Suppose the clat varies from 0 to 999 (usec), the straightforward
+ * method is to keep an array of (999 + 1) buckets, in which a counter
+ * keeps the count of samples which fall in the bucket, e.g.,
+ * {[0],[1],...,[999]}. However this consumes a huge amount of space,
+ * and can be avoided if an approximation is acceptable.
+ *
+ * One such method is to let the range of the bucket to be greater
+ * than one. This method has low accuracy when the value is small. For
+ * example, let the buckets be {[0,99],[100,199],...,[900,999]}, and
+ * the represented value of each bucket be the mean of the range. Then
+ * a value 0 has an round-off error of 49.5. To improve on this, we
+ * use buckets with non-uniform ranges, while bounding the error of
+ * each bucket within a ratio of the sample value. A simple example
+ * would be when error_bound = 0.005, buckets are {
+ * {[0],[1],...,[99]}, {[100,101],[102,103],...,[198,199]},..,
+ * {[900,909],[910,919]...} }. The total range is partitioned into
+ * groups with different ranges, then buckets with uniform ranges. An
+ * upper bound of the error is (range_of_bucket/2)/value_of_bucket
+ *
+ * For better efficiency, we implement this using base two. We group
+ * samples by their Most Significant Bit (MSB), extract the next M bit
+ * of them as an index within the group, and discard the rest of the
+ * bits.
+ *
+ * E.g., assume a sample 'x' whose MSB is bit n (starting from bit 0),
+ * and use M bit for indexing
+ *
+ * | n | M bits | bit (n-M-1) ... bit 0 |
+ *
+ * Because x is at least 2^n, and bit 0 to bit (n-M-1) is at most
+ * (2^(n-M) - 1), discarding bit 0 to (n-M-1) makes the round-off
+ * error
+ *
+ * 2^(n-M)-1 2^(n-M) 1
+ * e <= --------- <= ------- = ---
+ * 2^n 2^n 2^M
+ *
+ * Furthermore, we use "mean" of the range to represent the bucket,
+ * the error e can be lowered by half to 1 / 2^(M+1). By using M bits
+ * as the index, each group must contains 2^M buckets.
+ *
+ * E.g. Let M (FIO_IO_U_PLAT_BITS) be 6
+ * Error bound is 1/2^(6+1) = 0.0078125 (< 1%)
+ *
+ * Group MSB #discarded range of #buckets
+ * error_bits value
+ * ----------------------------------------------------------------
+ * 0* 0~5 0 [0,63] 64
+ * 1* 6 0 [64,127] 64
+ * 2 7 1 [128,255] 64
+ * 3 8 2 [256,511] 64
+ * 4 9 3 [512,1023] 64
+ * ... ... ... [...,...] ...
+ * 18 23 17 [8838608,+inf]** 64
+ *
+ * * Special cases: when n < (M-1) or when n == (M-1), in both cases,
+ * the value cannot be rounded off. Use all bits of the sample as
+ * index.
+ *
+ * ** If a sample's MSB is greater than 23, it will be counted as 23.
+ */
+
+#define FIO_IO_U_PLAT_BITS 6
+#define FIO_IO_U_PLAT_VAL (1 << FIO_IO_U_PLAT_BITS)
+#define FIO_IO_U_PLAT_GROUP_NR 19
+#define FIO_IO_U_PLAT_NR (FIO_IO_U_PLAT_GROUP_NR * FIO_IO_U_PLAT_VAL)
+#define FIO_IO_U_LIST_MAX_LEN 20 /* The size of the default and user-specified
+ list of percentiles */
+
+#define MAX_PATTERN_SIZE 512
+#define FIO_JOBNAME_SIZE 128
+#define FIO_VERROR_SIZE 128
+
+struct thread_stat {
+ char name[FIO_JOBNAME_SIZE];
+ char verror[FIO_VERROR_SIZE];
+ uint32_t error;
+ uint32_t groupid;
+ uint32_t pid;
+ char description[FIO_JOBNAME_SIZE];
+ uint32_t members;
+
+ /*
+ * bandwidth and latency stats
+ */
+ struct io_stat clat_stat[2]; /* completion latency */
+ struct io_stat slat_stat[2]; /* submission latency */
+ struct io_stat lat_stat[2]; /* total latency */
+ struct io_stat bw_stat[2]; /* bandwidth stats */
+
+ /*
+ * fio system usage accounting
+ */
+ uint64_t usr_time;
+ uint64_t sys_time;
+ uint64_t ctx;
+ uint64_t minf, majf;
+
+ /*
+ * IO depth and latency stats
+ */
+ uint64_t clat_percentiles;
+ fio_fp64_t percentile_list[FIO_IO_U_LIST_MAX_LEN];
+
+ uint32_t io_u_map[FIO_IO_U_MAP_NR];
+ uint32_t io_u_submit[FIO_IO_U_MAP_NR];
+ uint32_t io_u_complete[FIO_IO_U_MAP_NR];
+ uint32_t io_u_lat_u[FIO_IO_U_LAT_U_NR];
+ uint32_t io_u_lat_m[FIO_IO_U_LAT_M_NR];
+ uint32_t io_u_plat[2][FIO_IO_U_PLAT_NR];
+ uint64_t total_io_u[3];
+ uint64_t short_io_u[3];
+ uint64_t total_submit;
+ uint64_t total_complete;
+
+ uint64_t io_bytes[2];
+ uint64_t runtime[2];
+ uint64_t total_run_time;
+
+ /*
+ * IO Error related stats
+ */
+ uint16_t continue_on_error;
+ uint64_t total_err_count;
+ uint32_t first_error;
+
+ uint32_t kb_base;
+};
+
+struct jobs_eta {
+ uint32_t nr_running;
+ uint32_t nr_ramp;
+ uint32_t nr_pending;
+ uint32_t files_open;
+ uint32_t m_rate, t_rate;
+ uint32_t m_iops, t_iops;
+ uint32_t rate[2];
+ uint32_t iops[2];
+ uint64_t elapsed_sec;
+ uint64_t eta_sec;
+
+ /*
+ * Network 'copy' of run_str[]
+ */
+ uint32_t nr_threads;
+ uint8_t run_str[0];
+};
+
+extern void show_thread_status(struct thread_stat *ts, struct group_run_stats *rs);
+extern void show_group_stats(struct group_run_stats *rs);
+extern int calc_thread_status(struct jobs_eta *je);
+extern void display_thread_status(struct jobs_eta *je);
+
+#endif