C++ API#

namespace kvikio#

KvikIO namespace.

Typedefs

using PageAlignedBounceBufferPool = BounceBufferPool<PageAlignedAllocator>#

Bounce buffer pool using page-aligned host memory.

Use for: Host-only Direct I/O operations (no CUDA context involvement)

using CudaPinnedBounceBufferPool = BounceBufferPool<CudaPinnedAllocator>#

Bounce buffer pool using CUDA pinned memory.

Use for: Device I/O operations without Direct I/O Note: Not page-aligned - cannot be used with Direct I/O

using CudaPageAlignedPinnedBounceBufferPool = BounceBufferPool<CudaPageAlignedPinnedAllocator>#

Bounce buffer pool using page-aligned CUDA-registered pinned memory.

Use for: Device I/O operations with Direct I/O enabled Provides both page alignment (for Direct I/O) and CUDA registration (for efficient transfers)

using Clock = std::chrono::steady_clock#

The clock KvikIO timestamps observations with.

using TimePoint = Clock::time_point#

A point in time on Clock.

using Duration = std::chrono::nanoseconds#

A length of time, in nanoseconds.

using ThreadPool = BS::thread_pool#

Thread pool type used for parallel I/O operations.

Enums

enum class CompatMode : uint8_t#

I/O compatibility mode.

Values:

enumerator OFF#

Enforce cuFile I/O. GDS will be activated if the system requirements for cuFile are met and cuFile is properly configured. However, if the system is not suited for cuFile, I/O operations under the OFF option may error out.

enumerator ON#

Enforce POSIX I/O.

enumerator AUTO#

Try cuFile I/O first, and fall back to POSIX I/O if the system requirements for cuFile are not met.

enum class IoBackend : std::uint8_t#

The I/O backend that carried out an operation.

Values:

enumerator POSIX#

POSIX pread/pwrite, including the compatibility-mode path.

enumerator GDS#

cuFile / GPUDirect Storage.

enumerator MMAP#

Memory-mapped file access.

enumerator REMOTE_HTTP#

Remote I/O over HTTP(S), including S3.

enumerator REMOTE_HDFS#

Remote I/O over WebHDFS.

enum class TransferDirection : std::uint8_t#

The direction of an I/O operation.

Values:

enumerator READ#

Data moves from the file or endpoint into the buffer.

enumerator WRITE#

Data moves from the buffer into the file or endpoint.

enum class MemoryKind : std::uint8_t#

The kind of memory the caller’s buffer lives in.

Values:

enumerator HOST#

Host (CPU) memory.

enumerator DEVICE#

Device (GPU) memory.

enum class ObservationKind : std::uint8_t#

What layer an observation describes.

Values:

enumerator LOGICAL#

One user-facing call, such as one FileHandle::pread().

enumerator PHYSICAL#

One transfer occupying a thread or a connection, such as one thread-pool task.

enum class RemoteEndpointType : uint8_t#

Types of remote file endpoints supported by KvikIO.

This enum defines the different protocols and services that can be used to access remote files. It is used to specify or detect the type of remote endpoint when opening files.

Values:

enumerator AUTO#

Automatically detect the endpoint type from the URL. KvikIO will attempt to infer the appropriate protocol based on the URL format.

enumerator S3#

AWS S3 endpoint using credentials-based authentication. Requires AWS environment variables (such as AWS_ACCESS_KEY_ID, AWS_SECRET_ACCESS_KEY, AWS_DEFAULT_REGION) to be set.

enumerator S3_PUBLIC#

AWS S3 endpoint for publicly accessible objects. No credentials required as the objects have public read permissions enabled. Used for open datasets and public buckets.

enumerator S3_PRESIGNED_URL#

AWS S3 endpoint using a presigned URL. No credentials required as authentication is embedded in the URL with time-limited access.

enumerator WEBHDFS#

Apache Hadoop WebHDFS (Web-based Hadoop Distributed File System) endpoint for accessing files stored in HDFS over HTTP/HTTPS.

enumerator HTTP#

Generic HTTP or HTTPS endpoint for accessing files from web servers. This is used for standard web resources that do not fit the other specific categories.

enum class RemoteIOBackend : uint8_t#

Selects the remote I/O backend.

Naming scheme is <libcurl-API-flavor>_<execution-method>. Selection is controlled by the environment variable KVIKIO_REMOTE_IO_BACKEND.

Values:

enumerator EASY_THREADPOOL#

Libcurl easy API running in the KvikIO thread pool. Each sub-range of a pread() is dispatched to a worker thread that blocks in curl_easy_perform() until its transfer completes. Concurrency is bounded by the thread pool size: one busy thread per in-flight transfer.

enumerator MULTI_POLL#

Libcurl multi API driven by N reactor threads, each of which blocks in curl_multi_poll(). A single reactor multiplexes many in-flight easy handles concurrently, so the number of simultaneous transfers is not bounded by the reactor count. See KVIKIO_REMOTE_IO_NUM_REACTORS and KVIKIO_REMOTE_IO_REACTOR_DISPATCH.

enum class RemoteReactorDispatch : uint8_t#

How sub-ranges of a single pread() are distributed across reactor threads when the MULTI_POLL backend is active.

Controlled by KVIKIO_REMOTE_IO_REACTOR_DISPATCH. When only one reactor is used, both modes are equivalent.

Values:

enumerator PER_CHUNK#

Sub-ranges are routed to reactors round-robin, independently of which pread() they belong to. This maximizes load balance across reactors. Trade-off: two sub-ranges of the same file may land on different reactors, each with its own libcurl connection cache, so they may not share an established TCP/TLS connection.

enumerator PER_PREAD#

All sub-ranges of a single pread() are submitted to the same reactor (the reactor is itself chosen round-robin per pread() call). The sub-ranges then share that reactor’s libcurl connection cache, allowing an established TCP/TLS connection to be reused. Best for HTTPS, where the TLS handshake cost is non-trivial.

Functions

void buffer_register(
void const *devPtr_base,
std::size_t size,
int flags = 0,
std::vector<int> const &errors_to_ignore = std::vector<int>()
)#

Register a device memory region with cuFile for GPUDirect Storage access.

This is the low-level registration function that requires the caller to specify the exact base address and size of the memory region to register. For a convenience wrapper that automatically discovers the allocation boundaries, see memory_register().

Registration pins the memory for GPU Direct DMA transfers, which can improve performance when the same buffer is reused across multiple cuFile I/O operations.

In compatibility mode (when GDS is unavailable), this function is a no-op.

See also

memory_register for automatic discovery of allocation base address and size.

See also

buffer_deregister to deregister the memory.

Warning

This API is intended for streaming buffers reused across multiple cuFile I/O operations. For one-time transfers, the overhead of registration may outweigh the benefits.

Parameters:
  • devPtr_base – Base address of the device memory region to register.

  • size – Size in bytes of the memory region to register.

  • flags – Registration flags. Should be 0 or CU_FILE_RDMA_REGISTER (experimental).

  • errors_to_ignore – cuFile error codes to silently ignore, such as CU_FILE_MEMORY_ALREADY_REGISTERED or CU_FILE_INVALID_MAPPING_SIZE.

Throws:

CUfileException – If cuFile registration fails with an error not in errors_to_ignore.

void buffer_deregister(void const *devPtr_base)#

Deregister a device memory region from cuFile.

This is the low-level deregistration function that requires the caller to specify the exact base address that was previously registered. For a convenience wrapper that automatically discovers the allocation boundaries, see memory_deregister().

In compatibility mode (when GDS is unavailable), this function is a no-op.

See also

memory_deregister for automatic discovery of allocation base address.

See also

buffer_register to register the memory.

Parameters:

devPtr_base – Base address of the device memory region to deregister. Must match the address used in the corresponding buffer_register() call.

Throws:

CUfileException – If cuFile deregistration fails.

void memory_register(
void const *devPtr,
int flags = 0,
std::vector<int> const &errors_to_ignore = {}
)#

Register a device memory allocation with cuFile for GPUDirect Storage access. Use this function together with FileHandle::pread() and FileHandle::pwrite().

This is a convenience wrapper around buffer_register() that automatically discovers the base address and size of the CUDA memory allocation containing devPtr. The entire underlying allocation is registered, regardless of which portion devPtr points to.

Registration pins the memory for GPU Direct DMA transfers, which can improve performance when the same buffer is reused across multiple cuFile I/O operations.

In compatibility mode (when GDS is unavailable), this function is a no-op.

See also

buffer_register for registering with explicit base address and size.

See also

memory_deregister to deregister the memory.

Warning

This API is intended for streaming buffers reused across multiple cuFile I/O operations. For one-time transfers, the overhead of registration may outweigh the benefits.

Parameters:
  • devPtr – Pointer anywhere within a CUDA device memory allocation.

  • flags – Registration flags. Should be 0 or CU_FILE_RDMA_REGISTER (experimental).

  • errors_to_ignore – cuFile error codes to silently ignore, such as CU_FILE_MEMORY_ALREADY_REGISTERED or CU_FILE_INVALID_MAPPING_SIZE.

Throws:

CUfileException – If cuFile registration fails with an error not in errors_to_ignore.

void memory_deregister(void const *devPtr)#

Deregister a device memory allocation from cuFile.

This is a convenience wrapper around buffer_deregister() that automatically discovers the base address of the CUDA memory allocation containing devPtr. The entire underlying allocation is deregistered, regardless of which portion devPtr points to.

In compatibility mode (when GDS is unavailable), this function is a no-op.

See also

buffer_deregister for deregistering with explicit base address.

See also

memory_register to register the memory.

Parameters:

devPtr – Pointer anywhere within a previously registered CUDA device memory allocation.

Throws:

CUfileException – If cuFile deregistration fails.

std::string const &config_path()#

Get the filepath to cuFile’s config file (cufile.json) or the empty string.

This lookup is cached.

Returns:

The filepath to the cufile.json file or the empty string if it isn’t found.

template<typename T>
T getenv_or(
std::string_view env_var_name,
T default_val
)#
template<>
bool getenv_or(
std::string_view env_var_name,
bool default_val
)#
template<>
CompatMode getenv_or(
std::string_view env_var_name,
CompatMode default_val
)#
template<>
std::vector<int> getenv_or(
std::string_view env_var_name,
std::vector<int> default_val
)#
template<>
RemoteIOBackend getenv_or(
std::string_view env_var_name,
RemoteIOBackend default_val
)#
template<>
RemoteReactorDispatch getenv_or(
std::string_view env_var_name,
RemoteReactorDispatch default_val
)#
template<typename T>
std::tuple<std::string_view, T, bool> getenv_or(
std::initializer_list<std::string_view> env_var_names,
T default_val
)#

Get the environment variable value from a candidate list.

Template Parameters:

T – Type of the environment variable value

Parameters:
  • env_var_names – Candidate list containing the names of environment variable

  • default_val – Default value of the environment variable, if none of the candidates has been found

Throws:

std::invalid_argument – if:

  • env_var_names is empty.

  • The environment variable is not defined to be string type and is assigned an empty value (in other words, string-type environment variables are allowed to hold an empty value).

  • More than one candidates have been set with different values.

  • An invalid value is given, e.g. value that cannot be converted to type T.

Returns:

A tuple of (env_var_name, result, has_found), where:

  • If the environment variable is not set by any of the candidates, has_found will be false, result will be default_val, and env_var_name will be empty.

  • If the environment variable is set by env_var_name, then has_found will be true, and result be the set value. If more than one candidates have been set with the same value, env_var_name will be assigned the last candidate.

int open_fd_parse_flags(std::string const &flags, bool o_direct)#

Parse open file flags given as a string and return oflags.

Parameters:
  • flags – The flags

  • o_direct – Append O_DIRECT to the open flags

Throws:
  • std::invalid_argument – if the specified flags are not supported.

  • std::invalid_argument – if o_direct is true, but O_DIRECT is not supported.

Returns:

oflags

int open_fd(
std::string const &file_path,
std::string const &flags,
bool o_direct,
mode_t mode
)#

Open file using open(2)

Parameters:
  • flags – Open flags given as a string

  • o_direct – Append O_DIRECT to flags

  • mode – Access modes

Returns:

File descriptor

int open_flags(int fd)#

Get the flags of the file descriptor (see open(2))

Returns:

Open flags

std::size_t get_file_size(std::string const &file_path)#

Get file size from file descriptor fstat(3)

Parameters:

file_path – Path to the file

Returns:

The number of bytes

std::size_t get_file_size(int file_descriptor)#

Get file size given the file path.

Parameters:

file_descriptor – Open file descriptor

Returns:

The number of bytes

std::pair<std::size_t, std::size_t> get_page_cache_info(
std::string const &file_path,
std::size_t offset = 0,
std::size_t length = 0
)#

Obtain the page cache residency information for a given file.

Note

See get_page_cache_info(int, std::size_t, std::size_t) for detailed behavior and caveats

Parameters:
  • file_path – Path to the file.

  • offset – Starting byte offset (default: 0 for beginning of file)

  • length – Number of bytes to query (default: 0, meaning entire file from offset)

Returns:

A pair containing the number of pages resident in the page cache and the total number of pages.

std::pair<std::size_t, std::size_t> get_page_cache_info(
int fd,
std::size_t offset = 0,
std::size_t length = 0
)#

Obtain the page cache residency information for a given file.

Note

If offset is beyond the end of the file, returns {0, 0}.

Note

If offset + length extends beyond the file, the query is clamped to the file size.

Note

The page cache residency query takes place in granularity of full pages. If the specified range does not align to page boundaries, partial pages at the start and end of the range are included.

Parameters:
  • fd – Open file descriptor.

  • offset – Starting byte offset (default: 0 for beginning of file)

  • length – Number of bytes to query (default: 0, meaning entire file from offset)

Returns:

A pair containing the number of pages resident in the page cache and the total number of pages.

void drop_file_page_cache(
int fd,
std::size_t offset = 0,
std::size_t length = 0,
bool sync_first = true
)#

Drop page cache for a specific file.

Advises the kernel to evict cached pages for the specified file descriptor using posix_fadvise with POSIX_FADV_DONTNEED.

Note

This is the preferred method for benchmark cache invalidation as it:

  • Requires no elevated privileges

  • Affects only the specified file, not other processes

  • Has minimal overhead (no child process spawned)

Note

The page cache dropping takes place in granularity of full pages. If the specified range does not align to page boundaries, partial pages at the start and end of the range are retained. Only pages fully contained within the range are dropped.

Note

For dropping page cache system-wide (requires elevated privileges), see drop_system_page_cache().

Parameters:
  • fd – Open file descriptor

  • offset – Starting byte offset (default: 0 for beginning of file)

  • length – Number of bytes to drop (default: 0, meaning entire file from offset)

  • sync_first – Whether to flush dirty pages to disk before dropping. If true, fdatasync will be called prior to dropping. This ensures dirty pages become clean and thus droppable. Can be set to false if we are certain no dirty pages exist for this file.

Throws:

kvikio::GenericSystemError – if the file descriptor is invalid, or the file cannot be synchronized, or the attempt to drop the page cache fails.

void drop_file_page_cache(
std::string const &file_path,
std::size_t offset = 0,
std::size_t length = 0,
bool sync_first = true
)#

Drop page cache for a specific file.

Convenience overload that opens the file, drops its page cache, and closes it.

Note

For dropping page cache system-wide (requires elevated privileges), see drop_system_page_cache().

Note

See drop_file_page_cache(int, std::size_t, std::size_t, bool) for detailed behavior and caveats

Parameters:
  • file_path – Path to the file

  • offset – Starting byte offset (default: 0 for beginning of file)

  • length – Number of bytes to drop (default: 0, meaning entire file from offset)

  • sync_first – Whether to flush dirty pages to disk before dropping. If true, fdatasync will be called prior to dropping. This ensures dirty pages become clean and thus droppable. Can be set to false if we are certain no dirty pages exist for this file.

Throws:

kvikio::GenericSystemError – if the file cannot be opened, or the file cannot be synchronized, or the attempt to drop the page cache fails.

bool drop_system_page_cache(
bool reclaim_dentries_and_inodes = true,
bool sync_first = true
)#

Drop the system page cache.

Note

This drops page cache system-wide, affecting all processes. For dropping cache for a specific file without elevated privileges, see drop_file_page_cache(int, std::size_t, std::size_t, bool).

Note

This function creates a child process and executes the cache dropping shell command in the following order:

  • Execute the command without sudo prefix. This is for the superuser and also for specially configured systems where unprivileged users cannot execute /usr/bin/sudo but can execute /sbin/sysctl. If this step succeeds, the function returns true immediately.

  • Execute the command with sudo prefix. This is for the general case where selective unprivileged users have permission to run /sbin/sysctl with sudo prefix.

Parameters:
  • reclaim_dentries_and_inodes – Whether to free reclaimable slab objects which include dentries and inodes.

    • If true, equivalent to executing /sbin/sysctl vm.drop_caches=3;

    • If false, equivalent to executing /sbin/sysctl vm.drop_caches=1.

  • sync_first – Whether to flush dirty pages to disk before dropping. If true, sync will be called prior to dropping. This ensures dirty pages become clean and thus droppable.

Throws:

kvikio::GenericSystemError – if somehow the child process could not be created.

Returns:

Whether the page cache has been successfully dropped.

bool clear_page_cache(
bool reclaim_dentries_and_inodes = true,
bool clear_dirty_pages = true
)#

Drop the system page cache. Deprecated. Use drop_system_page_cache instead.

BlockDeviceInfo get_block_device_info(std::string const &file_path)#

Get information about the physical block device hosting a file.

Resolves the underlying block device for a given file path, handling:

  • Partitions: walks up to the parent block device (e.g., sda1 -> sda)

  • NVMe namespaces: maps to the controller (e.g., nvme0n1 -> nvme0)

  • Other block devices (SATA, SAS, dm, md): returns the device’s own info

Note

Limitations:

  • For device-mapper devices (LVM, dm-crypt), this returns the dm device ID, not the underlying physical device(s). This may be suboptimal when multiple LVs share the same underlying physical drive (over-subscription) or when a single LV is striped across multiple drives (under-utilization).

  • Files residing on virtual filesystems (overlayfs, tmpfs) or network filesystems (NFS, CIFS, FUSE) are not backed by a local block device, and this function will throw.

Parameters:

file_path – Path to the file whose block device ID is to be determined.

Throws:

kvikio::GenericSystemError – if the file does not exist, or if the block device cannot be determined (e.g., virtual or network filesystem).

Returns:

Block device info for the underlying physical block device.

rapids_logger::logger &default_logger()#

Returns the global logger instance for KvikIO.

The logger is configured once on first access using the following environment variables:

  • KVIKIO_LOG_LEVEL: Sets the log level. Accepted values (case-insensitive) are TRACE, DEBUG, INFO, WARN, ERROR, CRITICAL, and OFF. If unset or set to any other value, logging is disabled.

  • KVIKIO_LOG_FILE: If set, log output is written to this file path (overwritten on each process start). If the file cannot be opened, falls back to stderr with a warning. Has no effect when logging is disabled.

Returns:

Reference to the global KvikIO logger

std::string_view to_string(IoBackend backend) noexcept#

Human-readable name of an I/O backend.

Parameters:

backend – The backend.

Returns:

A static string such as "POSIX".

std::string_view to_string(TransferDirection direction) noexcept#

Human-readable name of a transfer direction.

Parameters:

direction – The direction.

Returns:

A static string such as "READ".

std::string_view to_string(MemoryKind memory_kind) noexcept#

Human-readable name of a memory kind.

Parameters:

memory_kind – The memory kind.

Returns:

A static string such as "DEVICE".

std::string_view to_string(ObservationKind kind) noexcept#

Human-readable name of an observation kind.

Parameters:

kind – The kind.

Returns:

A static string such as "LOGICAL".

std::uint64_t register_monitor(
Monitor *monitor,
ObservationKind kind = ObservationKind::LOGICAL
)#

Register a monitor, which begins receiving both notifications.

Parameters:
  • monitor – The monitor. Not owned, and must outlive the registration.

  • kind – Which observations it watches.

Throws:
  • std::invalid_argument – if monitor is null.

  • std::runtime_error – if called from inside a monitor callback.

Returns:

An id for unregister_monitor().

void unregister_monitor(std::uint64_t id)#

Unregister a monitor.

Blocks until no thread is inside either callback, so the monitor may be destroyed once this returns.

Parameters:

id – The id from register_monitor().

RemoteEndpointType infer_remote_endpoint_type(std::string const &url)#

Infer remote endpoint type from URL.

This function follows the same endpoint-selection order as RemoteHandle::open() in RemoteEndpointType::AUTO mode, but only infers the endpoint type and does not create a handle. Note that this function will not return RemoteEndpointType::S3_PUBLIC, because disambiguating between a URL that’s accessible only with authorization or only anonymously is not possible without making an HTTP request.

Parameters:

url – The URL of the remote file.

Returns:

The inferred endpoint type.

void stream_register(CUstream stream, unsigned flags)#

Registers the CUDA stream to the cuFile subsystem.

Parameters:
void stream_deregister(CUstream stream)#

Deregisters the CUDA stream from the cuFile subsystem.

Parameters:

stream – CUDA stream which queues the async I/O operations

inline std::function<void()> make_thread_pool_init_task(
std::string prefix
)#

Build a BS::thread_pool init task that names each worker thread.

The returned functor is intended to be passed to BS::thread_pool’s constructor or reset() overloads that accept an init_task. It runs once per worker as the pool starts each thread, setting the OS-level thread name (comm on Linux) to "<prefix>-<index>" so profilers such as nsys, top -H, and /proc/<pid>/task/<tid>/comm show meaningful names instead of the parent process name.

The per-thread index is assigned via an atomic counter captured by the returned functor, so different pools can share the same prefix without colliding.

Linux caps thread names at 15 characters plus NUL, so keep prefix short (typically 10 characters or fewer).

Parameters:

prefix – Name prefix, e.g. "kvikio" or "kvikio-bdev".

Returns:

An init task suitable for BS::thread_pool.

std::size_t get_page_size()#
off_t convert_size2off(std::size_t x)#
ssize_t convert_size2ssize(std::size_t x)#
CUdeviceptr convert_void2deviceptr(void const *devPtr)#
template<typename T, std::enable_if_t<std::is_integral_v<T>>* = nullptr>
std::int64_t convert_to_64bit(
T value
)#

Help function to convert value to 64 bit signed integer.

inline std::uint64_t convert_to_64bit(std::uint64_t value)#

Helper function to allow NVTX payload of type std::uint64_t to pass through without doing anything.

template<typename T, std::enable_if_t<std::is_floating_point_v<T>>* = nullptr>
double convert_to_64bit(
T value
)#

Help function to convert value to 64 bit float.

bool is_host_memory(void const *ptr)#

Check if ptr points to host memory (as opposed to device memory)

In this context, managed memory counts as device memory

Parameters:

ptr – Memory pointer to query

Returns:

The boolean answer

int get_device_ordinal_from_pointer(CUdeviceptr dev_ptr)#

Return the device owning the pointer.

Parameters:

ptr – Device pointer to query

Returns:

The device ordinal

CUcontext get_primary_cuda_context(int ordinal)#

Given a device ordinal, return the primary context of the device.

This function caches the primary contexts retrieved until program exit

Parameters:

ordinal – Device ordinal - an integer between 0 and the number of CUDA devices

Returns:

Primary CUDA context

std::optional<CUcontext> get_context_associated_pointer(
CUdeviceptr dev_ptr
)#

Return the CUDA context associated the given device pointer, if any.

Parameters:

dev_ptr – Device pointer to query

Returns:

Usable CUDA context, if one were found.

bool current_context_can_access_pointer(CUdeviceptr dev_ptr)#

Check if the current CUDA context can access the given device pointer.

Parameters:

dev_ptr – Device pointer to query

Returns:

The boolean answer

CUcontext get_context_from_pointer(void const *devPtr)#

Return a CUDA context that can be used with the given device pointer.

For robustness, we look for an usabale context in the following order: 1) If a context has been associated with devPtr, it is returned. 2) If the current context exists and can access devPtr, it is returned. 3) Return the primary context of the device that owns devPtr. We assume the primary context can access devPtr, which might not be true in the exceptional disjoint addressing cases mention in the CUDA docs[1]. In these cases, the user has to set an usable current context before reading/writing using KvikIO.

[1] https://docs.nvidia.com/cuda/cuda-driver-api/group__CUDA__UNIFIED.html

Parameters:

devPtr – Device pointer to query

Returns:

Usable CUDA context

std::tuple<void*, std::size_t, std::size_t> get_alloc_info(
void const *devPtr,
CUcontext *ctx = nullptr
)#
template<typename T>
std::future<std::decay_t<T>> make_ready_future(
T &&t
)#

Create a shared state in a future object that is immediately ready.

A partial implementation of the namesake function from the concurrency TS (https://en.cppreference.com/w/cpp/experimental/make_ready_future). The cases of std::reference_wrapper and void are not implemented.

Template Parameters:

T – Type of the value provided.

Parameters:

t – Object provided.

Returns:

A future holding a decayed copy of the object provided.

template<typename T>
bool is_future_done(T const &future)#

Check the status of the future object. True indicates that the result is available in the future’s shared state. False otherwise.

The future shall not be created using std::async(std::launch::deferred). Otherwise, this function always returns true.

Template Parameters:

T – Type of the future.

Parameters:

future – Instance of the future.

Returns:

Boolean answer indicating if the future is ready or not.

Variables

constexpr std::size_t num_io_backends = static_cast<std::size_t>(IoBackend::REMOTE_HDFS) + 1#

Number of IoBackend values.

constexpr std::size_t num_observation_kinds = static_cast<std::size_t>(ObservationKind::PHYSICAL) + 1#

Number of ObservationKind values.

struct BatchOp#
#include <batch.hpp>

IO operation used when submitting batches.

class BatchHandle#
#include <batch.hpp>

Handle of an cuFile batch using semantic.

The workflow is as follows: 1) Create a batch with a large enough max_num_events. 2) Call .submit() with a vector of operations (vector.size() <= max_num_events). 3) Call .status() to wait on the operations to finish, or 3) Call .cancel() to cancel the operations. 4) Go to step 2 or call .close() to free up resources.

Notice, a batch handle can only handle one “submit” at a time and is closed in the destructor automatically.

Public Functions

BatchHandle(int max_num_events)#

Construct a batch handle.

Parameters:

max_num_events – The maximum number of operations supported by this instance.

BatchHandle(BatchHandle const&) = delete#

BatchHandle support move semantic but isn’t copyable.

void close() noexcept#

Destroy the batch handle and free up resources.

void submit(std::vector<BatchOp> const &operations)#

Submit a vector of batch operations.

Parameters:

operations – The vector of batch operations, which must not exceed the max_num_events.

std::vector<CUfileIOEvents_t> status(
unsigned min_nr,
unsigned max_nr,
struct timespec *timeout = nullptr
)#

Get status of submitted operations.

Parameters:
  • min_nr – The minimum number of IO entries for which status is requested.

  • max_nr – The maximum number of IO requests to poll for.

  • timeout – This parameter is used to specify the amount of time to wait for in this API, even if the minimum number of requests have not completed. If the timeout hits, it is possible that the number of returned IOs can be less than min_nr

Returns:

Vector of the status of the completed I/Os in the batch.

class PageAlignedAllocator#
#include <bounce_buffer.hpp>

Allocator for page-aligned host memory.

Uses std::aligned_alloc to allocate host memory aligned to page boundaries (typically 4096 bytes). This allocator is suitable for Direct I/O operations that require page-aligned buffers but do not need CUDA context (i.e., host-to-host transfers only).

Public Functions

void *allocate(std::size_t size)#

Allocate page-aligned host memory.

Parameters:

size – Requested size in bytes (will be rounded up to page boundary)

Returns:

Pointer to allocated memory

void deallocate(void *buffer, std::size_t size)#

Deallocate memory previously allocated by this allocator.

Parameters:
  • buffer – Pointer to memory to deallocate

  • size – Size of the allocation (unused, for interface consistency)

class CudaPinnedAllocator#
#include <bounce_buffer.hpp>

Allocator for CUDA pinned host memory.

Uses cudaMemHostAlloc to allocate pinned (page-locked) host memory that can be efficiently transferred to/from GPU device memory. The allocation is only guaranteed to be aligned to “at

least 256 bytes”. It is NOT guaranteed to be page aligned.

Note

Do NOT use with Direct I/O - lacks page alignment guarantee

Public Functions

void *allocate(std::size_t size)#

Allocate CUDA pinned host memory.

Parameters:

size – Requested size in bytes

Returns:

Pointer to allocated pinned memory

void deallocate(void *buffer, std::size_t size)#

Deallocate memory previously allocated by this allocator.

Parameters:
  • buffer – Pointer to memory to deallocate

  • size – Size of the allocation (unused, for interface consistency)

class CudaPageAlignedPinnedAllocator#
#include <bounce_buffer.hpp>

Allocator for page-aligned AND CUDA-registered pinned host memory.

Combines the benefits of both page alignment (for Direct I/O) and CUDA registration (for efficient host-device transfers). Uses std::aligned_alloc followed by cudaMemHostRegister to achieve both properties.

Note

This is the required allocator for Direct I/O with device memory. Requires a valid CUDA context when allocating.

Public Functions

void *allocate(std::size_t size)#

Allocate page-aligned CUDA-registered pinned host memory.

Parameters:

size – Requested size in bytes (will be rounded up to page boundary)

Returns:

Pointer to allocated memory

void deallocate(void *buffer, std::size_t size)#

Deallocate memory previously allocated by this allocator.

Parameters:
  • buffer – Pointer to memory to deallocate

  • size – Size of the allocation (unused, for interface consistency)

template<typename Allocator = CudaPinnedAllocator>
class BounceBufferPool#
#include <bounce_buffer.hpp>

Thread-safe singleton pool for reusable bounce buffers.

Manages a pool of host memory buffers used for staging data during I/O operations. Buffers are retained and reused across calls to minimize allocation overhead. The pool uses a LIFO (stack) allocation strategy optimized for cache locality.

All buffers in the pool have the same size, controlled by defaults::bounce_buffer_size(). If the buffer size changes, all cached buffers are cleared and reallocated at the new size.

Call BounceBufferPool::get to get an allocation that will be retained when it goes out of scope (RAII). The size of all retained allocations are the same.

Note

The destructor intentionally leaks allocations to avoid CUDA cleanup issues when static destructors run after CUDA context destruction

Template Parameters:

Allocator – The allocator policy that determines buffer properties:

Public Functions

Buffer get()#

Acquire a bounce buffer from the pool.

Returns a cached buffer if available, otherwise allocates a new one. The returned Buffer object will automatically return the buffer to the pool when it goes out of scope.

Throws:

CudaError – if allocation fails (e.g., invalid CUDA context for pinned allocators)

Returns:

RAII Buffer object wrapping the allocated memory

void put(void *buffer, std::size_t size) noexcept#

Return a buffer to the pool for reuse.

Typically called automatically by Buffer’s destructor. Only adds the buffer to the pool if its size matches the current pool buffer size; otherwise the buffer is deallocated immediately.

Note

noexcept: any failure during the underlying push or deallocation is caught and logged.

Parameters:
  • buffer – Pointer to memory to return

  • size – Size of the buffer in bytes

std::size_t clear()#

Free all retained allocations in the pool.

Clears the pool and deallocates all cached buffers. Useful for reclaiming memory when bounce buffers are no longer needed.

Returns:

The number of bytes cleared

std::size_t num_free_buffers() const#

Get the number of free buffers currently available in the pool.

Returns the count of buffers that have been returned to the pool and are ready for reuse.

Returns:

The number of buffers available for reuse

std::size_t buffer_size() const#

Get the current buffer size used by the pool.

Returns the size of buffers currently managed by the pool. This reflects the value of defaults::bounce_buffer_size() as of the last pool operation.

Returns:

The size in bytes of each buffer in the pool

Public Static Functions

static BounceBufferPool &instance()#

Get the singleton instance of the pool.

Each template instantiation (different Allocator) has its own singleton instance.

Returns:

Reference to the singleton pool instance

class Buffer#
#include <bounce_buffer.hpp>

RAII wrapper for a host bounce buffer allocation.

Automatically returns the buffer to the pool when destroyed (RAII pattern). Provides access to the underlying memory and its size.

Note

Non-copyable but movable to allow transfer of ownership while maintaining RAII

class CompatModeManager#
#include <compat_mode_manager.hpp>

Store and manage the compatibility mode data associated with a FileHandle.

Public Functions

CompatModeManager() noexcept = default#

Construct an empty compatibility mode manager.

CompatModeManager(
std::string const &file_path,
std::string const &flags,
mode_t mode,
CompatMode compat_mode_requested,
FileHandle *file_handle
)#

Construct a compatibility mode manager associated with a FileHandle.

According to the file path, requested compatibility mode, and the system configuration, the compatibility manager:

  • Infers the final compatibility modes for synchronous and asynchronous I/O paths, respectively.

  • Initializes the file wrappers and cuFile handle associated with a FileHandle.

Parameters:
bool is_compat_mode_preferred() const noexcept#

Check if the compatibility mode for synchronous I/O of the associated FileHandle is expected to be CompatMode::ON.

Returns:

Boolean answer.

bool is_compat_mode_preferred_for_async() const noexcept#

Check if the compatibility mode for asynchronous I/O of the associated FileHandle is expected to be CompatMode::ON.

Returns:

Boolean answer.

CompatMode compat_mode_requested() const noexcept#

Retrieve the original compatibility mode requested.

Returns:

The original compatibility mode requested.

void validate_compat_mode_for_async() const#

Determine if asynchronous I/O can be performed or not (throw exceptions) according to the existing compatibility mode data in the manager.

Asynchronous I/O cannot be performed, for instance, when compat_mode_requested() is CompatMode::OFF, is_compat_mode_preferred() is CompatMode::OFF, but is_compat_mode_preferred_for_async() is CompatMode::ON (due to missing cuFile stream API or cuFile configuration file).

struct DriverInitializer#
struct DriverProperties#
class defaults#
#include <defaults.hpp>

Singleton class of default values used throughout KvikIO.

Public Static Functions

static CompatMode compat_mode()#

Return whether the KvikIO library is running in compatibility mode or not.

Notice, this is not the same as the compatibility mode in cuFile. That is, cuFile can run in compatibility mode while KvikIO is not.

When KvikIO is running in compatibility mode, it doesn’t load libcufile.so. Instead, reads and writes are done using POSIX.

Set the environment variable KVIKIO_COMPAT_MODE to enable/disable compatibility mode. By default, compatibility mode is enabled:

  • when libcufile cannot be found

  • when running in Windows Subsystem for Linux (WSL)

  • when /run/udev isn’t readable, which typically happens when running inside a docker image not launched with --volume /run/udev:/run/udev:ro

Returns:

Compatibility mode.

static void set_compat_mode(CompatMode compat_mode)#

Set the value of kvikio::defaults::compat_mode().

Changing the compatibility mode affects all the new FileHandles whose compat_mode argument is not explicitly set, but it never affects existing FileHandles.

Parameters:

compat_mode – Compatibility mode.

static CompatMode infer_compat_mode_if_auto(
CompatMode compat_mode
) noexcept#

Infer the AUTO compatibility mode from the system runtime.

If the requested compatibility mode is AUTO, set the expected compatibility mode to ON or OFF by performing a system config check; otherwise, do nothing. Effectively, this function reduces the requested compatibility mode from three possible states (ON/OFF/AUTO) to two (ON/OFF) so as to determine the actual I/O path. This function is lightweight as the inferred result is cached.

static bool is_compat_mode_preferred(CompatMode compat_mode) noexcept#

Given a requested compatibility mode, whether it is expected to reduce to ON.

This function returns true if any of the two condition is satisfied:

  • The compatibility mode is ON.

  • It is AUTO but inferred to be ON.

Conceptually, the opposite of this function is whether requested compatibility mode is expected to be OFF, which would occur if any of the two condition is satisfied:

  • The compatibility mode is OFF.

  • It is AUTO but inferred to be OFF.

Parameters:

compat_mode – Compatibility mode.

Returns:

Boolean answer.

static bool is_compat_mode_preferred()#

Whether the global compatibility mode from class defaults is expected to be ON.

This function returns true if any of the two condition is satisfied:

  • The compatibility mode is ON.

  • It is AUTO but inferred to be ON.

Conceptually, the opposite of this function is whether the global compatibility mode is expected to be OFF, which would occur if any of the two condition is satisfied:

  • The compatibility mode is OFF.

  • It is AUTO but inferred to be OFF.

Returns:

Boolean answer.

static ThreadPool &thread_pool()#

Get the default thread pool.

Notice, it is not possible to change the default thread pool. KvikIO will always use the same thread pool however it is possible to change number of threads in the pool (see kvikio::default::set_thread_pool_nthreads()).

Returns:

The default thread pool instance.

static unsigned int thread_pool_nthreads()#

Get the number of threads in the default thread pool.

Set the default value using kvikio::default::set_thread_pool_nthreads() or by setting the KVIKIO_NTHREADS environment variable. If not set, the default value is 1.

Returns:

The number of threads.

static void set_thread_pool_nthreads(unsigned int nthreads)#

Set the number of threads in the default thread pool. Waits for all currently running tasks to be completed, then destroys all threads in the pool and creates a new thread pool with the new number of threads. Any tasks that were waiting in the queue before the pool was reset will then be executed by the new threads.

Parameters:

nthreads – The number of threads to use.

static unsigned int num_threads()#

Alias of thread_pool_nthreads

Returns:

The number of threads

static void set_num_threads(unsigned int nthreads)#

Alias of set_thread_pool_nthreads

Parameters:

nthreads – The number of threads to use

static std::size_t task_size()#

Get the default task size used for parallel IO operations.

Set the default value using kvikio::default::set_task_size() or by setting the KVIKIO_TASK_SIZE environment variable. If not set, the default value is 4 MiB.

Returns:

The default task size in bytes.

static void set_task_size(std::size_t nbytes)#

Set the default task size used for parallel IO operations.

When opportunistic Direct I/O read is enabled (KVIKIO_AUTO_DIRECT_IO_READ=1), this value should be a multiple of page size (typically 4 KiB) so that parallel tasks start at page-aligned file offsets, avoiding buffered I/O fallback.

Parameters:

nbytes – The default task size in bytes.

static std::size_t gds_threshold()#

Get the default GDS threshold, which is the minimum size to use GDS (in bytes).

In order to improve performance of small IO, .pread() and .pwrite() implement a shortcut that circumvent the threadpool and use the POSIX backend directly.

Set the default value using kvikio::default::set_gds_threshold() or by setting the KVIKIO_GDS_THRESHOLD environment variable. If not set, the default value is 1 MiB.

Returns:

The default GDS threshold size in bytes.

static void set_gds_threshold(std::size_t nbytes)#

Set the default GDS threshold, which is the minimum size to use GDS (in bytes).

Parameters:

nbytes – The default GDS threshold size in bytes.

static std::size_t bounce_buffer_size()#

Get the size of the bounce buffer used to stage data in host memory.

Set the value using kvikio::default::set_bounce_buffer_size() or by setting the KVIKIO_BOUNCE_BUFFER_SIZE environment variable. If not set, the value is 16 MiB.

Returns:

The bounce buffer size in bytes.

static void set_bounce_buffer_size(std::size_t nbytes)#

Set the size of the bounce buffer used to stage data in host memory.

Parameters:

nbytes – The bounce buffer size in bytes.

static std::size_t http_max_attempts()#

Get the maximum number of attempts per remote IO read.

Set the value using kvikio::default::set_http_max_attempts() or by setting the KVIKIO_HTTP_MAX_ATTEMPTS environment variable. If not set, the value is 3.

Returns:

The maximum number of remote IO reads to attempt before raising an error.

static void set_http_max_attempts(std::size_t attempts)#

Set the maximum number of attempts per remote IO read.

Parameters:

attempts – The maximum number of attempts to try before raising an error.

static long http_timeout()#

The maximum time, in seconds, the transfer is allowed to complete.

Set the value using kvikio::default::set_http_timeout() or by setting the KVIKIO_HTTP_TIMEOUT environment variable. If not set, the value is 60.

Returns:

The maximum time the transfer is allowed to complete.

static void set_http_timeout(long timeout_seconds)#

Reset the http timeout.

Parameters:

timeout_seconds – The maximum time the transfer is allowed to complete.

static std::vector<int> const &http_status_codes()#

The list of HTTP status codes to retry.

Set the value using kvikio::default::set_http_status_codes() or by setting the KVIKIO_HTTP_STATUS_CODES environment variable. If not set, the default value is

  • 429

  • 500

  • 502

  • 503

  • 504

Returns:

The list of HTTP status codes to retry.

static void set_http_status_codes(std::vector<int> status_codes)#

Set the list of HTTP status codes to retry.

Parameters:

status_codes – The HTTP status codes to retry.

static bool auto_direct_io_read()#

Check if Direct I/O is enabled for POSIX reads.

Returns true if KvikIO should attempt to use Direct I/O (O_DIRECT) for POSIX read operations.

Returns:

Boolean answer

static void set_auto_direct_io_read(bool flag)#

Enable or disable Direct I/O for POSIX reads.

Controls whether KvikIO should attempt to use Direct I/O (O_DIRECT) for POSIX read operations.

Parameters:

flag – true to enable opportunistic Direct I/O reads, false to disable

static bool auto_direct_io_read_overread()#

Check if Direct I/O over-read alignment is enabled for device reads.

When enabled, device memory reads use pure Direct I/O by aligning offsets down and sizes up to page boundaries, at the cost of reading extra bytes from disk. When disabled (default), unaligned portions fall back to buffered I/O. Only affects the device memory read path (disk to bounce buffer to GPU). Host memory reads are unaffected.

Requires auto_direct_io_read() to be enabled to have any effect.

Returns:

Boolean answer

static void set_auto_direct_io_read_overread(bool flag)#

Enable or disable Direct I/O over-read alignment for device reads.

Parameters:

flag – true to enable over-read alignment, false to use opportunistic DIO with buffered I/O fallback for unaligned portions (default)

static bool auto_direct_io_write()#

Check if Direct I/O is enabled for POSIX writes.

Returns true if KvikIO should attempt to use Direct I/O (O_DIRECT) for POSIX write operations.

Returns:

Boolean answer

static void set_auto_direct_io_write(bool flag)#

Enable or disable Direct I/O for POSIX writes.

Controls whether KvikIO should attempt to use Direct I/O (O_DIRECT) for POSIX write operations.

Parameters:

flag – true to enable opportunistic Direct I/O writes, false to disable

static bool thread_pool_per_block_device()#

Check if per-block-device thread pools are enabled.

The initial value is determined by the environment variable KVIKIO_THREAD_POOL_PER_BLOCK_DEVICE. If not set, defaults to false.

Returns:

Boolean answer

static void set_thread_pool_per_block_device(bool flag)#

Enable or disable per-block-device thread pools.

Each pool is initialized with the number of threads specified by thread_pool_nthreads(). Changes take effect only for files opened after this call. Files already opened retain their existing thread pool assignments.

Parameters:

flagtrue to enable per-block-device thread pools, false to use the single global thread pool for all I/O operations.

static RemoteIOBackend remote_io_backend()#

The remote I/O backend selected.

Controlled by the environment variable KVIKIO_REMOTE_IO_BACKEND, parsed case-insensitively. The only accepted values are the canonical names EASY_THREADPOOL and MULTI_POLL. If unset, defaults to EASY_THREADPOOL.

Returns:

The remote I/O backend.

static void set_remote_io_backend(RemoteIOBackend backend)#

Select the remote I/O backend at runtime, overriding KVIKIO_REMOTE_IO_BACKEND.

Parameters:

backend – The remote I/O backend.

static unsigned int remote_io_num_reactors()#

Number of reactor threads used by the MULTI_POLL remote I/O backend.

Controlled by KVIKIO_REMOTE_IO_NUM_REACTORS. Must be a positive integer. Defaults to 1. Ignored when the active backend is not MULTI_POLL.

Returns:

The configured reactor count.

static RemoteReactorDispatch remote_io_reactor_dispatch()#

How sub-ranges of one pread() are distributed across reactor threads under the MULTI_POLL remote I/O backend.

Controlled by KVIKIO_REMOTE_IO_REACTOR_DISPATCH, parsed case-insensitively.

Returns:

The reactor dispatch policy.

static std::size_t remote_io_max_concurrent_requests()#

Maximum number of concurrent in-flight requests across all reactor threads under the MULTI_POLL remote I/O backend.

One pread() of a large file is split into many sub-range requests (one libcurl easy handle each). This bounds how many of them are attached to the reactors’ multi handles at once, summed across all reactor threads.

The budget is divided into an equal private share per reactor, so the effective total is approximate: it rounds down when the value is not a multiple of the reactor count, and up when it is smaller than the reactor count (each reactor is floored to at least 1).

Controlled by KVIKIO_REMOTE_IO_MAX_CONCURRENT_REQUESTS. Must be a non-negative integer. 0 means unlimited. Defaults to 256. Ignored when the active backend is not MULTI_POLL (EASY_THREADPOOL is already bounded by KVIKIO_NTHREADS).

Returns:

The configured concurrent-request ceiling, or 0 for unlimited.

struct CUfileException : public std::runtime_error#
class GenericSystemError : public std::system_error#
class FileHandle#
#include <file_handle.hpp>

Handle of an open file registered with cufile.

In order to utilize cufile and GDS, a file must be registered with cufile.

Public Functions

FileHandle(
std::string const &file_path,
std::string const &flags = "r",
mode_t mode = m644,
CompatMode compat_mode = defaults::compat_mode()
)#

Construct a file handle from a file path.

FileHandle opens the file twice and maintains two file descriptors. One file is opened with the specified flags and the other file is opened with the flags plus the O_DIRECT flag.

Parameters:
  • file_path – File path to the file

  • flags – Open flags (see also fopen(3)): “r” -> “open for reading (default)” “w” -> “open for writing, truncating the file first” “a” -> “open for writing, appending to the end of file if it exists” “+” -> “open for updating (reading and writing)”

  • mode – Access modes (see open(2)).

  • compat_mode – Set KvikIO’s compatibility mode for this file.

FileHandle(FileHandle const&) = delete#

FileHandle support move semantic but isn’t copyable.

bool closed() const noexcept#

Whether the file is closed according to its initialization status.

Returns:

Boolean answer.

void close() noexcept#

Deregister the file and close the two files.

CUfileHandle_t handle()#

Get the underlying cuFile file handle.

The file handle must be open and not in compatibility mode i.e. both closed() and is_compat_mode_preferred() must be false.

Returns:

cuFile’s file handle

int fd(bool o_direct = false) const noexcept#

Get one of the file descriptors.

Notice, FileHandle maintains two file descriptors - one opened with the O_DIRECT flag and one without.

Parameters:

o_direct – Whether to get the file descriptor opened with the O_DIRECT flag.

Returns:

File descriptor

int fd_open_flags(bool o_direct = false) const#

Get the flags of one of the file descriptors (see open(2))

Notice, FileHandle maintains two file descriptors - one opened with the O_DIRECT flag and one without.

Parameters:

o_direct – Whether to get the flags of the file descriptor opened with the O_DIRECT flag.

Returns:

File descriptor

std::size_t nbytes() const#

Get the file size.

The value are cached.

Returns:

The number of bytes

std::size_t read(
void *devPtr_base,
std::size_t size,
std::size_t file_offset,
std::size_t devPtr_offset,
bool sync_default_stream = true
)#

Reads specified bytes from the file into the device memory.

This API reads the data from the GPU memory to the file at a specified offset and size bytes by using GDS functionality. The API works correctly for unaligned offset and data sizes, although the performance is not on-par with aligned read. This is a synchronous call and will block until the IO is complete.

Note

For the devPtr_offset, if data will be read starting exactly from the devPtr_base that is registered with buffer_register, devPtr_offset should be set to 0. To read starting from an offset in the registered buffer range, the relative offset should be specified in the devPtr_offset, and the devPtr_base must remain set to the base address that was used in the buffer_register call.

Parameters:
  • devPtr_base – Base address of buffer in device memory. For registered buffers, devPtr_base must remain set to the base address used in the buffer_register call.

  • size – Size in bytes to read.

  • file_offset – Offset in the file to read from.

  • devPtr_offset – Offset relative to the devPtr_base pointer to read into. This parameter should be used only with registered buffers.

  • sync_default_stream – Synchronize the CUDA default (null) stream prior to calling cuFile. Contrary to most of the non-async CUDA API, cuFile does not have the semantic of being ordered with respect to other non-cuFile work in the default stream. By enabling sync_default_stream, KvikIO will synchronize the default stream and order the operation with respect to other work in the null stream. When in KvikIO’s compatibility mode or when accessing host memory, the operation is always default stream ordered like the rest of the non-async CUDA API. In this case, the value of sync_default_stream is ignored.

Returns:

Size of bytes that were successfully read.

std::size_t write(
void const *devPtr_base,
std::size_t size,
std::size_t file_offset,
std::size_t devPtr_offset,
bool sync_default_stream = true
)#

Writes specified bytes from the device memory into the file.

This API writes the data from the GPU memory to the file at a specified offset and size bytes by using GDS functionality. The API works correctly for unaligned offset and data sizes, although the performance is not on-par with aligned writes. This is a synchronous call and will block until the IO is complete.

Note

GDS functionality modified the standard file system metadata in SysMem. However, GDS functionality does not take any special responsibility for writing that metadata back to permanent storage. The data is not guaranteed to be present after a system crash unless the application uses an explicit fsync(2) call. If the file is opened with an O_SYNC flag, the metadata will be written to the disk before the call is complete. Refer to the note in read for more information about devPtr_offset.

Parameters:
  • devPtr_base – Base address of buffer in device memory. For registered buffers, devPtr_base must remain set to the base address used in the buffer_register call.

  • size – Size in bytes to write.

  • file_offset – Offset in the file to write from.

  • devPtr_offset – Offset relative to the devPtr_base pointer to write from. This parameter should be used only with registered buffers.

  • sync_default_stream – Synchronize the CUDA default (null) stream prior to calling cuFile. Contrary to most of the non-async CUDA API, cuFile does not have the semantic of being ordered with respect to other non-cuFile work in the default stream. By enabling sync_default_stream, KvikIO will synchronize the default stream and order the operation with respect to other work in the null stream. When in KvikIO’s compatibility mode or when accessing host memory, the operation is always default stream ordered like the rest of the non-async CUDA API. In this case, the value of sync_default_stream is ignored.

Returns:

Size of bytes that were successfully written.

std::future<std::size_t> pread(
void *buf,
std::size_t size,
std::size_t file_offset = 0,
std::size_t task_size = defaults::task_size(),
std::size_t gds_threshold = defaults::gds_threshold(),
bool sync_default_stream = true,
ThreadPool *thread_pool = &defaults::thread_pool()
)#

Reads specified bytes from the file into the device or host memory in parallel.

This API is a parallel async version of .read() that partition the operation into tasks of size task_size for execution in the default thread pool.

In order to improve performance of small buffers, when size < gds_threshold a shortcut that circumvent the threadpool and use the POSIX backend directly is used.

Note

For cuFile reads, the base address of the allocation buf is part of is used. This means that when registering buffers, use the base address of the allocation. This is what memory_register and memory_deregister do automatically.

Note

The returned std::future object must not outlive either the FileHandle or the thread pool. Calling wait() or get() on the future after the FileHandle or thread pool has been destroyed results in undefined behavior.

Parameters:
  • buf – Address to device or host memory.

  • size – Size in bytes to read.

  • file_offset – Offset in the file to read from.

  • task_size – Size of each task in bytes.

  • gds_threshold – Minimum buffer size to use GDS and the thread pool.

  • sync_default_stream – Synchronize the CUDA default (null) stream prior to calling cuFile. Contrary to most of the non-async CUDA API, cuFile does not have the semantic of being ordered with respect to other non-cuFile work in the default stream. By enabling sync_default_stream, KvikIO will synchronize the default stream and order the operation with respect to other work in the null stream. When in KvikIO’s compatibility mode or when accessing host memory, the operation is always default stream ordered like the rest of the non-async CUDA API. In this case, the value of sync_default_stream is ignored.

  • thread_pool – Thread pool to use for parallel execution. Defaults to the global default thread pool. The caller is responsible for ensuring that the thread pool remains valid until the returned future is consumed (i.e., until get() or wait() is called on it).

Returns:

Future that on completion returns the size of bytes that were successfully read.

std::future<std::size_t> pwrite(
void const *buf,
std::size_t size,
std::size_t file_offset = 0,
std::size_t task_size = defaults::task_size(),
std::size_t gds_threshold = defaults::gds_threshold(),
bool sync_default_stream = true,
ThreadPool *thread_pool = &defaults::thread_pool()
)#

Writes specified bytes from device or host memory into the file in parallel.

This API is a parallel async version of .write() that partition the operation into tasks of size task_size for execution in the default thread pool.

In order to improve performance of small buffers, when size < gds_threshold a shortcut that circumvent the threadpool and use the POSIX backend directly is used.

Note

For cuFile reads, the base address of the allocation buf is part of is used. This means that when registering buffers, use the base address of the allocation. This is what memory_register and memory_deregister do automatically.

Note

The returned std::future object must not outlive either the FileHandle or the thread pool. Calling wait() or get() on the future after the FileHandle or thread pool has been destroyed results in undefined behavior.

Parameters:
  • buf – Address to device or host memory.

  • size – Size in bytes to write.

  • file_offset – Offset in the file to write from.

  • task_size – Size of each task in bytes.

  • gds_threshold – Minimum buffer size to use GDS and the thread pool.

  • sync_default_stream – Synchronize the CUDA default (null) stream prior to calling cuFile. Contrary to most of the non-async CUDA API, cuFile does not have the semantic of being ordered with respect to other non-cuFile work in the default stream. By enabling sync_default_stream, KvikIO will synchronize the default stream and order the operation with respect to other work in the null stream. When in KvikIO’s compatibility mode or when accessing host memory, the operation is always default stream ordered like the rest of the non-async CUDA API. In this case, the value of sync_default_stream is ignored.

  • thread_pool – Thread pool to use for parallel execution. Defaults to the global default thread pool. The caller is responsible for ensuring that the thread pool remains valid until the returned future is consumed (i.e., until get() or wait() is called on it).

Returns:

Future that on completion returns the size of bytes that were successfully written.

void read_async(
void *devPtr_base,
std::size_t *size_p,
off_t *file_offset_p,
off_t *devPtr_offset_p,
ssize_t *bytes_read_p,
CUstream stream
)#

Reads specified bytes from the file into the device memory asynchronously.

This is an asynchronous version of .read(), which will be executed in sequence for the specified stream.

The arguments have the same meaning as in .read() but some of them are deferred. That is, the values pointed to by size_p, file_offset_p and devPtr_offset_p will not be evaluated until execution time. Notice, this behavior can be changed using cuFile’s cuFileStreamRegister API.

Parameters:
  • devPtr_base – Base address of buffer in device memory. For registered buffers, devPtr_base must remain set to the base address used in the buffer_register call.

  • size_p – Pointer to size in bytes to read. If the exact size is not known at the time of I/O submission, then you must set it to the maximum possible I/O size for that stream I/O. Later the actual size can be set prior to the stream I/O execution.

  • file_offset_p – Pointer to offset in the file from which to read. Unless otherwise set using cuFileStreamRegister API, this value will not be evaluated until execution time.

  • devPtr_offset_p – Pointer to the offset relative to the bufPtr_base from which to write. Unless otherwise set using cuFileStreamRegister API, this value will not be evaluated until execution time.

  • bytes_read_p – Pointer to the bytes read from file. This pointer should be a non-NULL value and *bytes_read_p set to 0. The bytes_read_p memory should be allocated with cuMemHostAlloc/malloc/mmap or registered with cuMemHostRegister. After successful execution of the operation in the stream, the value *bytes_read_p will contain either:

    • The number of bytes successfully read.

    • -1 on IO errors.

    • All other errors return a negative integer value of the CUfileOpError enum value.

  • stream – CUDA stream in which to enqueue the operation. If NULL, make this operation synchronous.

StreamFuture read_async(
void *devPtr_base,
std::size_t size,
off_t file_offset = 0,
off_t devPtr_offset = 0,
CUstream stream = nullptr
)#

Reads specified bytes from the file into the device memory asynchronously.

This is an asynchronous version of .read(), which will be executed in sequence for the specified stream.

The arguments have the same meaning as in .read() but returns a StreamFuture object that the caller must keep alive until all data has been read from disk. One way to do this, is by calling StreamFuture.check_bytes_done(), which will synchronize the associated stream and return the number of bytes read.

Parameters:
  • devPtr_base – Base address of buffer in device memory. For registered buffers, devPtr_base must remain set to the base address used in the buffer_register call.

  • size – Size in bytes to read.

  • file_offset – Offset in the file to read from.

  • devPtr_offset – Offset relative to the devPtr_base pointer to read into. This parameter should be used only with registered buffers.

  • stream – CUDA stream in which to enqueue the operation. If NULL, make this operation synchronous.

Returns:

A future object that must be kept alive until all data has been read to disk e.g. by synchronizing stream.

void write_async(
void *devPtr_base,
std::size_t *size_p,
off_t *file_offset_p,
off_t *devPtr_offset_p,
ssize_t *bytes_written_p,
CUstream stream
)#

Writes specified bytes from the device memory into the file asynchronously.

This is an asynchronous version of .write(), which will be executed in sequence for the specified stream.

The arguments have the same meaning as in .write() but some of them are deferred. That is, the values pointed to by size_p, file_offset_p and devPtr_offset_p will not be evaluated until execution time. Notice, this behavior can be changed using cuFile’s cuFileStreamRegister API.

Parameters:
  • devPtr_base – Base address of buffer in device memory. For registered buffers, devPtr_base must remain set to the base address used in the buffer_register call.

  • size_p – Pointer to size in bytes to read. If the exact size is not known at the time of I/O submission, then you must set it to the maximum possible I/O size for that stream I/O. Later the actual size can be set prior to the stream I/O execution.

  • file_offset_p – Pointer to offset in the file from which to read. Unless otherwise set using cuFileStreamRegister API, this value will not be evaluated until execution time.

  • devPtr_offset_p – Pointer to the offset relative to the bufPtr_base from which to read. Unless otherwise set using cuFileStreamRegister API, this value will not be evaluated until execution time.

  • bytes_written_p – Pointer to the bytes read from file. This pointer should be a non-NULL value and *bytes_written_p set to 0. The bytes_written_p memory should be allocated with cuMemHostAlloc/malloc/mmap or registered with cuMemHostRegister. After successful execution of the operation in the stream, the value *bytes_written_p will contain either:

    • The number of bytes successfully read.

    • -1 on IO errors.

    • All other errors return a negative integer value of the CUfileOpError enum value.

  • stream – CUDA stream in which to enqueue the operation. If NULL, make this operation synchronous.

StreamFuture write_async(
void *devPtr_base,
std::size_t size,
off_t file_offset = 0,
off_t devPtr_offset = 0,
CUstream stream = nullptr
)#

Writes specified bytes from the device memory into the file asynchronously.

This is an asynchronous version of .write(), which will be executed in sequence for the specified stream.

The arguments have the same meaning as in .write() but returns a StreamFuture object that the caller must keep alive until all data has been written to disk. One way to do this, is by calling StreamFuture.check_bytes_done(), which will synchronize the associated stream and return the number of bytes written.

Parameters:
  • devPtr_base – Base address of buffer in device memory. For registered buffers, devPtr_base must remain set to the base address used in the buffer_register call.

  • size – Size in bytes to write.

  • file_offset – Offset in the file to write from.

  • devPtr_offset – Offset relative to the devPtr_base pointer to write from. This parameter should be used only with registered buffers.

  • stream – CUDA stream in which to enqueue the operation. If NULL, make this operation synchronous.

Returns:

A future object that must be kept alive until all data has been written to disk e.g. by synchronizing stream.

const CompatModeManager &get_compat_mode_manager() const noexcept#

Get the associated compatibility mode manager, which can be used to query the original requested compatibility mode or the expected compatibility modes for synchronous and asynchronous I/O.

Returns:

The associated compatibility mode manager.

bool is_direct_io_supported() const noexcept#

Whether Direct I/O is supported on this file handle. This is determined by two factors:

  • Direct I/O support from the operating system and the file system

  • KvikIO global setting auto_direct_io_read and auto_direct_io_write. If both values are false, Direct I/O will not be supported on this file handle.

Returns:

Boolean answer.

class FileWrapper#
#include <file_utils.hpp>

Class that provides RAII for file handling.

Public Functions

FileWrapper(
std::string const &file_path,
std::string const &flags,
bool o_direct,
mode_t mode
)#

Open file.

Parameters:
  • file_path – File path.

  • flags – Open flags given as a string.

  • o_direct – Append O_DIRECT to flags.

  • mode – Access modes.

FileWrapper() noexcept = default#

Construct an empty file wrapper object without opening a file.

void open(
std::string const &file_path,
std::string const &flags,
bool o_direct,
mode_t mode
)#

Open file using open(2)

Parameters:
  • file_path – File path.

  • flags – Open flags given as a string.

  • o_direct – Append O_DIRECT to flags.

  • mode – Access modes.

bool opened() const noexcept#

Check if the file has been opened.

Returns:

A boolean answer indicating if the file has been opened.

void close() noexcept#

Close the file if it is opened; do nothing otherwise.

int fd() const noexcept#

Return the file descriptor.

Returns:

File descriptor.

class CUFileHandleWrapper#
#include <file_utils.hpp>

Class that provides RAII for the cuFile handle.

Public Functions

std::optional<CUfileError_t> register_handle(int fd) noexcept#

Register the file handle given the file descriptor.

Parameters:

fd – File descriptor.

Returns:

Return the cuFile error code from handle register. If the handle has already been registered by calling register_handle(), return std::nullopt.

bool registered() const noexcept#

Check if the handle has been registered.

Returns:

A boolean answer indicating if the handle has been registered.

CUfileHandle_t handle() const noexcept#

Return the cuFile handle.

Returns:

The cuFile handle.

void unregister_handle() noexcept#

Unregister the handle if it has been registered; do nothing otherwise.

struct BlockDeviceInfo#
#include <file_utils.hpp>

Information about a block device.

Public Members

dev_t id#

Combined major:minor device ID (suitable for use as map key)

unsigned major#

Major device number.

unsigned minor#

Minor device number.

std::string name#

Device name (e.g., “nvme0”, “sda”, “dm-0”)

class WebHdfsEndpoint : public kvikio::RemoteEndpoint#
#include <hdfs.hpp>

A remote endpoint for Apache Hadoop WebHDFS.

This endpoint is for accessing HDFS files via the WebHDFS REST API over HTTP/HTTPS. If KvikIO is run within Docker, pass --network host to the docker run command to ensure proper name node connectivity.

Public Functions

explicit WebHdfsEndpoint(
std::string url,
std::optional<std::string> username = std::nullopt
)#

Create an WebHDFS endpoint from a url.

Note

The optional username for authentication is determined in the following descending priority order:

  • Function parameter username

  • Query string in URL (?user.name=xxx)

  • Environment variable KVIKIO_WEBHDFS_USERNAME

Parameters:
  • url – The WebHDFS HTTP/HTTPS url to the remote file.

  • username – Optional user name.

explicit WebHdfsEndpoint(
std::string host,
std::string port,
std::string remote_file_path,
std::optional<std::string> username = std::nullopt
)#

Create an WebHDFS endpoint from the host, port, file path and optionally username.

Note

The optional username for authentication is determined in the following descending priority order:

  • Function parameter username

  • Environment variable KVIKIO_WEBHDFS_USERNAME

Parameters:
  • host – Host

  • port – Port

  • remote_file_path – Remote file path

  • username – Optional user name.

virtual void setopt(CurlHandle &curl) override#

Set needed connection options on a curl handle.

Subsequently, a call to curl.perform() should connect to the endpoint.

Parameters:

curl – The curl handle.

virtual std::string str() const override#

Get a description of this remote point instance.

Returns:

A string description.

virtual std::size_t get_file_size() override#

Get the size of the remote file.

Returns:

The file size

virtual void setup_range_request(
CurlHandle &curl,
std::size_t file_offset,
std::size_t size
) override#

Set up the range request in order to read part of a file given the file offset and read size.

Public Static Functions

static bool is_url_valid(std::string const &url) noexcept#

Whether the given URL is valid for the WebHDFS endpoints.

Parameters:

url – A URL.

Returns:

Boolean answer.

class MmapHandle#
#include <mmap.hpp>

Handle of a memory-mapped file.

This utility class facilitates the use of file-backed memory by providing a performant method pread() to read a range of data into user-provided memory residing on the host or device.

File-backed memory can be considered when a large number of nonadjacent file ranges (specified by the offset and size pair) are to be frequently accessed. It can potentially reduce memory usage due to demand paging (compared to reading the entire file with read(2)), and may improve I/O performance compared to frequent calls to read(2).

Public Functions

MmapHandle() noexcept = default#

Construct an empty memory-mapped file.

MmapHandle(
std::string const &file_path,
std::string const &flags = "r",
std::optional<std::size_t> initial_map_size = std::nullopt,
std::size_t initial_map_offset = 0,
mode_t mode = FileHandle::m644,
std::optional<int> map_flags = std::nullopt
)#

Construct a new memory-mapped file.

Parameters:
  • file_path – File path

  • flags – Open flags (see also fopen(3)):

    • ”r”: “open for reading (default)”

    • ”w”: “open for writing, truncating the file first”

    • ”a”: “open for writing, appending to the end of file if it exists”

    • ”+”: “open for updating (reading and writing)”

  • initial_map_size – Size in bytes of the mapped region. Must be greater than 0. If not specified, map the region starting from initial_map_offset to the end of file

  • initial_map_offset – File offset of the mapped region

  • mode – Access mode

  • map_flags – Flags to be passed to the system call mmap. See mmap(2) for details

Throws:
  • std::out_of_range – if initial_map_offset (left bound of the mapped region) is equal to or greater than the file size

  • std::out_of_range – if the sum of initial_map_offset and initial_map_size (right bound of the mapped region) is greater than the file size

  • std::invalid_argument – if initial_map_size is given but is 0

std::size_t initial_map_size() const noexcept#

Size in bytes of the mapped region when the mapping handle was constructed.

Returns:

Initial size of the mapped region

std::size_t initial_map_offset() const noexcept#

File offset of the mapped region when the mapping handle was constructed.

Returns:

Initial file offset of the mapped region

std::size_t file_size() const#

Get the file size if the file is open. Returns 0 if the file is closed.

The behavior of this method is consistent with FileHandle::nbytes.

Returns:

The file size in bytes

std::size_t nbytes() const#

Alias of file_size

Returns:

The file size in bytes

bool closed() const noexcept#

Whether the mapping handle is closed.

Returns:

Boolean answer

void close() noexcept#

Close the mapping handle if it is open; do nothing otherwise.

std::size_t read(
void *buf,
std::optional<std::size_t> size = std::nullopt,
std::size_t offset = 0
)#

Sequential read size bytes from the file (with the offset offset) to the destination buffer buf

Parameters:
  • buf – Address of the host or device memory (destination buffer)

  • size – Size in bytes to read. Can be 0 in which case nothing will be read. If not specified, read starts from offset to the end of file

  • offset – File offset

Throws:
  • std::out_of_range – if the read region specified by offset and size is outside the initial region specified when the mapping handle was constructed

  • std::runtime_error – if the mapping handle is closed

Returns:

Number of bytes that have been read

std::future<std::size_t> pread(
void *buf,
std::optional<std::size_t> size = std::nullopt,
std::size_t offset = 0,
std::size_t task_size = defaults::task_size(),
ThreadPool *thread_pool = &defaults::thread_pool()
)#

Parallel read size bytes from the file (with the offset offset) to the destination buffer buf

Note

The returned std::future object must not outlive either the MmapHandle or the thread pool. Calling wait() or get() on the future after the MmapHandle or thread pool has been destroyed results in undefined behavior.

Parameters:
  • buf – Address of the host or device memory (destination buffer)

  • size – Size in bytes to read. Can be 0 in which case nothing will be read. If not specified, read starts from offset to the end of file

  • offset – File offset

  • task_size – Size of each task in bytes

  • thread_pool – Thread pool to use for parallel execution. Defaults to the global default thread pool. The caller is responsible for ensuring that the thread pool remains valid until the returned future is consumed (i.e., until get() or wait() is called on it).

Throws:
  • std::out_of_range – if the read region specified by offset and size is outside the initial region specified when the mapping handle was constructed

  • std::runtime_error – if the mapping handle is closed

Returns:

Future that on completion returns the size of bytes that were successfully read.

struct ClockAnchor#
#include <observation.hpp>

A reading of Clock and of the wall clock, taken together.

Clock cannot be compared with anything outside this process, so an anchor is what relates an observation to a log line, to another process, or to a profiler trace.

Public Functions

inline std::chrono::system_clock::time_point to_wall_clock(
TimePoint time
) const noexcept#

Convert a point on Clock to wall-clock time.

Warning

The wall clock can be stepped or slewed by NTP or an operator, so an old anchor may no longer be valid. Take an anchor at each end of a long run and compare them to detect an in-flight adjustment to the system time.

Parameters:

time – The point to convert.

Returns:

The corresponding wall-clock time.

Public Members

TimePoint steady = {}#

The reading of Clock.

std::chrono::system_clock::time_point wall = {}#

The reading of the wall clock, taken at the same moment.

Public Static Functions

static ClockAnchor now() noexcept#

Read both clocks, one immediately after the other.

Warning

The readings are tens of nanoseconds apart, since the two clocks cannot be read at once, so the anchor’s offset is out by that much. Well below what a wall clock is worth anyway, NTP agreeing between machines to microseconds at best.

Returns:

The pair of readings.

struct Observation#
#include <observation.hpp>

One I/O operation, as observed by KvikIO.

[start, end) covers the operation from issue to completion.

Public Functions

inline Duration duration() const noexcept#

How long the operation took.

Returns:

end - start, or zero if the span is degenerate.

inline double bytes_per_sec() const noexcept#

Throughput of this single operation.

Warning

Averaging this across operations does not give the throughput of the program. For that, divide total bytes by a span of elapsed time.

Returns:

Bytes per second, or zero if the operation had no measurable duration.

Public Members

TimePoint start = {}#

When the operation started.

TimePoint end = {}#

When the operation finished.

std::size_t offset = {}#

Byte offset into the file or remote object.

std::size_t size = {}#

Number of bytes requested.

std::size_t bytes_transferred = {}#

Number of bytes actually transferred. Differs from size on a short read, and is zero for an operation that failed.

std::uint64_t id = {}#

Identifies this operation, uniquely within the process.

std::optional<std::uint64_t> parent_id = {}#

For a physical observation, the id of the logical operation it belongs to. Empty on a logical observation, and on a physical one whose call started with no monitor registered for logical observations. Nothing else leaves it empty.

char const *http_method = {nullptr}#

HTTP method, e.g. "GET". Null for local I/O.

std::string_view source = {}#

The file path or URL the operation went to. Owned by the handle, and valid only for the duration of the callback. Copy what is needed later.

ObservationKind kind = {ObservationKind::LOGICAL}#

What layer this describes.

IoBackend backend = {IoBackend::POSIX}#

The backend that carried out the operation.

TransferDirection direction = {TransferDirection::READ}#

The direction of the operation.

MemoryKind memory_kind = {MemoryKind::HOST}#

The kind of memory the caller’s buffer lives in.

bool ok = {true}#

False if the operation failed.

class Monitor#
#include <observation.hpp>

Watches operations, from the moment they start until they finish.

Derive from this and register it to be told what KvikIO is doing. Two notifications per operation: on_start() when it begins, carrying the record as it stands at submission, and on_finish() when it ends, carrying the finished record.

Which operations it is told about is chosen at registration. A LOGICAL monitor is told about user-facing calls: one FileHandle::pread() is one operation however many reads KvikIO issued underneath. A PHYSICAL monitor is told about the individual transfers instead, each linked to its call by Observation::parent_id.

// Reports how many KvikIO operations are in flight at any moment.
class QueueDepth : public kvikio::Monitor {
 public:
  [[nodiscard]] int depth() const noexcept { return _in_flight.load(); }

 private:
  void on_start(kvikio::Observation const&) noexcept override { ++_in_flight; }
  void on_finish(kvikio::Observation const&) noexcept override { --_in_flight; }

  std::atomic<int> _in_flight{0};
};

QueueDepth gauge;
auto const id = kvikio::register_monitor(&gauge);
...
kvikio::unregister_monitor(id);  // Waits, so `gauge` may now be destroyed.

Every operation that reports a start reports exactly one finish, on whichever thread does the work.

Note

Not everything is reported:

  • The cuFile asynchronous API (FileHandle::read_async(), FileHandle::write_async()) on a system with working GDS reports nothing. In compatibility mode those calls fall back to read()/write() and are reported, so the same program is seen differently depending on whether GDS is available.

  • The batch API (BatchHandle) reports nothing.

  • RemoteHandle::pread() into device memory finishes when the last pinned-to-device copy is issued rather than completed, so its span is slightly short. It is never too long.

Warning

A monitor runs inline with the I/O, on the thread performing it, on both the submission and the completion path. Keep it light, make it thread-safe, and do not call back into KvikIO, which throws std::runtime_error. Neither callback may throw.

Subclassed by kvikio::statistics::SummaryMonitor

Public Functions

virtual void on_start(Observation const &observation) noexcept = 0#

An operation has started.

The observation is not finished: end and bytes_transferred are zero, and ok is true only because nothing has failed yet. All three are set by the time on_finish() is called.

Warning

Runs inline with the I/O, on the thread performing it. Keep it light, make it thread-safe, and do not call back into KvikIO, which throws std::runtime_error.

Parameters:

observation – The operation, as far as it is known. The reference is valid only for the duration of this call. Copy what is needed later.

virtual void on_finish(Observation const &observation) noexcept = 0#

An operation has completed.

A monitor registered after observation.start never saw the matching on_start(), and should ignore such an operation.

Warning

Runs inline with the I/O, on the thread performing it. Keep it light, make it thread-safe, and do not call back into KvikIO, which throws std::runtime_error.

Parameters:

observation – The completed operation. The reference is valid only for the duration of this call. Copy what is needed later.

class RemoteEndpoint#
#include <remote_handle.hpp>

Abstract base class for remote endpoints.

In this context, an endpoint refers to a remote file using a specific communication protocol.

Each communication protocol, such as HTTP or S3, needs to implement this ABC and implement its own ctor that takes communication protocol specific arguments.

Subclassed by kvikio::HttpEndpoint, kvikio::S3Endpoint, kvikio::S3EndpointWithPresignedUrl, kvikio::S3PublicEndpoint, kvikio::WebHdfsEndpoint

Public Functions

virtual void setopt(CurlHandle &curl) = 0#

Set needed connection options on a curl handle.

Subsequently, a call to curl.perform() should connect to the endpoint.

Parameters:

curl – The curl handle.

virtual std::string str() const = 0#

Get a description of this remote point instance.

Returns:

A string description.

virtual std::size_t get_file_size() = 0#

Get the size of the remote file.

Returns:

The file size

virtual void setup_range_request(
CurlHandle &curl,
std::size_t file_offset,
std::size_t size
) = 0#

Set up the range request in order to read part of a file given the file offset and read size.

RemoteEndpointType remote_endpoint_type() const noexcept#

Get the type of the remote file.

Returns:

The type of the remote file.

class HttpEndpoint : public kvikio::RemoteEndpoint#
#include <remote_handle.hpp>

A remote endpoint for HTTP/HTTPS resources.

This endpoint is for accessing files via standard HTTP/HTTPS protocols without any specialized authentication.

Public Functions

HttpEndpoint(std::string url)#

Create an http endpoint from a url.

Parameters:

url – The full http url to the remote file.

virtual void setopt(CurlHandle &curl) override#

Set needed connection options on a curl handle.

Subsequently, a call to curl.perform() should connect to the endpoint.

Parameters:

curl – The curl handle.

virtual std::string str() const override#

Get a description of this remote point instance.

Returns:

A string description.

virtual std::size_t get_file_size() override#

Get the size of the remote file.

Returns:

The file size

virtual void setup_range_request(
CurlHandle &curl,
std::size_t file_offset,
std::size_t size
) override#

Set up the range request in order to read part of a file given the file offset and read size.

Public Static Functions

static bool is_url_valid(std::string const &url) noexcept#

Whether the given URL is valid for HTTP/HTTPS endpoints.

Parameters:

url – A URL.

Returns:

Boolean answer.

class S3Endpoint : public kvikio::RemoteEndpoint#
#include <remote_handle.hpp>

A remote endpoint for AWS S3 storage requiring credentials.

This endpoint is for accessing private S3 objects using AWS credentials (access key, secret key, region and optional session token).

Public Functions

S3Endpoint(
std::string url,
std::optional<std::string> aws_region = std::nullopt,
std::optional<std::string> aws_access_key = std::nullopt,
std::optional<std::string> aws_secret_access_key = std::nullopt,
std::optional<std::string> aws_session_token = std::nullopt
)#

Create a S3 endpoint from a url.

Parameters:
  • url – The full http url to the S3 file. NB: this should be an url starting with “http://” or “https://”. If you have an S3 url of the form “s3://<bucket>/<object>”, please use S3Endpoint::parse_s3_url() and `S3Endpoint::url_from_bucket_and_object() to convert it.

  • aws_region – The AWS region, such as “us-east-1”, to use. If nullopt, the value of the AWS_DEFAULT_REGION environment variable is used.

  • aws_access_key – The AWS access key to use. If nullopt, the value of the AWS_ACCESS_KEY_ID environment variable is used.

  • aws_secret_access_key – The AWS secret access key to use. If nullopt, the value of the AWS_SECRET_ACCESS_KEY environment variable is used.

  • aws_session_token – The AWS session token to use. If nullopt, the value of the AWS_SESSION_TOKEN environment variable is used.

S3Endpoint(
std::pair<std::string, std::string> bucket_and_object_names,
std::optional<std::string> aws_region = std::nullopt,
std::optional<std::string> aws_access_key = std::nullopt,
std::optional<std::string> aws_secret_access_key = std::nullopt,
std::optional<std::string> aws_endpoint_url = std::nullopt,
std::optional<std::string> aws_session_token = std::nullopt
)#

Create a S3 endpoint from a bucket and object name.

Parameters:
  • bucket_and_object_names – The bucket and object names of the S3 bucket.

  • aws_region – The AWS region, such as “us-east-1”, to use. If nullopt, the value of the AWS_DEFAULT_REGION environment variable is used.

  • aws_access_key – The AWS access key to use. If nullopt, the value of the AWS_ACCESS_KEY_ID environment variable is used.

  • aws_secret_access_key – The AWS secret access key to use. If nullopt, the value of the AWS_SECRET_ACCESS_KEY environment variable is used.

  • aws_endpoint_url – Overwrite the endpoint url (including the protocol part) by using the scheme: “<aws_endpoint_url>/<bucket_name>/<object_name>”. If nullopt, the value of the AWS_ENDPOINT_URL environment variable is used. If this is also not set, the regular AWS url scheme is used: “https://<bucket_name>.s3.<region>.amazonaws.com/<object_name>”.

  • aws_session_token – The AWS session token to use. If nullopt, the value of the AWS_SESSION_TOKEN environment variable is used.

virtual void setopt(CurlHandle &curl) override#

Set needed connection options on a curl handle.

Subsequently, a call to curl.perform() should connect to the endpoint.

Parameters:

curl – The curl handle.

virtual std::string str() const override#

Get a description of this remote point instance.

Returns:

A string description.

virtual std::size_t get_file_size() override#

Get the size of the remote file.

Returns:

The file size

virtual void setup_range_request(
CurlHandle &curl,
std::size_t file_offset,
std::size_t size
) override#

Set up the range request in order to read part of a file given the file offset and read size.

Public Static Functions

static std::string url_from_bucket_and_object(
std::string bucket_name,
std::string object_name,
std::optional<std::string> aws_region,
std::optional<std::string> aws_endpoint_url
)#

Get url from a AWS S3 bucket and object name.

Throws:

std::invalid_argument – if no region is specified and no default region is specified in the environment.

Parameters:
  • bucket_name – The name of the S3 bucket.

  • object_name – The name of the S3 object.

  • aws_region – The AWS region, such as “us-east-1”, to use. If nullopt, the value of the AWS_DEFAULT_REGION environment variable is used.

  • aws_endpoint_url – Overwrite the endpoint url (including the protocol part) by using the scheme: “<aws_endpoint_url>/<bucket_name>/<object_name>”. If nullopt, the value of the AWS_ENDPOINT_URL environment variable is used. If this is also not set, the regular AWS url scheme is used: “https://<bucket_name>.s3.<region>.amazonaws.com/<object_name>”.

static std::pair<std::string, std::string> parse_s3_url(
std::string const &s3_url
)#

Given an url like “s3://<bucket>/<object>”, return the name of the bucket and object.

Throws:

std::invalid_argument – if url is ill-formed or is missing the bucket or object name.

Parameters:

s3_url – S3 url.

Returns:

Pair of strings: [bucket-name, object-name].

static bool is_url_valid(std::string const &url) noexcept#

Whether the given URL is valid for S3 endpoints (excluding presigned URL).

Parameters:

url – A URL.

Returns:

Boolean answer.

class S3PublicEndpoint : public kvikio::RemoteEndpoint#
#include <remote_handle.hpp>

A remote endpoint for publicly accessible S3 objects without authentication.

This endpoint is for accessing S3 objects configured with public read permissions, requiring no authentication. Supports AWS S3 services with anonymous access enabled.

Public Functions

virtual void setopt(CurlHandle &curl) override#

Set needed connection options on a curl handle.

Subsequently, a call to curl.perform() should connect to the endpoint.

Parameters:

curl – The curl handle.

virtual std::string str() const override#

Get a description of this remote point instance.

Returns:

A string description.

virtual std::size_t get_file_size() override#

Get the size of the remote file.

Returns:

The file size

virtual void setup_range_request(
CurlHandle &curl,
std::size_t file_offset,
std::size_t size
) override#

Set up the range request in order to read part of a file given the file offset and read size.

Public Static Functions

static bool is_url_valid(std::string const &url) noexcept#

Whether the given URL is valid for S3 public endpoints.

Parameters:

url – A URL.

Returns:

Boolean answer.

class S3EndpointWithPresignedUrl : public kvikio::RemoteEndpoint#
#include <remote_handle.hpp>

A remote endpoint for AWS S3 storage using presigned URLs.

This endpoint is for accessing S3 objects via presigned URLs, which provide time-limited access without requiring AWS credentials on the client side.

Public Functions

virtual void setopt(CurlHandle &curl) override#

Set needed connection options on a curl handle.

Subsequently, a call to curl.perform() should connect to the endpoint.

Parameters:

curl – The curl handle.

virtual std::string str() const override#

Get a description of this remote point instance.

Returns:

A string description.

virtual std::size_t get_file_size() override#

Get the size of the remote file.

Returns:

The file size

virtual void setup_range_request(
CurlHandle &curl,
std::size_t file_offset,
std::size_t size
) override#

Set up the range request in order to read part of a file given the file offset and read size.

Public Static Functions

static bool is_url_valid(std::string const &url) noexcept#

Whether the given URL is valid for S3 endpoints with presigned URL.

Parameters:

url – A URL.

Returns:

Boolean answer.

class RemoteHandle#
#include <remote_handle.hpp>

Handle of remote file.

Public Functions

RemoteHandle(
std::unique_ptr<RemoteEndpoint> endpoint,
std::size_t nbytes
)#

Create a new remote handle from an endpoint and a file size.

Parameters:
  • endpoint – Remote endpoint used for subsequent IO.

  • nbytes – The size of the remote file (in bytes).

RemoteHandle(std::unique_ptr<RemoteEndpoint> endpoint)#

Create a new remote handle from an endpoint (infers the file size).

The file size is received from the remote server using endpoint.

Parameters:

endpoint – Remote endpoint used for subsequently IO.

RemoteEndpointType remote_endpoint_type() const noexcept#

Get the type of the remote file.

Returns:

The type of the remote file.

std::size_t nbytes() const noexcept#

Get the file size.

Note, the file size is retrieved at construction so this method is very fast, no communication needed.

Returns:

The number of bytes.

RemoteEndpoint const &endpoint() const noexcept#

Get a const reference to the underlying remote endpoint.

Returns:

The remote endpoint.

std::size_t read(
void *buf,
std::size_t size,
std::size_t file_offset = 0
)#

Read from remote source into buffer (host or device memory).

When reading into device memory, a bounce buffer is used to avoid many small memory copies to device. Use kvikio::default::bounce_buffer_size_reset() to set the size of this bounce buffer (default 16 MiB).

Parameters:
  • buf – Pointer to host or device memory.

  • size – Number of bytes to read.

  • file_offset – File offset in bytes.

Returns:

Number of bytes read, which is always size.

std::future<std::size_t> pread(
void *buf,
std::size_t size,
std::size_t file_offset = 0,
std::size_t task_size = defaults::task_size(),
ThreadPool *thread_pool = &defaults::thread_pool()
)#

Read from a remote source into a buffer in parallel.

The parallel async counterpart of read(). The byte range is partitioned into sub-ranges of size task_size and dispatched to the active remote-IO backend.

  • EASY_THREADPOOL (default): each sub-range runs on a worker of the supplied thread_pool, blocking in curl_easy_perform().

  • MULTI_POLL: each sub-range is handed to a process-wide reactor pool that drives many libcurl easy handles via curl_multi_poll(). The thread_pool argument is ignored. The first failure surfaces via the returned future. See KVIKIO_REMOTE_IO_NUM_REACTORS and KVIKIO_REMOTE_IO_REACTOR_DISPATCH for tuning knobs.

Note

The returned std::future must not outlive the RemoteHandle (both backends). Under EASY_THREADPOOL it must additionally not outlive the supplied thread_pool. Calling wait() or get() on the future after either has been destroyed results in undefined behavior.

Parameters:
  • buf – Pointer to host or device memory.

  • size – Number of bytes to read.

  • file_offset – File offset in bytes.

  • task_size – Size of each sub-range in bytes.

  • thread_pool – Thread pool to use under EASY_THREADPOOL. Ignored under MULTI_POLL. Defaults to the global default thread pool. The caller is responsible for keeping the thread pool valid until the returned future is consumed.

Returns:

Future that on completion returns the number of bytes read, which is always size.

Public Static Functions

static RemoteHandle open(
std::string const &url,
RemoteEndpointType remote_endpoint_type = RemoteEndpointType::AUTO,
std::optional<std::vector<RemoteEndpointType>> allow_list = std::nullopt,
std::optional<std::size_t> nbytes = std::nullopt
)#

Create a remote file handle from a URL.

This function creates a RemoteHandle for reading data from various remote endpoints including HTTP/HTTPS servers, AWS S3 buckets, S3 presigned URLs, and WebHDFS. The endpoint type can be automatically detected from the URL or explicitly specified.

If not provided, defaults to all supported types in this order: RemoteEndpointType::S3, RemoteEndpointType::S3_PRESIGNED_URL, RemoteEndpointType::WEBHDFS, and RemoteEndpointType::HTTP

.

Example:

  • Auto-detect endpoint type from URL

    auto handle = kvikio::RemoteHandle::open(
        "https://bucket.s3.amazonaws.com/object?X-Amz-Algorithm=AWS4-HMAC-SHA256"
        "&X-Amz-Credential=...&X-Amz-Signature=..."
    );
    

  • Open S3 file with explicit endpoint type

    auto handle = kvikio::RemoteHandle::open(
        "https://my-bucket.s3.us-east-1.amazonaws.com/data.bin",
        kvikio::RemoteEndpointType::S3
    );
    

  • Restrict endpoint type candidates

    std::vector<kvikio::RemoteEndpointType> allow_list = {
        kvikio::RemoteEndpointType::HTTP,
        kvikio::RemoteEndpointType::S3_PRESIGNED_URL
    };
    auto handle = kvikio::RemoteHandle::open(
        user_provided_url,
        kvikio::RemoteEndpointType::AUTO,
        allow_list
    );
    

  • Provide known file size to skip HEAD request

    auto handle = kvikio::RemoteHandle::open(
        "https://example.com/large-file.bin",
        kvikio::RemoteEndpointType::HTTP,
        std::nullopt,
        1024 * 1024 * 100  // 100 MB
    );
    

Parameters:
  • url – The URL of the remote file. Supported formats include:

    • S3 with credentials

    • S3 presigned URL

    • WebHDFS

    • HTTP/HTTPS

  • remote_endpoint_type – The type of remote endpoint. Default is RemoteEndpointType::AUTO which automatically detects the endpoint type from the URL. Can be explicitly set to RemoteEndpointType::S3, RemoteEndpointType::S3_PRESIGNED_URL, RemoteEndpointType::WEBHDFS, or RemoteEndpointType::HTTP to force a specific endpoint type.

  • allow_list – Optional list of allowed endpoint types. If provided:

    • If remote_endpoint_type is RemoteEndpointType::AUTO, Types are tried in the exact order specified until a match is found.

    • In explicit mode, the specified type must be in this list, otherwise an exception is thrown.

  • nbytes – Optional file size in bytes. If not provided, the function sends additional request to the server to query the file size.

Throws:

std::runtime_error – If:

  • If the URL is malformed or missing required components.

  • RemoteEndpointType::AUTO mode is used and the URL doesn’t match any supported endpoint type.

  • The specified endpoint type is not in the allow_list.

  • The URL is invalid for the specified endpoint type.

  • Unable to connect to the remote server or determine file size (when nbytes not provided).

Returns:

A RemoteHandle object that can be used to read data from the remote file.

class StreamFuture#
#include <stream.hpp>

Future of an asynchronous IO operation.

This class shouldn’t be used directly, instead some stream operations such as FileHandle.read_async and FileHandle.write_async returns an instance of this class. Use .check_bytes_done() to synchronize the associated CUDA stream and return the number of bytes read or written by the operation.

The goal of this class is twofold:

  • Have read_async and write_async return an object that clearly associates the function arguments with the CUDA stream used. This is useful because the current validity of the arguments depends on the stream.

  • Support of by-value arguments. In many cases, a user will use read_async and write_async like most other asynchronous CUDA functions that take by-value arguments.

To support by-value arguments, we allocate the arguments on the heap (malloc ArgByVal) and have the by-reference arguments points into ArgByVal. This way, the read_async and write_async can call .get_args() to get the by-reference arguments required by cuFile’s stream API.

Public Functions

StreamFuture(StreamFuture const&) = delete#

StreamFuture support move semantic but isn’t copyable.

std::tuple<void*, std::size_t*, off_t*, off_t*, ssize_t*, CUstream> get_args(
) const#

Return the arguments of the future call.

Returns:

Tuple of the arguments in the order matching FileHandle.read() and FileHandle.write()

std::size_t check_bytes_done()#

Return the number of bytes read or written by the future operation.

Synchronize the associated CUDA stream.

Returns:

Number of bytes read or written by the future operation.

~StreamFuture() noexcept#

Free the by-value arguments and make sure the associated CUDA stream has been synchronized.

class PushAndPopContext#
#include <utils.hpp>

Push CUDA context on creation and pop it on destruction.

namespace detail#

Functions

CompatMode parse_compat_mode_str(std::string_view compat_mode_str)#

Parse a string into a CompatMode enum.

Parameters:

compat_mode_str – Compatibility mode in string format (case-insensitive). Valid values are:

  • ON (alias: TRUE, YES, 1)

  • OFF (alias: FALSE, NO, 0)

  • AUTO

Returns:

A CompatMode enum.

template<typename Exception, typename MsgFunc>
void kvikio_fail(
MsgFunc &&msg_func,
int line_number,
char const *filename
)#

Throw an exception with a formatted error message including source location.

Template Parameters:
  • Exception – The exception type to throw.

  • MsgFunc – A callable type that returns a std::string error message.

Parameters:
  • msg_func – Callable that produces the error message string.

  • line_number – Source line number (typically from LINE).

  • filename – Source file name (typically from FILE).

Throws:

Exception – Always thrown with a message containing the source location and user message.

template<typename Exception>
void cuda_driver_try(
CUresult error,
int line_number,
char const *filename
)#

Check a CUDA driver API return code and throw on failure.

Template Parameters:

Exception – The exception type to throw.

Parameters:
  • error – The CUresult return code from a CUDA driver API call.

  • line_number – Source line number (typically from LINE).

  • filename – Source file name (typically from FILE).

Throws:

Exception – Thrown if error is not CUDA_SUCCESS.

template<typename Exception>
void cufile_try(
CUfileError_t error,
int line_number,
char const *filename
)#

Check a cuFile API return code and throw on failure.

If the error indicates an underlying CUDA driver error, delegates to cuda_driver_try().

Template Parameters:

Exception – The exception type to throw.

Parameters:
  • error – The CUfileError_t return code from a cuFile API call.

  • line_number – Source line number (typically from LINE).

  • filename – Source file name (typically from FILE).

Throws:

Exception – Thrown if error does not indicate CU_FILE_SUCCESS.

template<typename Exception>
void cufile_check_bytes_done(
ssize_t nbytes_done,
int line_number,
char const *filename
)#

Check the byte count returned by a cuFile read/write and throw on failure.

A negative value encodes an error: either a cuFile operation error (if above CUFILEOP_BASE_ERR) or a standard errno value.

Template Parameters:

Exception – The exception type to throw.

Parameters:
  • nbytes_done – The byte count returned by a cuFile read/write operation.

  • line_number – Source line number (typically from LINE).

  • filename – Source file name (typically from FILE).

Throws:

Exception – Thrown if nbytes_done is negative.

inline void handle_linux_call_error(
int line_number,
char const *filename,
std::string_view extra_msg
)#

Throw a GenericSystemError with the current errno and a formatted message.

This is the shared error-reporting path for check_linux_call() overloads.

Parameters:
  • line_number – Source line number (typically from LINE).

  • filename – Source file name (typically from FILE).

  • extra_msg – Optional extra context prepended to the error message.

Throws:

kvikio::GenericSystemError – Always thrown, capturing the current errno.

inline void check_linux_call(
long return_value,
int line_number,
char const *filename,
std::string_view extra_msg = "",
long error_value = -1
)#

Check the return value of a Linux system call and throw on failure.

This non-template overload handles the common case where the return value is a long type (Linux system call return type).

Parameters:
  • return_value – The return value of the system call.

  • line_number – Source line number (typically from LINE).

  • filename – Source file name (typically from FILE).

  • extra_msg – Optional extra context for the error message (default: empty).

  • error_value – The sentinel value indicating failure (default: -1).

Throws:

kvikio::GenericSystemError – Thrown if return_value equals error_value.

template<typename T>
void check_linux_call(
T return_value,
int line_number,
char const *filename,
std::string_view extra_msg,
T error_value
)#

Check the return value of a Linux system call and throw on failure.

This template overload handles non-integer return types such as void* from mmap().

Template Parameters:

T – The return type of the system call.

Parameters:
  • return_value – The return value of the system call.

  • line_number – Source line number (typically from LINE).

  • filename – Source file name (typically from FILE).

  • extra_msg – Extra context for the error message.

  • error_value – The sentinel value indicating failure (e.g. MAP_FAILED for mmap).

Throws:

kvikio::GenericSystemError – Thrown if return_value equals error_value.

std::vector<int> parse_http_status_codes(
std::string_view env_var_name,
std::string const &status_codes
)#

Parse a string of comma-separated string of HTTP status codes.

Parameters:
  • env_var_name – The environment variable holding the string. Used to report errors.

  • status_codes – The comma-separated string of HTTP status codes. Each code should be a 3-digit integer.

Returns:

The vector with the parsed, integer HTTP status codes.

void count_remote_size_probe(Duration probing) noexcept#

Record asking a remote endpoint how big a file is.

Parameters:

probing – How long the round trip took.

void count_http_connection(
std::uint64_t connections,
Duration dns,
Duration tcp,
Duration tls
) noexcept#

Record what a finished HTTP transfer spent getting connected.

Parameters:
  • connections – Connections opened, which is zero when one was reused.

  • dns – Time resolving the name.

  • tcp – Time establishing the transport connection.

  • tls – Time shaking hands, or zero without TLS.

void count_http_retry(Duration backoff) noexcept#

Record an HTTP request that hit a retryable error and will be tried again.

Parameters:

backoff – How long the next attempt waits before it goes out.

template<typename Record>
class ScopedTimer#
#include <counters.hpp>

Times a scope and records the duration however the scope is left.

detail::ScopedTimer const probe{detail::count_remote_size_probe};
curl.perform();  // Counted whether it returns or throws.
Template Parameters:

Record – What to hand the duration to. A count_*() below, or a lambda for one that takes more than a duration.

namespace statistics#

Enums

enum class ReportRows : std::uint8_t#

Which rows report() prints.

Values:

enumerator USED#

Only the backends and subsystems the run used.

enumerator ALL#

Every row, including the ones the run never used.

Functions

Counters counters() noexcept#

Every counter as it stands now.

Returns:

The running totals.

struct Counters#
#include <counters.hpp>

Totals for work that belongs to no single operation.

An Observation records one operation, and part of what I/O costs does not fit there, either because nothing correlates it with one operation or because it is shared between many.

These counters count from the moment the process starts, whether or not anybody is watching, so there is nothing to enable and nothing to switch off. Reading them is counters(), and the cost of an interval is the difference between two readings.

Public Functions

Counters since(Counters const &previous) const noexcept#

The difference between this reading and an earlier one.

Parameters:

previous – An earlier reading.

Returns:

What was spent in between, saturating at zero.

bool empty() const noexcept#

Whether anything was counted at all.

Returns:

True if every counter is zero.

std::string to_json() const#

Serialise to JSON.

Returns:

A JSON object as a string.

std::string report(ReportRows rows = ReportRows::USED) const#

Format a human-readable report.

Grouped by subsystem, and a group the run never touched is left out, so an empty reading formats as an empty string.

http size probes     12 probes, 600 ms
http handshake       128 connections, 40 ms dns, 900 ms tcp, 1.90 s tls
Parameters:

rows – Which rows to print.

Returns:

The report, newline-terminated, or empty.

Public Members

std::uint64_t remote_size_probes = {}#

Remote file sizes asked for, which is an HTTP round trip each.

Duration remote_size_probing = {}#

Time spent waiting for them.

std::uint64_t http_connections = {}#

Connections libcurl opened, as opposed to reused. Against the requests a run made, this is whether connections are being reused at all, which against a TLS endpoint dominates everything else.

Duration http_dns = {}#

Time those connections spent resolving, connecting, and shaking hands. libcurl measures these whether or not anybody asks, so reading them costs nothing.

std::uint64_t http_retries = {}#

Requests the endpoint turned away with a retryable error, and the time spent sleeping before trying again. A request that was retried twice counts twice. Nothing else records this, since a retry that eventually succeeds is reported as a success.

struct Summary#
#include <summary.hpp>

Running totals of the I/O KvikIO has performed.

Everything here describes logical operations: one pread() is one operation however many reads KvikIO issued underneath, and its duration covers the call from issue to completion.

Public Functions

Duration wall() const noexcept#

Wall-clock span this summary covers.

Returns:

end - start, or zero if the span is degenerate.

double busy_bytes_per_sec() const noexcept#

Throughput while KvikIO was actually busy.

Understates while an operation is in flight, since its time counts from the moment it starts and its bytes only once it completes.

Returns:

Bytes per second while busy, or zero if no time was spent busy.

double busy_fraction() const noexcept#

Fraction of the span during which KvikIO was doing something.

Returns:

The ratio, or zero if the span is degenerate.

Duration mean_duration() const noexcept#

Average time one operation took.

Returns:

total_duration / num_ops, or zero if nothing completed.

Summary since(Summary const &previous) const#

Totals for the interval between an earlier reading and this one.

Every field is the difference of the two readings. total_duration is the exception: an operation counts whole in the interval it finished in, however long it had been running.

Parameters:

previous – An earlier reading of the same span.

Throws:

std::invalid_argument – if previous is not one, which covers an interval from since(), a reading from another monitor, and one from before a reset().

Returns:

The interval’s totals.

std::string to_json() const#

Serialize to JSON.

The timestamps are against the wall clock, so another program can line the summary up with its own log.

Returns:

A JSON object as a string.

std::vector<std::byte> serialize() const#

Serialize to bytes, exactly.

Everything survives, including the clock anchor and the monotonic timestamps, so a summary that has been through a pipe is still a valid previous for SummaryMonitor::since().

Warning

Not a wire format. The payload is this build’s representation of the struct, so the bytes are readable only by the same architecture and the same version of KvikIO. The header carries the version, the size and a byte-order mark, so deserialize() refuses anything it does not recognise and a mismatch is an exception rather than a wrong number. to_json() is the format to use when something else has to read it.

Returns:

The bytes, the same number of them for every summary.

std::string report(ReportRows rows = ReportRows::USED) const#

Format a human-readable report of every field.

Byte counts, durations and rates are scaled to readable units. Use to_json() instead when the output is going to be parsed.

KvikIO I/O summary
  wall time              243.80 ms
  operations             7
  ...
Parameters:

rows – Which rows to print. Under ReportRows::USED a backend the run never reached and a counter group it never touched are left out.

Returns:

The report, one field per line, newline-terminated.

Public Members

TimePoint start = {}#

When counting started, or was last reset.

TimePoint end = {}#

When the summary was read.

ClockAnchor anchor = {}#

What relates start and end to the wall clock.

They are read from a monotonic clock, so nothing outside this process can interpret them. The monitor takes one anchor when it is constructed and stamps every reading with it. See kvikio::ClockAnchor for how far to trust it over a long run.

std::uint64_t num_ops = {}#

Number of user-facing operations.

std::uint64_t num_reads = {}#

Of those, how many were reads and how many were writes.

std::uint64_t bytes_requested = {}#

Bytes the operations asked for.

std::uint64_t bytes_transferred = {}#

Bytes actually transferred. Differs from bytes_requested on a short read.

std::uint64_t bytes_read = {}#

Of those, how many were read and how many were written.

std::uint64_t num_errors = {}#

Number of operations that failed.

std::array<BackendTotals, num_io_backends> by_backend = {}#

What each backend carried, indexed by IoBackend. Compatibility mode decides per call whether a read reaches cuFile or falls back to POSIX, so this is where that shows.

Counters counters = {}#

The work in the span that belongs to no single operation.

The counters run for the life of the process, and this is the part of them that falls inside the span.

Duration total_duration = {}#

The operations’ durations added up, every operation counted.

Duration busy = {}#

Wall-clock time during which at least one operation was in flight.

The union of the operations’ time spans: overlapping work is counted once, the gaps between calls count as idle, and it never exceeds wall(). An operation still running when the reading is taken counts for the time it has been running so far.

Warning

An approximation, in both directions: a report delivered later than it was stamped can have an idle gap counted as busy, or busy time missed. Both need two threads and a report delayed past a whole operation. Against an exact merge of every span, the error is under 1.5 % for 8 B reads on 8 threads and zero for a pread() of 4 KiB or more. Either way busy <= wall() is guaranteed.

ObservationKind kind = {ObservationKind::LOGICAL}#

Which observations these totals are over. LOGICAL counts one operation per user-facing call, PHYSICAL one per transfer.

std::array<std::byte, 7> _reserved = {}#

Named rather than implicit padding, so that serialize() never copies an indeterminate byte.

Public Static Functions

static Summary deserialize(std::vector<std::byte> const &bytes)#

Rebuild a summary from serialize().

Parameters:

bytes – What serialize() produced, on this architecture and this build.

Throws:

std::invalid_argument – if the bytes are not a summary, are the wrong length, or come from a build whose summary differs from this one’s.

Returns:

The summary.

struct BackendTotals#
#include <summary.hpp>

What one backend carried, for Summary::by_backend.

Every operation belongs to exactly one backend, so these add up to the summary’s own. busy has no counterpart here: it is a union over wall time, which two backends running at once would both claim, so per-backend unions would not sum to the total.

Public Members

Duration total_duration = {}#

The durations added up. Divide the bytes by this for what one operation averaged.

class SummaryMonitor : private kvikio::Monitor#
#include <summary.hpp>

Turns on I/O statistics for the process and accumulates them while it exists.

Create one early, keep it, and read it whenever a report is wanted:

kvikio::statistics::SummaryMonitor const monitor;  // statistics are now on
...
auto const s = monitor.get();                      // totals so far
std::cout << s.bytes_transferred << " B, " << s.busy_bytes_per_sec() / 1e9 << " GB/s\n";

For a rate over an interval rather than since the beginning, difference two readings. The result spans [previous reading, now):

auto const before = monitor.get();
run_a_phase();
std::cout << monitor.since(before).busy_bytes_per_sec() / 1e9 << " GB/s during that phase\n";

Or hand it a callback and let it report itself once, when it goes out of scope:

kvikio::statistics::SummaryMonitor const monitor{[](kvikio::statistics::Summary const& s) {
  std::cout << s.bytes_transferred << " bytes\n";
}};

By default a monitor counts one row per user-facing call. Pass ObservationKind::PHYSICAL to count one row per transfer instead, so a call split across the thread pool contributes one row per task and total_duration covers the transfers rather than the calls waiting for a thread. The bytes are the same either way. Registering one of each gives both views of the same run:

kvikio::statistics::SummaryMonitor const calls;
kvikio::statistics::SummaryMonitor const transfers{kvikio::ObservationKind::PHYSICAL};
...
auto const queue_wait = calls.get().total_duration - transfers.get().total_duration;

Monitors are independent. Any number can exist at once, nested or overlapping, and resetting one has no effect on the others. An operation already in flight when the monitor is created is ignored entirely, neither counted nor timed.

Thread-safe: get(), reset() and stop() may be called from any thread while I/O is in flight.

Warning

A monitor measures the whole process, not a scope. It counts every thread’s I/O while it exists, not only the I/O of the thread that created it, and it cannot attribute I/O to a particular call. Wrapping a block in a monitor therefore measures that block only in a program where nothing else is doing I/O at the same time. In a library, or anywhere with a background reader, it will also count work you did not write.

Warning

The totals cover only what KvikIO observes, which is not every call it serves. See kvikio::Monitor for what is left out. A program doing all its I/O that way reports zero operations.

Public Types

using Callback = std::function<void(Summary const&)>#

Called with the final totals when the monitor is destroyed.

Public Functions

explicit SummaryMonitor(
ObservationKind kind = ObservationKind::LOGICAL
)#

Create a monitor and begin counting.

Parameters:

kind – Which observations to count. LOGICAL totals one row per user-facing call. PHYSICAL totals one row per transfer, so busy covers the transfers themselves rather than the calls that were waiting for a thread.

explicit SummaryMonitor(
Callback on_destruction,
ObservationKind kind = ObservationKind::LOGICAL
)#

Create a monitor that reports itself when it goes out of scope.

Parameters:
  • on_destruction – Invoked with the totals from the destructor. Exceptions it throws are caught and logged, since a destructor cannot propagate them.

  • kind – Which observations to count.

~SummaryMonitor()#

Stop counting, invoke the callback if there is one, and release the registration.

Waits for any in-flight observation delivery to finish, so the monitor’s state is never touched after this returns.

Summary get() const#

Read the totals accumulated since construction, or since the last reset().

Safe to call repeatedly, and non-destructive.

Warning

This is a snapshot of a moving target. Operations still in flight are not included, and neither are completed ones whose observation has not been delivered yet, so a reading taken immediately after a single operation may fall short. Nothing is lost. It lands in a later reading.

Returns:

The totals.

void reset()#

Zero the totals and restart the wall-clock span, as if the monitor had just been constructed.

Note

On a monitor that has been stopped this leaves an empty summary over a degenerate span: the origin moves forward, the end stays where stop() fixed it, and nothing further can be counted.

Summary since(Summary const &previous) const#

Totals for the interval between an earlier reading and one taken now.

get().since(previous), for the common case of reporting one interval.

auto const before = monitor.get();
run_a_phase();
std::cout << monitor.since(before).busy_bytes_per_sec() << " B/s during that phase\n";

Reporting periodically wants one reading per tick, differenced against the last, rather than this. Two calls would leave a gap between them, and an operation that completed in the gap would fall into both intervals.

auto baseline = monitor.get();
while (running) {
  sleep_for(interval);
  auto const now = monitor.get();
  report(now.since(baseline));
  baseline = now;
}
Parameters:

previous – An earlier reading from get() on this monitor, taken since the last reset().

Throws:

std::invalid_argument – if previous is not one. See Summary::since().

Returns:

The interval’s totals.

void stop()#

Stop counting. Idempotent, and one-way, there is no resuming.

Safe to call from more than one thread, where the totals are final once any of them returns.

The totals are final once this returns, and get() keeps returning them. The measured span ends here too, so wall() and busy_fraction() describe the interval that was measured and do not drift as the process goes on to do other things.