Does your internet speed match what's advertised in your plan? Why does Wi-Fi in the far room work slower than near the router? To get accurate answers to these questions, ordinary speed test websites aren't enough. You need a professional tool — iperf3.
In this guide, we'll set up an automatic monitoring system that will regularly measure the "pure" bandwidth of your network and send the results to VizIoT for clear visualization.
Before we dive into the technical details, let's see what we'll get. You'll be able to create an interactive dashboard that displays in real-time:
This dashboard will allow you not just to "measure speed," but to continuously monitor the quality of your network connection.
iperf3 is a command-line utility for measuring maximum network bandwidth between two points (client and server).
Unlike websites, iperf3 measures the "pure" performance of your connection, excluding factors like browser performance, web server load, or the impact of ads on the page. It's the "gold standard" for network engineers, allowing you to:
Our script will use three key modes to get the complete picture:
Setting up the system consists of three simple steps: preparing the environment, creating the script, and setting up automatic execution.
On the machine that will run the tests, install everything necessary.
Detailed documentation: https://nodejs.org
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
\. "$HOME/.nvm/nvm.sh"
nvm install 24
node -vnpm -v
sudo apt update
sudo apt install iperf3 -y
mkdir viziot_iperf3 && cd viziot_iperf3
npm init -y
npm install viziot-mqtt-client-nodejs
package.json file and add the line "type": "module",. nano main.js
import VizIoTMQTT from 'viziot-mqtt-client-nodejs'
import { spawn } from 'child_process'
// ===================================
// ========== SETTINGS ===============
// ===================================
// 1. Enter your VizIoT device key and password
const keyDevice = '________________';
const passDevice = '____________________';
// 2. Set the test interval (currently - once per hour)
const intervalTime = 1000 * 60 * 60; // 1 hour in milliseconds
// 3. Configure the list of servers for testing.
const servers = [
{
// Example: Testing to a public server in Bulgaria
name: 'BG_Sofia',
command: '-c 37.19.203.1',
}
];
// --- The rest of the code is universal and requires no changes ---
const MAX_RETRIES = 3;
const RETRY_DELAY_MS = 30 * 1000;
const IPERF_TIMEOUT_MS = 60 * 1000;
const objMQTTClient = new VizIoTMQTT(keyDevice, passDevice);
const delay = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
function runCommand(command, args, timeoutMs) {
return new Promise((resolve) => {
const child = spawn(command, args, { timeout: timeoutMs });
let stdout = '', stderr = '', timedOut = false;
child.stdout.on('data', (data) => (stdout += data.toString()));
child.stderr.on('data', (data) => (stderr += data.toString()));
child.on('error', (err) => (stderr += `\nFailed to start subprocess: ${err.message}`));
child.on('close', (code, signal) => {
if (signal === 'SIGTERM') {
timedOut = true;
stderr += '\nProcess was terminated due to timeout.';
}
resolve({ stdout, stderr, code, timedOut });
});
});
}
async function runIperfTest(iperfPath, serverCommand, testMode) {
const testType = testMode.charAt(0).toUpperCase() + testMode.slice(1);
const args = serverCommand.trim().split(' ');
args.push('-J'); // JSON output
if (testMode === 'download') args.push('-R');
else if (testMode === 'bidir') args.push('--bidir');
for (let attempt = 1; attempt <= MAX_RETRIES; attempt++) {
console.log(`[INFO] Running test: ${testType} for ${serverCommand.trim()}. Attempt ${attempt}/${MAX_RETRIES}...`);
const { stdout, stderr, timedOut } = await runCommand(iperfPath, args, IPERF_TIMEOUT_MS);
const errorResult = { upload: -1, download: -1 };
if (timedOut) { console.error(`[ERROR] Test (${testType}) exceeded timeout.`); return errorResult; }
if (!stdout) { console.error(`[CRIT] iperf3 command returned no result. Stderr: ${stderr.trim()}`); return errorResult; }
try {
const result = JSON.parse(stdout);
if (result.error) {
if (result.error.includes('the server is busy')) {
console.warn(`[WARN] Server is busy. Attempt ${attempt} failed.`);
if (attempt < MAX_RETRIES) { await delay(RETRY_DELAY_MS); continue; }
else { console.error(`[ERROR] Server busy after ${MAX_RETRIES} attempts.`); return errorResult; }
} else { console.error(`[ERROR] iperf3:`, result.error); return errorResult; }
}
const finalResult = { upload: -1, download: -1 };
const summary = result.end;
if (summary.sum_sent) finalResult.upload = parseFloat((summary.sum_sent.bits_per_second / 1000000).toFixed(2));
if (summary.sum_received) finalResult.download = parseFloat((summary.sum_received.bits_per_second / 1000000).toFixed(2));
console.log(`[SUCCESS] Result (${testType}): Upload: ${finalResult.upload} Mbps, Download: ${finalResult.download} Mbps`);
return finalResult;
} catch (parseError) { console.error(`[CRIT] Failed to parse JSON response from iperf3. Response:`, stdout); return errorResult; }
}
return { upload: -1, download: -1 };
}
async function getPacketAndSendToServer() {
const results = {};
for (const server of servers) {
console.log(`\n--- Starting test series for server: ${server.name} ---`);
const uploadResult = await runIperfTest('iperf3', server.command, 'upload');
results[`${server.name}_upload`] = uploadResult.upload;
const downloadResult = await runIperfTest('iperf3', server.command, 'download');
results[`${server.name}_download`] = downloadResult.download;
const bidirResult = await runIperfTest('iperf3', server.command, 'bidir');
results[`${server.name}_bidir_upload`] = bidirResult.upload;
results[`${server.name}_bidir_download`] = bidirResult.download;
}
console.log('\n[SUCCESS] All tests completed. Final data packet:', results);
return results;
}
async function main() {
console.log('[INFO] Script started. Connecting to VizIoT and running first test...');
objMQTTClient.connect(() => console.log('[INFO] Successfully connected to VizIoT MQTT.'));
const initialPacket = await getPacketAndSendToServer();
objMQTTClient.sendDataToVizIoT(initialPacket, (err) => {
if (err) console.error(new Date(), 'ERROR sending initial data:', err);
else console.log(new Date(), 'Initial data sent successfully.');
});
setInterval(async () => {
console.log(`\n[INFO] Running scheduled test (interval: ${intervalTime / 1000} seconds)...`);
const packet = await getPacketAndSendToServer();
objMQTTClient.sendDataToVizIoT(packet, (err) => {
if (err) console.error(new Date(), 'ERROR sending interval data:', err);
else console.log(new Date(), 'Interval data sent successfully.');
});
}, intervalTime);
}
main();
To keep the script running 24/7, we'll use the PM2 process manager.
npm install pm2 -gpm2 start main.js --name viziot_iperf3pm2 startup (and execute the command it suggests)pm2 saveUseful PM2 commands:
pm2 list: Show list of running processes.pm2 logs viziot_iperf3: View logs.pm2 restart viziot_iperf3: Restart.Now that you have a working system, it's easy to improve it.
For maximum accuracy, it's best to test to a server you control (e.g., VPS or computer on your local network).
sudo apt update && sudo apt install iperf3 -y
sudo ufw allow 5201/tcp # Open port in firewall
(Agree to run iperf3 in service mode)
main.js and add your server to the list. const servers = [
{
name: 'BG_Sofia', // Public server
command: '-c 37.19.203.1',
},
{
name: 'LOCAL_SERVER', // Your server
command: '-c 192.168.0.100', // Specify your server's IP
}
];
pm2 restart viziot_iperf3.Now your dashboard will show speed graphs to your personal server.
After launching, data will start flowing to your device. Log into VizIoT, create a dashboard, and add widgets.
..._upload and ..._download.Experiment to create a dashboard that will be most informative for you.