diff --git a/cuda_bindings/cuda/bindings/cufile.pyx b/cuda_bindings/cuda/bindings/cufile.pyx index a4e5c23994..4744aaea3a 100644 --- a/cuda_bindings/cuda/bindings/cufile.pyx +++ b/cuda_bindings/cuda/bindings/cufile.pyx @@ -8,9 +8,6 @@ cimport cython # NOQA from libc cimport errno from ._internal.utils cimport (get_buffer_pointer, get_nested_resource_ptr, nested_resource) -import numpy as _numpy -from cpython cimport buffer as _buffer -from cpython.memoryview cimport PyMemoryView_FromMemory from enum import IntEnum as _IntEnum cimport cpython @@ -18,6 +15,25 @@ import cython from cuda.bindings.driver import CUresult as pyCUresult +from libc.stdlib cimport calloc, free, malloc +from cython cimport view +cimport cpython.buffer +cimport cpython.memoryview +from libc.string cimport memcmp, memcpy +import numpy as _numpy + + +cdef __from_data(data, dtype_name, expected_dtype, lowpp_type): + # _numpy.recarray is a subclass of _numpy.ndarray, so implicitly handled here. + if isinstance(data, lowpp_type): + return data + if not isinstance(data, _numpy.ndarray): + raise TypeError("data argument must be a NumPy ndarray") + if data.size != 1: + raise ValueError("data array must have a size of 1") + if data.dtype != expected_dtype: + raise ValueError(f"data array must be of dtype {dtype_name}") + return lowpp_type.from_ptr(data.ctypes.data, not data.flags.writeable, data) ############################################################################### # POD @@ -39,13 +55,25 @@ cdef class _py_anon_pod1: .. seealso:: `_anon_pod1` """ cdef: - readonly object _data + _anon_pod1 *_ptr + object _owner + bint _owned + bint _readonly def __init__(self): - arr = _numpy.empty(1, dtype=_py_anon_pod1_dtype) - self._data = arr.view(_numpy.recarray) - assert self._data.itemsize == sizeof((NULL).handle), \ - f"itemsize {self._data.itemsize} mismatches union size {sizeof((NULL).handle)}" + self._ptr = <_anon_pod1 *>calloc(1, sizeof((NULL).handle)) + if self._ptr == NULL: + raise MemoryError("Error allocating _py_anon_pod1") + self._owner = None + self._owned = True + self._readonly = False + + def __dealloc__(self): + cdef _anon_pod1 *ptr + if self._owned and self._ptr != NULL: + ptr = self._ptr + self._ptr = NULL + free(ptr) def __repr__(self): return f"<{__name__}._py_anon_pod1 object at {hex(id(self))}>" @@ -53,91 +81,106 @@ cdef class _py_anon_pod1: @property def ptr(self): """Get the pointer address to the data as Python :class:`int`.""" - return self._data.ctypes.data + return (self._ptr) cdef intptr_t _get_ptr(self): - """Get the pointer address to the data as Python :class:`int`.""" - return self._data.ctypes.data + return (self._ptr) def __int__(self): - return self._data.ctypes.data + return (self._ptr) def __eq__(self, other): + cdef _py_anon_pod1 other_ if not isinstance(other, _py_anon_pod1): return False - if self._data.size != other._data.size: - return False - if self._data.dtype != other._data.dtype: - return False - return bool((self._data == other._data).all()) + other_ = other + return (memcmp((self._ptr), (other_._ptr), sizeof((NULL).handle)) == 0) + + def __setitem__(self, key, val): + if key == 0 and isinstance(val, _numpy.ndarray): + self._ptr = <_anon_pod1 *>malloc(sizeof((NULL).handle)) + if self._ptr == NULL: + raise MemoryError("Error allocating _py_anon_pod1") + memcpy(self._ptr, val.ctypes.data, sizeof((NULL).handle)) + self._owner = None + self._owned = True + self._readonly = not val.flags.writeable + else: + setattr(self, key, val) @property def fd(self): """int: """ - return int(self._data.fd[0]) + return self._ptr[0].fd @fd.setter def fd(self, val): - self._data.fd = val + if self._readonly: + raise ValueError("This _py_anon_pod1 instance is read-only") + self._ptr[0].fd = val @property def handle(self): """int: """ - return int(self._data.handle[0]) + return (self._ptr[0].handle) @handle.setter def handle(self, val): - self._data.handle = val - - def __setitem__(self, key, val): - self._data[key] = val + if self._readonly: + raise ValueError("This _py_anon_pod1 instance is read-only") + self._ptr[0].handle = val @staticmethod def from_data(data): """Create an _py_anon_pod1 instance wrapping the given NumPy array. Args: - data (_numpy.ndarray): a 1D array of dtype `_py_anon_pod1_dtype` holding the data. + data (_numpy.ndarray): a single-element array of dtype `_py_anon_pod1_dtype` holding the data. """ - cdef _py_anon_pod1 obj = _py_anon_pod1.__new__(_py_anon_pod1) - if not isinstance(data, (_numpy.ndarray, _numpy.recarray)): - raise TypeError("data argument must be a NumPy ndarray") - if data.ndim != 1: - raise ValueError("data array must be 1D") - if data.dtype != _py_anon_pod1_dtype: - raise ValueError("data array must be of dtype _py_anon_pod1_dtype") - obj._data = data.view(_numpy.recarray) - - return obj + return __from_data(data, "_py_anon_pod1_dtype", _py_anon_pod1_dtype, _py_anon_pod1) @staticmethod - def from_ptr(intptr_t ptr, bint readonly=False): + def from_ptr(intptr_t ptr, bint readonly=False, object owner=None): """Create an _py_anon_pod1 instance wrapping the given pointer. Args: ptr (intptr_t): pointer address as Python :class:`int` to the data. + owner (object): The Python object that owns the pointer. If not provided, data will be copied. readonly (bool): whether the data is read-only (to the user). default is `False`. """ if ptr == 0: raise ValueError("ptr must not be null (0)") cdef _py_anon_pod1 obj = _py_anon_pod1.__new__(_py_anon_pod1) - cdef flag = _buffer.PyBUF_READ if readonly else _buffer.PyBUF_WRITE - cdef object buf = PyMemoryView_FromMemory( - ptr, sizeof((NULL).handle), flag) - data = _numpy.ndarray((1,), buffer=buf, - dtype=_py_anon_pod1_dtype) - obj._data = data.view(_numpy.recarray) - + if owner is None: + obj._ptr = <_anon_pod1 *>malloc(sizeof((NULL).handle)) + if obj._ptr == NULL: + raise MemoryError("Error allocating _py_anon_pod1") + memcpy((obj._ptr), ptr, sizeof((NULL).handle)) + obj._owner = None + obj._owned = True + else: + obj._ptr = <_anon_pod1 *>ptr + obj._owner = owner + obj._owned = False + obj._readonly = readonly return obj -_py_anon_pod3_dtype = _numpy.dtype([ - ("dev_ptr_base", _numpy.intp, ), - ("file_offset", _numpy.int64, ), - ("dev_ptr_offset", _numpy.int64, ), - ("size_", _numpy.uint64, ), - ], align=True) +cdef _get__py_anon_pod3_dtype_offsets(): + cdef _anon_pod3 pod = _anon_pod3() + return _numpy.dtype({ + 'names': ['dev_ptr_base', 'file_offset', 'dev_ptr_offset', 'size_'], + 'formats': [_numpy.intp, _numpy.int64, _numpy.int64, _numpy.uint64], + 'offsets': [ + (&(pod.devPtr_base)) - (&pod), + (&(pod.file_offset)) - (&pod), + (&(pod.devPtr_offset)) - (&pod), + (&(pod.size)) - (&pod), + ], + 'itemsize': sizeof((NULL).u.batch), + }) +_py_anon_pod3_dtype = _get__py_anon_pod3_dtype_offsets() cdef class _py_anon_pod3: """Empty-initialize an instance of `_anon_pod3`. @@ -146,13 +189,25 @@ cdef class _py_anon_pod3: .. seealso:: `_anon_pod3` """ cdef: - readonly object _data + _anon_pod3 *_ptr + object _owner + bint _owned + bint _readonly def __init__(self): - arr = _numpy.empty(1, dtype=_py_anon_pod3_dtype) - self._data = arr.view(_numpy.recarray) - assert self._data.itemsize == sizeof((NULL).u.batch), \ - f"itemsize {self._data.itemsize} mismatches struct size {sizeof((NULL).u.batch)}" + self._ptr = <_anon_pod3 *>calloc(1, sizeof((NULL).u.batch)) + if self._ptr == NULL: + raise MemoryError("Error allocating _py_anon_pod3") + self._owner = None + self._owned = True + self._readonly = False + + def __dealloc__(self): + cdef _anon_pod3 *ptr + if self._owned and self._ptr != NULL: + ptr = self._ptr + self._ptr = NULL + free(ptr) def __repr__(self): return f"<{__name__}._py_anon_pod3 object at {hex(id(self))}>" @@ -160,108 +215,127 @@ cdef class _py_anon_pod3: @property def ptr(self): """Get the pointer address to the data as Python :class:`int`.""" - return self._data.ctypes.data + return (self._ptr) cdef intptr_t _get_ptr(self): - """Get the pointer address to the data as Python :class:`int`.""" - return self._data.ctypes.data + return (self._ptr) def __int__(self): - return self._data.ctypes.data + return (self._ptr) def __eq__(self, other): + cdef _py_anon_pod3 other_ if not isinstance(other, _py_anon_pod3): return False - if self._data.size != other._data.size: - return False - if self._data.dtype != other._data.dtype: - return False - return bool((self._data == other._data).all()) + other_ = other + return (memcmp((self._ptr), (other_._ptr), sizeof((NULL).u.batch)) == 0) + + def __setitem__(self, key, val): + if key == 0 and isinstance(val, _numpy.ndarray): + self._ptr = <_anon_pod3 *>malloc(sizeof((NULL).u.batch)) + if self._ptr == NULL: + raise MemoryError("Error allocating _py_anon_pod3") + memcpy(self._ptr, val.ctypes.data, sizeof((NULL).u.batch)) + self._owner = None + self._owned = True + self._readonly = not val.flags.writeable + else: + setattr(self, key, val) @property def dev_ptr_base(self): """int: """ - return int(self._data.dev_ptr_base[0]) + return (self._ptr[0].devPtr_base) @dev_ptr_base.setter def dev_ptr_base(self, val): - self._data.dev_ptr_base = val + if self._readonly: + raise ValueError("This _py_anon_pod3 instance is read-only") + self._ptr[0].devPtr_base = val @property def file_offset(self): """int: """ - return int(self._data.file_offset[0]) + return self._ptr[0].file_offset @file_offset.setter def file_offset(self, val): - self._data.file_offset = val + if self._readonly: + raise ValueError("This _py_anon_pod3 instance is read-only") + self._ptr[0].file_offset = val @property def dev_ptr_offset(self): """int: """ - return int(self._data.dev_ptr_offset[0]) + return self._ptr[0].devPtr_offset @dev_ptr_offset.setter def dev_ptr_offset(self, val): - self._data.dev_ptr_offset = val + if self._readonly: + raise ValueError("This _py_anon_pod3 instance is read-only") + self._ptr[0].devPtr_offset = val @property def size_(self): """int: """ - return int(self._data.size_[0]) + return self._ptr[0].size @size_.setter def size_(self, val): - self._data.size_ = val - - def __setitem__(self, key, val): - self._data[key] = val + if self._readonly: + raise ValueError("This _py_anon_pod3 instance is read-only") + self._ptr[0].size = val @staticmethod def from_data(data): """Create an _py_anon_pod3 instance wrapping the given NumPy array. Args: - data (_numpy.ndarray): a 1D array of dtype `_py_anon_pod3_dtype` holding the data. + data (_numpy.ndarray): a single-element array of dtype `_py_anon_pod3_dtype` holding the data. """ - cdef _py_anon_pod3 obj = _py_anon_pod3.__new__(_py_anon_pod3) - if not isinstance(data, (_numpy.ndarray, _numpy.recarray)): - raise TypeError("data argument must be a NumPy ndarray") - if data.ndim != 1: - raise ValueError("data array must be 1D") - if data.dtype != _py_anon_pod3_dtype: - raise ValueError("data array must be of dtype _py_anon_pod3_dtype") - obj._data = data.view(_numpy.recarray) - - return obj + return __from_data(data, "_py_anon_pod3_dtype", _py_anon_pod3_dtype, _py_anon_pod3) @staticmethod - def from_ptr(intptr_t ptr, bint readonly=False): + def from_ptr(intptr_t ptr, bint readonly=False, object owner=None): """Create an _py_anon_pod3 instance wrapping the given pointer. Args: ptr (intptr_t): pointer address as Python :class:`int` to the data. + owner (object): The Python object that owns the pointer. If not provided, data will be copied. readonly (bool): whether the data is read-only (to the user). default is `False`. """ if ptr == 0: raise ValueError("ptr must not be null (0)") cdef _py_anon_pod3 obj = _py_anon_pod3.__new__(_py_anon_pod3) - cdef flag = _buffer.PyBUF_READ if readonly else _buffer.PyBUF_WRITE - cdef object buf = PyMemoryView_FromMemory( - ptr, sizeof((NULL).u.batch), flag) - data = _numpy.ndarray((1,), buffer=buf, - dtype=_py_anon_pod3_dtype) - obj._data = data.view(_numpy.recarray) - + if owner is None: + obj._ptr = <_anon_pod3 *>malloc(sizeof((NULL).u.batch)) + if obj._ptr == NULL: + raise MemoryError("Error allocating _py_anon_pod3") + memcpy((obj._ptr), ptr, sizeof((NULL).u.batch)) + obj._owner = None + obj._owned = True + else: + obj._ptr = <_anon_pod3 *>ptr + obj._owner = owner + obj._owned = False + obj._readonly = readonly return obj -io_events_dtype = _numpy.dtype([ - ("cookie", _numpy.intp, ), - ("status", _numpy.int32, ), - ("ret", _numpy.uint64, ), - ], align=True) +cdef _get_io_events_dtype_offsets(): + cdef CUfileIOEvents_t pod = CUfileIOEvents_t() + return _numpy.dtype({ + 'names': ['cookie', 'status', 'ret'], + 'formats': [_numpy.intp, _numpy.int32, _numpy.uint64], + 'offsets': [ + (&(pod.cookie)) - (&pod), + (&(pod.status)) - (&pod), + (&(pod.ret)) - (&pod), + ], + 'itemsize': sizeof(CUfileIOEvents_t), + }) +io_events_dtype = _get_io_events_dtype_offsets() cdef class IOEvents: """Empty-initialize an array of `CUfileIOEvents_t`. @@ -278,11 +352,13 @@ cdef class IOEvents: cdef: readonly object _data + + def __init__(self, size=1): arr = _numpy.empty(size, dtype=io_events_dtype) self._data = arr.view(_numpy.recarray) assert self._data.itemsize == sizeof(CUfileIOEvents_t), \ - f"itemsize {self._data.itemsize} mismatches struct size {sizeof(CUfileIOEvents_t)}" + f"itemsize {self._data.itemsize} mismatches struct size { sizeof(CUfileIOEvents_t) }" def __repr__(self): if self._data.size > 1: @@ -296,7 +372,6 @@ cdef class IOEvents: return self._data.ctypes.data cdef intptr_t _get_ptr(self): - """Get the pointer address to the data as Python :class:`int`.""" return self._data.ctypes.data def __int__(self): @@ -309,13 +384,10 @@ cdef class IOEvents: return self._data.size def __eq__(self, other): - if not isinstance(other, IOEvents): - return False - if self._data.size != other._data.size: - return False - if self._data.dtype != other._data.dtype: + cdef object self_data = self._data + if (not isinstance(other, IOEvents)) or self_data.size != other._data.size or self_data.dtype != other._data.dtype: return False - return bool((self._data == other._data).all()) + return bool((self_data == other._data).all()) @property def cookie(self): @@ -351,13 +423,16 @@ cdef class IOEvents: self._data.ret = val def __getitem__(self, key): + cdef ssize_t key_ + cdef ssize_t size if isinstance(key, int): + key_ = key size = self._data.size - if key >= size or key <= -(size+1): + if key_ >= size or key_ <= -(size+1): raise IndexError("index is out of bounds") - if key < 0: - key += size - return IOEvents.from_data(self._data[key:key+1]) + if key_ < 0: + key_ += size + return IOEvents.from_data(self._data[key_:key_+1]) out = self._data[key] if isinstance(out, _numpy.recarray) and out.dtype == io_events_dtype: return IOEvents.from_data(out) @@ -374,7 +449,7 @@ cdef class IOEvents: data (_numpy.ndarray): a 1D array of dtype `io_events_dtype` holding the data. """ cdef IOEvents obj = IOEvents.__new__(IOEvents) - if not isinstance(data, (_numpy.ndarray, _numpy.recarray)): + if not isinstance(data, _numpy.ndarray): raise TypeError("data argument must be a NumPy ndarray") if data.ndim != 1: raise ValueError("data array must be 1D") @@ -396,21 +471,28 @@ cdef class IOEvents: if ptr == 0: raise ValueError("ptr must not be null (0)") cdef IOEvents obj = IOEvents.__new__(IOEvents) - cdef flag = _buffer.PyBUF_READ if readonly else _buffer.PyBUF_WRITE - cdef object buf = PyMemoryView_FromMemory( + cdef flag = cpython.buffer.PyBUF_READ if readonly else cpython.buffer.PyBUF_WRITE + cdef object buf = cpython.memoryview.PyMemoryView_FromMemory( ptr, sizeof(CUfileIOEvents_t) * size, flag) - data = _numpy.ndarray((size,), buffer=buf, - dtype=io_events_dtype) + data = _numpy.ndarray(size, buffer=buf, dtype=io_events_dtype) obj._data = data.view(_numpy.recarray) return obj -op_counter_dtype = _numpy.dtype([ - ("ok", _numpy.uint64, ), - ("err", _numpy.uint64, ), - ], align=True) +cdef _get_op_counter_dtype_offsets(): + cdef CUfileOpCounter_t pod = CUfileOpCounter_t() + return _numpy.dtype({ + 'names': ['ok', 'err'], + 'formats': [_numpy.uint64, _numpy.uint64], + 'offsets': [ + (&(pod.ok)) - (&pod), + (&(pod.err)) - (&pod), + ], + 'itemsize': sizeof(CUfileOpCounter_t), + }) +op_counter_dtype = _get_op_counter_dtype_offsets() cdef class OpCounter: """Empty-initialize an instance of `CUfileOpCounter_t`. @@ -419,13 +501,25 @@ cdef class OpCounter: .. seealso:: `CUfileOpCounter_t` """ cdef: - readonly object _data + CUfileOpCounter_t *_ptr + object _owner + bint _owned + bint _readonly def __init__(self): - arr = _numpy.empty(1, dtype=op_counter_dtype) - self._data = arr.view(_numpy.recarray) - assert self._data.itemsize == sizeof(CUfileOpCounter_t), \ - f"itemsize {self._data.itemsize} mismatches struct size {sizeof(CUfileOpCounter_t)}" + self._ptr = calloc(1, sizeof(CUfileOpCounter_t)) + if self._ptr == NULL: + raise MemoryError("Error allocating OpCounter") + self._owner = None + self._owned = True + self._readonly = False + + def __dealloc__(self): + cdef CUfileOpCounter_t *ptr + if self._owned and self._ptr != NULL: + ptr = self._ptr + self._ptr = NULL + free(ptr) def __repr__(self): return f"<{__name__}.OpCounter object at {hex(id(self))}>" @@ -433,120 +527,141 @@ cdef class OpCounter: @property def ptr(self): """Get the pointer address to the data as Python :class:`int`.""" - return self._data.ctypes.data + return (self._ptr) cdef intptr_t _get_ptr(self): - """Get the pointer address to the data as Python :class:`int`.""" - return self._data.ctypes.data + return (self._ptr) def __int__(self): - return self._data.ctypes.data + return (self._ptr) def __eq__(self, other): + cdef OpCounter other_ if not isinstance(other, OpCounter): return False - if self._data.size != other._data.size: - return False - if self._data.dtype != other._data.dtype: - return False - return bool((self._data == other._data).all()) + other_ = other + return (memcmp((self._ptr), (other_._ptr), sizeof(CUfileOpCounter_t)) == 0) + + def __setitem__(self, key, val): + if key == 0 and isinstance(val, _numpy.ndarray): + self._ptr = malloc(sizeof(CUfileOpCounter_t)) + if self._ptr == NULL: + raise MemoryError("Error allocating OpCounter") + memcpy(self._ptr, val.ctypes.data, sizeof(CUfileOpCounter_t)) + self._owner = None + self._owned = True + self._readonly = not val.flags.writeable + else: + setattr(self, key, val) @property def ok(self): """int: """ - return int(self._data.ok[0]) + return self._ptr[0].ok @ok.setter def ok(self, val): - self._data.ok = val + if self._readonly: + raise ValueError("This OpCounter instance is read-only") + self._ptr[0].ok = val @property def err(self): """int: """ - return int(self._data.err[0]) + return self._ptr[0].err @err.setter def err(self, val): - self._data.err = val - - def __setitem__(self, key, val): - self._data[key] = val + if self._readonly: + raise ValueError("This OpCounter instance is read-only") + self._ptr[0].err = val @staticmethod def from_data(data): """Create an OpCounter instance wrapping the given NumPy array. Args: - data (_numpy.ndarray): a 1D array of dtype `op_counter_dtype` holding the data. + data (_numpy.ndarray): a single-element array of dtype `op_counter_dtype` holding the data. """ - cdef OpCounter obj = OpCounter.__new__(OpCounter) - if not isinstance(data, (_numpy.ndarray, _numpy.recarray)): - raise TypeError("data argument must be a NumPy ndarray") - if data.ndim != 1: - raise ValueError("data array must be 1D") - if data.dtype != op_counter_dtype: - raise ValueError("data array must be of dtype op_counter_dtype") - obj._data = data.view(_numpy.recarray) - - return obj + return __from_data(data, "op_counter_dtype", op_counter_dtype, OpCounter) @staticmethod - def from_ptr(intptr_t ptr, bint readonly=False): + def from_ptr(intptr_t ptr, bint readonly=False, object owner=None): """Create an OpCounter instance wrapping the given pointer. Args: ptr (intptr_t): pointer address as Python :class:`int` to the data. + owner (object): The Python object that owns the pointer. If not provided, data will be copied. readonly (bool): whether the data is read-only (to the user). default is `False`. """ if ptr == 0: raise ValueError("ptr must not be null (0)") cdef OpCounter obj = OpCounter.__new__(OpCounter) - cdef flag = _buffer.PyBUF_READ if readonly else _buffer.PyBUF_WRITE - cdef object buf = PyMemoryView_FromMemory( - ptr, sizeof(CUfileOpCounter_t), flag) - data = _numpy.ndarray((1,), buffer=buf, - dtype=op_counter_dtype) - obj._data = data.view(_numpy.recarray) - + if owner is None: + obj._ptr = malloc(sizeof(CUfileOpCounter_t)) + if obj._ptr == NULL: + raise MemoryError("Error allocating OpCounter") + memcpy((obj._ptr), ptr, sizeof(CUfileOpCounter_t)) + obj._owner = None + obj._owned = True + else: + obj._ptr = ptr + obj._owner = owner + obj._owned = False + obj._readonly = readonly return obj -per_gpu_stats_dtype = _numpy.dtype([ - ("uuid", _numpy.int8, (16,)), - ("read_bytes", _numpy.uint64, ), - ("read_bw_bytes_per_sec", _numpy.uint64, ), - ("read_utilization", _numpy.uint64, ), - ("read_duration_us", _numpy.uint64, ), - ("n_total_reads", _numpy.uint64, ), - ("n_p2p_reads", _numpy.uint64, ), - ("n_nvfs_reads", _numpy.uint64, ), - ("n_posix_reads", _numpy.uint64, ), - ("n_unaligned_reads", _numpy.uint64, ), - ("n_dr_reads", _numpy.uint64, ), - ("n_sparse_regions", _numpy.uint64, ), - ("n_inline_regions", _numpy.uint64, ), - ("n_reads_err", _numpy.uint64, ), - ("writes_bytes", _numpy.uint64, ), - ("write_bw_bytes_per_sec", _numpy.uint64, ), - ("write_utilization", _numpy.uint64, ), - ("write_duration_us", _numpy.uint64, ), - ("n_total_writes", _numpy.uint64, ), - ("n_p2p_writes", _numpy.uint64, ), - ("n_nvfs_writes", _numpy.uint64, ), - ("n_posix_writes", _numpy.uint64, ), - ("n_unaligned_writes", _numpy.uint64, ), - ("n_dr_writes", _numpy.uint64, ), - ("n_writes_err", _numpy.uint64, ), - ("n_mmap", _numpy.uint64, ), - ("n_mmap_ok", _numpy.uint64, ), - ("n_mmap_err", _numpy.uint64, ), - ("n_mmap_free", _numpy.uint64, ), - ("reg_bytes", _numpy.uint64, ), - ], align=True) - +cdef _get_per_gpu_stats_dtype_offsets(): + cdef CUfilePerGpuStats_t pod = CUfilePerGpuStats_t() + return _numpy.dtype({ + 'names': ['uuid', 'read_bytes', 'read_bw_bytes_per_sec', 'read_utilization', 'read_duration_us', 'n_total_reads', 'n_p2p_reads', 'n_nvfs_reads', 'n_posix_reads', 'n_unaligned_reads', 'n_dr_reads', 'n_sparse_regions', 'n_inline_regions', 'n_reads_err', 'writes_bytes', 'write_bw_bytes_per_sec', 'write_utilization', 'write_duration_us', 'n_total_writes', 'n_p2p_writes', 'n_nvfs_writes', 'n_posix_writes', 'n_unaligned_writes', 'n_dr_writes', 'n_writes_err', 'n_mmap', 'n_mmap_ok', 'n_mmap_err', 'n_mmap_free', 'reg_bytes'], + 'formats': [_numpy.int8, _numpy.uint64, _numpy.uint64, _numpy.uint64, _numpy.uint64, _numpy.uint64, _numpy.uint64, _numpy.uint64, _numpy.uint64, _numpy.uint64, _numpy.uint64, _numpy.uint64, _numpy.uint64, _numpy.uint64, _numpy.uint64, _numpy.uint64, _numpy.uint64, _numpy.uint64, _numpy.uint64, _numpy.uint64, _numpy.uint64, _numpy.uint64, _numpy.uint64, _numpy.uint64, _numpy.uint64, _numpy.uint64, _numpy.uint64, _numpy.uint64, _numpy.uint64, _numpy.uint64], + 'offsets': [ + (&(pod.uuid)) - (&pod), + (&(pod.read_bytes)) - (&pod), + (&(pod.read_bw_bytes_per_sec)) - (&pod), + (&(pod.read_utilization)) - (&pod), + (&(pod.read_duration_us)) - (&pod), + (&(pod.n_total_reads)) - (&pod), + (&(pod.n_p2p_reads)) - (&pod), + (&(pod.n_nvfs_reads)) - (&pod), + (&(pod.n_posix_reads)) - (&pod), + (&(pod.n_unaligned_reads)) - (&pod), + (&(pod.n_dr_reads)) - (&pod), + (&(pod.n_sparse_regions)) - (&pod), + (&(pod.n_inline_regions)) - (&pod), + (&(pod.n_reads_err)) - (&pod), + (&(pod.writes_bytes)) - (&pod), + (&(pod.write_bw_bytes_per_sec)) - (&pod), + (&(pod.write_utilization)) - (&pod), + (&(pod.write_duration_us)) - (&pod), + (&(pod.n_total_writes)) - (&pod), + (&(pod.n_p2p_writes)) - (&pod), + (&(pod.n_nvfs_writes)) - (&pod), + (&(pod.n_posix_writes)) - (&pod), + (&(pod.n_unaligned_writes)) - (&pod), + (&(pod.n_dr_writes)) - (&pod), + (&(pod.n_writes_err)) - (&pod), + (&(pod.n_mmap)) - (&pod), + (&(pod.n_mmap_ok)) - (&pod), + (&(pod.n_mmap_err)) - (&pod), + (&(pod.n_mmap_free)) - (&pod), + (&(pod.reg_bytes)) - (&pod), + ], + 'itemsize': sizeof(CUfilePerGpuStats_t), + }) + +per_gpu_stats_dtype = _get_per_gpu_stats_dtype_offsets() cdef class PerGpuStats: - """Empty-initialize an instance of `CUfilePerGpuStats_t`. + """Empty-initialize an array of `CUfilePerGpuStats_t`. + + The resulting object is of length `size` and of dtype `per_gpu_stats_dtype`. + If default-constructed, the instance represents a single struct. + + Args: + size (int): number of structs, default=1. .. seealso:: `CUfilePerGpuStats_t` @@ -554,14 +669,19 @@ cdef class PerGpuStats: cdef: readonly object _data - def __init__(self): - arr = _numpy.empty(1, dtype=per_gpu_stats_dtype) + + + def __init__(self, size=1): + arr = _numpy.empty(size, dtype=per_gpu_stats_dtype) self._data = arr.view(_numpy.recarray) assert self._data.itemsize == sizeof(CUfilePerGpuStats_t), \ - f"itemsize {self._data.itemsize} mismatches struct size {sizeof(CUfilePerGpuStats_t)}" + f"itemsize {self._data.itemsize} mismatches struct size { sizeof(CUfilePerGpuStats_t) }" def __repr__(self): - return f"<{__name__}.PerGpuStats object at {hex(id(self))}>" + if self._data.size > 1: + return f"<{__name__}.PerGpuStats_Array_{self._data.size} object at {hex(id(self))}>" + else: + return f"<{__name__}.PerGpuStats object at {hex(id(self))}>" @property def ptr(self): @@ -569,20 +689,22 @@ cdef class PerGpuStats: return self._data.ctypes.data cdef intptr_t _get_ptr(self): - """Get the pointer address to the data as Python :class:`int`.""" return self._data.ctypes.data def __int__(self): + if self._data.size > 1: + raise TypeError("int() argument must be a bytes-like object of size 1. " + "To get the pointer address of an array, use .ptr") return self._data.ctypes.data + def __len__(self): + return self._data.size + def __eq__(self, other): - if not isinstance(other, PerGpuStats): - return False - if self._data.size != other._data.size: + cdef object self_data = self._data + if (not isinstance(other, PerGpuStats)) or self_data.size != other._data.size or self_data.dtype != other._data.dtype: return False - if self._data.dtype != other._data.dtype: - return False - return bool((self._data == other._data).all()) + return bool((self_data == other._data).all()) @property def uuid(self): @@ -595,8 +717,10 @@ cdef class PerGpuStats: @property def read_bytes(self): - """int: """ - return int(self._data.read_bytes[0]) + """Union[~_numpy.uint64, int]: """ + if self._data.size == 1: + return int(self._data.read_bytes[0]) + return self._data.read_bytes @read_bytes.setter def read_bytes(self, val): @@ -604,8 +728,10 @@ cdef class PerGpuStats: @property def read_bw_bytes_per_sec(self): - """int: """ - return int(self._data.read_bw_bytes_per_sec[0]) + """Union[~_numpy.uint64, int]: """ + if self._data.size == 1: + return int(self._data.read_bw_bytes_per_sec[0]) + return self._data.read_bw_bytes_per_sec @read_bw_bytes_per_sec.setter def read_bw_bytes_per_sec(self, val): @@ -613,8 +739,10 @@ cdef class PerGpuStats: @property def read_utilization(self): - """int: """ - return int(self._data.read_utilization[0]) + """Union[~_numpy.uint64, int]: """ + if self._data.size == 1: + return int(self._data.read_utilization[0]) + return self._data.read_utilization @read_utilization.setter def read_utilization(self, val): @@ -622,8 +750,10 @@ cdef class PerGpuStats: @property def read_duration_us(self): - """int: """ - return int(self._data.read_duration_us[0]) + """Union[~_numpy.uint64, int]: """ + if self._data.size == 1: + return int(self._data.read_duration_us[0]) + return self._data.read_duration_us @read_duration_us.setter def read_duration_us(self, val): @@ -631,8 +761,10 @@ cdef class PerGpuStats: @property def n_total_reads(self): - """int: """ - return int(self._data.n_total_reads[0]) + """Union[~_numpy.uint64, int]: """ + if self._data.size == 1: + return int(self._data.n_total_reads[0]) + return self._data.n_total_reads @n_total_reads.setter def n_total_reads(self, val): @@ -640,8 +772,10 @@ cdef class PerGpuStats: @property def n_p2p_reads(self): - """int: """ - return int(self._data.n_p2p_reads[0]) + """Union[~_numpy.uint64, int]: """ + if self._data.size == 1: + return int(self._data.n_p2p_reads[0]) + return self._data.n_p2p_reads @n_p2p_reads.setter def n_p2p_reads(self, val): @@ -649,8 +783,10 @@ cdef class PerGpuStats: @property def n_nvfs_reads(self): - """int: """ - return int(self._data.n_nvfs_reads[0]) + """Union[~_numpy.uint64, int]: """ + if self._data.size == 1: + return int(self._data.n_nvfs_reads[0]) + return self._data.n_nvfs_reads @n_nvfs_reads.setter def n_nvfs_reads(self, val): @@ -658,8 +794,10 @@ cdef class PerGpuStats: @property def n_posix_reads(self): - """int: """ - return int(self._data.n_posix_reads[0]) + """Union[~_numpy.uint64, int]: """ + if self._data.size == 1: + return int(self._data.n_posix_reads[0]) + return self._data.n_posix_reads @n_posix_reads.setter def n_posix_reads(self, val): @@ -667,8 +805,10 @@ cdef class PerGpuStats: @property def n_unaligned_reads(self): - """int: """ - return int(self._data.n_unaligned_reads[0]) + """Union[~_numpy.uint64, int]: """ + if self._data.size == 1: + return int(self._data.n_unaligned_reads[0]) + return self._data.n_unaligned_reads @n_unaligned_reads.setter def n_unaligned_reads(self, val): @@ -676,8 +816,10 @@ cdef class PerGpuStats: @property def n_dr_reads(self): - """int: """ - return int(self._data.n_dr_reads[0]) + """Union[~_numpy.uint64, int]: """ + if self._data.size == 1: + return int(self._data.n_dr_reads[0]) + return self._data.n_dr_reads @n_dr_reads.setter def n_dr_reads(self, val): @@ -685,8 +827,10 @@ cdef class PerGpuStats: @property def n_sparse_regions(self): - """int: """ - return int(self._data.n_sparse_regions[0]) + """Union[~_numpy.uint64, int]: """ + if self._data.size == 1: + return int(self._data.n_sparse_regions[0]) + return self._data.n_sparse_regions @n_sparse_regions.setter def n_sparse_regions(self, val): @@ -694,8 +838,10 @@ cdef class PerGpuStats: @property def n_inline_regions(self): - """int: """ - return int(self._data.n_inline_regions[0]) + """Union[~_numpy.uint64, int]: """ + if self._data.size == 1: + return int(self._data.n_inline_regions[0]) + return self._data.n_inline_regions @n_inline_regions.setter def n_inline_regions(self, val): @@ -703,8 +849,10 @@ cdef class PerGpuStats: @property def n_reads_err(self): - """int: """ - return int(self._data.n_reads_err[0]) + """Union[~_numpy.uint64, int]: """ + if self._data.size == 1: + return int(self._data.n_reads_err[0]) + return self._data.n_reads_err @n_reads_err.setter def n_reads_err(self, val): @@ -712,8 +860,10 @@ cdef class PerGpuStats: @property def writes_bytes(self): - """int: """ - return int(self._data.writes_bytes[0]) + """Union[~_numpy.uint64, int]: """ + if self._data.size == 1: + return int(self._data.writes_bytes[0]) + return self._data.writes_bytes @writes_bytes.setter def writes_bytes(self, val): @@ -721,8 +871,10 @@ cdef class PerGpuStats: @property def write_bw_bytes_per_sec(self): - """int: """ - return int(self._data.write_bw_bytes_per_sec[0]) + """Union[~_numpy.uint64, int]: """ + if self._data.size == 1: + return int(self._data.write_bw_bytes_per_sec[0]) + return self._data.write_bw_bytes_per_sec @write_bw_bytes_per_sec.setter def write_bw_bytes_per_sec(self, val): @@ -730,8 +882,10 @@ cdef class PerGpuStats: @property def write_utilization(self): - """int: """ - return int(self._data.write_utilization[0]) + """Union[~_numpy.uint64, int]: """ + if self._data.size == 1: + return int(self._data.write_utilization[0]) + return self._data.write_utilization @write_utilization.setter def write_utilization(self, val): @@ -739,8 +893,10 @@ cdef class PerGpuStats: @property def write_duration_us(self): - """int: """ - return int(self._data.write_duration_us[0]) + """Union[~_numpy.uint64, int]: """ + if self._data.size == 1: + return int(self._data.write_duration_us[0]) + return self._data.write_duration_us @write_duration_us.setter def write_duration_us(self, val): @@ -748,8 +904,10 @@ cdef class PerGpuStats: @property def n_total_writes(self): - """int: """ - return int(self._data.n_total_writes[0]) + """Union[~_numpy.uint64, int]: """ + if self._data.size == 1: + return int(self._data.n_total_writes[0]) + return self._data.n_total_writes @n_total_writes.setter def n_total_writes(self, val): @@ -757,8 +915,10 @@ cdef class PerGpuStats: @property def n_p2p_writes(self): - """int: """ - return int(self._data.n_p2p_writes[0]) + """Union[~_numpy.uint64, int]: """ + if self._data.size == 1: + return int(self._data.n_p2p_writes[0]) + return self._data.n_p2p_writes @n_p2p_writes.setter def n_p2p_writes(self, val): @@ -766,8 +926,10 @@ cdef class PerGpuStats: @property def n_nvfs_writes(self): - """int: """ - return int(self._data.n_nvfs_writes[0]) + """Union[~_numpy.uint64, int]: """ + if self._data.size == 1: + return int(self._data.n_nvfs_writes[0]) + return self._data.n_nvfs_writes @n_nvfs_writes.setter def n_nvfs_writes(self, val): @@ -775,8 +937,10 @@ cdef class PerGpuStats: @property def n_posix_writes(self): - """int: """ - return int(self._data.n_posix_writes[0]) + """Union[~_numpy.uint64, int]: """ + if self._data.size == 1: + return int(self._data.n_posix_writes[0]) + return self._data.n_posix_writes @n_posix_writes.setter def n_posix_writes(self, val): @@ -784,8 +948,10 @@ cdef class PerGpuStats: @property def n_unaligned_writes(self): - """int: """ - return int(self._data.n_unaligned_writes[0]) + """Union[~_numpy.uint64, int]: """ + if self._data.size == 1: + return int(self._data.n_unaligned_writes[0]) + return self._data.n_unaligned_writes @n_unaligned_writes.setter def n_unaligned_writes(self, val): @@ -793,8 +959,10 @@ cdef class PerGpuStats: @property def n_dr_writes(self): - """int: """ - return int(self._data.n_dr_writes[0]) + """Union[~_numpy.uint64, int]: """ + if self._data.size == 1: + return int(self._data.n_dr_writes[0]) + return self._data.n_dr_writes @n_dr_writes.setter def n_dr_writes(self, val): @@ -802,8 +970,10 @@ cdef class PerGpuStats: @property def n_writes_err(self): - """int: """ - return int(self._data.n_writes_err[0]) + """Union[~_numpy.uint64, int]: """ + if self._data.size == 1: + return int(self._data.n_writes_err[0]) + return self._data.n_writes_err @n_writes_err.setter def n_writes_err(self, val): @@ -811,8 +981,10 @@ cdef class PerGpuStats: @property def n_mmap(self): - """int: """ - return int(self._data.n_mmap[0]) + """Union[~_numpy.uint64, int]: """ + if self._data.size == 1: + return int(self._data.n_mmap[0]) + return self._data.n_mmap @n_mmap.setter def n_mmap(self, val): @@ -820,8 +992,10 @@ cdef class PerGpuStats: @property def n_mmap_ok(self): - """int: """ - return int(self._data.n_mmap_ok[0]) + """Union[~_numpy.uint64, int]: """ + if self._data.size == 1: + return int(self._data.n_mmap_ok[0]) + return self._data.n_mmap_ok @n_mmap_ok.setter def n_mmap_ok(self, val): @@ -829,8 +1003,10 @@ cdef class PerGpuStats: @property def n_mmap_err(self): - """int: """ - return int(self._data.n_mmap_err[0]) + """Union[~_numpy.uint64, int]: """ + if self._data.size == 1: + return int(self._data.n_mmap_err[0]) + return self._data.n_mmap_err @n_mmap_err.setter def n_mmap_err(self, val): @@ -838,8 +1014,10 @@ cdef class PerGpuStats: @property def n_mmap_free(self): - """int: """ - return int(self._data.n_mmap_free[0]) + """Union[~_numpy.uint64, int]: """ + if self._data.size == 1: + return int(self._data.n_mmap_free[0]) + return self._data.n_mmap_free @n_mmap_free.setter def n_mmap_free(self, val): @@ -847,13 +1025,31 @@ cdef class PerGpuStats: @property def reg_bytes(self): - """int: """ - return int(self._data.reg_bytes[0]) + """Union[~_numpy.uint64, int]: """ + if self._data.size == 1: + return int(self._data.reg_bytes[0]) + return self._data.reg_bytes @reg_bytes.setter def reg_bytes(self, val): self._data.reg_bytes = val + def __getitem__(self, key): + cdef ssize_t key_ + cdef ssize_t size + if isinstance(key, int): + key_ = key + size = self._data.size + if key_ >= size or key_ <= -(size+1): + raise IndexError("index is out of bounds") + if key_ < 0: + key_ += size + return PerGpuStats.from_data(self._data[key_:key_+1]) + out = self._data[key] + if isinstance(out, _numpy.recarray) and out.dtype == per_gpu_stats_dtype: + return PerGpuStats.from_data(out) + return out + def __setitem__(self, key, val): self._data[key] = val @@ -865,7 +1061,7 @@ cdef class PerGpuStats: data (_numpy.ndarray): a 1D array of dtype `per_gpu_stats_dtype` holding the data. """ cdef PerGpuStats obj = PerGpuStats.__new__(PerGpuStats) - if not isinstance(data, (_numpy.ndarray, _numpy.recarray)): + if not isinstance(data, _numpy.ndarray): raise TypeError("data argument must be a NumPy ndarray") if data.ndim != 1: raise ValueError("data array must be 1D") @@ -876,32 +1072,40 @@ cdef class PerGpuStats: return obj @staticmethod - def from_ptr(intptr_t ptr, bint readonly=False): + def from_ptr(intptr_t ptr, size_t size=1, bint readonly=False): """Create an PerGpuStats instance wrapping the given pointer. Args: ptr (intptr_t): pointer address as Python :class:`int` to the data. + size (int): number of structs, default=1. readonly (bool): whether the data is read-only (to the user). default is `False`. """ if ptr == 0: raise ValueError("ptr must not be null (0)") cdef PerGpuStats obj = PerGpuStats.__new__(PerGpuStats) - cdef flag = _buffer.PyBUF_READ if readonly else _buffer.PyBUF_WRITE - cdef object buf = PyMemoryView_FromMemory( - ptr, sizeof(CUfilePerGpuStats_t), flag) - data = _numpy.ndarray((1,), buffer=buf, - dtype=per_gpu_stats_dtype) + cdef flag = cpython.buffer.PyBUF_READ if readonly else cpython.buffer.PyBUF_WRITE + cdef object buf = cpython.memoryview.PyMemoryView_FromMemory( + ptr, sizeof(CUfilePerGpuStats_t) * size, flag) + data = _numpy.ndarray(size, buffer=buf, dtype=per_gpu_stats_dtype) obj._data = data.view(_numpy.recarray) return obj -descr_dtype = _numpy.dtype([ - ("type", _numpy.int32, ), - ("handle", _py_anon_pod1_dtype, ), - ("fs_ops", _numpy.intp, ), - ], align=True) +cdef _get_descr_dtype_offsets(): + cdef CUfileDescr_t pod = CUfileDescr_t() + return _numpy.dtype({ + 'names': ['type', 'handle', 'fs_ops'], + 'formats': [_numpy.int32, _py_anon_pod1_dtype, _numpy.intp], + 'offsets': [ + (&(pod.type)) - (&pod), + (&(pod.handle)) - (&pod), + (&(pod.fs_ops)) - (&pod), + ], + 'itemsize': sizeof(CUfileDescr_t), + }) +descr_dtype = _get_descr_dtype_offsets() cdef class Descr: """Empty-initialize an array of `CUfileDescr_t`. @@ -918,11 +1122,13 @@ cdef class Descr: cdef: readonly object _data + + def __init__(self, size=1): arr = _numpy.empty(size, dtype=descr_dtype) self._data = arr.view(_numpy.recarray) assert self._data.itemsize == sizeof(CUfileDescr_t), \ - f"itemsize {self._data.itemsize} mismatches struct size {sizeof(CUfileDescr_t)}" + f"itemsize {self._data.itemsize} mismatches struct size { sizeof(CUfileDescr_t) }" def __repr__(self): if self._data.size > 1: @@ -936,7 +1142,6 @@ cdef class Descr: return self._data.ctypes.data cdef intptr_t _get_ptr(self): - """Get the pointer address to the data as Python :class:`int`.""" return self._data.ctypes.data def __int__(self): @@ -949,13 +1154,10 @@ cdef class Descr: return self._data.size def __eq__(self, other): - if not isinstance(other, Descr): - return False - if self._data.size != other._data.size: + cdef object self_data = self._data + if (not isinstance(other, Descr)) or self_data.size != other._data.size or self_data.dtype != other._data.dtype: return False - if self._data.dtype != other._data.dtype: - return False - return bool((self._data == other._data).all()) + return bool((self_data == other._data).all()) @property def type(self): @@ -989,13 +1191,16 @@ cdef class Descr: self._data.fs_ops = val def __getitem__(self, key): + cdef ssize_t key_ + cdef ssize_t size if isinstance(key, int): + key_ = key size = self._data.size - if key >= size or key <= -(size+1): + if key_ >= size or key_ <= -(size+1): raise IndexError("index is out of bounds") - if key < 0: - key += size - return Descr.from_data(self._data[key:key+1]) + if key_ < 0: + key_ += size + return Descr.from_data(self._data[key_:key_+1]) out = self._data[key] if isinstance(out, _numpy.recarray) and out.dtype == descr_dtype: return Descr.from_data(out) @@ -1012,7 +1217,7 @@ cdef class Descr: data (_numpy.ndarray): a 1D array of dtype `descr_dtype` holding the data. """ cdef Descr obj = Descr.__new__(Descr) - if not isinstance(data, (_numpy.ndarray, _numpy.recarray)): + if not isinstance(data, _numpy.ndarray): raise TypeError("data argument must be a NumPy ndarray") if data.ndim != 1: raise ValueError("data array must be 1D") @@ -1034,11 +1239,10 @@ cdef class Descr: if ptr == 0: raise ValueError("ptr must not be null (0)") cdef Descr obj = Descr.__new__(Descr) - cdef flag = _buffer.PyBUF_READ if readonly else _buffer.PyBUF_WRITE - cdef object buf = PyMemoryView_FromMemory( + cdef flag = cpython.buffer.PyBUF_READ if readonly else cpython.buffer.PyBUF_WRITE + cdef object buf = cpython.memoryview.PyMemoryView_FromMemory( ptr, sizeof(CUfileDescr_t) * size, flag) - data = _numpy.ndarray((size,), buffer=buf, - dtype=descr_dtype) + data = _numpy.ndarray(size, buffer=buf, dtype=descr_dtype) obj._data = data.view(_numpy.recarray) return obj @@ -1059,15 +1263,25 @@ cdef class _py_anon_pod2: .. seealso:: `_anon_pod2` """ cdef: - readonly object _data - - readonly object _batch + _anon_pod2 *_ptr + object _owner + bint _owned + bint _readonly def __init__(self): - arr = _numpy.empty(1, dtype=_py_anon_pod2_dtype) - self._data = arr.view(_numpy.recarray) - assert self._data.itemsize == sizeof((NULL).u), \ - f"itemsize {self._data.itemsize} mismatches union size {sizeof((NULL).u)}" + self._ptr = <_anon_pod2 *>calloc(1, sizeof((NULL).u)) + if self._ptr == NULL: + raise MemoryError("Error allocating _py_anon_pod2") + self._owner = None + self._owned = True + self._readonly = False + + def __dealloc__(self): + cdef _anon_pod2 *ptr + if self._owned and self._ptr != NULL: + ptr = self._ptr + self._ptr = NULL + free(ptr) def __repr__(self): return f"<{__name__}._py_anon_pod2 object at {hex(id(self))}>" @@ -1075,121 +1289,135 @@ cdef class _py_anon_pod2: @property def ptr(self): """Get the pointer address to the data as Python :class:`int`.""" - return self._data.ctypes.data + return (self._ptr) cdef intptr_t _get_ptr(self): - """Get the pointer address to the data as Python :class:`int`.""" - return self._data.ctypes.data + return (self._ptr) def __int__(self): - return self._data.ctypes.data + return (self._ptr) def __eq__(self, other): + cdef _py_anon_pod2 other_ if not isinstance(other, _py_anon_pod2): return False - if self._data.size != other._data.size: - return False - if self._data.dtype != other._data.dtype: - return False - return bool((self._data == other._data).all()) + other_ = other + return (memcmp((self._ptr), (other_._ptr), sizeof((NULL).u)) == 0) + + def __setitem__(self, key, val): + if key == 0 and isinstance(val, _numpy.ndarray): + self._ptr = <_anon_pod2 *>malloc(sizeof((NULL).u)) + if self._ptr == NULL: + raise MemoryError("Error allocating _py_anon_pod2") + memcpy(self._ptr, val.ctypes.data, sizeof((NULL).u)) + self._owner = None + self._owned = True + self._readonly = not val.flags.writeable + else: + setattr(self, key, val) @property def batch(self): """_py_anon_pod3: """ - return self._batch + return _py_anon_pod3.from_ptr(&(self._ptr[0].batch), self._readonly, self) - def __setitem__(self, key, val): - self._data[key] = val + @batch.setter + def batch(self, val): + if self._readonly: + raise ValueError("This _py_anon_pod2 instance is read-only") + cdef _py_anon_pod3 val_ = val + memcpy(&(self._ptr[0].batch), (val_._get_ptr()), sizeof(_anon_pod3) * 1) @staticmethod def from_data(data): """Create an _py_anon_pod2 instance wrapping the given NumPy array. Args: - data (_numpy.ndarray): a 1D array of dtype `_py_anon_pod2_dtype` holding the data. + data (_numpy.ndarray): a single-element array of dtype `_py_anon_pod2_dtype` holding the data. """ - cdef _py_anon_pod2 obj = _py_anon_pod2.__new__(_py_anon_pod2) - if not isinstance(data, (_numpy.ndarray, _numpy.recarray)): - raise TypeError("data argument must be a NumPy ndarray") - if data.ndim != 1: - raise ValueError("data array must be 1D") - if data.dtype != _py_anon_pod2_dtype: - raise ValueError("data array must be of dtype _py_anon_pod2_dtype") - obj._data = data.view(_numpy.recarray) - - batch_addr = obj._data.batch[0].__array_interface__['data'][0] - obj._batch = _py_anon_pod3.from_ptr(batch_addr) - return obj + return __from_data(data, "_py_anon_pod2_dtype", _py_anon_pod2_dtype, _py_anon_pod2) @staticmethod - def from_ptr(intptr_t ptr, bint readonly=False): + def from_ptr(intptr_t ptr, bint readonly=False, object owner=None): """Create an _py_anon_pod2 instance wrapping the given pointer. Args: ptr (intptr_t): pointer address as Python :class:`int` to the data. + owner (object): The Python object that owns the pointer. If not provided, data will be copied. readonly (bool): whether the data is read-only (to the user). default is `False`. """ if ptr == 0: raise ValueError("ptr must not be null (0)") cdef _py_anon_pod2 obj = _py_anon_pod2.__new__(_py_anon_pod2) - cdef flag = _buffer.PyBUF_READ if readonly else _buffer.PyBUF_WRITE - cdef object buf = PyMemoryView_FromMemory( - ptr, sizeof((NULL).u), flag) - data = _numpy.ndarray((1,), buffer=buf, - dtype=_py_anon_pod2_dtype) - obj._data = data.view(_numpy.recarray) - - batch_addr = obj._data.batch[0].__array_interface__['data'][0] - obj._batch = _py_anon_pod3.from_ptr(batch_addr) + if owner is None: + obj._ptr = <_anon_pod2 *>malloc(sizeof((NULL).u)) + if obj._ptr == NULL: + raise MemoryError("Error allocating _py_anon_pod2") + memcpy((obj._ptr), ptr, sizeof((NULL).u)) + obj._owner = None + obj._owned = True + else: + obj._ptr = <_anon_pod2 *>ptr + obj._owner = owner + obj._owned = False + obj._readonly = readonly return obj -stats_level1_dtype = _numpy.dtype([ - ("read_ops", op_counter_dtype, ), - ("write_ops", op_counter_dtype, ), - ("hdl_register_ops", op_counter_dtype, ), - ("hdl_deregister_ops", op_counter_dtype, ), - ("buf_register_ops", op_counter_dtype, ), - ("buf_deregister_ops", op_counter_dtype, ), - ("read_bytes", _numpy.uint64, ), - ("write_bytes", _numpy.uint64, ), - ("read_bw_bytes_per_sec", _numpy.uint64, ), - ("write_bw_bytes_per_sec", _numpy.uint64, ), - ("read_lat_avg_us", _numpy.uint64, ), - ("write_lat_avg_us", _numpy.uint64, ), - ("read_ops_per_sec", _numpy.uint64, ), - ("write_ops_per_sec", _numpy.uint64, ), - ("read_lat_sum_us", _numpy.uint64, ), - ("write_lat_sum_us", _numpy.uint64, ), - ("batch_submit_ops", op_counter_dtype, ), - ("batch_complete_ops", op_counter_dtype, ), - ("batch_setup_ops", op_counter_dtype, ), - ("batch_cancel_ops", op_counter_dtype, ), - ("batch_destroy_ops", op_counter_dtype, ), - ("batch_enqueued_ops", op_counter_dtype, ), - ("batch_posix_enqueued_ops", op_counter_dtype, ), - ("batch_processed_ops", op_counter_dtype, ), - ("batch_posix_processed_ops", op_counter_dtype, ), - ("batch_nvfs_submit_ops", op_counter_dtype, ), - ("batch_p2p_submit_ops", op_counter_dtype, ), - ("batch_aio_submit_ops", op_counter_dtype, ), - ("batch_iouring_submit_ops", op_counter_dtype, ), - ("batch_mixed_io_submit_ops", op_counter_dtype, ), - ("batch_total_submit_ops", op_counter_dtype, ), - ("batch_read_bytes", _numpy.uint64, ), - ("batch_write_bytes", _numpy.uint64, ), - ("batch_read_bw_bytes", _numpy.uint64, ), - ("batch_write_bw_bytes", _numpy.uint64, ), - ("batch_submit_lat_avg_us", _numpy.uint64, ), - ("batch_completion_lat_avg_us", _numpy.uint64, ), - ("batch_submit_ops_per_sec", _numpy.uint64, ), - ("batch_complete_ops_per_sec", _numpy.uint64, ), - ("batch_submit_lat_sum_us", _numpy.uint64, ), - ("batch_completion_lat_sum_us", _numpy.uint64, ), - ("last_batch_read_bytes", _numpy.uint64, ), - ("last_batch_write_bytes", _numpy.uint64, ), - ], align=True) - +cdef _get_stats_level1_dtype_offsets(): + cdef CUfileStatsLevel1_t pod = CUfileStatsLevel1_t() + return _numpy.dtype({ + 'names': ['read_ops', 'write_ops', 'hdl_register_ops', 'hdl_deregister_ops', 'buf_register_ops', 'buf_deregister_ops', 'read_bytes', 'write_bytes', 'read_bw_bytes_per_sec', 'write_bw_bytes_per_sec', 'read_lat_avg_us', 'write_lat_avg_us', 'read_ops_per_sec', 'write_ops_per_sec', 'read_lat_sum_us', 'write_lat_sum_us', 'batch_submit_ops', 'batch_complete_ops', 'batch_setup_ops', 'batch_cancel_ops', 'batch_destroy_ops', 'batch_enqueued_ops', 'batch_posix_enqueued_ops', 'batch_processed_ops', 'batch_posix_processed_ops', 'batch_nvfs_submit_ops', 'batch_p2p_submit_ops', 'batch_aio_submit_ops', 'batch_iouring_submit_ops', 'batch_mixed_io_submit_ops', 'batch_total_submit_ops', 'batch_read_bytes', 'batch_write_bytes', 'batch_read_bw_bytes', 'batch_write_bw_bytes', 'batch_submit_lat_avg_us', 'batch_completion_lat_avg_us', 'batch_submit_ops_per_sec', 'batch_complete_ops_per_sec', 'batch_submit_lat_sum_us', 'batch_completion_lat_sum_us', 'last_batch_read_bytes', 'last_batch_write_bytes'], + 'formats': [op_counter_dtype, op_counter_dtype, op_counter_dtype, op_counter_dtype, op_counter_dtype, op_counter_dtype, _numpy.uint64, _numpy.uint64, _numpy.uint64, _numpy.uint64, _numpy.uint64, _numpy.uint64, _numpy.uint64, _numpy.uint64, _numpy.uint64, _numpy.uint64, op_counter_dtype, op_counter_dtype, op_counter_dtype, op_counter_dtype, op_counter_dtype, op_counter_dtype, op_counter_dtype, op_counter_dtype, op_counter_dtype, op_counter_dtype, op_counter_dtype, op_counter_dtype, op_counter_dtype, op_counter_dtype, op_counter_dtype, _numpy.uint64, _numpy.uint64, _numpy.uint64, _numpy.uint64, _numpy.uint64, _numpy.uint64, _numpy.uint64, _numpy.uint64, _numpy.uint64, _numpy.uint64, _numpy.uint64, _numpy.uint64], + 'offsets': [ + (&(pod.read_ops)) - (&pod), + (&(pod.write_ops)) - (&pod), + (&(pod.hdl_register_ops)) - (&pod), + (&(pod.hdl_deregister_ops)) - (&pod), + (&(pod.buf_register_ops)) - (&pod), + (&(pod.buf_deregister_ops)) - (&pod), + (&(pod.read_bytes)) - (&pod), + (&(pod.write_bytes)) - (&pod), + (&(pod.read_bw_bytes_per_sec)) - (&pod), + (&(pod.write_bw_bytes_per_sec)) - (&pod), + (&(pod.read_lat_avg_us)) - (&pod), + (&(pod.write_lat_avg_us)) - (&pod), + (&(pod.read_ops_per_sec)) - (&pod), + (&(pod.write_ops_per_sec)) - (&pod), + (&(pod.read_lat_sum_us)) - (&pod), + (&(pod.write_lat_sum_us)) - (&pod), + (&(pod.batch_submit_ops)) - (&pod), + (&(pod.batch_complete_ops)) - (&pod), + (&(pod.batch_setup_ops)) - (&pod), + (&(pod.batch_cancel_ops)) - (&pod), + (&(pod.batch_destroy_ops)) - (&pod), + (&(pod.batch_enqueued_ops)) - (&pod), + (&(pod.batch_posix_enqueued_ops)) - (&pod), + (&(pod.batch_processed_ops)) - (&pod), + (&(pod.batch_posix_processed_ops)) - (&pod), + (&(pod.batch_nvfs_submit_ops)) - (&pod), + (&(pod.batch_p2p_submit_ops)) - (&pod), + (&(pod.batch_aio_submit_ops)) - (&pod), + (&(pod.batch_iouring_submit_ops)) - (&pod), + (&(pod.batch_mixed_io_submit_ops)) - (&pod), + (&(pod.batch_total_submit_ops)) - (&pod), + (&(pod.batch_read_bytes)) - (&pod), + (&(pod.batch_write_bytes)) - (&pod), + (&(pod.batch_read_bw_bytes)) - (&pod), + (&(pod.batch_write_bw_bytes)) - (&pod), + (&(pod.batch_submit_lat_avg_us)) - (&pod), + (&(pod.batch_completion_lat_avg_us)) - (&pod), + (&(pod.batch_submit_ops_per_sec)) - (&pod), + (&(pod.batch_complete_ops_per_sec)) - (&pod), + (&(pod.batch_submit_lat_sum_us)) - (&pod), + (&(pod.batch_completion_lat_sum_us)) - (&pod), + (&(pod.last_batch_read_bytes)) - (&pod), + (&(pod.last_batch_write_bytes)) - (&pod), + ], + 'itemsize': sizeof(CUfileStatsLevel1_t), + }) + +stats_level1_dtype = _get_stats_level1_dtype_offsets() cdef class StatsLevel1: """Empty-initialize an instance of `CUfileStatsLevel1_t`. @@ -1198,13 +1426,25 @@ cdef class StatsLevel1: .. seealso:: `CUfileStatsLevel1_t` """ cdef: - readonly object _data + CUfileStatsLevel1_t *_ptr + object _owner + bint _owned + bint _readonly def __init__(self): - arr = _numpy.empty(1, dtype=stats_level1_dtype) - self._data = arr.view(_numpy.recarray) - assert self._data.itemsize == sizeof(CUfileStatsLevel1_t), \ - f"itemsize {self._data.itemsize} mismatches struct size {sizeof(CUfileStatsLevel1_t)}" + self._ptr = calloc(1, sizeof(CUfileStatsLevel1_t)) + if self._ptr == NULL: + raise MemoryError("Error allocating StatsLevel1") + self._owner = None + self._owned = True + self._readonly = False + + def __dealloc__(self): + cdef CUfileStatsLevel1_t *ptr + if self._owned and self._ptr != NULL: + ptr = self._ptr + self._ptr = NULL + free(ptr) def __repr__(self): return f"<{__name__}.StatsLevel1 object at {hex(id(self))}>" @@ -1212,461 +1452,579 @@ cdef class StatsLevel1: @property def ptr(self): """Get the pointer address to the data as Python :class:`int`.""" - return self._data.ctypes.data + return (self._ptr) cdef intptr_t _get_ptr(self): - """Get the pointer address to the data as Python :class:`int`.""" - return self._data.ctypes.data + return (self._ptr) def __int__(self): - return self._data.ctypes.data + return (self._ptr) def __eq__(self, other): + cdef StatsLevel1 other_ if not isinstance(other, StatsLevel1): return False - if self._data.size != other._data.size: - return False - if self._data.dtype != other._data.dtype: - return False - return bool((self._data == other._data).all()) + other_ = other + return (memcmp((self._ptr), (other_._ptr), sizeof(CUfileStatsLevel1_t)) == 0) + + def __setitem__(self, key, val): + if key == 0 and isinstance(val, _numpy.ndarray): + self._ptr = malloc(sizeof(CUfileStatsLevel1_t)) + if self._ptr == NULL: + raise MemoryError("Error allocating StatsLevel1") + memcpy(self._ptr, val.ctypes.data, sizeof(CUfileStatsLevel1_t)) + self._owner = None + self._owned = True + self._readonly = not val.flags.writeable + else: + setattr(self, key, val) @property def read_ops(self): - """: """ - return self._data.read_ops + """OpCounter: """ + return OpCounter.from_ptr(&(self._ptr[0].read_ops), self._readonly, self) @read_ops.setter def read_ops(self, val): - self._data.read_ops = val + if self._readonly: + raise ValueError("This StatsLevel1 instance is read-only") + cdef OpCounter val_ = val + memcpy(&(self._ptr[0].read_ops), (val_._get_ptr()), sizeof(CUfileOpCounter_t) * 1) @property def write_ops(self): - """: """ - return self._data.write_ops + """OpCounter: """ + return OpCounter.from_ptr(&(self._ptr[0].write_ops), self._readonly, self) @write_ops.setter def write_ops(self, val): - self._data.write_ops = val + if self._readonly: + raise ValueError("This StatsLevel1 instance is read-only") + cdef OpCounter val_ = val + memcpy(&(self._ptr[0].write_ops), (val_._get_ptr()), sizeof(CUfileOpCounter_t) * 1) @property def hdl_register_ops(self): - """: """ - return self._data.hdl_register_ops + """OpCounter: """ + return OpCounter.from_ptr(&(self._ptr[0].hdl_register_ops), self._readonly, self) @hdl_register_ops.setter def hdl_register_ops(self, val): - self._data.hdl_register_ops = val + if self._readonly: + raise ValueError("This StatsLevel1 instance is read-only") + cdef OpCounter val_ = val + memcpy(&(self._ptr[0].hdl_register_ops), (val_._get_ptr()), sizeof(CUfileOpCounter_t) * 1) @property def hdl_deregister_ops(self): - """: """ - return self._data.hdl_deregister_ops + """OpCounter: """ + return OpCounter.from_ptr(&(self._ptr[0].hdl_deregister_ops), self._readonly, self) @hdl_deregister_ops.setter def hdl_deregister_ops(self, val): - self._data.hdl_deregister_ops = val + if self._readonly: + raise ValueError("This StatsLevel1 instance is read-only") + cdef OpCounter val_ = val + memcpy(&(self._ptr[0].hdl_deregister_ops), (val_._get_ptr()), sizeof(CUfileOpCounter_t) * 1) @property def buf_register_ops(self): - """: """ - return self._data.buf_register_ops + """OpCounter: """ + return OpCounter.from_ptr(&(self._ptr[0].buf_register_ops), self._readonly, self) @buf_register_ops.setter def buf_register_ops(self, val): - self._data.buf_register_ops = val + if self._readonly: + raise ValueError("This StatsLevel1 instance is read-only") + cdef OpCounter val_ = val + memcpy(&(self._ptr[0].buf_register_ops), (val_._get_ptr()), sizeof(CUfileOpCounter_t) * 1) @property def buf_deregister_ops(self): - """: """ - return self._data.buf_deregister_ops + """OpCounter: """ + return OpCounter.from_ptr(&(self._ptr[0].buf_deregister_ops), self._readonly, self) @buf_deregister_ops.setter def buf_deregister_ops(self, val): - self._data.buf_deregister_ops = val - - @property - def read_bytes(self): - """int: """ - return int(self._data.read_bytes[0]) - - @read_bytes.setter - def read_bytes(self, val): - self._data.read_bytes = val - - @property - def write_bytes(self): - """int: """ - return int(self._data.write_bytes[0]) - - @write_bytes.setter - def write_bytes(self, val): - self._data.write_bytes = val - - @property - def read_bw_bytes_per_sec(self): - """int: """ - return int(self._data.read_bw_bytes_per_sec[0]) - - @read_bw_bytes_per_sec.setter - def read_bw_bytes_per_sec(self, val): - self._data.read_bw_bytes_per_sec = val - - @property - def write_bw_bytes_per_sec(self): - """int: """ - return int(self._data.write_bw_bytes_per_sec[0]) - - @write_bw_bytes_per_sec.setter - def write_bw_bytes_per_sec(self, val): - self._data.write_bw_bytes_per_sec = val - - @property - def read_lat_avg_us(self): - """int: """ - return int(self._data.read_lat_avg_us[0]) - - @read_lat_avg_us.setter - def read_lat_avg_us(self, val): - self._data.read_lat_avg_us = val - - @property - def write_lat_avg_us(self): - """int: """ - return int(self._data.write_lat_avg_us[0]) - - @write_lat_avg_us.setter - def write_lat_avg_us(self, val): - self._data.write_lat_avg_us = val - - @property - def read_ops_per_sec(self): - """int: """ - return int(self._data.read_ops_per_sec[0]) - - @read_ops_per_sec.setter - def read_ops_per_sec(self, val): - self._data.read_ops_per_sec = val - - @property - def write_ops_per_sec(self): - """int: """ - return int(self._data.write_ops_per_sec[0]) - - @write_ops_per_sec.setter - def write_ops_per_sec(self, val): - self._data.write_ops_per_sec = val - - @property - def read_lat_sum_us(self): - """int: """ - return int(self._data.read_lat_sum_us[0]) - - @read_lat_sum_us.setter - def read_lat_sum_us(self, val): - self._data.read_lat_sum_us = val - - @property - def write_lat_sum_us(self): - """int: """ - return int(self._data.write_lat_sum_us[0]) - - @write_lat_sum_us.setter - def write_lat_sum_us(self, val): - self._data.write_lat_sum_us = val + if self._readonly: + raise ValueError("This StatsLevel1 instance is read-only") + cdef OpCounter val_ = val + memcpy(&(self._ptr[0].buf_deregister_ops), (val_._get_ptr()), sizeof(CUfileOpCounter_t) * 1) @property def batch_submit_ops(self): - """: """ - return self._data.batch_submit_ops + """OpCounter: """ + return OpCounter.from_ptr(&(self._ptr[0].batch_submit_ops), self._readonly, self) @batch_submit_ops.setter def batch_submit_ops(self, val): - self._data.batch_submit_ops = val + if self._readonly: + raise ValueError("This StatsLevel1 instance is read-only") + cdef OpCounter val_ = val + memcpy(&(self._ptr[0].batch_submit_ops), (val_._get_ptr()), sizeof(CUfileOpCounter_t) * 1) @property def batch_complete_ops(self): - """: """ - return self._data.batch_complete_ops + """OpCounter: """ + return OpCounter.from_ptr(&(self._ptr[0].batch_complete_ops), self._readonly, self) @batch_complete_ops.setter def batch_complete_ops(self, val): - self._data.batch_complete_ops = val + if self._readonly: + raise ValueError("This StatsLevel1 instance is read-only") + cdef OpCounter val_ = val + memcpy(&(self._ptr[0].batch_complete_ops), (val_._get_ptr()), sizeof(CUfileOpCounter_t) * 1) @property def batch_setup_ops(self): - """: """ - return self._data.batch_setup_ops + """OpCounter: """ + return OpCounter.from_ptr(&(self._ptr[0].batch_setup_ops), self._readonly, self) @batch_setup_ops.setter def batch_setup_ops(self, val): - self._data.batch_setup_ops = val + if self._readonly: + raise ValueError("This StatsLevel1 instance is read-only") + cdef OpCounter val_ = val + memcpy(&(self._ptr[0].batch_setup_ops), (val_._get_ptr()), sizeof(CUfileOpCounter_t) * 1) @property def batch_cancel_ops(self): - """: """ - return self._data.batch_cancel_ops + """OpCounter: """ + return OpCounter.from_ptr(&(self._ptr[0].batch_cancel_ops), self._readonly, self) @batch_cancel_ops.setter def batch_cancel_ops(self, val): - self._data.batch_cancel_ops = val + if self._readonly: + raise ValueError("This StatsLevel1 instance is read-only") + cdef OpCounter val_ = val + memcpy(&(self._ptr[0].batch_cancel_ops), (val_._get_ptr()), sizeof(CUfileOpCounter_t) * 1) @property def batch_destroy_ops(self): - """: """ - return self._data.batch_destroy_ops + """OpCounter: """ + return OpCounter.from_ptr(&(self._ptr[0].batch_destroy_ops), self._readonly, self) @batch_destroy_ops.setter def batch_destroy_ops(self, val): - self._data.batch_destroy_ops = val + if self._readonly: + raise ValueError("This StatsLevel1 instance is read-only") + cdef OpCounter val_ = val + memcpy(&(self._ptr[0].batch_destroy_ops), (val_._get_ptr()), sizeof(CUfileOpCounter_t) * 1) @property def batch_enqueued_ops(self): - """: """ - return self._data.batch_enqueued_ops + """OpCounter: """ + return OpCounter.from_ptr(&(self._ptr[0].batch_enqueued_ops), self._readonly, self) @batch_enqueued_ops.setter def batch_enqueued_ops(self, val): - self._data.batch_enqueued_ops = val + if self._readonly: + raise ValueError("This StatsLevel1 instance is read-only") + cdef OpCounter val_ = val + memcpy(&(self._ptr[0].batch_enqueued_ops), (val_._get_ptr()), sizeof(CUfileOpCounter_t) * 1) @property def batch_posix_enqueued_ops(self): - """: """ - return self._data.batch_posix_enqueued_ops + """OpCounter: """ + return OpCounter.from_ptr(&(self._ptr[0].batch_posix_enqueued_ops), self._readonly, self) @batch_posix_enqueued_ops.setter def batch_posix_enqueued_ops(self, val): - self._data.batch_posix_enqueued_ops = val + if self._readonly: + raise ValueError("This StatsLevel1 instance is read-only") + cdef OpCounter val_ = val + memcpy(&(self._ptr[0].batch_posix_enqueued_ops), (val_._get_ptr()), sizeof(CUfileOpCounter_t) * 1) @property def batch_processed_ops(self): - """: """ - return self._data.batch_processed_ops + """OpCounter: """ + return OpCounter.from_ptr(&(self._ptr[0].batch_processed_ops), self._readonly, self) @batch_processed_ops.setter def batch_processed_ops(self, val): - self._data.batch_processed_ops = val + if self._readonly: + raise ValueError("This StatsLevel1 instance is read-only") + cdef OpCounter val_ = val + memcpy(&(self._ptr[0].batch_processed_ops), (val_._get_ptr()), sizeof(CUfileOpCounter_t) * 1) @property def batch_posix_processed_ops(self): - """: """ - return self._data.batch_posix_processed_ops + """OpCounter: """ + return OpCounter.from_ptr(&(self._ptr[0].batch_posix_processed_ops), self._readonly, self) @batch_posix_processed_ops.setter def batch_posix_processed_ops(self, val): - self._data.batch_posix_processed_ops = val + if self._readonly: + raise ValueError("This StatsLevel1 instance is read-only") + cdef OpCounter val_ = val + memcpy(&(self._ptr[0].batch_posix_processed_ops), (val_._get_ptr()), sizeof(CUfileOpCounter_t) * 1) @property def batch_nvfs_submit_ops(self): - """: """ - return self._data.batch_nvfs_submit_ops + """OpCounter: """ + return OpCounter.from_ptr(&(self._ptr[0].batch_nvfs_submit_ops), self._readonly, self) @batch_nvfs_submit_ops.setter def batch_nvfs_submit_ops(self, val): - self._data.batch_nvfs_submit_ops = val + if self._readonly: + raise ValueError("This StatsLevel1 instance is read-only") + cdef OpCounter val_ = val + memcpy(&(self._ptr[0].batch_nvfs_submit_ops), (val_._get_ptr()), sizeof(CUfileOpCounter_t) * 1) @property def batch_p2p_submit_ops(self): - """: """ - return self._data.batch_p2p_submit_ops + """OpCounter: """ + return OpCounter.from_ptr(&(self._ptr[0].batch_p2p_submit_ops), self._readonly, self) @batch_p2p_submit_ops.setter def batch_p2p_submit_ops(self, val): - self._data.batch_p2p_submit_ops = val + if self._readonly: + raise ValueError("This StatsLevel1 instance is read-only") + cdef OpCounter val_ = val + memcpy(&(self._ptr[0].batch_p2p_submit_ops), (val_._get_ptr()), sizeof(CUfileOpCounter_t) * 1) @property def batch_aio_submit_ops(self): - """: """ - return self._data.batch_aio_submit_ops + """OpCounter: """ + return OpCounter.from_ptr(&(self._ptr[0].batch_aio_submit_ops), self._readonly, self) @batch_aio_submit_ops.setter def batch_aio_submit_ops(self, val): - self._data.batch_aio_submit_ops = val + if self._readonly: + raise ValueError("This StatsLevel1 instance is read-only") + cdef OpCounter val_ = val + memcpy(&(self._ptr[0].batch_aio_submit_ops), (val_._get_ptr()), sizeof(CUfileOpCounter_t) * 1) @property def batch_iouring_submit_ops(self): - """: """ - return self._data.batch_iouring_submit_ops + """OpCounter: """ + return OpCounter.from_ptr(&(self._ptr[0].batch_iouring_submit_ops), self._readonly, self) @batch_iouring_submit_ops.setter def batch_iouring_submit_ops(self, val): - self._data.batch_iouring_submit_ops = val + if self._readonly: + raise ValueError("This StatsLevel1 instance is read-only") + cdef OpCounter val_ = val + memcpy(&(self._ptr[0].batch_iouring_submit_ops), (val_._get_ptr()), sizeof(CUfileOpCounter_t) * 1) @property def batch_mixed_io_submit_ops(self): - """: """ - return self._data.batch_mixed_io_submit_ops + """OpCounter: """ + return OpCounter.from_ptr(&(self._ptr[0].batch_mixed_io_submit_ops), self._readonly, self) @batch_mixed_io_submit_ops.setter def batch_mixed_io_submit_ops(self, val): - self._data.batch_mixed_io_submit_ops = val + if self._readonly: + raise ValueError("This StatsLevel1 instance is read-only") + cdef OpCounter val_ = val + memcpy(&(self._ptr[0].batch_mixed_io_submit_ops), (val_._get_ptr()), sizeof(CUfileOpCounter_t) * 1) @property def batch_total_submit_ops(self): - """: """ - return self._data.batch_total_submit_ops + """OpCounter: """ + return OpCounter.from_ptr(&(self._ptr[0].batch_total_submit_ops), self._readonly, self) @batch_total_submit_ops.setter def batch_total_submit_ops(self, val): - self._data.batch_total_submit_ops = val + if self._readonly: + raise ValueError("This StatsLevel1 instance is read-only") + cdef OpCounter val_ = val + memcpy(&(self._ptr[0].batch_total_submit_ops), (val_._get_ptr()), sizeof(CUfileOpCounter_t) * 1) + + @property + def read_bytes(self): + """int: """ + return self._ptr[0].read_bytes + + @read_bytes.setter + def read_bytes(self, val): + if self._readonly: + raise ValueError("This StatsLevel1 instance is read-only") + self._ptr[0].read_bytes = val + + @property + def write_bytes(self): + """int: """ + return self._ptr[0].write_bytes + + @write_bytes.setter + def write_bytes(self, val): + if self._readonly: + raise ValueError("This StatsLevel1 instance is read-only") + self._ptr[0].write_bytes = val + + @property + def read_bw_bytes_per_sec(self): + """int: """ + return self._ptr[0].read_bw_bytes_per_sec + + @read_bw_bytes_per_sec.setter + def read_bw_bytes_per_sec(self, val): + if self._readonly: + raise ValueError("This StatsLevel1 instance is read-only") + self._ptr[0].read_bw_bytes_per_sec = val + + @property + def write_bw_bytes_per_sec(self): + """int: """ + return self._ptr[0].write_bw_bytes_per_sec + + @write_bw_bytes_per_sec.setter + def write_bw_bytes_per_sec(self, val): + if self._readonly: + raise ValueError("This StatsLevel1 instance is read-only") + self._ptr[0].write_bw_bytes_per_sec = val + + @property + def read_lat_avg_us(self): + """int: """ + return self._ptr[0].read_lat_avg_us + + @read_lat_avg_us.setter + def read_lat_avg_us(self, val): + if self._readonly: + raise ValueError("This StatsLevel1 instance is read-only") + self._ptr[0].read_lat_avg_us = val + + @property + def write_lat_avg_us(self): + """int: """ + return self._ptr[0].write_lat_avg_us + + @write_lat_avg_us.setter + def write_lat_avg_us(self, val): + if self._readonly: + raise ValueError("This StatsLevel1 instance is read-only") + self._ptr[0].write_lat_avg_us = val + + @property + def read_ops_per_sec(self): + """int: """ + return self._ptr[0].read_ops_per_sec + + @read_ops_per_sec.setter + def read_ops_per_sec(self, val): + if self._readonly: + raise ValueError("This StatsLevel1 instance is read-only") + self._ptr[0].read_ops_per_sec = val + + @property + def write_ops_per_sec(self): + """int: """ + return self._ptr[0].write_ops_per_sec + + @write_ops_per_sec.setter + def write_ops_per_sec(self, val): + if self._readonly: + raise ValueError("This StatsLevel1 instance is read-only") + self._ptr[0].write_ops_per_sec = val + + @property + def read_lat_sum_us(self): + """int: """ + return self._ptr[0].read_lat_sum_us + + @read_lat_sum_us.setter + def read_lat_sum_us(self, val): + if self._readonly: + raise ValueError("This StatsLevel1 instance is read-only") + self._ptr[0].read_lat_sum_us = val + + @property + def write_lat_sum_us(self): + """int: """ + return self._ptr[0].write_lat_sum_us + + @write_lat_sum_us.setter + def write_lat_sum_us(self, val): + if self._readonly: + raise ValueError("This StatsLevel1 instance is read-only") + self._ptr[0].write_lat_sum_us = val @property def batch_read_bytes(self): """int: """ - return int(self._data.batch_read_bytes[0]) + return self._ptr[0].batch_read_bytes @batch_read_bytes.setter def batch_read_bytes(self, val): - self._data.batch_read_bytes = val + if self._readonly: + raise ValueError("This StatsLevel1 instance is read-only") + self._ptr[0].batch_read_bytes = val @property def batch_write_bytes(self): """int: """ - return int(self._data.batch_write_bytes[0]) + return self._ptr[0].batch_write_bytes @batch_write_bytes.setter def batch_write_bytes(self, val): - self._data.batch_write_bytes = val + if self._readonly: + raise ValueError("This StatsLevel1 instance is read-only") + self._ptr[0].batch_write_bytes = val @property def batch_read_bw_bytes(self): """int: """ - return int(self._data.batch_read_bw_bytes[0]) + return self._ptr[0].batch_read_bw_bytes @batch_read_bw_bytes.setter def batch_read_bw_bytes(self, val): - self._data.batch_read_bw_bytes = val + if self._readonly: + raise ValueError("This StatsLevel1 instance is read-only") + self._ptr[0].batch_read_bw_bytes = val @property def batch_write_bw_bytes(self): """int: """ - return int(self._data.batch_write_bw_bytes[0]) + return self._ptr[0].batch_write_bw_bytes @batch_write_bw_bytes.setter def batch_write_bw_bytes(self, val): - self._data.batch_write_bw_bytes = val + if self._readonly: + raise ValueError("This StatsLevel1 instance is read-only") + self._ptr[0].batch_write_bw_bytes = val @property def batch_submit_lat_avg_us(self): """int: """ - return int(self._data.batch_submit_lat_avg_us[0]) + return self._ptr[0].batch_submit_lat_avg_us @batch_submit_lat_avg_us.setter def batch_submit_lat_avg_us(self, val): - self._data.batch_submit_lat_avg_us = val + if self._readonly: + raise ValueError("This StatsLevel1 instance is read-only") + self._ptr[0].batch_submit_lat_avg_us = val @property def batch_completion_lat_avg_us(self): """int: """ - return int(self._data.batch_completion_lat_avg_us[0]) + return self._ptr[0].batch_completion_lat_avg_us @batch_completion_lat_avg_us.setter def batch_completion_lat_avg_us(self, val): - self._data.batch_completion_lat_avg_us = val + if self._readonly: + raise ValueError("This StatsLevel1 instance is read-only") + self._ptr[0].batch_completion_lat_avg_us = val @property def batch_submit_ops_per_sec(self): """int: """ - return int(self._data.batch_submit_ops_per_sec[0]) + return self._ptr[0].batch_submit_ops_per_sec @batch_submit_ops_per_sec.setter def batch_submit_ops_per_sec(self, val): - self._data.batch_submit_ops_per_sec = val + if self._readonly: + raise ValueError("This StatsLevel1 instance is read-only") + self._ptr[0].batch_submit_ops_per_sec = val @property def batch_complete_ops_per_sec(self): """int: """ - return int(self._data.batch_complete_ops_per_sec[0]) + return self._ptr[0].batch_complete_ops_per_sec @batch_complete_ops_per_sec.setter def batch_complete_ops_per_sec(self, val): - self._data.batch_complete_ops_per_sec = val + if self._readonly: + raise ValueError("This StatsLevel1 instance is read-only") + self._ptr[0].batch_complete_ops_per_sec = val @property def batch_submit_lat_sum_us(self): """int: """ - return int(self._data.batch_submit_lat_sum_us[0]) + return self._ptr[0].batch_submit_lat_sum_us @batch_submit_lat_sum_us.setter def batch_submit_lat_sum_us(self, val): - self._data.batch_submit_lat_sum_us = val + if self._readonly: + raise ValueError("This StatsLevel1 instance is read-only") + self._ptr[0].batch_submit_lat_sum_us = val @property def batch_completion_lat_sum_us(self): """int: """ - return int(self._data.batch_completion_lat_sum_us[0]) + return self._ptr[0].batch_completion_lat_sum_us @batch_completion_lat_sum_us.setter def batch_completion_lat_sum_us(self, val): - self._data.batch_completion_lat_sum_us = val + if self._readonly: + raise ValueError("This StatsLevel1 instance is read-only") + self._ptr[0].batch_completion_lat_sum_us = val @property def last_batch_read_bytes(self): """int: """ - return int(self._data.last_batch_read_bytes[0]) + return self._ptr[0].last_batch_read_bytes @last_batch_read_bytes.setter def last_batch_read_bytes(self, val): - self._data.last_batch_read_bytes = val + if self._readonly: + raise ValueError("This StatsLevel1 instance is read-only") + self._ptr[0].last_batch_read_bytes = val @property def last_batch_write_bytes(self): """int: """ - return int(self._data.last_batch_write_bytes[0]) + return self._ptr[0].last_batch_write_bytes @last_batch_write_bytes.setter def last_batch_write_bytes(self, val): - self._data.last_batch_write_bytes = val - - def __setitem__(self, key, val): - self._data[key] = val + if self._readonly: + raise ValueError("This StatsLevel1 instance is read-only") + self._ptr[0].last_batch_write_bytes = val @staticmethod def from_data(data): """Create an StatsLevel1 instance wrapping the given NumPy array. Args: - data (_numpy.ndarray): a 1D array of dtype `stats_level1_dtype` holding the data. + data (_numpy.ndarray): a single-element array of dtype `stats_level1_dtype` holding the data. """ - cdef StatsLevel1 obj = StatsLevel1.__new__(StatsLevel1) - if not isinstance(data, (_numpy.ndarray, _numpy.recarray)): - raise TypeError("data argument must be a NumPy ndarray") - if data.ndim != 1: - raise ValueError("data array must be 1D") - if data.dtype != stats_level1_dtype: - raise ValueError("data array must be of dtype stats_level1_dtype") - obj._data = data.view(_numpy.recarray) - - return obj + return __from_data(data, "stats_level1_dtype", stats_level1_dtype, StatsLevel1) @staticmethod - def from_ptr(intptr_t ptr, bint readonly=False): + def from_ptr(intptr_t ptr, bint readonly=False, object owner=None): """Create an StatsLevel1 instance wrapping the given pointer. Args: ptr (intptr_t): pointer address as Python :class:`int` to the data. + owner (object): The Python object that owns the pointer. If not provided, data will be copied. readonly (bool): whether the data is read-only (to the user). default is `False`. """ if ptr == 0: raise ValueError("ptr must not be null (0)") cdef StatsLevel1 obj = StatsLevel1.__new__(StatsLevel1) - cdef flag = _buffer.PyBUF_READ if readonly else _buffer.PyBUF_WRITE - cdef object buf = PyMemoryView_FromMemory( - ptr, sizeof(CUfileStatsLevel1_t), flag) - data = _numpy.ndarray((1,), buffer=buf, - dtype=stats_level1_dtype) - obj._data = data.view(_numpy.recarray) - + if owner is None: + obj._ptr = malloc(sizeof(CUfileStatsLevel1_t)) + if obj._ptr == NULL: + raise MemoryError("Error allocating StatsLevel1") + memcpy((obj._ptr), ptr, sizeof(CUfileStatsLevel1_t)) + obj._owner = None + obj._owned = True + else: + obj._ptr = ptr + obj._owner = owner + obj._owned = False + obj._readonly = readonly return obj -io_params_dtype = _numpy.dtype([ - ("mode", _numpy.int32, ), - ("u", _py_anon_pod2_dtype, ), - ("fh", _numpy.intp, ), - ("opcode", _numpy.int32, ), - ("cookie", _numpy.intp, ), - ], align=True) +cdef _get_io_params_dtype_offsets(): + cdef CUfileIOParams_t pod = CUfileIOParams_t() + return _numpy.dtype({ + 'names': ['mode', 'u', 'fh', 'opcode', 'cookie'], + 'formats': [_numpy.int32, _py_anon_pod2_dtype, _numpy.intp, _numpy.int32, _numpy.intp], + 'offsets': [ + (&(pod.mode)) - (&pod), + (&(pod.u)) - (&pod), + (&(pod.fh)) - (&pod), + (&(pod.opcode)) - (&pod), + (&(pod.cookie)) - (&pod), + ], + 'itemsize': sizeof(CUfileIOParams_t), + }) +io_params_dtype = _get_io_params_dtype_offsets() cdef class IOParams: """Empty-initialize an array of `CUfileIOParams_t`. @@ -1683,11 +2041,13 @@ cdef class IOParams: cdef: readonly object _data + + def __init__(self, size=1): arr = _numpy.empty(size, dtype=io_params_dtype) self._data = arr.view(_numpy.recarray) assert self._data.itemsize == sizeof(CUfileIOParams_t), \ - f"itemsize {self._data.itemsize} mismatches struct size {sizeof(CUfileIOParams_t)}" + f"itemsize {self._data.itemsize} mismatches struct size { sizeof(CUfileIOParams_t) }" def __repr__(self): if self._data.size > 1: @@ -1701,7 +2061,6 @@ cdef class IOParams: return self._data.ctypes.data cdef intptr_t _get_ptr(self): - """Get the pointer address to the data as Python :class:`int`.""" return self._data.ctypes.data def __int__(self): @@ -1714,13 +2073,10 @@ cdef class IOParams: return self._data.size def __eq__(self, other): - if not isinstance(other, IOParams): + cdef object self_data = self._data + if (not isinstance(other, IOParams)) or self_data.size != other._data.size or self_data.dtype != other._data.dtype: return False - if self._data.size != other._data.size: - return False - if self._data.dtype != other._data.dtype: - return False - return bool((self._data == other._data).all()) + return bool((self_data == other._data).all()) @property def mode(self): @@ -1776,13 +2132,16 @@ cdef class IOParams: self._data.cookie = val def __getitem__(self, key): + cdef ssize_t key_ + cdef ssize_t size if isinstance(key, int): + key_ = key size = self._data.size - if key >= size or key <= -(size+1): + if key_ >= size or key_ <= -(size+1): raise IndexError("index is out of bounds") - if key < 0: - key += size - return IOParams.from_data(self._data[key:key+1]) + if key_ < 0: + key_ += size + return IOParams.from_data(self._data[key_:key_+1]) out = self._data[key] if isinstance(out, _numpy.recarray) and out.dtype == io_params_dtype: return IOParams.from_data(out) @@ -1799,7 +2158,7 @@ cdef class IOParams: data (_numpy.ndarray): a 1D array of dtype `io_params_dtype` holding the data. """ cdef IOParams obj = IOParams.__new__(IOParams) - if not isinstance(data, (_numpy.ndarray, _numpy.recarray)): + if not isinstance(data, _numpy.ndarray): raise TypeError("data argument must be a NumPy ndarray") if data.ndim != 1: raise ValueError("data array must be 1D") @@ -1821,22 +2180,29 @@ cdef class IOParams: if ptr == 0: raise ValueError("ptr must not be null (0)") cdef IOParams obj = IOParams.__new__(IOParams) - cdef flag = _buffer.PyBUF_READ if readonly else _buffer.PyBUF_WRITE - cdef object buf = PyMemoryView_FromMemory( + cdef flag = cpython.buffer.PyBUF_READ if readonly else cpython.buffer.PyBUF_WRITE + cdef object buf = cpython.memoryview.PyMemoryView_FromMemory( ptr, sizeof(CUfileIOParams_t) * size, flag) - data = _numpy.ndarray((size,), buffer=buf, - dtype=io_params_dtype) + data = _numpy.ndarray(size, buffer=buf, dtype=io_params_dtype) obj._data = data.view(_numpy.recarray) return obj -stats_level2_dtype = _numpy.dtype([ - ("basic", stats_level1_dtype, ), - ("read_size_kb_hist", _numpy.uint64, (32,)), - ("write_size_kb_hist", _numpy.uint64, (32,)), - ], align=True) +cdef _get_stats_level2_dtype_offsets(): + cdef CUfileStatsLevel2_t pod = CUfileStatsLevel2_t() + return _numpy.dtype({ + 'names': ['basic', 'read_size_kb_hist', 'write_size_kb_hist'], + 'formats': [stats_level1_dtype, _numpy.uint64, _numpy.uint64], + 'offsets': [ + (&(pod.basic)) - (&pod), + (&(pod.read_size_kb_hist)) - (&pod), + (&(pod.write_size_kb_hist)) - (&pod), + ], + 'itemsize': sizeof(CUfileStatsLevel2_t), + }) +stats_level2_dtype = _get_stats_level2_dtype_offsets() cdef class StatsLevel2: """Empty-initialize an instance of `CUfileStatsLevel2_t`. @@ -1845,13 +2211,25 @@ cdef class StatsLevel2: .. seealso:: `CUfileStatsLevel2_t` """ cdef: - readonly object _data + CUfileStatsLevel2_t *_ptr + object _owner + bint _owned + bint _readonly def __init__(self): - arr = _numpy.empty(1, dtype=stats_level2_dtype) - self._data = arr.view(_numpy.recarray) - assert self._data.itemsize == sizeof(CUfileStatsLevel2_t), \ - f"itemsize {self._data.itemsize} mismatches struct size {sizeof(CUfileStatsLevel2_t)}" + self._ptr = calloc(1, sizeof(CUfileStatsLevel2_t)) + if self._ptr == NULL: + raise MemoryError("Error allocating StatsLevel2") + self._owner = None + self._owned = True + self._readonly = False + + def __dealloc__(self): + cdef CUfileStatsLevel2_t *ptr + if self._owned and self._ptr != NULL: + ptr = self._ptr + self._ptr = NULL + free(ptr) def __repr__(self): return f"<{__name__}.StatsLevel2 object at {hex(id(self))}>" @@ -1859,99 +2237,125 @@ cdef class StatsLevel2: @property def ptr(self): """Get the pointer address to the data as Python :class:`int`.""" - return self._data.ctypes.data + return (self._ptr) cdef intptr_t _get_ptr(self): - """Get the pointer address to the data as Python :class:`int`.""" - return self._data.ctypes.data + return (self._ptr) def __int__(self): - return self._data.ctypes.data + return (self._ptr) def __eq__(self, other): + cdef StatsLevel2 other_ if not isinstance(other, StatsLevel2): return False - if self._data.size != other._data.size: - return False - if self._data.dtype != other._data.dtype: - return False - return bool((self._data == other._data).all()) + other_ = other + return (memcmp((self._ptr), (other_._ptr), sizeof(CUfileStatsLevel2_t)) == 0) + + def __setitem__(self, key, val): + if key == 0 and isinstance(val, _numpy.ndarray): + self._ptr = malloc(sizeof(CUfileStatsLevel2_t)) + if self._ptr == NULL: + raise MemoryError("Error allocating StatsLevel2") + memcpy(self._ptr, val.ctypes.data, sizeof(CUfileStatsLevel2_t)) + self._owner = None + self._owned = True + self._readonly = not val.flags.writeable + else: + setattr(self, key, val) @property def basic(self): - """: """ - return self._data.basic + """StatsLevel1: """ + return StatsLevel1.from_ptr(&(self._ptr[0].basic), self._readonly, self) @basic.setter def basic(self, val): - self._data.basic = val + if self._readonly: + raise ValueError("This StatsLevel2 instance is read-only") + cdef StatsLevel1 val_ = val + memcpy(&(self._ptr[0].basic), (val_._get_ptr()), sizeof(CUfileStatsLevel1_t) * 1) @property def read_size_kb_hist(self): """~_numpy.uint64: (array of length 32).""" - return self._data.read_size_kb_hist + cdef view.array arr = view.array(shape=(32,), itemsize=sizeof(uint64_t), format="Q", mode="c", allocate_buffer=False) + arr.data = (&(self._ptr[0].read_size_kb_hist)) + return arr @read_size_kb_hist.setter def read_size_kb_hist(self, val): - self._data.read_size_kb_hist = val + if self._readonly: + raise ValueError("This StatsLevel2 instance is read-only") + cdef view.array arr = view.array(shape=(32,), itemsize=sizeof(uint64_t), format="Q", mode="c") + arr[:] = _numpy.asarray(val, dtype=_numpy.uint64) + memcpy((&(self._ptr[0].read_size_kb_hist)), (arr.data), sizeof(uint64_t) * len(val)) @property def write_size_kb_hist(self): """~_numpy.uint64: (array of length 32).""" - return self._data.write_size_kb_hist + cdef view.array arr = view.array(shape=(32,), itemsize=sizeof(uint64_t), format="Q", mode="c", allocate_buffer=False) + arr.data = (&(self._ptr[0].write_size_kb_hist)) + return arr @write_size_kb_hist.setter def write_size_kb_hist(self, val): - self._data.write_size_kb_hist = val - - def __setitem__(self, key, val): - self._data[key] = val + if self._readonly: + raise ValueError("This StatsLevel2 instance is read-only") + cdef view.array arr = view.array(shape=(32,), itemsize=sizeof(uint64_t), format="Q", mode="c") + arr[:] = _numpy.asarray(val, dtype=_numpy.uint64) + memcpy((&(self._ptr[0].write_size_kb_hist)), (arr.data), sizeof(uint64_t) * len(val)) @staticmethod def from_data(data): """Create an StatsLevel2 instance wrapping the given NumPy array. Args: - data (_numpy.ndarray): a 1D array of dtype `stats_level2_dtype` holding the data. + data (_numpy.ndarray): a single-element array of dtype `stats_level2_dtype` holding the data. """ - cdef StatsLevel2 obj = StatsLevel2.__new__(StatsLevel2) - if not isinstance(data, (_numpy.ndarray, _numpy.recarray)): - raise TypeError("data argument must be a NumPy ndarray") - if data.ndim != 1: - raise ValueError("data array must be 1D") - if data.dtype != stats_level2_dtype: - raise ValueError("data array must be of dtype stats_level2_dtype") - obj._data = data.view(_numpy.recarray) - - return obj + return __from_data(data, "stats_level2_dtype", stats_level2_dtype, StatsLevel2) @staticmethod - def from_ptr(intptr_t ptr, bint readonly=False): + def from_ptr(intptr_t ptr, bint readonly=False, object owner=None): """Create an StatsLevel2 instance wrapping the given pointer. Args: ptr (intptr_t): pointer address as Python :class:`int` to the data. + owner (object): The Python object that owns the pointer. If not provided, data will be copied. readonly (bool): whether the data is read-only (to the user). default is `False`. """ if ptr == 0: raise ValueError("ptr must not be null (0)") cdef StatsLevel2 obj = StatsLevel2.__new__(StatsLevel2) - cdef flag = _buffer.PyBUF_READ if readonly else _buffer.PyBUF_WRITE - cdef object buf = PyMemoryView_FromMemory( - ptr, sizeof(CUfileStatsLevel2_t), flag) - data = _numpy.ndarray((1,), buffer=buf, - dtype=stats_level2_dtype) - obj._data = data.view(_numpy.recarray) - + if owner is None: + obj._ptr = malloc(sizeof(CUfileStatsLevel2_t)) + if obj._ptr == NULL: + raise MemoryError("Error allocating StatsLevel2") + memcpy((obj._ptr), ptr, sizeof(CUfileStatsLevel2_t)) + obj._owner = None + obj._owned = True + else: + obj._ptr = ptr + obj._owner = owner + obj._owned = False + obj._readonly = readonly return obj -stats_level3_dtype = _numpy.dtype([ - ("detailed", stats_level2_dtype, ), - ("num_gpus", _numpy.uint32, ), - ("per_gpu_stats", per_gpu_stats_dtype, (16,)), - ], align=True) +cdef _get_stats_level3_dtype_offsets(): + cdef CUfileStatsLevel3_t pod = CUfileStatsLevel3_t() + return _numpy.dtype({ + 'names': ['detailed', 'num_gpus', 'per_gpu_stats'], + 'formats': [stats_level2_dtype, _numpy.uint32, per_gpu_stats_dtype], + 'offsets': [ + (&(pod.detailed)) - (&pod), + (&(pod.num_gpus)) - (&pod), + (&(pod.per_gpu_stats)) - (&pod), + ], + 'itemsize': sizeof(CUfileStatsLevel3_t), + }) +stats_level3_dtype = _get_stats_level3_dtype_offsets() cdef class StatsLevel3: """Empty-initialize an instance of `CUfileStatsLevel3_t`. @@ -1960,13 +2364,25 @@ cdef class StatsLevel3: .. seealso:: `CUfileStatsLevel3_t` """ cdef: - readonly object _data + CUfileStatsLevel3_t *_ptr + object _owner + bint _owned + bint _readonly def __init__(self): - arr = _numpy.empty(1, dtype=stats_level3_dtype) - self._data = arr.view(_numpy.recarray) - assert self._data.itemsize == sizeof(CUfileStatsLevel3_t), \ - f"itemsize {self._data.itemsize} mismatches struct size {sizeof(CUfileStatsLevel3_t)}" + self._ptr = calloc(1, sizeof(CUfileStatsLevel3_t)) + if self._ptr == NULL: + raise MemoryError("Error allocating StatsLevel3") + self._owner = None + self._owned = True + self._readonly = False + + def __dealloc__(self): + cdef CUfileStatsLevel3_t *ptr + if self._owned and self._ptr != NULL: + ptr = self._ptr + self._ptr = NULL + free(ptr) def __repr__(self): return f"<{__name__}.StatsLevel3 object at {hex(id(self))}>" @@ -1974,107 +2390,106 @@ cdef class StatsLevel3: @property def ptr(self): """Get the pointer address to the data as Python :class:`int`.""" - return self._data.ctypes.data + return (self._ptr) cdef intptr_t _get_ptr(self): - """Get the pointer address to the data as Python :class:`int`.""" - return self._data.ctypes.data + return (self._ptr) def __int__(self): - return self._data.ctypes.data + return (self._ptr) def __eq__(self, other): + cdef StatsLevel3 other_ if not isinstance(other, StatsLevel3): return False - if self._data.size != other._data.size: - return False - if self._data.dtype != other._data.dtype: - return False - return bool((self._data == other._data).all()) + other_ = other + return (memcmp((self._ptr), (other_._ptr), sizeof(CUfileStatsLevel3_t)) == 0) + + def __setitem__(self, key, val): + if key == 0 and isinstance(val, _numpy.ndarray): + self._ptr = malloc(sizeof(CUfileStatsLevel3_t)) + if self._ptr == NULL: + raise MemoryError("Error allocating StatsLevel3") + memcpy(self._ptr, val.ctypes.data, sizeof(CUfileStatsLevel3_t)) + self._owner = None + self._owned = True + self._readonly = not val.flags.writeable + else: + setattr(self, key, val) @property def detailed(self): - """: """ - return self._data.detailed + """StatsLevel2: """ + return StatsLevel2.from_ptr(&(self._ptr[0].detailed), self._readonly, self) @detailed.setter def detailed(self, val): - self._data.detailed = val - - @property - def num_gpus(self): - """int: """ - return int(self._data.num_gpus[0]) - - @num_gpus.setter - def num_gpus(self, val): - self._data.num_gpus = val + if self._readonly: + raise ValueError("This StatsLevel3 instance is read-only") + cdef StatsLevel2 val_ = val + memcpy(&(self._ptr[0].detailed), (val_._get_ptr()), sizeof(CUfileStatsLevel2_t) * 1) @property def per_gpu_stats(self): - """per_gpu_stats_dtype: (array of length 16).""" - return self._data.per_gpu_stats + """PerGpuStats: """ + return PerGpuStats.from_ptr(&(self._ptr[0].per_gpu_stats), 16, self._readonly) @per_gpu_stats.setter def per_gpu_stats(self, val): - self._data.per_gpu_stats = val + if self._readonly: + raise ValueError("This StatsLevel3 instance is read-only") + cdef PerGpuStats val_ = val + if len(val) != 16: + raise ValueError(f"Expected length 16 for field per_gpu_stats, got {len(val)}") + memcpy(&(self._ptr[0].per_gpu_stats), (val_._get_ptr()), sizeof(CUfilePerGpuStats_t) * 16) - def __setitem__(self, key, val): - self._data[key] = val + @property + def num_gpus(self): + """int: """ + return self._ptr[0].num_gpus + + @num_gpus.setter + def num_gpus(self, val): + if self._readonly: + raise ValueError("This StatsLevel3 instance is read-only") + self._ptr[0].num_gpus = val @staticmethod def from_data(data): """Create an StatsLevel3 instance wrapping the given NumPy array. Args: - data (_numpy.ndarray): a 1D array of dtype `stats_level3_dtype` holding the data. + data (_numpy.ndarray): a single-element array of dtype `stats_level3_dtype` holding the data. """ - cdef StatsLevel3 obj = StatsLevel3.__new__(StatsLevel3) - if not isinstance(data, (_numpy.ndarray, _numpy.recarray)): - raise TypeError("data argument must be a NumPy ndarray") - if data.ndim != 1: - raise ValueError("data array must be 1D") - if data.dtype != stats_level3_dtype: - raise ValueError("data array must be of dtype stats_level3_dtype") - obj._data = data.view(_numpy.recarray) - - return obj + return __from_data(data, "stats_level3_dtype", stats_level3_dtype, StatsLevel3) @staticmethod - def from_ptr(intptr_t ptr, bint readonly=False): + def from_ptr(intptr_t ptr, bint readonly=False, object owner=None): """Create an StatsLevel3 instance wrapping the given pointer. Args: ptr (intptr_t): pointer address as Python :class:`int` to the data. + owner (object): The Python object that owns the pointer. If not provided, data will be copied. readonly (bool): whether the data is read-only (to the user). default is `False`. """ if ptr == 0: raise ValueError("ptr must not be null (0)") cdef StatsLevel3 obj = StatsLevel3.__new__(StatsLevel3) - cdef flag = _buffer.PyBUF_READ if readonly else _buffer.PyBUF_WRITE - cdef object buf = PyMemoryView_FromMemory( - ptr, sizeof(CUfileStatsLevel3_t), flag) - data = _numpy.ndarray((1,), buffer=buf, - dtype=stats_level3_dtype) - obj._data = data.view(_numpy.recarray) - + if owner is None: + obj._ptr = malloc(sizeof(CUfileStatsLevel3_t)) + if obj._ptr == NULL: + raise MemoryError("Error allocating StatsLevel3") + memcpy((obj._ptr), ptr, sizeof(CUfileStatsLevel3_t)) + obj._owner = None + obj._owned = True + else: + obj._ptr = ptr + obj._owner = owner + obj._owned = False + obj._readonly = readonly return obj -# Hack: Overwrite the generated descr_dtype, which NumPy deduced the offset wrong. -descr_dtype = _numpy.dtype({ - "names": ['type', 'handle', 'fs_ops'], - "formats": [_numpy.int32, _py_anon_pod1_dtype, _numpy.intp], - "offsets": [0, 8, 16], -}, align=True) - -# Hack: Overwrite the generated io_params_dtype, which NumPy deduced the offset wrong. -io_params_dtype = _numpy.dtype({ - "names": ['mode', 'u', 'fh', 'opcode', 'cookie'], - "formats": [_numpy.int32, _py_anon_pod2_dtype, _numpy.intp, _numpy.int32, _numpy.intp], - "offsets": [0, 8, 40, 48, 56], -}, align=True) - ############################################################################### # Enum diff --git a/cuda_bindings/cuda/bindings/cycufile.pxd b/cuda_bindings/cuda/bindings/cycufile.pxd index c57b18b95d..aa8ea93d48 100644 --- a/cuda_bindings/cuda/bindings/cycufile.pxd +++ b/cuda_bindings/cuda/bindings/cycufile.pxd @@ -30,8 +30,10 @@ cdef extern from "": ctypedef sockaddr sockaddr_t -cdef extern from '': + + # enums +cdef extern from '': ctypedef enum CUfileOpError: CU_FILE_SUCCESS CU_FILE_DRIVER_NOT_INITIALIZED @@ -83,6 +85,7 @@ cdef extern from '': CU_FILE_BATCH_NOCOMPAT_ERROR CU_FILE_IO_MAX_ERROR +cdef extern from '': ctypedef enum CUfileDriverStatusFlags_t: CU_FILE_LUSTRE_SUPPORTED CU_FILE_WEKAFS_SUPPORTED @@ -97,25 +100,30 @@ cdef extern from '': CU_FILE_NVME_P2P_SUPPORTED CU_FILE_SCATEFS_SUPPORTED +cdef extern from '': ctypedef enum CUfileDriverControlFlags_t: CU_FILE_USE_POLL_MODE CU_FILE_ALLOW_COMPAT_MODE +cdef extern from '': ctypedef enum CUfileFeatureFlags_t: CU_FILE_DYN_ROUTING_SUPPORTED CU_FILE_BATCH_IO_SUPPORTED CU_FILE_STREAMS_SUPPORTED CU_FILE_PARALLEL_IO_SUPPORTED +cdef extern from '': ctypedef enum CUfileFileHandleType: CU_FILE_HANDLE_TYPE_OPAQUE_FD CU_FILE_HANDLE_TYPE_OPAQUE_WIN32 CU_FILE_HANDLE_TYPE_USERSPACE_FS +cdef extern from '': ctypedef enum CUfileOpcode_t: CUFILE_READ CUFILE_WRITE +cdef extern from '': ctypedef enum CUfileStatus_t: CUFILE_WAITING CUFILE_PENDING @@ -125,9 +133,11 @@ cdef extern from '': CUFILE_TIMEOUT CUFILE_FAILED +cdef extern from '': ctypedef enum CUfileBatchMode_t: CUFILE_BATCH +cdef extern from '': ctypedef enum CUFileSizeTConfigParameter_t: CUFILE_PARAM_PROFILE_STATS CUFILE_PARAM_EXECUTION_MAX_IO_QUEUE_DEPTH @@ -142,6 +152,7 @@ cdef extern from '': CUFILE_PARAM_POLLTHRESHOLD_SIZE_KB CUFILE_PARAM_PROPERTIES_BATCH_IO_TIMEOUT_MS +cdef extern from '': ctypedef enum CUFileBoolConfigParameter_t: CUFILE_PARAM_PROPERTIES_USE_POLL_MODE CUFILE_PARAM_PROPERTIES_ALLOW_COMPAT_MODE @@ -156,53 +167,73 @@ cdef extern from '': CUFILE_PARAM_SKIP_TOPOLOGY_DETECTION CUFILE_PARAM_STREAM_MEMOPS_BYPASS +cdef extern from '': ctypedef enum CUFileStringConfigParameter_t: CUFILE_PARAM_LOGGING_LEVEL CUFILE_PARAM_ENV_LOGFILE_PATH CUFILE_PARAM_LOG_DIR +cdef extern from '': ctypedef enum CUFileArrayConfigParameter_t: CUFILE_PARAM_POSIX_POOL_SLAB_SIZE_KB CUFILE_PARAM_POSIX_POOL_SLAB_COUNT # types +cdef extern from '': ctypedef void* CUfileHandle_t 'CUfileHandle_t' + +cdef extern from '': ctypedef void* CUfileBatchHandle_t 'CUfileBatchHandle_t' + +cdef extern from '': ctypedef struct CUfileError_t 'CUfileError_t': CUfileOpError err CUresult cu_err - cdef struct _anon_pod0 '_anon_pod0': - unsigned int major_version - unsigned int minor_version - size_t poll_thresh_size - size_t max_direct_io_size - unsigned int dstatusflags - unsigned int dcontrolflags + +cdef struct _anon_pod0 '_anon_pod0': + unsigned int major_version + unsigned int minor_version + size_t poll_thresh_size + size_t max_direct_io_size + unsigned int dstatusflags + unsigned int dcontrolflags + +cdef extern from '': ctypedef struct cufileRDMAInfo_t 'cufileRDMAInfo_t': int version int desc_len char* desc_str + +cdef extern from '': ctypedef struct CUfileFSOps_t 'CUfileFSOps_t': char* (*fs_type)(void*) int (*getRDMADeviceList)(void*, sockaddr_t**) int (*getRDMADevicePriority)(void*, char*, size_t, loff_t, sockaddr_t*) ssize_t (*read)(void*, char*, size_t, loff_t, cufileRDMAInfo_t*) ssize_t (*write)(void*, const char*, size_t, loff_t, cufileRDMAInfo_t*) - cdef union _anon_pod1 '_anon_pod1': - int fd - void* handle - cdef struct _anon_pod3 '_anon_pod3': - void* devPtr_base - off_t file_offset - off_t devPtr_offset - size_t size + +cdef union _anon_pod1 '_anon_pod1': + int fd + void* handle + +cdef struct _anon_pod3 '_anon_pod3': + void* devPtr_base + off_t file_offset + off_t devPtr_offset + size_t size + +cdef extern from '': ctypedef struct CUfileIOEvents_t 'CUfileIOEvents_t': void* cookie CUfileStatus_t status size_t ret + +cdef extern from '': ctypedef struct CUfileOpCounter_t 'CUfileOpCounter_t': uint64_t ok uint64_t err + +cdef extern from '': ctypedef struct CUfilePerGpuStats_t 'CUfilePerGpuStats_t': char uuid[16] uint64_t read_bytes @@ -234,6 +265,8 @@ cdef extern from '': uint64_t n_mmap_err uint64_t n_mmap_free uint64_t reg_bytes + +cdef extern from '': ctypedef struct CUfileDrvProps_t 'CUfileDrvProps_t': _anon_pod0 nvfs unsigned int fflags @@ -242,12 +275,17 @@ cdef extern from '': unsigned int max_device_pinned_mem_size unsigned int max_batch_io_size unsigned int max_batch_io_timeout_msecs + +cdef extern from '': ctypedef struct CUfileDescr_t 'CUfileDescr_t': CUfileFileHandleType type _anon_pod1 handle CUfileFSOps_t* fs_ops - cdef union _anon_pod2 '_anon_pod2': - _anon_pod3 batch + +cdef union _anon_pod2 '_anon_pod2': + _anon_pod3 batch + +cdef extern from '': ctypedef struct CUfileStatsLevel1_t 'CUfileStatsLevel1_t': CUfileOpCounter_t read_ops CUfileOpCounter_t write_ops @@ -292,22 +330,29 @@ cdef extern from '': uint64_t batch_completion_lat_sum_us uint64_t last_batch_read_bytes uint64_t last_batch_write_bytes + +cdef extern from '': ctypedef struct CUfileIOParams_t 'CUfileIOParams_t': CUfileBatchMode_t mode _anon_pod2 u CUfileHandle_t fh CUfileOpcode_t opcode void* cookie + +cdef extern from '': ctypedef struct CUfileStatsLevel2_t 'CUfileStatsLevel2_t': CUfileStatsLevel1_t basic uint64_t read_size_kb_hist[32] uint64_t write_size_kb_hist[32] + +cdef extern from '': ctypedef struct CUfileStatsLevel3_t 'CUfileStatsLevel3_t': CUfileStatsLevel2_t detailed uint32_t num_gpus CUfilePerGpuStats_t per_gpu_stats[16] + cdef extern from *: """ // This is the missing piece we need to supply to help Cython & C++ compilers. diff --git a/cuda_bindings/docs/source/release/13.X.Y-notes.rst b/cuda_bindings/docs/source/release/13.X.Y-notes.rst index 34e8303cf7..6f4a071cb4 100644 --- a/cuda_bindings/docs/source/release/13.X.Y-notes.rst +++ b/cuda_bindings/docs/source/release/13.X.Y-notes.rst @@ -3,7 +3,7 @@ .. module:: cuda.bindings -``cuda-bindings`` 13.X.Y Release notes +````cuda-bindings```` 13.X.Y Release notes ====================================== @@ -15,10 +15,18 @@ Highlights Bug fixes --------- +Backward incompatible changes +----------------------------- +In ``cuda.bindings.cufile``, the following class members are no longer Numpy arrays, but are Python ``memoryview`` objects. To get Numpy array behavior, pass the result to ``numpy.asarray``: + +- ``StatsLevel2.read_size_kb_hist`` +- ``StatsLevel2.write_size_kb_hist`` + +Additionally, ``cufile.StatsLevel3.per_gpu_stats`` no longer returns a raw array, instead it returns a ``cufile.PerGpuStats`` array. Known issues ------------ -* Updating from older versions (v12.6.2.post1 and below) via ``pip install -U cuda-python`` might not work. Please do a clean re-installation by uninstalling ``pip uninstall -y cuda-python`` followed by installing ``pip install cuda-python``. -* The graphics APIs in ``cuda.bindings.runtime`` are inadvertently disabled in 13.0.2. Users needing these APIs should update to 13.0.3. +* Updating from older versions (v12.6.2.post1 and below) via ````pip install -U cuda-python```` might not work. Please do a clean re-installation by uninstalling ````pip uninstall -y cuda-python```` followed by installing ````pip install cuda-python````. +* The graphics APIs in ````cuda.bindings.runtime```` are inadvertently disabled in 13.0.2. Users needing these APIs should update to 13.0.3. diff --git a/cuda_bindings/tests/test_cufile.py b/cuda_bindings/tests/test_cufile.py index 8ac12dfc7c..fe78244f13 100644 --- a/cuda_bindings/tests/test_cufile.py +++ b/cuda_bindings/tests/test_cufile.py @@ -12,6 +12,7 @@ from functools import cache import cuda.bindings.driver as cuda +import numpy as np import pytest # Configure logging to show INFO level and above @@ -2097,9 +2098,8 @@ def test_get_stats_l2(): cufile.get_stats_l2(stats.ptr) # Verify L2 histogram fields contain data - # Access numpy array fields: histograms are numpy arrays - read_hist_total = int(stats.read_size_kb_hist.sum()) - write_hist_total = int(stats.write_size_kb_hist.sum()) + read_hist_total = int(np.asarray(stats.read_size_kb_hist).sum()) + write_hist_total = int(np.asarray(stats.write_size_kb_hist).sum()) assert read_hist_total > 0 or write_hist_total > 0, "Expected L2 histogram data" # L2 also contains L1 basic stats - verify using OpCounter class @@ -2212,15 +2212,14 @@ def test_get_stats_l3(): # Access per-GPU stats using PerGpuStats class # stats.per_gpu_stats has shape (1, 16), we need to get [0] first to get the (16,) array # then slice [i:i+1] to get a 1-d array view (required by from_data) - per_gpu_array = stats.per_gpu_stats[0] # Get the (16,) array - gpu_stats = cufile.PerGpuStats.from_data(per_gpu_array[i : i + 1]) + gpu_stats = stats.per_gpu_stats[i] # Get the (16,) array if gpu_stats.n_total_reads > 0 or gpu_stats.read_bytes > 0: gpu_with_data = True break # L3 also contains L2 detailed stats (which includes L1 basic stats) detailed_stats = cufile.StatsLevel2.from_data(stats.detailed) - read_hist_total = int(detailed_stats.read_size_kb_hist.sum()) + read_hist_total = int(np.asarray(detailed_stats.read_size_kb_hist).sum()) logging.info( f"L3 Stats: num_gpus={num_gpus}, gpu_with_data={gpu_with_data}, detailed_read_hist={read_hist_total}"