├── .gitignore ├── package.json ├── LICENSE ├── README.md └── bench.js /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | .DS_Store -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "cloud-bench", 3 | "description": "CLI to benchmark the CPU, network and disk performance of cloud servers over time.", 4 | "homepage": "https://github.com/goldfire/node-cloud-bench", 5 | "version": "1.1.0", 6 | "keywords": [ 7 | "cloud", 8 | "performance", 9 | "vps", 10 | "ec2", 11 | "aws", 12 | "server benchmark", 13 | "benchmark", 14 | "cloud benchmark", 15 | "bench" 16 | ], 17 | "author": "James Simpson (http://goldfirestudios.com)", 18 | "repository": { 19 | "type": "git", 20 | "url": "git://github.com/goldfire/node-cloud-bench.git" 21 | }, 22 | "main": "bench.js", 23 | "license": "MIT", 24 | "dependencies": { 25 | "colors": ">=1.4.0", 26 | "nomnom": ">0.0.1", 27 | "universal-speedtest": "2.0.2" 28 | }, 29 | "engines": [ 30 | "node >= 12.0.0" 31 | ] 32 | } 33 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 James Simpson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Description 2 | node-cloud-bench is a small CLI utility written with Node.js that allows for easy benchmarking of cloud servers, specifically to test CPU performance, network speeds and disk IOPS. When it comes to cloud computing, seeing how consistent the values are over time is just as important as the values themselves, which is why you can specify an interval, length of the test and a location to output a CSV file with the results. 3 | 4 | The results collected include: 5 | 6 | * Network CDN Download (Mbps) 7 | * Network Ping (seconds) 8 | * Network Download (Mbps) 9 | * Network Upload (Mbps) 10 | * CPU Time (seconds) 11 | * Read IOPS 12 | * Write IOPS 13 | * I/O Ping 14 | 15 | ## Installation 16 | 17 | ``` 18 | npm install cloud-bench 19 | ``` 20 | 21 | This script makes use of the following dependencies that you may need to install: 22 | 23 | * [fio](https://github.com/axboe/fio) 24 | * [ioping](https://github.com/koct9i/ioping) 25 | 26 | ## Usage 27 | 28 | ```bash 29 | node cloud-bench \ 30 | --interval [seconds between tests] \ 31 | --limit [number of iterations] \ 32 | --nodisk 33 | --out [output filename] 34 | ``` 35 | 36 | The below command will run performance tests every 30 minutes for 24 hours. 37 | 38 | ```bash 39 | node cloud-bench \ 40 | --interval 1800 \ 41 | --limit 48 \ 42 | --out results.csv 43 | ``` 44 | 45 | Sample output: 46 | 47 | | Time | Download (CDN) | Ping | Download | Upload | CPU Time | Read IOPS | Write IOPS | IO Ping | 48 | | ----- | ---------------- | ----- | ---------- | ------- | ---------- | ----------- | ---------- | --------| 49 | | Mon Apr 02 2018 21:35:05 GMT-0500 (CDT) | 32 | 28 | 49 | 11 | 23.387 | 37936 | 18428 |15.4us | 50 | 51 | If you are running these tests on a remote machine, it is recommended to pair it with something like [forever](https://github.com/nodejitsu/forever) to turn the test into a daemon. 52 | 53 | ## License 54 | 55 | Copyright (c) 2018 [James Simpson](https://twitter.com/GoldFireStudios) and [GoldFire Studios, Inc.](https://goldfirestudios.com) 56 | 57 | Released under the MIT License. 58 | -------------------------------------------------------------------------------- /bench.js: -------------------------------------------------------------------------------- 1 | /*! 2 | * node-cloud-bench 3 | * https://github.com/goldfire/node-cloud-bench 4 | * 5 | * (c) 2018, James Simpson of GoldFire Studios 6 | * goldfirestudios.com 7 | * 8 | * MIT License 9 | */ 10 | 11 | const fs = require('fs'); 12 | const crypto = require('crypto'); 13 | const {exec} = require('child_process'); 14 | const colors = require('colors'); 15 | const opts = require('nomnom').option('nodisk', {flag: true}).parse(); 16 | const {UniversalSpeedtest, SpeedUnits} = require('universal-speedtest'); 17 | 18 | // Prep the speed test. 19 | const universalSpeedtest = new UniversalSpeedtest({ 20 | measureUpload: true, 21 | downloadUnit: SpeedUnits.MBps, 22 | }); 23 | 24 | // Check the usage. 25 | const {interval, limit, out, nodisk} = opts; 26 | const isMac = process.platform === 'darwin'; 27 | const delay = 3000; 28 | if (!interval || !limit || !out) { 29 | console.log("Usage: node cloud-bench --interval [seconds] --limit [number] [--nodisk] --out [output file]".yellow.bold); 30 | return; 31 | } 32 | 33 | // Create the empty output file. 34 | fs.writeFileSync(out, 'Time,Download (CDN),Ping,Download,Upload,CPU Time,Read IOPS,Write IOPS,IO Ping'); 35 | 36 | // Get the high resolution time. 37 | const now = () => { 38 | const hrtime = process.hrtime(); 39 | return hrtime[0] * 1000000 + hrtime[1] / 1000; 40 | }; 41 | 42 | // Begin the interval. 43 | let total = 0; 44 | const bench = () => { 45 | // Check if we've reached the limit. 46 | if (total >= limit) { 47 | console.log("WIN - Benchmark complete!".green.bold); 48 | process.exit(); 49 | return; 50 | } 51 | total += 1; 52 | 53 | let output = fs.readFileSync(out, 'utf-8') + '\n' + new Date(); 54 | 55 | // Execute the download benchmark (uses various CDN tests). 56 | const netBench1 = () => { 57 | let total = 0; 58 | const urls = [ 59 | 'https://cachefly.cachefly.net/100mb.test', 60 | 'https://mirror.nl.leaseweb.net/speedtest/100mb.bin', 61 | 'https://speed.hetzner.de/100MB.bin', 62 | 'https://ping.online.net/100Mo.dat', 63 | 'https://proof.ovh.net/files/100Mb.dat', 64 | ]; 65 | 66 | return Promise.all(urls.map((url) => { 67 | return new Promise((resolve) => { 68 | exec(`curl --max-time 10 -so /dev/null -w '%{speed_download}\n' '${url}'`, (err, stdout) => { 69 | total += parseFloat(stdout) / 1024 / 1024; 70 | 71 | setTimeout(resolve, 5000); 72 | }); 73 | }); 74 | })).then(() => { 75 | // Update the values in the data. 76 | output += `,${total / urls.length}`; 77 | 78 | return new Promise((resolve) => { 79 | setTimeout(resolve, delay); 80 | }); 81 | }); 82 | }; 83 | 84 | // Execute the network benchmark (uses speedtest.net for ping, download & upload). 85 | const netBench2 = () => { 86 | return new Promise((resolve) => { 87 | universalSpeedtest.runCloudflareCom().then((result) => { 88 | const {ping, downloadSpeed, uploadSpeed} = result; 89 | 90 | // Update the values in the data. 91 | output += `,${ping},${downloadSpeed},${uploadSpeed}`; 92 | 93 | setTimeout(resolve, delay); 94 | }); 95 | }); 96 | }; 97 | 98 | // Execute the CPU benchmark (this will run various array, hashing, etc operations and time it). 99 | const cpuBench = () => { 100 | return new Promise((resolve) => { 101 | const start = process.hrtime(); 102 | let hashes = []; 103 | 104 | // Generate md5 hashes. 105 | for (let i = 0; i < 2500000; i += 1) { 106 | hashes.push(crypto.createHash('sha256').update(`${i}`).digest('hex')); 107 | } 108 | 109 | // Sort the array alphabetically. 110 | hashes.sort((a, b) => { 111 | if (a < b) { 112 | return -1; 113 | } 114 | if (a > b) { 115 | return 1; 116 | } 117 | 118 | return 0; 119 | }); 120 | 121 | // Filter out hashes that have an "a" as the first character. 122 | hashes = hashes.filter(hash => hash[0] !== 'a'); 123 | 124 | // Loop through the hashes and splice them from the array. 125 | for (let i = hashes.length - 1; i >= 0; i -= 1) { 126 | hashes.splice(i, 1); 127 | } 128 | 129 | // Add the total time to complete to the output. 130 | const diff = process.hrtime(start); 131 | const seconds = (diff[0] * 1e9 + diff[1]) / 1e9; 132 | output += `,${seconds}`; 133 | 134 | setTimeout(resolve, delay); 135 | }); 136 | }; 137 | 138 | // Execute the disk IO benchmark (random read with fio). 139 | const diskReadBench = () => { 140 | if (nodisk) { 141 | output += ',N/A'; 142 | return Promise.resolve(); 143 | } 144 | 145 | return new Promise((resolve) => { 146 | exec(`fio --name=randread --ioengine=${isMac ? 'posixaio' : 'libaio'} --direct=1 --bs=4k --iodepth=64 --size=4G --rw=randread --gtod_reduce=1 --output-format=json`, (err, stdout) => { 147 | const {iops} = JSON.parse(stdout).jobs[0].read; 148 | 149 | // Update the values in the data. 150 | output += `,${iops}`; 151 | 152 | setTimeout(resolve, delay); 153 | }); 154 | }); 155 | }; 156 | 157 | // Execute the disk IO benchmark (random write with fio). 158 | const diskWriteBench = () => { 159 | if (nodisk) { 160 | output += ',N/A'; 161 | return Promise.resolve(); 162 | } 163 | 164 | return new Promise((resolve) => { 165 | exec(`fio --name=randwrite --ioengine=${isMac ? 'posixaio' : 'libaio'} --direct=1 --bs=4k --iodepth=64 --size=4G --rw=randwrite --gtod_reduce=1 --output-format=json`, (err, stdout) => { 166 | const {iops} = JSON.parse(stdout).jobs[0].write; 167 | 168 | // Update the values in the data. 169 | output += `,${iops}`; 170 | 171 | setTimeout(resolve, delay); 172 | }); 173 | }); 174 | }; 175 | 176 | // Execute the disk ping benchmark (using ioping). 177 | const diskPingBench = () => { 178 | if (nodisk) { 179 | output += ',N/A'; 180 | return Promise.resolve(); 181 | } 182 | 183 | return new Promise((resolve) => { 184 | exec('ioping -c 10 .', (err, stdout) => { 185 | const parsed = new RegExp(/ \/\s(.+?)\s\/ /g).exec(stdout); 186 | 187 | // Update the values in the data. 188 | output += `,${parsed[1]}`; 189 | 190 | setTimeout(resolve, delay); 191 | }); 192 | }); 193 | }; 194 | 195 | // Write the data to the output file. 196 | const writeData = () => { 197 | fs.writeFileSync(out, output); 198 | }; 199 | 200 | netBench1() 201 | .then(netBench2) 202 | .then(cpuBench) 203 | .then(diskReadBench) 204 | .then(diskWriteBench) 205 | .then(diskPingBench) 206 | .then(writeData) 207 | .catch(console.log); 208 | }; 209 | 210 | // Setup the interval to run the benchmarks. 211 | setInterval(bench, interval * 1000); 212 | bench(); 213 | 214 | console.log(("Benchmark underway: " + limit + " " + interval + " second intervals.").cyan); 215 | --------------------------------------------------------------------------------