This guide is for contributors who need to understand or troubleshoot qrypt at runtime. It starts with the common workflows, then lists the lower-level commands and endpoints.
The debug tools collect structured evidence from a running qrypt process. Most
read-only commands return JSON and include a diagnostics section when qrypt can
identify likely problems.
Debug output is read-only unless a command is clearly named test or bench.
Secrets are masked by the driver and config layers, but local paths, filenames,
remote IDs, and short error messages may still be sensitive. Please review any
bundle before sharing it.
Use scripts/dev/debug-mount.sh when you need to run one probe test against
a real qrypt mount from the current checkout, but do not need to keep the mount
running for interactive investigation. This is useful for repeatable driver and
VFS regression checks such as batchupload, batchmove, and fs: the script
builds qrypt, creates an isolated temporary mount point and debug socket, waits
for both to become ready, runs the selected test, then unmounts and removes the
temporary local files. Read caches, thumbnail caches, upload staging, driver
state, logs, and temporary data are also redirected below that temporary
directory instead of using the config's [storage] paths. For valid test
arguments, standard output contains only the qrypt probe test result, so it
can be captured or compared directly.
The selected mount must exist in the config and have test_enabled = true.
The script supports single-mount tests only; use the normal qrypt mount and
qrypt probe test xfer commands when testing transfers between two mounts. Use
the manual workflow later in this guide when the mount must remain available
for collect, watch, raw endpoints, or an interactive reproduction.
Run a test with the repository qrypt.toml:
./scripts/dev/debug-mount.sh quark-test batchmove --count 50 --size 4k
./scripts/dev/debug-mount.sh quark-test read --size 256m --samples 3 --cache-mode both --pattern both --seek-count 10 --seek-size 1m
./scripts/dev/debug-mount.sh quark-test read --size 256m --cache-mode cold --pattern seek --seek-scenario prefetchPass a different config before the mount name:
./scripts/dev/debug-mount.sh --config ./path/to/qrypt.toml quark-test fs --size 30mList all available test cases or inspect the wrapper options:
./scripts/dev/debug-mount.sh quark-test --help
./scripts/dev/debug-mount.sh --helpThere are three layers worth keeping separate:
- Driver: talks to the cloud provider.
- VFS: tracks pending writes, staging files, upload queues, delayed deletes, read cache, and namespace mounts.
- Debug socket: exposes runtime state through local
/v1/...endpoints.
Most runtime debug commands need a mount scope:
--mount quark-testRepeat --mount when two or more mounts are relevant:
--mount local --mount quarkUse --all-mounts only when the whole namespace is part of the issue. It can
produce much larger reports.
Start qrypt with a local debug socket:
go run ./cmd/qrypt mount --config ./qrypt.tomlMount always starts the built-in per-config local endpoint. Unix-like systems use a filesystem socket; Windows uses a named pipe. Then connect using the normal command:
go run ./cmd/qrypt debug collect --mount quark-testWhen the same config is discoverable or passed with --config, debug commands
resolve the built-in endpoint automatically:
go run ./cmd/qrypt debug collect --config ./qrypt.toml --mount quark-testThen collect one focused report:
go run ./cmd/qrypt debug collect --mount quark-testIf a specific path is involved, include it:
go run ./cmd/qrypt debug collect /quark-test/example.bin --mount quark-testOn Unix-like systems the socket file is created with 0600 permissions.
qrypt refuses to replace a live endpoint and removes stale Unix socket files
during startup. Windows named pipes use the operating system's pipe access
control instead of filesystem permissions.
Android and remote-device debugging uses the Android app's explicit loopback
debug server and tools such as adb forward; the CLI mount endpoint is local
and does not expose the debug API on the LAN.
When the issue involves FUSE callbacks, retries, upload failures, or timing, enable debug logs in the config:
[logging]
log_level = "debug"
log_file = "~/.qrypt/qrypt.log"
error_file = "~/.qrypt/qrypt-error.log"The debug log includes callback names, paths, return codes, durations, and sampled runtime messages. The error log is usually the fastest place to look for warnings and failures after a reproduction.
On Windows, qrypt uses a named pipe derived from the per-config runtime identity; the CLI does not require an endpoint path:
qrypt.exe mount --config .\qrypt.tomlKeep the mount process running, then use the same path in another PowerShell window:
qrypt.exe debug collect --mount local
qrypt.exe debug collect /local/example.txt --mount local
qrypt.exe debug raw healthThe runtime identity is created from the resolved qrypt configuration and is shared by mount, debug, probe, and control commands. Named pipe access is restricted by the Windows pipe security descriptor; no password is required for the default same-user local workflow.
| Goal | Command |
|---|---|
| Collect one structured diagnosis | debug collect --mount NAME |
| Sample state while reproducing a timing issue | debug watch --mount NAME |
| Create a shareable zip artifact | debug bundle --mount NAME --out FILE |
| Query one socket endpoint | debug raw ENDPOINT |
| Verify driver credentials | probe test auth --mount NAME |
| Verify driver CRUD behavior | probe test crud --mount NAME |
| Verify VFS upload behavior | probe test fs --mount NAME |
| Measure full mounted-file read latency and throughput | probe test read --mount NAME --mount-point PATH |
| Verify same-directory batch upload caching | probe test batchupload --mount NAME |
| Measure same-mount batch moves | probe test batchmove --mount NAME |
| Verify resumable upload recovery | probe test resume --mount NAME |
| Run the full contract suite | probe test contract --mount NAME |
| Compare performance or regressions | probe bench ... |
probe test contract runs the complete suite against the named mount
(auth, contract, crud, fs, instantupload, resume, multipart) and requires a
running mount server (qrypt mount --config ./qrypt.toml). The built-in
per-config probe socket is resolved automatically. The mount must
have test_enabled = true in its [[mounts]] block; unlisted mounts are
rejected with 403. This is the same suite the Contract Tests CI workflow
runs nightly against real providers.
Use collect first when you are unsure. Use raw when you already know which
endpoint you need. Use watch when the issue only appears during a time window,
for example upload retries or stale reads.
This is the most useful workflow for staging, pending upload, and slow upload problems.
If you need to trigger a controlled VFS write, run:
go run ./cmd/qrypt probe test fs --mount quark-test --size 30mprobe test fs creates temporary files through the VFS, writes data, flushes,
waits for upload, reads back, and removes the test data. It writes to the
selected mount, so use it only when temporary remote writes are acceptable.
For small-file and move workloads, run:
go run ./cmd/qrypt probe test batchupload --mount quark-test --count 50 --size 4k
go run ./cmd/qrypt probe test batchmove --mount quark-test --count 50 --size 4kbatchupload writes and flushes every file through VFS, waits for the upload
queue to become idle, verifies the directory and sampled content, and reports
the parent-directory cache miss/hit/shared counts. When upload history is
available, more than one parent-cache miss fails the test. batchmove first
uploads the source batch and waits for it to become idle, then measures only
the sequential VFS Rename calls before verifying the empty source and the
destination data. Both tests clean their randomized temporary directories.
To measure the complete mounted-file read path, including the OS filesystem, FUSE/macFUSE adapter, VFS read cache and remote driver, run:
./scripts/dev/debug-mount.sh quark-test read --size 256m --samples 3 --cache-mode both --pattern both --seek-count 10 --seek-size 1mThe wrapper supplies its temporary mount point automatically. When invoking qrypt directly, pass the real FUSE mount directory explicitly:
go run ./cmd/qrypt probe test read --mount quark-test --mount-point /Volumes/qrypt --size 256m --block-size 1m --samples 3 --cache-mode both --pattern both --seek-count 10 --seek-size 1mThe test creates and uploads a deterministic temporary file before timing,
then reads it with os.Open through the supplied mount point. Setup and cleanup
are excluded from the read measurements. cold clears qrypt's durable and
in-memory read caches; every measured read requests OS cache bypass/drop where
supported, while warm keeps qrypt's read-cache state from the preceding read.
both reports both modes. Each measurement includes open and time-to-first-byte
latency, total and steady throughput, peak throughput over a one-second window,
data-bearing read-call latency percentiles, cache hit/miss deltas, VFS read-call
count, byte count, and content-hash validation. The test fails when the supplied
path bypasses qrypt (for example, when --mount-point names the backing
directory instead of the FUSE mount). metrics_truncated marks a measurement
whose phase-level history exceeded the bounded runtime history. The test does
not use a fixed network-speed threshold.
--pattern sequential preserves the full-file test above and remains the
default. --pattern seek opens the mounted file, seeks to widely spaced
offsets, and loads --seek-size bytes at each offset;
--pattern both runs both patterns. Seek output separates the local seek call
(seek_us) from the following data load (seek_load_us) and their combined
latency (seek_total_us). seek_summary reports cold/warm P50, P95, P99, max,
and load throughput. Cold probes clear qrypt's read cache before every offset;
warm probes read the same offset immediately after it has been primed. The
result also requires vfs_offset_match=true, proving that the measured FUSE
read reached qrypt at the requested seek offset. The defaults are 10 probes of
1 MiB each, with a maximum of 32 probes.
Use --seek-scenario to control the load present when each seek starts:
isolatedis the default baseline and issues no intentional competing read.prefetchfirst reads sequential chunks on the same file descriptor, waits until qrypt reports an active background prefetch for that file, then seeks on that descriptor.concurrentstarts a bounded sequential read on a second file descriptor, waits until qrypt reports its active chunk load or resulting prefetch, then seeks on the first descriptor.
For example:
./scripts/dev/debug-mount.sh quark-test read \
--size 256m --cache-mode cold --pattern seek \
--seek-scenario concurrent --seek-warmup-chunks 2 \
--seek-overlap-timeout 2sThe loaded range and seek target are kept apart so the competing request does
not pre-warm the bytes being measured. The test synchronizes against qrypt's
active-operation state instead of sleeping for a fixed interval. Every loaded
scenario reports overlap, overlap_wait_us, active_kind, active_offset,
and active_requested; it fails if no matching in-flight request is observed
within --seek-overlap-timeout. Every probe waits for its reads and prefetches
to become idle before its events are collected; the wait is excluded from
seek_load_us. This prevents old work from satisfying the next overlap check,
repopulating a newly cleared cache, or being attributed to the following
measurement. chunks_touched distinguishes aligned single-chunk loads from
cross-chunk loads. Compare isolated, prefetch, and concurrent runs with the
same size, cache mode, probe count, and seek size.
If the issue is about interrupted uploads or resumable upload sessions, run:
go run ./cmd/qrypt probe test resume --mount quark-test --size 64mprobe test resume writes one temporary file, injects a one-shot upload
context canceled fault after upload progress starts, waits for qrypt to retry,
then checks that the final remote file has the expected size. A passing result
means the VFS upload path recovered from the cancellation. The JSON outcome
field tells you whether the driver reused a resumable session or safely fell
back to a fresh upload after the provider rejected the old session.
To inspect the current state without continuous sampling, use these raw endpoints:
go run ./cmd/qrypt debug raw '/v1/uploads?mount=quark-test&history=1'
go run ./cmd/qrypt debug raw '/v1/pending?mount=quark-test'
go run ./cmd/qrypt debug raw '/v1/staging?mount=quark-test'
go run ./cmd/qrypt debug raw '/v1/cache?mount=quark-test'
go run ./cmd/qrypt debug raw '/v1/events?level=warn&limit=100'
go run ./cmd/qrypt debug raw '/v1/events?level=warn&mount=quark-test'/v1/events filters on the event's fields, not on the message text: mount=
(repeatable, matching any listed mount) selects lines produced by a
mount-scoped call site, and component= matches the message's leading [TAG].
A line is therefore never returned for a mount it merely mentions in its text,
and path= remains a plain substring match over the message.
Mount attribution currently covers the pending-upload and read-cache stores, so
their journal, eviction and cleanup warnings carry mount="NAME" in both the
log text and the event field. Call sites that are not yet mount-scoped produce
lines with no mount field, and /v1/events?mount= does not return them — an
empty result means "no attributed line for that mount", not "that mount was
quiet".
Useful fields:
uploads.uploads[]: active and recent upload attempts, state, retry count, uploaded bytes, and last error.pending.pending[]: files waiting for upload.staging.mounts[].files[]: local staging file size and whether it matches the pending size.staging.mounts[].orphans[]: staging files not referenced by pending state.cache.mounts[].cache.journal: pending journal size, duplicate entries, and whether compaction is recommended.events.events[]: recent warnings and errors.faults[]: currently armed debug upload-cancel faults. This should normally be empty outside an activeprobe test resumerun.
For one report instead of separate raw calls:
go run ./cmd/qrypt debug collect --mount quark-testFor time-series samples during a reproduction window:
go run ./cmd/qrypt debug watch --mount quark-test --duration 120s --interval 2sFor stale reads, unexpectedly slow reads, or cache behavior, start with a path focused report:
go run ./cmd/qrypt debug collect /quark-test/path/file.bin --mount quark-testRaw endpoints:
go run ./cmd/qrypt debug raw '/v1/reads?mount=quark-test&path=/quark-test/path/file.bin'
go run ./cmd/qrypt debug raw '/v1/cache?mount=quark-test&path=/quark-test/path/file.bin'
go run ./cmd/qrypt debug raw '/v1/consistency?path=/quark-test/path/file.bin'Useful fields:
reads.reads[]: recent read events, source, bytes, cache hits/misses, and errors.cache.mounts[].cache: read cache counters and per-file cache details.consistency.report: whether the path is pending, found remotely, and size matched.
cache.mounts[].cache carries the counters visible to the eviction policy, and
they answer two different questions:
unproven_bytes/proven_bytessplit the cache by state: a chunk is unproven until a read run other than the one that admitted it touches it. A sequential stream consuming its own read-ahead only ever produces unproven content, which is why a one-pass scan shows up here rather than as hits.stream_bytesis the budget view: the unproven bytes that live in the large-file class, i.e. what the sequential sub-budget is currently charging. It is bounded by a share of the large-class ceiling whenever a drain runs. Small-class unproven content has no sub-budget, sostream_bytesis normally smaller thanunproven_bytes.protected_bytes/probationary_bytessplit the proven content by segment. A drain empties probationary before touching protected, so a cache whose protected bytes hold steady across a large scan is working as intended.evicted_bytesandpromotionsare event counters: how much the cache has released, and how many chunks a later run has confirmed as reused.
Expect the disk footprint to look different from older builds: content read once
in a single pass is held in the sub-budget rather than filling the whole
large-class ceiling, so a cache serving only streaming reads will settle below
its max_size instead of at it. Content that is read again is promoted and
released from that sub-budget, so repeatedly used files still fill the cache.
Read events are kept in two bounded rings and merged into one append-ordered list: summaries (one per read: offset, bytes, cache hits/misses, duration, throughput, error) retain 128 entries, and details (per chunk and per window phase timing) retain 512 entries. Summaries are never evicted by a read's detail burst, so the recent reads stay visible even while a whole-file read walks every chunk; details are recency-biased and a large sequential read can flush older ones. The two rings are bounded at roughly 320 KiB per mount, allocated only once the mount has served a read.
Each mount also carries cumulative runtime.counters, which answer "how fast
and how reliable overall" where the bounded histories answer "which read, and
where did it spend its time":
ops,errors,bytes,duration_ms,mean_msare totals since process start — they never reset, so they do not equal anything in the bounded event history and are the right baseline for spotting degradation.latency.bounds_ms/latency.countsare a fixed-bucket histogram, with the last count being the overflow bucket. Derive a percentile from the counts: the reported value is that bucket's upper bound, so read it as "p95 is at most this".exceedis true when the percentile falls in the overflow bucket.bytescounts what the read path materialized: a remote read counts the data it fetched, while a staging passthrough counts 0 because it only opens a local file and hands the stream back. The read event for that passthrough instead reports the bytes the consumer drained, so the two differ by design.
For copy or transfer issues, provide both endpoints and both mount names:
go run ./cmd/qrypt debug collect /local/source.bin \
--dest /quark/archive/source.bin \
--mount local \
--mount quarkUseful commands:
go run ./cmd/qrypt probe test xfer --source local --dest quark --size 16m
go run ./cmd/qrypt probe test xfer --source local --dest quark --size 16m --vfs
go run ./cmd/qrypt probe bench xfer --source local --dest quark --size 16m --samples 3 --sample-interval 2stest xfer and bench xfer create temporary remote objects. The --vfs mode
tests transfer behavior through the VFS layer instead of directly through
drivers.
Use driver tests when the question is about the provider API or credentials, not local staging or VFS upload.
Read-only auth check:
go run ./cmd/qrypt probe test auth --mount quark-testWrite-capable CRUD check:
go run ./cmd/qrypt probe test crud --mount quark-testDriver state and health:
go run ./cmd/qrypt debug raw '/v1/driver?mount=quark-test'
go run ./cmd/qrypt debug raw '/v1/mounts/health?mount=quark-test'probe test auth does not create remote objects. probe test crud creates
temporary remote objects and cleans them up best-effort.
Use bundle when you want a zip file that another contributor or AI assistant
can inspect:
go run ./cmd/qrypt debug bundle --mount quark-test --out /tmp/qrypt-debug.zipFor a path-specific issue:
go run ./cmd/qrypt debug bundle /quark-test/path/file.bin \
--mount quark-test \
--out /tmp/qrypt-path-debug.zipFor a transfer issue:
go run ./cmd/qrypt debug bundle /local/source.bin \
--dest /quark/archive/source.bin \
--mount local \
--mount quark \
--out /tmp/qrypt-transfer-debug.zipFor timing-sensitive issues, include a watch window:
go run ./cmd/qrypt debug bundle /quark-test/path/file.bin \
--mount quark-test \
--watch 30s \
--out /tmp/qrypt-watch-debug.zipExisting files are not overwritten unless --force is provided. --goroutines
adds a goroutine dump and should be used only when runtime deadlock or blocking
is suspected. --all-mounts can make the bundle much larger; prefer
--mount NAME when the relevant mount is known.
Offline checks read cache files and do not require a running mount:
go run ./cmd/qrypt fs pending --config ./qrypt.toml
go run ./cmd/qrypt fs pending --config ./qrypt.toml --verbose
go run ./cmd/qrypt fs journal --config ./qrypt.toml
go run ./cmd/qrypt fs journal --config ./qrypt.toml --json
go run ./cmd/qrypt fs journal --config ./qrypt.toml --mount quark-testfs pending --verbose shows virtual path, expected size, staging file path,
retry count, last error, and next retry time. fs journal checks
pending.jsonl, missing staging files, size mismatches, and orphan staging
files.
Use debug raw when you need one endpoint directly:
go run ./cmd/qrypt debug raw health
go run ./cmd/qrypt debug raw '/v1/state?mount=quark-test'Common endpoints:
| Endpoint | Purpose | Size notes |
|---|---|---|
/v1/health |
Debug socket health, including task-journal persistence status | Small |
/v1/runtime |
Go runtime and memory summary | Small |
/v1/debug/stacks |
Goroutine stack dump | Text, can be large |
/v1/state?mount=NAME |
Mount snapshot | Can grow with pending, cache, events |
/v1/pending?mount=NAME |
Pending upload files | Grows with pending count |
/v1/tasks?type=upload_stream_batch&state=waiting_input&limit=200 |
Persistent tasks with item progress and recovery linkage | Grows with task and item count; use filters |
/v1/task-events?after_seq=N&wait_ms=25000 |
Incremental task snapshots with cursor replay and gap recovery | Bounded by the task event history and response batch |
/v1/uploads?mount=NAME&history=1 |
Active and recent uploads | Bounded history |
/v1/ops?mount=NAME |
Active VFS read, chunk, window, and prefetch ops | Small, current in-flight ops only |
/v1/reads?mount=NAME&limit=200&since=2m |
Recent read events: 128 summaries plus 512 phase/chunk details per mount | Bounded history, filtered server-side |
/v1/staging?mount=NAME |
Staging files and orphan staging files | Grows with staging files |
/v1/cache?mount=NAME |
Read cache and pending journal health | Grows with cache files |
/v1/events?level=warn&limit=100&mount=NAME&component=CACHE |
Recent warnings/errors, filtered by mount and component fields | Limited by limit |
/v1/driver?mount=NAME&limit=200&since=2m |
Driver debug snapshot and metrics | Driver dependent, metrics filtered server-side |
/v1/mounts/health?mount=NAME |
Recent operation health | Small |
/v1/resolve?path=PATH |
Resolve a virtual path | Small |
/v1/consistency?path=PATH |
Compare pending and remote state for a path | Small for one path |
Raw endpoints can take repeated mount=NAME query parameters when multiple
mounts are relevant.
/v1/reads and /v1/driver also support operation, phase, op_id,
remote_id, and error_only=1.
Use benchmarks when you need comparable performance artifacts:
go run ./cmd/qrypt probe bench crud --mount quark-test
go run ./cmd/qrypt probe bench crud --mount quark-test --samples 3 --sample-interval 2s
go run ./cmd/qrypt probe bench fs --mount quark-test --samples 3 --sample-interval 2s
go run ./cmd/qrypt probe bench xfer --source local --dest quark --size 16m --samples 3 --sample-interval 2s
go run ./cmd/qrypt probe bench compare --base baseline.json --current current.jsonBenchmark reports include summary, assessment, environment.network_probe,
samples[], cases[], and events[]. With --samples, the summary includes
median, p95, max duration, throughput statistics, and coefficient of variation.
bench fs reuses the VFS smoke test. It reports VFS operations such as
write, flush, wait_upload, read, remove, and wait_cleanup. Its
summary.vfs section includes pending/upload/delete drain state, cache hit
ratio, cache errors, staging orphan counts, staging size mismatch counts, and
active loader counters.
Mount state is reported as vfs.MountSnapshot:
identity: mount name, driver name, root, capabilities, and encryption statequeues: upload/delete queue and timer stateoverlay: pending files, delayed deletes, rename overlays, restored directories, and hidden copy entriesupload_state: active and historical uploadscache: read cache and pending journal healthevents: recentdrive.MetricEventstreams such as VFS reads and driver metricsruntime: chunk, window, and prefetch activity counters
Events use drive.MetricEvent. op_id, step, and name are correlation
labels. kind identifies the event family such as driver, vfs_read,
vfs_upload, or transfer. operation is the low-cardinality operation name
used for comparisons, and phase is the measured sub-step.
HTTP debug events must keep secrets masked. Include method, sanitized URL without query parameters, status, duration, request field names, response size, and short masked error snippets only. Do not include cookies, session keys, signed upload URLs, encrypted request blobs, or full response bodies.
collect,watch,bundle, and normalrawGET endpoints are observation tools.probe testandprobe benchmay create, upload, read, rename, or delete temporary remote objects.probe testandprobe benchonly run against mounts withtest_enabled = truein[[mounts]]. Transfer tests require both source and destination mounts to opt in.probe test fs --sizeaccepts bytes ork,m,gsuffixes. Large values consume upload time and provider bandwidth.probe test batchuploadandprobe test batchmoveaccept--count(default 50, maximum 100) and--size(default 4 KiB per file, maximum 1 MiB).probe test readrequires--mount-point. It accepts--size(default 256 MiB, maximum 2 GiB),--block-size(default 1 MiB),--samples(default 1, maximum 10),--cache-mode cold|warm|both(defaultboth),--pattern sequential|seek|both(defaultsequential),--seek-count(default 10, maximum 32),--seek-size(default 1 MiB, maximum 16 MiB),--seek-scenario isolated|prefetch|concurrent(defaultisolated),--seek-warmup-chunks(default 2, maximum 16), and--seek-overlap-timeout(default 2s, range 100ms to 30s). It creates and removes a temporary remote file of the requested size.probe test resume --sizehas the same size format. It intentionally cancels one upload attempt through the VFS debug fault injector, then waits for normal retry or resumable-upload recovery.--all-mountscan significantly increase report size.--goroutinesis useful for blocking or deadlock investigations, but it can make bundles larger.