Statistics#
KvikIO can report what a run did: how many operations, how many bytes, how long it was busy, and which backend carried the work.
A monitor accumulates for as long as it exists, and get()
reads the totals so far.
import cupy
import kvikio
def main(path):
# Statistics are off until a monitor exists. This one counts every operation below.
monitor = kvikio.SummaryMonitor()
a = cupy.arange(100)
f = kvikio.CuFile(path, "w")
# Write whole array to file
f.write(a)
f.close()
b = cupy.empty_like(a)
f = kvikio.CuFile(path, "r")
# Read whole array from file
f.read(b)
assert all(a == b)
# Use contexmanager
c = cupy.empty_like(a)
with kvikio.CuFile(path, "r") as f:
f.read(c)
assert all(a == c)
# Non-blocking read
d = cupy.empty_like(a)
with kvikio.CuFile(path, "r") as f:
future1 = f.pread(d[:50])
future2 = f.pread(d[50:], file_offset=d[:50].nbytes)
future1.get() # Wait for first read
future2.get() # Wait for second read
assert all(a == d)
# Five calls, one write and four reads, however many reads KvikIO issued underneath.
# The two `pread()`s above are one operation each, not one per thread-pool task.
summary = monitor.get()
assert summary.num_ops == 5
print(summary)
print(f"{summary.bytes_transferred} bytes in {summary.num_ops} operations")
if __name__ == "__main__":
main("/tmp/kvikio-hello-world-file")
Printing a summary, or calling report(), gives a report meant to be read
by a person:
KvikIO I/O summary
wall time 238.22 ms
busy time 5.66 ms (2.38 % of the wall time)
busy bandwidth 565.50 kB/s
operations 5 (4 read, 1 write)
mean duration 1.13 ms
bytes 3.12 KiB of 3.12 KiB requested (2.34 KiB read, 800 B written)
errors 0
backend POSIX 3.12 KiB in 5 ops, 5.66 ms, 565.50 kB/s
backend GDS unused
backend MMAP unused
backend REMOTE_HTTP unused
backend REMOTE_HDFS unused
Busy time and bandwidth#
Busy time is the union of the operations’ spans, so overlapping work counts once and the gaps between calls count as idle.
Busy bandwidth divides the bytes by that rather than by the wall time. A program that
reads for 10 ms and then computes for 90 ms is doing I/O at its storage’s speed for a
tenth of its life, and dividing by the wall time would report it as ten times slower than
it is. Multiply by busy_fraction to recover the whole-span rate.
Getting a summary out of the process#
to_json() gives the same content for anything that would rather
parse it, with the timestamps against the wall clock so another program can line the
summary up with its own log.
serialize() and deserialize() move a summary
between processes. They are exact, so one that has been through a pipe is still a valid
previous for since(), and they are what pickling uses.
raw = summary.serialize()
assert kvikio.Summary.deserialize(raw) == summary
The bytes are not a wire format. They are a copy of the C++ struct, readable only by the same architecture and the same build of KvikIO, and anything else is refused rather than misread.
Summaries are not additive. Most fields could be added across processes, but busy time cannot, since two processes are genuinely busy at the same moment.