Skip to content

Instantly share code, notes, and snippets.

@luavixen
Last active December 15, 2021 08:11
Show Gist options
  • Save luavixen/042563eb36681ae70bc6c8445ff9e29e to your computer and use it in GitHub Desktop.
Save luavixen/042563eb36681ae70bc6c8445ff9e29e to your computer and use it in GitHub Desktop.
Node.js script to find the fastest Arch Linux mirrors.
const target = '/core/os/x86_64/linux-firmware-20210818.c46b8c3-1-any.pkg.tar.zst';
const targetReplace = '/$repo/os/$arch';
const timeoutMs = 20_000;
const mirrors = [
// Canada
'https://mirror.0xem.ma/arch/$repo/os/$arch',
'https://mirror.csclub.uwaterloo.ca/archlinux/$repo/os/$arch',
'https://mirror2.evolution-host.com/archlinux/$repo/os/$arch',
'https://muug.ca/mirror/archlinux/$repo/os/$arch',
'https://arch.powerfly.ca/$repo/os/$arch',
'https://mirror.scd31.com/arch/$repo/os/$arch',
'https://mirror.sergal.org/archlinux/$repo/os/$arch',
// United States
'https://america.mirror.pkgbuild.com/$repo/os/$arch',
'https://mirror.arizona.edu/archlinux/$repo/os/$arch',
'https://arlm.tyzoid.com/$repo/os/$arch',
'https://mirror.ava.dev/archlinux/$repo/os/$arch',
'https://mirror.clarkson.edu/archlinux/$repo/os/$arch',
'https://arch.mirror.constant.com/$repo/os/$arch',
'https://mirror.cybersecurity.nmt.edu/archlinux/$repo/os/$arch',
'https://mirror.ette.biz/archlinux/$repo/os/$arch',
'https://mirror.hackingand.coffee/arch/$repo/os/$arch',
'https://mirror.hodgepodge.dev/archlinux/$repo/os/$arch',
'https://mirror.hostup.org/archlinux/$repo/os/$arch',
'https://arch.hu.fo/archlinux/$repo/os/$arch',
'https://repo.ialab.dsu.edu/archlinux/$repo/os/$arch',
'https://mirrors.kernel.org/archlinux/$repo/os/$arch',
'https://mirror.dal10.us.leaseweb.net/archlinux/$repo/os/$arch',
'https://mirror.mia11.us.leaseweb.net/archlinux/$repo/os/$arch',
'https://mirror.sfo12.us.leaseweb.net/archlinux/$repo/os/$arch',
'https://mirror.wdc1.us.leaseweb.net/archlinux/$repo/os/$arch',
'https://mirror.lty.me/archlinux/$repo/os/$arch',
'https://mirrors.lug.mtu.edu/archlinux/$repo/os/$arch',
'https://mirror.kaminski.io/archlinux/$repo/os/$arch',
'https://iad.mirrors.misaka.one/archlinux/$repo/os/$arch',
'https://mirrors.mit.edu/archlinux/$repo/os/$arch',
'https://mirrors.ocf.berkeley.edu/archlinux/$repo/os/$arch',
'https://archmirror1.octyl.net/$repo/os/$arch',
'https://dfw.mirror.rackspace.com/archlinux/$repo/os/$arch',
'https://iad.mirror.rackspace.com/archlinux/$repo/os/$arch',
'https://ord.mirror.rackspace.com/archlinux/$repo/os/$arch',
'https://mirrors.radwebhosting.com/archlinux/$repo/os/$arch',
'https://plug-mirror.rcac.purdue.edu/archlinux/$repo/os/$arch',
'https://mirrors.rit.edu/archlinux/$repo/os/$arch',
'https://mirrors.rutgers.edu/archlinux/$repo/os/$arch',
'https://mirrors.sonic.net/archlinux/$repo/os/$arch',
'https://mirror.phx1.us.spryservers.net/archlinux/$repo/os/$arch',
'https://arch.mirror.square-r00t.net/$repo/os/$arch',
'https://mirror.stephen304.com/archlinux/$repo/os/$arch',
'https://ftp.sudhip.com/archlinux/$repo/os/$arch',
'https://mirror.pit.teraswitch.com/archlinux/$repo/os/$arch',
'https://mirror.theash.xyz/arch/$repo/os/$arch',
'https://mirrors.xtom.com/archlinux/$repo/os/$arch',
'https://zxcvfdsa.com/arch/$repo/os/$arch',
// Worldwide
'https://mirror.rackspace.com/archlinux/$repo/os/$arch'
];
////////////////////////////////////////////////////////////////////////////////
const https = require('https');
const request = (options, callback) => new Promise((resolve, reject) => {
const req = https.get(options, (res) => {
res.on('end', resolve);
res.on('error', reject);
res.on('data', (chunk) => {
try {
callback(chunk instanceof Buffer ? chunk : Buffer.from(chunk), res);
} catch (err) {
res.destroy(err);
}
});
if (Math.floor(res.statusCode / 100) !== 2) {
res.destroy(new Error(
`Bad response status code: ${res.statusCode} "${res.statusMessage}"`
));
}
});
req.on('error', reject);
setTimeout(() => {
reject(new Error('Server stopped sending data'));
req.destroy();
}, timeoutMs + 1000);
});
const formatMib = (bytes) => (bytes / 1048576).toFixed(2) + ' MiB';
const formatMbps = (bytes) => (bytes / 125000).toFixed(2) + ' Mbps';
const formatSeconds = (ms) => (ms / 1000).toFixed(1) + ' seconds';
const benchmark = (mirror) => {
console.log('Benchmarking: ' + mirror);
const url = mirror.replace(targetReplace, target);
const report = {
timeStarted: Date.now(),
timeTaken: () => Date.now() - report.timeStarted,
totalBytesDownloaded: 0,
averageBytesPerSecond: 0,
currentBytesPerSecond: 0,
lastBytesDownloaded: 0,
lastBytesPerSecondTime: 0,
lastLogTime: 0,
timeoutReached: false
};
return request(url, (chunk, res) => {
report.totalBytesDownloaded += chunk.byteLength;
const timeNow = report.timeTaken();
if (timeNow - report.lastBytesPerSecondTime > 1000) {
report.averageBytesPerSecond = report.totalBytesDownloaded / (timeNow / 1000);
report.currentBytesPerSecond = report.totalBytesDownloaded - report.lastBytesDownloaded;
report.lastBytesPerSecondTime = timeNow;
report.lastBytesDownloaded = report.totalBytesDownloaded;
}
if (timeNow - report.lastLogTime > 2000) {
console.log(`Downloaded ${
formatMib(report.totalBytesDownloaded)
} (${
formatMbps(report.currentBytesPerSecond)
})`);
report.lastLogTime = timeNow;
}
if (timeNow > timeoutMs) {
report.timeoutReached = true;
throw new Error('Request took too long (timed out)');
}
})
.then(() => {
const timeNow = report.timeTaken();
console.log(`Benchmark complete, took ${
formatSeconds(timeNow)
} to download ${
formatMib(report.totalBytesDownloaded)
}`);
console.log('Last speed: ' + formatMbps(report.currentBytesPerSecond));
console.log('Average speed: ' + formatMbps(report.averageBytesPerSecond));
return { mirror, report, time: timeNow, success: true };
})
.catch((err) => {
const timeNow = report.timeTaken();
if (report.timeoutReached) {
console.log(`Benchmark timed out after ${formatSeconds(timeNow)}!`);
} else {
console.log(`Benchmark failed after ${formatSeconds(timeNow)}!`, err);
}
return { mirror, report, time: timeNow, success: false };
});
};
const seperator = '-'.repeat(80);
(async () => {
const results = {
completed: [],
timeout: [],
failed: []
};
console.log(seperator);
for (const mirror of mirrors) {
const result = await benchmark(mirror);
if (result.success) {
results.completed.push(result);
} else if (result.report.timeoutReached) {
results.timeout.push(result);
} else {
results.failed.push(result);
}
console.log(seperator);
}
results.completed.sort((a, b) => {
return a.time - b.time;
});
results.timeout.sort((a, b) => {
return b.report.totalBytesDownloaded - a.report.totalBytesDownloaded;
});
const formatMirrorlistEntry = (result) => 'Server = ' + result.mirror;
const mirrorlist = [
'# Mirrorlist sorted by mirrorbench.js at ' + new Date().toString(),
'',
'# Fastest mirrors',
...results.completed.map(formatMirrorlistEntry),
'',
'# Slower mirrors (timed out while downloading)',
...results.timeout.map(formatMirrorlistEntry),
'',
'# Invalid mirrors (failed to download target file)',
...results.failed.map(formatMirrorlistEntry),
];
console.log(mirrorlist.join('\n'));
console.log(seperator);
process.exit(0);
})();
@luavixen
Copy link
Author

luavixen commented Sep 22, 2021

Configure mirrorbench.js by editing the constants at the top of the script (sorry, I'm lazy :P).
target and targetReplace are used to select the target test file to download.
timeoutMs is the number of milliseconds to wait before timing out and moving onto the next request.
mirrors is an array of mirrors you would like to benchmark, find mirrors here.

The default configuration is optimal for someone living in the U.S. or Canada with a 200+ Mbps download speed, but you will almost certainly need to update target once the linux-firmware package is updated, find the latest versions of the core packages here.

Get started with:

$ pacman -S nodejs
$ curl -o mirrorbench.js https://gist.githubusercontent.com/luavixen/042563eb36681ae70bc6c8445ff9e29e/raw/mirrorbench.js
$ node mirrorbench.js

Some example output:

--------------------------------------------------------------------------------
Benchmarking: https://mirror.sergal.org/archlinux/$repo/os/$arch
Downloaded 21.55 MiB (147.85 Mbps)
Downloaded 50.41 MiB (116.39 Mbps)
Downloaded 83.28 MiB (136.18 Mbps)
Downloaded 117.58 MiB (144.70 Mbps)
Downloaded 151.89 MiB (144.70 Mbps)
Benchmark complete, took 11.3 seconds to download 173.31 MiB
Last speed: 143.00 Mbps
Average speed: 128.50 Mbps
--------------------------------------------------------------------------------
Benchmarking: https://mirror.rackspace.com/archlinux/$repo/os/$arch
Downloaded 1.39 MiB (10.09 Mbps)
Downloaded 7.36 MiB (18.87 Mbps)
Downloaded 14.53 MiB (31.46 Mbps)
Downloaded 20.44 MiB (27.92 Mbps)
Downloaded 25.05 MiB (19.27 Mbps)
Downloaded 29.75 MiB (20.19 Mbps)
Downloaded 33.94 MiB (18.09 Mbps)
Downloaded 38.55 MiB (18.61 Mbps)
Downloaded 43.16 MiB (19.53 Mbps)
Benchmark timed out after 20.0 seconds!
--------------------------------------------------------------------------------
...
--------------------------------------------------------------------------------
# Mirrorlist sorted by mirrorbench.js at Wed Sep 22 2021 16:05:11 GMT-0700 (Pacific Daylight Time)

# Fastest mirrors
Server = https://muug.ca/mirror/archlinux/$repo/os/$arch
Server = https://mirror.sergal.org/archlinux/$repo/os/$arch
...

# Slower mirrors (timed out while downloading)
Server = https://mirror2.evolution-host.com/archlinux/$repo/os/$arch
Server = https://mirror.csclub.uwaterloo.ca/archlinux/$repo/os/$arch
Server = https://mirror.rackspace.com/archlinux/$repo/os/$arch
...

# Invalid mirrors (failed to download target file)
...
--------------------------------------------------------------------------------

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment