Skip to content

Instantly share code, notes, and snippets.

@pyropy
Last active July 14, 2025 12:06
Show Gist options
  • Save pyropy/e6ea0456ae374cde2557d2d1ea90f7ac to your computer and use it in GitHub Desktop.
Save pyropy/e6ea0456ae374cde2557d2d1ea90f7ac to your computer and use it in GitHub Desktop.
#!/usr/bin/env node
const AGGREGATORS = [
'https://agg.walrus.eosusa.io',
'https://aggregator.mainnet.walrus.mirai.cloud',
'https://aggregator.suicore.com',
'https://aggregator.walrus-mainnet.tududes.com',
'https://aggregator.walrus-mainnet.walrus.space',
'https://aggregator.walrus.atalma.io',
'https://aggregator.walrus.mainnet.mozcomputing.dev',
'https://aggregator.walrus.silentvalidator.com',
'https://mainnet-aggregator.hoh.zone',
'https://mainnet-aggregator.walrus.graphyte.dev',
'https://mainnet-walrus-aggregator.kiliglab.io',
'https://sm1-walrus-mainnet-aggregator.stakesquid.com',
'https://sui-walrus-mainnet-aggregator.bwarelabs.com',
'https://suiftly.mhax.io',
'https://wal-aggregator-mainnet.staketab.org',
'https://walmain.agg.chainflow.io',
'https://walrus-agg.mainnet.obelisk.sh',
'https://walrus-aggregator-mainnet.chainode.tech:9002',
'https://walrus-aggregator.brightlystake.com',
'https://walrus-aggregator.chainbase.online',
'https://walrus-aggregator.n1stake.com',
'https://walrus-aggregator.natsai.xyz',
'https://walrus-aggregator.rockfin.io',
'https://walrus-aggregator.rubynodes.io',
'https://walrus-aggregator.stakely.io',
'https://walrus-aggregator.stakin-nodes.com',
'https://walrus-aggregator.staking4all.org',
'https://walrus-aggregator.starduststaking.com',
'https://walrus-aggregator.talentum.id',
'https://walrus-aggregator.thcloud.ai',
'https://walrus-aggregator.thepassivetrust.com',
'https://walrus-cache-mainnet.latitude.sh',
'https://walrus-cache-mainnet.overclock.run',
'https://walrus-main-aggregator.4everland.org',
'https://walrus-mainnet-aggregator-1.zkv.xyz',
'https://walrus-mainnet-aggregator.crouton.digital',
'https://walrus-mainnet-aggregator.dzdaic.com',
'https://walrus-mainnet-aggregator.everstake.one',
'https://walrus-mainnet-aggregator.imperator.co',
'https://walrus-mainnet-aggregator.luckyresearch.org',
'https://walrus-mainnet-aggregator.nami.cloud',
'https://walrus-mainnet-aggregator.nodeinfra.com',
'https://walrus-mainnet-aggregator.redundex.com',
'https://walrus-mainnet-aggregator.rpc101.org',
'https://walrus-mainnet-aggregator.stakecraft.com',
'https://walrus-mainnet-aggregator.stakeengine.co.uk',
'https://walrus-mainnet-aggregator.stakingdefenseleague.com.',
'https://walrus-mainnet-aggregator.trusted-point.com',
'https://walrus.aggregator.stakepool.dev.br',
'https://walrus.blockscope.net',
'https://walrus.globalstake.io',
'https://walrus.lakestake.io',
'https://walrus.lionscraft.blockscape.network:9000',
'https://walrus.prostaking.com:9443',
'https://walrus.veera.com',
'https://walrusagg.pops.one',
'http://walrus-aggregator.winsnip.site:9000',
'http://walrus.equinoxdao.xyz:9000',
'http://67.220.194.10:9000',
]
/**
* @param {bigint} [seed]
* @returns
*/
function getRandomAggregator(seed) {
let idx
if (seed !== undefined) {
idx = Number(seed % BigInt(AGGREGATORS.length))
} else {
idx = Math.floor(Math.random() * AGGREGATORS.length)
}
return AGGREGATORS[idx]
}
function base64UrlToBigInt(base64url) {
const hex = '0x' + Buffer.from(base64url, 'base64url').toString('hex')
return BigInt(hex)
}
function nowMs() {
const [s, ns] = process.hrtime()
return s * 1000 + ns / 1e6
}
async function fetchWithTiming(url) {
const start = nowMs()
const stats = {
ttfb: null,
ttlb: null,
size: 0,
status: null,
headers: null,
}
const res = await fetch(url)
stats.status = res.status
stats.headers = res.headers
const reader = res.body.getReader()
let first = true
while (true) {
const t = await reader.read()
if (first) {
stats.ttfb = nowMs() - start
first = false
}
if (t.done) break
stats.size += t.value.length
}
stats.ttlb = nowMs() - start
return stats
}
/**
* Calculate percentiles for an array of values.
*
* @param {number[]} values
* @param {number[]} percentiles
* @returns {{`p${number}`: number}}
*/
function calculatePercentiles(values, percentiles) {
const sorted = values.slice().sort((a, b) => a - b)
const result = {}
for (const p of percentiles) {
const index = (p / 100) * (sorted.length - 1)
if (index % 1 === 0) {
result[`p${p}`] = sorted[index]
} else {
const lower = Math.floor(index)
const upper = Math.ceil(index)
const weight = index - lower
result[`p${p}`] = sorted[lower] * (1 - weight) + sorted[upper] * weight
}
}
return result
}
async function measureResponseTimes(url, iterations = 10) {
console.log(`\nRunning ${iterations} iterations for: ${url}`)
const results = {
ttfb: [],
ttlb: [],
size: [],
status: [],
errors: 0
}
for (let i = 0; i < iterations; i++) {
try {
const stats = await fetchWithTiming(url)
results.ttfb.push(stats.ttfb)
results.ttlb.push(stats.ttlb)
results.size.push(stats.size)
results.status.push(stats.status)
process.stdout.write(`\rProgress: ${i + 1}/${iterations}`)
} catch (e) {
results.errors++
console.error(`\nIteration ${i + 1} failed:`, e.message)
}
}
console.log() // New line after progress indicator
return results
}
function printStats(label, results) {
if (results.ttfb.length === 0) {
console.log(`${label}: All requests failed`)
return
}
const ttfbPercentiles = calculatePercentiles(results.ttfb, [50, 90, 99])
const ttlbPercentiles = calculatePercentiles(results.ttlb, [50, 90, 99])
const avgSize = results.size.reduce((a, b) => a + b, 0) / results.size.length
const successRate = ((results.ttfb.length / (results.ttfb.length + results.errors)) * 100).toFixed(1)
console.log(`${label}:`)
console.log(` Success Rate: ${successRate}%`)
console.log(` TTFB - p50: ${ttfbPercentiles.p50.toFixed(1)}ms, p90: ${ttfbPercentiles.p90.toFixed(1)}ms, p99: ${ttfbPercentiles.p99.toFixed(1)}ms`)
console.log(` TTLB - p50: ${ttlbPercentiles.p50.toFixed(1)}ms, p90: ${ttlbPercentiles.p90.toFixed(1)}ms, p99: ${ttlbPercentiles.p99.toFixed(1)}ms`)
console.log(` Average Size: ${avgSize.toFixed(0)} bytes`)
if (results.status.length > 0) {
console.log(` Status Codes: ${[...new Set(results.status)].join(', ')}`)
}
}
async function main() {
const [, , blobId, iterations = '10'] = process.argv
if (!blobId) {
console.error('Usage: measure-response-time.js <blobId> [iterations]')
process.exit(1)
}
const numIterations = parseInt(iterations, 10)
if (isNaN(numIterations) || numIterations < 1) {
console.error('Iterations must be a positive number')
process.exit(1)
}
// 1. Random aggregator (cold start)
const aggregator = getRandomAggregator()
const aggUrl = `${aggregator}/v1/blobs/${blobId}`
const aggResults = await measureResponseTimes(aggUrl, numIterations)
printStats('Random Aggregator (cold start)', aggResults)
// 2. CDN (cold start)
const mainnetUrl = `https://3rkdb89wnwzj3f2i7hnvuyna5vn7glk9vf3igtht9x6ejmboly.walcdn.io/${blobId}`
const mainnetResults = await measureResponseTimes(mainnetUrl, numIterations)
printStats('Mainnet (cold start)', mainnetResults)
// 3. Random aggregator (warmed up)
await fetch(aggUrl) // Warm up
const warmAggResults = await measureResponseTimes(aggUrl, numIterations)
printStats('Random Aggregator (warm start)', warmAggResults)
// 4. CDN (warmed up)
await fetch (mainnetUrl) // Warm up
const warmMainnetResults = await measureResponseTimes(mainnetUrl, numIterations)
printStats('Mainnet (warm start)', warmMainnetResults)
// 5. Pseudo-random aggregator (cold start)
const seed = base64UrlToBigInt(blobId)
const deterministicAggregator = getRandomAggregator(seed)
const detAggUrl = `${deterministicAggregator}/v1/blobs/${blobId}`
const detAggResults = await measureResponseTimes(detAggUrl, numIterations)
printStats('Pseudo-Random Aggregator (cold start)', detAggResults)
// 6. Pseudo-random aggregator (warmed up)
await fetch (detAggUrl) // Warm up
const warmDetAggResults = await measureResponseTimes(detAggUrl, numIterations)
printStats('Pseudo-Random Aggregator (warm start)', warmDetAggResults)
console.log('\nAll tests completed.')
}
main()
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment