Recent changes (master)

Jens Axboe <[email protected]> Tue, 19 Aug 2025 06:00:01 -0600 (MDT)
Newsgroups org.kernel.vger.fio
Message-ID <[email protected]>
The following changes since commit fef641096d925d737d81683626eca52b6e358e52:

  configure: skip isal64 check when isal check fails (2025-08-11 12:42:06 -0400)

are available in the Git repository at:

  git://git.kernel.dk/fio.git master

for you to fetch changes up to 7664dccc0b2e3de04a0fd6f0be63402716c5f6b7:

  Merge branch 'http-range-header' of https://github.com/sfc-gh-rnarubin/fio (2025-08-18 13:53:46 -0400)

----------------------------------------------------------------
Renar Narubin (1):
      engines/http: Add support for range reads

Vincent Fu (1):
      Merge branch 'http-range-header' of https://github.com/sfc-gh-rnarubin/fio

 HOWTO.rst      | 18 +++++++++++++
 engines/http.c | 83 ++++++++++++++++++++++++++++++++++++++++++++++++----------
 fio.1          | 20 ++++++++++++++
 3 files changed, 107 insertions(+), 14 deletions(-)

---

Diff of recent changes:

diff --git a/HOWTO.rst b/HOWTO.rst
index 55ebc388..3eb0d9fe 100644
--- a/HOWTO.rst
+++ b/HOWTO.rst
@@ -3058,6 +3058,24 @@ with the caveat that when used on the command line, they must come after the
 	turns on verbose logging from libcurl, 2 additionally enables
 	HTTP IO tracing. Default is **0**
 
+.. option:: http_object_mode=str : [http]
+
+        How to structure objects for HTTP IO: *block* or *range*.
+        Default is **block**.
+
+        In *block* mode, one object is created for every block. The HTTP engine
+        treats :option:`blocksize` as the size of the object to read or write,
+        and appends the block start/end offsets to the :option:`filename` to
+        create the target object path. Reads and writes operate on whole
+        objects at a time.
+
+        In *range* mode, one object is created for every file. The object path
+        is the filename directly for both read and write I/O. For read
+        requests, the :option:`blocksize` and :option:`offset` will be used to
+        set the "Range" header on read requests to issue partial reads of the
+        object. For write requests, blocksize is used to set the size of the
+        object, the same as in *block* mode.
+
 .. option:: uri=str : [nbd]
 
 	Specify the NBD URI of the server to test.  The string
diff --git a/engines/http.c b/engines/http.c
index b893ec7a..1a1787bf 100644
--- a/engines/http.c
+++ b/engines/http.c
@@ -35,13 +35,16 @@
 #pragma GCC diagnostic ignored "-Wdeprecated-declarations"
 
 enum {
-	FIO_HTTP_WEBDAV	    = 0,
-	FIO_HTTP_S3	    = 1,
-	FIO_HTTP_SWIFT	    = 2,
+	FIO_HTTP_WEBDAV		= 0,
+	FIO_HTTP_S3		= 1,
+	FIO_HTTP_SWIFT		= 2,
 
-	FIO_HTTPS_OFF	    = 0,
-	FIO_HTTPS_ON	    = 1,
-	FIO_HTTPS_INSECURE  = 2,
+	FIO_HTTPS_OFF		= 0,
+	FIO_HTTPS_ON		= 1,
+	FIO_HTTPS_INSECURE	= 2,
+
+	FIO_HTTP_OBJECT_BLOCK	= 0,
+	FIO_HTTP_OBJECT_RANGE	= 1,
 };
 
 struct http_data {
@@ -64,6 +67,7 @@ struct http_options {
 	char *swift_auth_token;
 	int verbose;
 	unsigned int mode;
+	unsigned int object_mode;
 };
 
 struct http_curl_stream {
@@ -239,6 +243,26 @@ static struct fio_option options[] = {
 		.category = FIO_OPT_C_ENGINE,
 		.group    = FIO_OPT_G_HTTP,
 	},
+	{
+		.name     = "http_object_mode",
+		.lname    = "Object mode to use",
+		.type     = FIO_OPT_STR,
+		.help     = "How to structure objects when issuing HTTP requests",
+		.off1     = offsetof(struct http_options, object_mode),
+		.def	  = "block",
+		.posval = {
+			  { .ival = "block",
+			    .oval = FIO_HTTP_OBJECT_BLOCK,
+			    .help = "One object per block",
+			  },
+			  { .ival = "range",
+			    .oval = FIO_HTTP_OBJECT_RANGE,
+			    .help = "One object per file, range reads per block",
+			  },
+		},
+		.category = FIO_OPT_C_ENGINE,
+		.group    = FIO_OPT_G_HTTP,
+	},
 	{
 		.name     = NULL,
 	},
@@ -611,6 +635,26 @@ static void _add_swift_header(CURL *curl, struct curl_slist *slist, struct http_
 	free(dsha);
 }
 
+static struct curl_slist* _append_range_header(struct curl_slist *slist, unsigned long long offset, unsigned long long length, unsigned long long file_size)
+{
+	char s[256];
+	unsigned long long end_byte;
+
+	/* Don't request beyond end of file */
+	if (offset >= file_size) {
+		return slist;
+	}
+
+	/* Calculate end byte, but cap it at file size - 1 because end range is inclusive */
+	end_byte = offset + length - 1;
+	if (end_byte >= file_size) {
+		end_byte = file_size - 1;
+	}
+
+	snprintf(s, sizeof(s), "Range: bytes=%llu-%llu", offset, end_byte);
+	return curl_slist_append(slist, s);
+}
+
 static void fio_http_cleanup(struct thread_data *td)
 {
 	struct http_data *http = td->io_ops_data;
@@ -667,30 +711,39 @@ static enum fio_q_status fio_http_queue(struct thread_data *td,
 	struct http_options *o = td->eo;
 	struct http_curl_stream _curl_stream;
 	struct curl_slist *slist = NULL;
-	char object[512];
+	char object_path_buf[512];
+	char *object_path;
 	char url[1024];
 	long status;
 	CURLcode res;
 
 	fio_ro_check(td, io_u);
 	memset(&_curl_stream, 0, sizeof(_curl_stream));
-	snprintf(object, sizeof(object), "%s_%llu_%llu", io_u->file->file_name,
-		io_u->offset, io_u->xfer_buflen);
+	if (o->object_mode == FIO_HTTP_OBJECT_BLOCK) {
+		snprintf(object_path_buf, sizeof(object_path_buf), "%s_%llu_%llu", io_u->file->file_name,
+			io_u->offset, io_u->xfer_buflen);
+		object_path = object_path_buf;
+	} else
+		object_path = io_u->file->file_name;
 	if (o->https == FIO_HTTPS_OFF)
-		snprintf(url, sizeof(url), "http://%s%s", o->host, object);
+		snprintf(url, sizeof(url), "http://%s%s", o->host, object_path);
 	else
-		snprintf(url, sizeof(url), "https://%s%s", o->host, object);
+		snprintf(url, sizeof(url), "https://%s%s", o->host, object_path);
+
 	curl_easy_setopt(http->curl, CURLOPT_URL, url);
 	_curl_stream.buf = io_u->xfer_buf;
 	_curl_stream.max = io_u->xfer_buflen;
 	curl_easy_setopt(http->curl, CURLOPT_SEEKDATA, &_curl_stream);
 	curl_easy_setopt(http->curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)io_u->xfer_buflen);
 
+	if (io_u->ddir == DDIR_READ && o->object_mode == FIO_HTTP_OBJECT_RANGE)
+		slist = _append_range_header(slist, io_u->offset, io_u->xfer_buflen, io_u->file->real_file_size);
+
 	if (o->mode == FIO_HTTP_S3)
-		_add_aws_auth_header(http->curl, slist, o, io_u->ddir, object,
+		_add_aws_auth_header(http->curl, slist, o, io_u->ddir, object_path,
 			io_u->xfer_buf, io_u->xfer_buflen);
 	else if (o->mode == FIO_HTTP_SWIFT)
-		_add_swift_header(http->curl, slist, o, io_u->ddir, object,
+		_add_swift_header(http->curl, slist, o, io_u->ddir, object_path,
 			io_u->xfer_buf, io_u->xfer_buflen);
 
 	if (io_u->ddir == DDIR_WRITE) {
@@ -712,7 +765,9 @@ static enum fio_q_status fio_http_queue(struct thread_data *td,
 		res = curl_easy_perform(http->curl);
 		if (res == CURLE_OK) {
 			curl_easy_getinfo(http->curl, CURLINFO_RESPONSE_CODE, &status);
-			if (status == 200)
+			/* 206 "Partial Content" means success when using the
+			 * Range header */
+			if (status == 200 || (o->object_mode == FIO_HTTP_OBJECT_RANGE && status == 206))
 				goto out;
 			else if (status == 404) {
 				/* Object doesn't exist. Pretend we read
diff --git a/fio.1 b/fio.1
index 5bcb1d46..6aa23f7d 100644
--- a/fio.1
+++ b/fio.1
@@ -2648,6 +2648,26 @@ Enable verbose requests from libcurl. Useful for debugging. 1 turns on
 verbose logging from libcurl, 2 additionally enables HTTP IO tracing.
 Default is \fB0\fR
 .TP
+.BI (http)http_object_mode \fR=\fPstr
+How to structure objects for HTTP IO: block or range. Default is \fBblock\fR.
+.RS
+.RS
+.TP
+.B block
+One object is created for every block. The HTTP engine treats \fBblocksize\fR
+as the size of the object to read or write, and appends the block start/end
+offsets to the \fBfilename\fR to create the target object path. Reads and
+writes operate on whole objects at a time.
+.TP
+.B range
+One object is created for every file. The object path is the filename directly
+for both read and write I/O. For read requests, the \fBblocksize\fR and
+\fBoffset\fR will be used to set the "Range" header on read requests to issue
+partial reads of the object. For write requests, blocksize is used to set the
+size of the object, the same as in \fBblock\fR mode.
+.RE
+.RE
+.TP
 .BI (mtd)skip_bad \fR=\fPbool
 Skip operations against known bad blocks.
 .TP