Skip to content

Commit bd42e45

Browse files
engines/http: Add support for range reads
The existing HTTP implementation treated blocks as individual objects when reading and writing. This is useful in as far as it enables read and write symmetry -- object stores generally don't allow writes to individual ranges of objects like file systems do with files, so treating blocks as objects is a practical way to replicate block reads and writes. Reading of object ranges, on the other hand, is widely supported by object stores with the "Range" HTTP header. This change adds a parameter which alters fio's object conventions to issue reads using the block size and offset parameters more like file IO. When enabled, both reads and writes will use the plain filename as the object path to issue IO. Reads will add a "Range: bytes=<start>-<end>" header to the requests, where the start and end positions are determined by the blocksize and offset of the benchmark. Aside from the object path, writes are unchanged for simplicity: the object size is determined by blocksize as before. Signed-off-by: Renar Narubin <[email protected]>
1 parent 404ab60 commit bd42e45

File tree

3 files changed

+107
-14
lines changed

3 files changed

+107
-14
lines changed

HOWTO.rst

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3038,6 +3038,24 @@ with the caveat that when used on the command line, they must come after the
30383038
turns on verbose logging from libcurl, 2 additionally enables
30393039
HTTP IO tracing. Default is **0**
30403040

3041+
.. option:: http_object_mode=str : [http]
3042+
3043+
How to structure objects for HTTP IO: *block* or *range*.
3044+
Default is **block**.
3045+
3046+
In *block* mode, one object is created for every block. The HTTP engine
3047+
treats :option:`blocksize` as the size of the object to read or write,
3048+
and appends the block start/end offsets to the :option:`filename` to
3049+
create the target object path. Reads and writes operate on whole
3050+
objects at a time.
3051+
3052+
In *range* mode, one object is created for every file. The object path
3053+
is the filename directly for both read and write I/O. For read
3054+
requests, the :option:`blocksize` and :option:`offset` will be used to
3055+
set the "Range" header on read requests to issue partial reads of the
3056+
object. For write requests, blocksize is used to set the size of the
3057+
object, the same as in *block* mode.
3058+
30413059
.. option:: uri=str : [nbd]
30423060

30433061
Specify the NBD URI of the server to test. The string

engines/http.c

Lines changed: 69 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,16 @@
3535
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
3636

3737
enum {
38-
FIO_HTTP_WEBDAV = 0,
39-
FIO_HTTP_S3 = 1,
40-
FIO_HTTP_SWIFT = 2,
38+
FIO_HTTP_WEBDAV = 0,
39+
FIO_HTTP_S3 = 1,
40+
FIO_HTTP_SWIFT = 2,
4141

42-
FIO_HTTPS_OFF = 0,
43-
FIO_HTTPS_ON = 1,
44-
FIO_HTTPS_INSECURE = 2,
42+
FIO_HTTPS_OFF = 0,
43+
FIO_HTTPS_ON = 1,
44+
FIO_HTTPS_INSECURE = 2,
45+
46+
FIO_HTTP_OBJECT_BLOCK = 0,
47+
FIO_HTTP_OBJECT_RANGE = 1,
4548
};
4649

4750
struct http_data {
@@ -63,6 +66,7 @@ struct http_options {
6366
char *swift_auth_token;
6467
int verbose;
6568
unsigned int mode;
69+
unsigned int object_mode;
6670
};
6771

6872
struct http_curl_stream {
@@ -228,6 +232,26 @@ static struct fio_option options[] = {
228232
.category = FIO_OPT_C_ENGINE,
229233
.group = FIO_OPT_G_HTTP,
230234
},
235+
{
236+
.name = "http_object_mode",
237+
.lname = "Object mode to use",
238+
.type = FIO_OPT_STR,
239+
.help = "How to structure objects when issuing HTTP requests",
240+
.off1 = offsetof(struct http_options, object_mode),
241+
.def = "block",
242+
.posval = {
243+
{ .ival = "block",
244+
.oval = FIO_HTTP_OBJECT_BLOCK,
245+
.help = "One object per block",
246+
},
247+
{ .ival = "range",
248+
.oval = FIO_HTTP_OBJECT_RANGE,
249+
.help = "One object per file, range reads per block",
250+
},
251+
},
252+
.category = FIO_OPT_C_ENGINE,
253+
.group = FIO_OPT_G_HTTP,
254+
},
231255
{
232256
.name = NULL,
233257
},
@@ -580,6 +604,26 @@ static void _add_swift_header(CURL *curl, struct curl_slist *slist, struct http_
580604
free(dsha);
581605
}
582606

607+
static struct curl_slist* _append_range_header(struct curl_slist *slist, unsigned long long offset, unsigned long long length, unsigned long long file_size)
608+
{
609+
char s[256];
610+
unsigned long long end_byte;
611+
612+
/* Don't request beyond end of file */
613+
if (offset >= file_size) {
614+
return slist;
615+
}
616+
617+
/* Calculate end byte, but cap it at file size - 1 because end range is inclusive */
618+
end_byte = offset + length - 1;
619+
if (end_byte >= file_size) {
620+
end_byte = file_size - 1;
621+
}
622+
623+
snprintf(s, sizeof(s), "Range: bytes=%llu-%llu", offset, end_byte);
624+
return curl_slist_append(slist, s);
625+
}
626+
583627
static void fio_http_cleanup(struct thread_data *td)
584628
{
585629
struct http_data *http = td->io_ops_data;
@@ -636,30 +680,39 @@ static enum fio_q_status fio_http_queue(struct thread_data *td,
636680
struct http_options *o = td->eo;
637681
struct http_curl_stream _curl_stream;
638682
struct curl_slist *slist = NULL;
639-
char object[512];
683+
char object_path_buf[512];
684+
char *object_path;
640685
char url[1024];
641686
long status;
642687
CURLcode res;
643688

644689
fio_ro_check(td, io_u);
645690
memset(&_curl_stream, 0, sizeof(_curl_stream));
646-
snprintf(object, sizeof(object), "%s_%llu_%llu", io_u->file->file_name,
647-
io_u->offset, io_u->xfer_buflen);
691+
if (o->object_mode == FIO_HTTP_OBJECT_BLOCK) {
692+
snprintf(object_path_buf, sizeof(object_path_buf), "%s_%llu_%llu", io_u->file->file_name,
693+
io_u->offset, io_u->xfer_buflen);
694+
object_path = object_path_buf;
695+
} else
696+
object_path = io_u->file->file_name;
648697
if (o->https == FIO_HTTPS_OFF)
649-
snprintf(url, sizeof(url), "http://%s%s", o->host, object);
698+
snprintf(url, sizeof(url), "http://%s%s", o->host, object_path);
650699
else
651-
snprintf(url, sizeof(url), "https://%s%s", o->host, object);
700+
snprintf(url, sizeof(url), "https://%s%s", o->host, object_path);
701+
652702
curl_easy_setopt(http->curl, CURLOPT_URL, url);
653703
_curl_stream.buf = io_u->xfer_buf;
654704
_curl_stream.max = io_u->xfer_buflen;
655705
curl_easy_setopt(http->curl, CURLOPT_SEEKDATA, &_curl_stream);
656706
curl_easy_setopt(http->curl, CURLOPT_INFILESIZE_LARGE, (curl_off_t)io_u->xfer_buflen);
657707

708+
if (io_u->ddir == DDIR_READ && o->object_mode == FIO_HTTP_OBJECT_RANGE)
709+
slist = _append_range_header(slist, io_u->offset, io_u->xfer_buflen, io_u->file->real_file_size);
710+
658711
if (o->mode == FIO_HTTP_S3)
659-
_add_aws_auth_header(http->curl, slist, o, io_u->ddir, object,
712+
_add_aws_auth_header(http->curl, slist, o, io_u->ddir, object_path,
660713
io_u->xfer_buf, io_u->xfer_buflen);
661714
else if (o->mode == FIO_HTTP_SWIFT)
662-
_add_swift_header(http->curl, slist, o, io_u->ddir, object,
715+
_add_swift_header(http->curl, slist, o, io_u->ddir, object_path,
663716
io_u->xfer_buf, io_u->xfer_buflen);
664717

665718
if (io_u->ddir == DDIR_WRITE) {
@@ -681,7 +734,9 @@ static enum fio_q_status fio_http_queue(struct thread_data *td,
681734
res = curl_easy_perform(http->curl);
682735
if (res == CURLE_OK) {
683736
curl_easy_getinfo(http->curl, CURLINFO_RESPONSE_CODE, &status);
684-
if (status == 200)
737+
/* 206 "Partial Content" means success when using the
738+
* Range header */
739+
if (status == 200 || (o->object_mode == FIO_HTTP_OBJECT_RANGE && status == 206))
685740
goto out;
686741
else if (status == 404) {
687742
/* Object doesn't exist. Pretend we read

fio.1

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2623,6 +2623,26 @@ Enable verbose requests from libcurl. Useful for debugging. 1 turns on
26232623
verbose logging from libcurl, 2 additionally enables HTTP IO tracing.
26242624
Default is \fB0\fR
26252625
.TP
2626+
.BI (http)http_object_mode \fR=\fPstr
2627+
How to structure objects for HTTP IO: block or range. Default is \fBblock\fR.
2628+
.RS
2629+
.RS
2630+
.TP
2631+
.B block
2632+
One object is created for every block. The HTTP engine treats \fBblocksize\fR
2633+
as the size of the object to read or write, and appends the block start/end
2634+
offsets to the \fBfilename\fR to create the target object path. Reads and
2635+
writes operate on whole objects at a time.
2636+
.TP
2637+
.B range
2638+
One object is created for every file. The object path is the filename directly
2639+
for both read and write I/O. For read requests, the \fBblocksize\fR and
2640+
\fBoffset\fR will be used to set the "Range" header on read requests to issue
2641+
partial reads of the object. For write requests, blocksize is used to set the
2642+
size of the object, the same as in \fBblock\fR mode.
2643+
.RE
2644+
.RE
2645+
.TP
26262646
.BI (mtd)skip_bad \fR=\fPbool
26272647
Skip operations against known bad blocks.
26282648
.TP

0 commit comments

Comments
 (0)