Last active
July 14, 2025 12:06
-
-
Save pyropy/e6ea0456ae374cde2557d2d1ea90f7ac to your computer and use it in GitHub Desktop.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| #!/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