Skip to content
43 changes: 43 additions & 0 deletions packages/qvac-lib-registry-server/client/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,47 @@ Downloading ... -> /absolute/path/ggml-tiny-q8_0.bin
Download complete: 41.52 MB
```

### Profile download performance

Diagnose slow downloads by collecting UDX network stats, connection info, and hypercore metrics — similar to [hyperdrive-profiler](https://github.com/holepunchto/hyperdrive-profiler) but for registry Hyperblobs:

```bash
$ qvac-registry profile \
"ggerganov/whisper.cpp/resolve/5359861c739e955e79d9a303bcbc70fb988958b1/ggml-tiny.bin" hf

--- 5.0s elapsed ---
Network (UDX)
Bytes received: 42.1MB (8.4MB/s)
Bytes transmitted: 128kB (25.6kB/s)
Packets rx/tx: 29034 / 1842
Packets dropped: 0
Connection
Firewalled: false
Blob peers: 2
Issues: rto=0 fast-recoveries=0 retransmits=0
Hypercore
Blob core: 665 / 665 (contiguous / length)
Hotswaps: 0
...
==================================================
FINAL SUMMARY
==================================================
Download
Model: ggerganov/whisper.cpp/resolve/.../ggml-tiny.bin
Size: 73.5MB (1120 blocks)
Metadata: 2.15s
Transfer: 8.72s
Avg speed: 8.4MB/s
Total: 10.87s
```

Flags:

```
--interval|-i [seconds] Stats print interval (default: 5)
--timeout|-t [ms] Stream read timeout (default: 120000)
```

### JSON output

All commands support `--json` for machine-readable output:
Expand All @@ -250,6 +291,7 @@ See the `examples/` folder for complete working examples:
- `download-model.js`: Download a single model to disk via metadata lookup
- `download-blob.js`: Download a blob directly using known blob coordinates
- `download-all-models.js`: Download all models in the registry
- `profile-download.js`: Profile download performance with network/connection/hypercore stats

Run examples:

Expand All @@ -259,6 +301,7 @@ node examples/example.js
node examples/download-model.js
node examples/download-blob.js
node examples/download-all-models.js
node examples/profile-download.js "model/path"
```

## Configuration
Expand Down
42 changes: 41 additions & 1 deletion packages/qvac-lib-registry-server/client/bin/cli.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

const { command, flag, arg, summary, header, footer, description } = require('paparam')
const { QVACRegistryClient } = require('../index')
const { profileDownload } = require('../lib/profiler')
const IdEnc = require('hypercore-id-encoding')
const path = require('#path')

Expand Down Expand Up @@ -191,6 +192,44 @@ const downloadCmd = command('download',
}
)

const profileCmd = command('profile',
summary('Profile download performance for a model'),
description('Downloads a model to a temp directory while collecting UDX network stats, connection info, and hypercore metrics. Similar to hyperdrive-profiler but for registry blobs.'),
arg('<path>', 'Model path'),
arg('[source]', 'Model source filter (e.g. hf, s3)'),
flag('--interval|-i [seconds]', 'Stats print interval in seconds (default: 5)'),
flag('--timeout|-t [ms]', 'Stream read timeout in ms (default: 120000)'),
async function (cmd) {
const rootFlags = getRootFlags(cmd)
const registryCoreKey = rootFlags.key || process.env.QVAC_REGISTRY_CORE_KEY

if (!registryCoreKey) {
console.error('Registry core key is required. Set QVAC_REGISTRY_CORE_KEY or use --key.')
process.exit(1)
}

const interval = cmd.flags.interval ? parseInt(cmd.flags.interval, 10) : 5
Comment thread
yuranich marked this conversation as resolved.
const timeout = cmd.flags.timeout ? parseInt(cmd.flags.timeout, 10) : 120000

if (Number.isNaN(interval) || interval <= 0) {
console.error('--interval must be a positive number')
process.exit(1)
}
if (Number.isNaN(timeout) || timeout <= 0) {
console.error('--timeout must be a positive number')
process.exit(1)
}

await profileDownload({
registryCoreKey,
modelPath: cmd.args.path,
source: cmd.args.source || undefined,
intervalSec: interval,
timeout
})
}
)

// --- Root command ---

const cmd = command('qvac-registry',
Expand All @@ -202,7 +241,8 @@ const cmd = command('qvac-registry',
flag('--verbose|-v', 'Enable verbose/debug logging'),
listCmd,
getCmd,
downloadCmd
downloadCmd,
profileCmd
)

cmd.parse()
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
'use strict'

const path = require('path')
require('dotenv').config({ path: path.resolve(__dirname, '../../.env') })

const { profileDownload } = require('../lib/profiler')

const REGISTRY_CORE_KEY = process.env.QVAC_REGISTRY_CORE_KEY
const STATS_INTERVAL_SEC = parseInt(process.env.PROFILE_INTERVAL || '5', 10)

function usage () {
console.log(`
Usage: node profile-download.js [model-path] [source]

model-path Path of the model to download (omit to list available models)
source Source filter, e.g. "hf" or "s3" (default: first match)

Environment:
QVAC_REGISTRY_CORE_KEY Registry view core key (required)
PROFILE_INTERVAL Stats print interval in seconds (default: 5)

Examples:
node profile-download.js
node profile-download.js "ggerganov/whisper.cpp/resolve/5359861c739e955e79d9a303bcbc70fb988958b1/ggml-tiny.bin"
PROFILE_INTERVAL=2 node profile-download.js "ggerganov/whisper.cpp/resolve/5359861c739e955e79d9a303bcbc70fb988958b1/ggml-tiny.bin" hf
`)
}

async function main () {
if (!REGISTRY_CORE_KEY) {
console.error('QVAC_REGISTRY_CORE_KEY is not set')
process.exit(1)
}

const modelPath = process.argv[2]
const sourceFilter = process.argv[3]

if (modelPath === '--help' || modelPath === '-h') {
usage()
process.exit(0)
}

if (!modelPath) {
usage()
process.exit(0)
}

await profileDownload({
registryCoreKey: REGISTRY_CORE_KEY,
modelPath,
source: sourceFilter,
intervalSec: STATS_INTERVAL_SEC
})
}

main().catch((err) => {
console.error('Profiler failed:', err)
process.exit(1)
})
Loading
Loading