+ size_t size = 1048576;
+ void *buf;
+
+ buf = malloc(size);
+ setvbuf(f, buf, _IOFBF, size);
+ return buf;
+}
+
+static void clear_file_buffer(void *buf)
+{
+ free(buf);
+}
+#else
+static void *set_file_buffer(FILE *f)
+{
+ return NULL;
+}
+
+static void clear_file_buffer(void *buf)
+{
+}
+#endif
+
+void free_log(struct io_log *log)
+{
+ free(log->log);
+ free(log->filename);
+ free(log);
+}
+
+static void flush_samples(FILE *f, void *samples, uint64_t sample_size)
+{
+ struct io_sample *s;
+ int log_offset;
+ uint64_t i, nr_samples;
+
+ if (!sample_size)
+ return;
+
+ s = __get_sample(samples, 0, 0);
+ log_offset = (s->__ddir & LOG_OFFSET_SAMPLE_BIT) != 0;
+
+ nr_samples = sample_size / __log_entry_sz(log_offset);
+
+ for (i = 0; i < nr_samples; i++) {
+ s = __get_sample(samples, log_offset, i);
+
+ if (!log_offset) {
+ fprintf(f, "%lu, %lu, %u, %u\n",
+ (unsigned long) s->time,
+ (unsigned long) s->val,
+ io_sample_ddir(s), s->bs);
+ } else {
+ struct io_sample_offset *so = (void *) s;
+
+ fprintf(f, "%lu, %lu, %u, %u, %llu\n",
+ (unsigned long) s->time,
+ (unsigned long) s->val,
+ io_sample_ddir(s), s->bs,
+ (unsigned long long) so->offset);
+ }
+ }
+}
+
+#ifdef CONFIG_ZLIB
+
+struct iolog_flush_data {
+ struct tp_work work;
+ struct io_log *log;
+ void *samples;
+ uint64_t nr_samples;
+};
+
+struct iolog_compress {
+ struct flist_head list;
+ void *buf;
+ size_t len;
+ unsigned int seq;
+};
+
+#define GZ_CHUNK 131072
+
+static struct iolog_compress *get_new_chunk(unsigned int seq)
+{
+ struct iolog_compress *c;
+
+ c = malloc(sizeof(*c));
+ INIT_FLIST_HEAD(&c->list);
+ c->buf = malloc(GZ_CHUNK);
+ c->len = 0;
+ c->seq = seq;
+ return c;
+}
+
+static void free_chunk(struct iolog_compress *ic)
+{
+ free(ic->buf);
+ free(ic);
+}
+
+static int z_stream_init(z_stream *stream, int gz_hdr)
+{
+ int wbits = 15;
+
+ stream->zalloc = Z_NULL;
+ stream->zfree = Z_NULL;
+ stream->opaque = Z_NULL;
+ stream->next_in = Z_NULL;
+
+ /*
+ * zlib magic - add 32 for auto-detection of gz header or not,
+ * if we decide to store files in a gzip friendly format.
+ */
+ if (gz_hdr)
+ wbits += 32;
+
+ if (inflateInit2(stream, wbits) != Z_OK)
+ return 1;
+
+ return 0;
+}
+
+struct inflate_chunk_iter {
+ unsigned int seq;
+ int err;
+ void *buf;
+ size_t buf_size;
+ size_t buf_used;
+ size_t chunk_sz;
+};
+
+static void finish_chunk(z_stream *stream, FILE *f,
+ struct inflate_chunk_iter *iter)
+{
+ int ret;
+
+ ret = inflateEnd(stream);
+ if (ret != Z_OK)
+ log_err("fio: failed to end log inflation (%d)\n", ret);
+
+ flush_samples(f, iter->buf, iter->buf_used);
+ free(iter->buf);
+ iter->buf = NULL;
+ iter->buf_size = iter->buf_used = 0;
+}
+
+/*
+ * Iterative chunk inflation. Handles cases where we cross into a new
+ * sequence, doing flush finish of previous chunk if needed.
+ */
+static size_t inflate_chunk(struct iolog_compress *ic, int gz_hdr, FILE *f,
+ z_stream *stream, struct inflate_chunk_iter *iter)
+{
+ size_t ret;
+
+ dprint(FD_COMPRESS, "inflate chunk size=%lu, seq=%u",
+ (unsigned long) ic->len, ic->seq);
+
+ if (ic->seq != iter->seq) {
+ if (iter->seq)
+ finish_chunk(stream, f, iter);
+
+ z_stream_init(stream, gz_hdr);
+ iter->seq = ic->seq;
+ }
+
+ stream->avail_in = ic->len;
+ stream->next_in = ic->buf;
+
+ if (!iter->buf_size) {
+ iter->buf_size = iter->chunk_sz;
+ iter->buf = malloc(iter->buf_size);
+ }
+
+ while (stream->avail_in) {
+ size_t this_out = iter->buf_size - iter->buf_used;
+ int err;
+
+ stream->avail_out = this_out;
+ stream->next_out = iter->buf + iter->buf_used;
+
+ err = inflate(stream, Z_NO_FLUSH);
+ if (err < 0) {
+ log_err("fio: failed inflating log: %d\n", err);
+ iter->err = err;
+ break;
+ }
+
+ iter->buf_used += this_out - stream->avail_out;
+
+ if (!stream->avail_out) {
+ iter->buf_size += iter->chunk_sz;
+ iter->buf = realloc(iter->buf, iter->buf_size);
+ continue;
+ }
+
+ if (err == Z_STREAM_END)
+ break;
+ }
+
+ ret = (void *) stream->next_in - ic->buf;
+
+ dprint(FD_COMPRESS, "inflated to size=%lu\n", (unsigned long) ret);
+
+ return ret;
+}
+
+/*
+ * Inflate stored compressed chunks, or write them directly to the log
+ * file if so instructed.
+ */
+static int inflate_gz_chunks(struct io_log *log, FILE *f)
+{
+ struct inflate_chunk_iter iter = { .chunk_sz = log->log_gz, };
+ z_stream stream;
+
+ while (!flist_empty(&log->chunk_list)) {
+ struct iolog_compress *ic;
+
+ ic = flist_first_entry(&log->chunk_list, struct iolog_compress, list);
+ flist_del(&ic->list);
+
+ if (log->log_gz_store) {
+ size_t ret;
+
+ dprint(FD_COMPRESS, "log write chunk size=%lu, "
+ "seq=%u\n", (unsigned long) ic->len, ic->seq);
+
+ ret = fwrite(ic->buf, ic->len, 1, f);
+ if (ret != 1 || ferror(f)) {
+ iter.err = errno;
+ log_err("fio: error writing compressed log\n");
+ }
+ } else
+ inflate_chunk(ic, log->log_gz_store, f, &stream, &iter);
+
+ free_chunk(ic);
+ }
+
+ if (iter.seq) {
+ finish_chunk(&stream, f, &iter);
+ free(iter.buf);
+ }
+
+ return iter.err;
+}
+
+/*
+ * Open compressed log file and decompress the stored chunks and
+ * write them to stdout. The chunks are stored sequentially in the
+ * file, so we iterate over them and do them one-by-one.
+ */
+int iolog_file_inflate(const char *file)
+{
+ struct inflate_chunk_iter iter = { .chunk_sz = 64 * 1024 * 1024, };
+ struct iolog_compress ic;
+ z_stream stream;
+ struct stat sb;
+ ssize_t ret;
+ size_t total;
+ void *buf;
+ FILE *f;
+
+ f = fopen(file, "r");
+ if (!f) {
+ perror("fopen");
+ return 1;
+ }
+
+ if (stat(file, &sb) < 0) {
+ fclose(f);
+ perror("stat");
+ return 1;
+ }
+
+ ic.buf = buf = malloc(sb.st_size);
+ ic.len = sb.st_size;
+ ic.seq = 1;
+
+ ret = fread(ic.buf, ic.len, 1, f);
+ if (ret < 0) {
+ perror("fread");
+ fclose(f);
+ return 1;
+ } else if (ret != 1) {
+ log_err("fio: short read on reading log\n");
+ fclose(f);
+ return 1;
+ }
+
+ fclose(f);
+
+ /*
+ * Each chunk will return Z_STREAM_END. We don't know how many
+ * chunks are in the file, so we just keep looping and incrementing
+ * the sequence number until we have consumed the whole compressed
+ * file.
+ */
+ total = ic.len;
+ do {
+ size_t ret;
+
+ ret = inflate_chunk(&ic, 1, stdout, &stream, &iter);
+ total -= ret;
+ if (!total)
+ break;
+ if (iter.err)
+ break;
+
+ ic.seq++;
+ ic.len -= ret;
+ ic.buf += ret;
+ } while (1);
+
+ if (iter.seq) {
+ finish_chunk(&stream, stdout, &iter);
+ free(iter.buf);
+ }
+
+ free(buf);
+ return iter.err;
+}
+
+#else
+
+static int inflate_gz_chunks(struct io_log *log, FILE *f)
+{
+ return 0;
+}
+
+int iolog_file_inflate(const char *file)
+{
+ log_err("fio: log inflation not possible without zlib\n");
+ return 1;
+}
+
+#endif
+
+void flush_log(struct io_log *log)
+{
+ void *buf;