
Raspberry Piより小さい40×40mmサイズでギガビットイーサ搭載するワンボードコンピュータ、 NanoPi NEO2 Blackと OLED HAT を使い、まるでネットワーク保守に使うケーブルテスタのように、対象のイーサネットポートに挿せばインターネットスピードテストを実行してくれる、そんな スピードテスタ を製作します。
Speedtest.net CLI
普段はPCのブラウザやスマホアプリからお世話になっているSpeedtest.netには、CLIベースの計測機能が用意されています。
各プラットフォームのOSでのインストール手順が示されており、その中のDebian系は次の通りインストールするよう解説されています。
1 2 3 4 5 6 |
sudo apt-get install gnupg1 apt-transport-https dirmngr export INSTALL_KEY=379CE192D401AB61 sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys $INSTALL_KEY echo "deb https://ookla.bintray.com/debian generic main" | sudo tee /etc/apt/sources.list.d/speedtest.list sudo apt-get update sudo apt-get install speedtest |
しかしながら今回は一行目のインストールは該当せず、レポジトリキーのインストールと登録、そしてaptによるバイナリインストールのみでした。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 |
$ export INSTALL_KEY=379CE192D401AB61 $ export DEB_DISTRO=$(lsb_release -sc) $ sudo apt-key adv --keyserver keyserver.ubuntu.com --recv-keys $INSTALL_KEY Executing: /tmp/tmp.Y6f3KG8EzR/gpg.1.sh --keyserver keyserver.ubuntu.com --recv-keys 379CE192D401AB61 gpg: requesting key D401AB61 from hkp server keyserver.ubuntu.com gpg: key D401AB61: public key "Bintray (by JFrog) <bintray@bintray.com>" imported gpg: Total number processed: 1 gpg: imported: 1 (RSA: 1) $ echo "deb https://ookla.bintray.com/debian ${DEB_DISTRO} main" | sudo tee /etc/apt/sources.list.d/speedtest.list deb https://ookla.bintray.com/debian xenial main $ sudo apt update $ sudo apt install speedtest Reading package lists... Done Building dependency tree Reading state information... Done The following NEW packages will be installed: speedtest 0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded. Need to get 915 kB of archives. After this operation, 2,259 kB of additional disk space will be used. Get:1 https://ookla.bintray.com/debian xenial/main arm64 speedtest arm64 1.0.0.2-1.5ae238b [915 kB] Fetched 915 kB in 2s (399 kB/s) debconf: delaying package configuration, since apt-utils is not installed Selecting previously unselected package speedtest. (Reading database ... 44838 files and directories currently installed.) Preparing to unpack .../speedtest_1.0.0.2-1.5ae238b_arm64.deb ... Unpacking speedtest (1.0.0.2-1.5ae238b) ... Setting up speedtest (1.0.0.2-1.5ae238b) ... |
【2021.06追記】
Speedtest CLIのレポジトリがpackagecloudへ変更になり、レポジトリを設定してくれるスクリプトが用意されていました。
1 2 |
$ curl -s https://install.speedtest.net/app/cli/install.deb.sh | sudo bash $ sudo apt-get install speedtest |
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
$ curl -s https://install.speedtest.net/app/cli/install.deb.sh | sudo bash Detected operating system as Ubuntu/focal. Checking for curl... Detected curl... Checking for gpg... Detected gpg... Running apt-get update... done. Installing apt-transport-https... done. Installing /etc/apt/sources.list.d/ookla_speedtest-cli.list...done. Importing packagecloud gpg key... done. Running apt-get update... done. The repository is setup! You can now install packages. $ sudo apt install speedtest Reading package lists... Done Building dependency tree Reading state information... Done The following NEW packages will be installed: speedtest 0 upgraded, 1 newly installed, 0 to remove and 0 not upgraded. Need to get 915 kB of archives. After this operation, 2,259 kB of additional disk space will be used. Get:1 https://packagecloud.io/ookla/speedtest-cli/ubuntu focal/main arm64 speedtest arm64 1.0.0.2-1.5ae238b [915 kB] Fetched 915 kB in 1s (1,491 kB/s) Selecting previously unselected package speedtest. (Reading database ... 96848 files and directories currently installed.) Preparing to unpack .../speedtest_1.0.0.2-1.5ae238b_arm64.deb ... Unpacking speedtest (1.0.0.2-1.5ae238b) ... Setting up speedtest (1.0.0.2-1.5ae238b) ... Processing triggers for man-db (2.9.1-1) ... |
【2021.06追記ここまで】
インストールされたspeedtestコマンドのオプションは次の通り。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 |
$ speedtest --help Speedtest by Ookla is the official command line client for testing the speed and performance of your internet connection. Version: speedtest 1.0.0.2 Usage: speedtest [<options>] -h, --help Print usage information -V, --version Print version number -L, --servers List nearest servers -s, --server-id=# Specify a server from the server list using its id -I, --interface=ARG Attempt to bind to the specified interface when connecting to servers -i, --ip=ARG Attempt to bind to the specified IP address when connecting to servers -o, --host=ARG Specify a server, from the server list, using its host's fully qualified domain name -p, --progress=yes|no Enable or disable progress bar (Note: only available for 'human-readable' or 'json' and defaults to yes when interactive) -P, --precision=# Number of decimals to use (0-8, default=2) -f, --format=ARG Output format (see below for valid formats) -u, --unit[=ARG] Output unit for displaying speeds (Note: this is only applicable for ‘human-readable’ output format and the default unit is Mbps) -a Shortcut for [-u auto-decimal-bits] -A Shortcut for [-u auto-decimal-bytes] -b Shortcut for [-u auto-binary-bits] -B Shortcut for [-u auto-binary-bytes] --selection-details Show server selection details --ca-certificate=ARG CA Certificate bundle path -v Logging verbosity. Specify multiple times for higher verbosity --output-header Show output header for CSV and TSV formats Valid output formats: human-readable (default), csv, tsv, json, jsonl, json-pretty Machine readable formats (csv, tsv, json, jsonl, json-pretty) use bytes as the unit of measure with max precision Valid units for [-u] flag: Decimal prefix, bits per second: bps, kbps, Mbps, Gbps Decimal prefix, bytes per second: B/s, kB/s, MB/s, GB/s Binary prefix, bits per second: kibps, Mibps, Gibps Binary prefix, bytes per second: kiB/s, MiB/s, GiB/s Auto-scaled prefix: auto-binary-bits, auto-binary-bytes, auto-decimal-bits, auto-decimal-bytes |
使い方はまず、-Lオプションで近くの計測サーバ一覧を取得し、適当なサーバIDを-sオプションで指定して計測する要領です。計測結果をブラウザベースで見るためのURLも、結果の最後に表示されます。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
$ speedtest -L Closest servers: ID Name Location Country ============================================================================== 1536 STC Hong Kong China 21182 Gemnet LLC Hong Kong Hong Kong 22126 i3D.net Hong Kong Hong Kong 2993 Website Solution Limited Hong Kong China 26461 Telin Hong Kong Hong Kong 28912 fdcservers.net Hong Kong Hong Kong 32155 China Mobile Hong Kong Hong Kong Hong Kong 19036 SmarTone Hong Kong China 33414 3HK Tsing Yi Hong Kong 13538 CSL Kwai Chung Hong Kong $ speedtest -s 1536 ============================================================================== You may only use this Speedtest software and information generated from it for personal, non-commercial use, through a command line interface on a personal computer. Your use of this software is subject to the End User License Agreement, Terms of Use and Privacy Policy at these URLs: https://www.speedtest.net/about/eula https://www.speedtest.net/about/terms https://www.speedtest.net/about/privacy ============================================================================== Do you accept the license? [type YES to accept]: YES License acceptance recorded. Continuing. Speedtest by Ookla Server: STC - Hong Kong (id = 1536) ISP: Hong Kong Broadband Network Latency: 2.98 ms (0.07 ms jitter) Download: 548.09 Mbps (data used: 794.4 MB) Upload: 754.43 Mbps (data used: 1.2 GB) Packet Loss: Not available. Result URL: https://www.speedtest.net/result/c/########-####-####-####-############ |
また、出力形式にJSONを指定することも出来るので、今回のように出力結果をNode.jsなどのプログラムで利用するのにも適しています。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
$ speedtest -s 1536 -f json {"type":"result", "timestamp":"2021-00-00T00:00:00Z", "ping":{ "jitter":0.65000000000000002, "latency":2.2850000000000001 }, "download":{ "bandwidth":69950573, "bytes":778063976, "elapsed":11014 }, "upload":{ "bandwidth":66933514, "bytes":922423888, "elapsed":15011 }, "isp":"Hong Kong Broadband Network", "interface":{ "internalIp":"192.168.26.193", "name":"eth0", "macAddr":"##:##:##:##:##:##", "isVpn":false, "externalIp":"###.###.###.###" }, "server":{ "id":1536, "name":"STC", "location":"Hong Kong", "country":"China", "host":"suntechspeedtest.com", "port":8080, "ip":"218.102.52.37" }, "result":{ "id":"########-####-####-####-############", "url":"https://www.speedtest.net/result/c/########-####-####-####-############" } } |
Node.js: byte→Mbps変換
上記のJSON形式で受け取った結果のうち、速度を示すbandwidthはバイト表記になっていました。それが仕様なので、別途Node.js側で読みやすいMbps表記へと変換することとします。様々なライブラリが公開されている中から、こちらのpretty-bytesライブラリを入れてみることにしました。
1 2 3 4 5 |
$ npm install pretty-bytes added 1 package, and audited 63 packages in 3s 3 packages are looking for funding run `npm fund` for details found 0 vulnerabilities |
実際の使い方は以下の要領です。バイト・ビット変換はせず、単純に数字を1024でキリの良いところまで割ってくれるだけの仕様でしたので、予め8倍した上で渡し、末尾に付与される「bit」を「bps」に置換すれば、見慣れた速度表記になります。
1 2 3 4 5 6 7 8 9 10 |
const prettyBytes = require('pretty-bytes'); const b = 66933514; console.log(prettyBytes(b, {maximumFractionDigits: 2})); > 66.93 MB console.log(prettyBytes(b*8, {maximumFractionDigits: 2})); > 535.47 MB console.log(prettyBytes(b*8, {bits: true, maximumFractionDigits: 2})); > 535.47 Mbit console.log(prettyBytes(b*8, {bits: true, maximumFractionDigits: 2}).replace('bit','bps')); > 535.47 Mbps |
Node.js: Pingで導通確認
ネットワーク上の適当なホストに対するPingには、Node-Pingライブラリを使います。インストールはこのように。
1 2 3 4 5 |
$ npm install ping added 3 packages, and audited 62 packages in 5s 2 packages are looking for funding run `npm fund` for details found 0 vulnerabilities |
ライブラリには様々な使い方が提示されているのですが、今回は外向きネットワークへの疎通確認に使いたいので、シンプルに組みます。
1 2 3 4 5 6 7 |
const ping = require('ping'); const host = '8.8.8.8'; ping.sys.probe(host, function(isAlive) { const msg = isAlive ? 'host ' + host + ' online' : 'host ' + host + ' offline'; console.log(msg); }); > host 8.8.8.8 online |
Node.js: WAN IPを取得
次に、外向きネットワークの疎通が確認されたら、現在のWAN IPを取得したいと思います。この手のライブラリも数多く存在しますが、IPアドレス表示サービスに使うサーバの既知性から、whatismyipライブラリにしてみました。
1 2 3 4 5 6 7 |
$ npm install whatismyip npm WARN deprecated har-validator@5.1.5: this library is no longer supported npm WARN deprecated request@2.88.2: request has been deprecated, see https://github.com/request/request/issues/3142 added 49 packages, and audited 59 packages in 22s 2 packages are looking for funding run `npm fund` for details found 0 vulnerabilities |
ライブラリページのサンプルをそのまま試してみて、戻り値を確認してみました。IPアドレスはdata.ipで簡単に取り出せそうです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 |
var ip = require('whatismyip'); var options = { url: 'http://checkip.dyndns.org/', truncate: '', timeout: 60000, matchIndex: 0}; ip.whatismyip(options, function(err, data){ if (err === null) {console.log(data);} }); > { url: 'http://checkip.dyndns.org/', truncate: '', timeout: 60000, matchIndex: 0, time: 1616037010565, ip: '###.###.###.###' } |
これまでにこなした命題と併せ、いよいよ一つのアプリケーションとして組み上げます。
コーディング組み上げ
アプリはpm2により自動起動すると、自身のLANポートのIPアドレスを定期的にチェックします。LANケーブルが繋がっていなかったり、DHCPサーバから有効なIPアドレスがもらえない場合はその旨表示し、エラーで落ちないようにハンドリングしつつ、繰り返し試行します。
LANポートにIPアドレスが付与されているのを確認したら、外向きにPingを打ち、応答があれば疎通有りを表示。自動で動作するのはここまでで、以降はボタン操作をにより開始されます。その割り当ては、
- 左ボタン:WAN IPチェック
- 中ボタン:スピードテスト
- 右ボタン:シャットダウン(前記事で実装済)
としていて、左ボタン押してから中ボタンを押すルーティンを想定しています。数年ぶりにリハビリ兼ねてガッツリ組んでみましたが、まだ慣れていないのでダラダラしたコーディングになってしまっているのは、ご勘弁を。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 |
// INTERNET SPEED TESTER // const Gpio = require('onoff').Gpio, btn0 = new Gpio(0, 'in', 'both'), btn2 = new Gpio(2, 'in', 'both'), btn3 = new Gpio(3, 'in', 'both'); var wanst = '---'; // OLED INIT const font = require('oled-font-5x7'); const i2c = require('i2c-bus'), i2cBus = i2c.openSync(0); var oled = require('oled-i2c-bus'); const opts = { width: 128, height: 64, address: 0x3C }; try { var oled = new oled(i2cBus, opts); oled.clearDisplay(); oled.turnOnDisplay(); } catch(err) { console.log('OLED HAT Err, Not Available'); console.log(err.message); process.exit(1); }; // LINE_1: SHOW LAN IP REPEATEDLY const os = require('os'); const printlanip = function () { const networkInterfaces = os.networkInterfaces(); var ip = "---"; try { ip = networkInterfaces.eth0[0].address; } catch (err) { console.log(err); } printStrSingle(1, 'LAN '+ip); } const tm1 = setInterval(printlanip, 2000); setTimeout(printlanip, 0); // LINE_2: SHOW WAN STATUS REPEATEDLY const ping = require('ping'); const printwanst = function () { const host = '8.8.8.8'; ping.sys.probe(host, function(isAlive) { const msg = isAlive ? 'host ' + host + ' online' : 'host ' + host + ' offline'; console.log(msg); if (isAlive) {wanst='RDY'} else {wanst='OFF'} }); printStrSingle(10, 'WAN '+wanst); } const tm2 = setInterval(printwanst, 10000); setTimeout(printwanst, 0); // LEFT BUTTON FOR WAN IP CHECK btn0.watch(function (err, value) { if (err) {throw err} if (value) { console.log ('*** 0:ON ***') if (wanst=='RDY') { // WAN IP CHECK clearInterval(tm2); printStrSingle(10, 'WAN CHK...'); printStrSingle(19, ' '); const printwanip = function () { const ip = require('whatismyip'); const options = { url: 'http://checkip.dyndns.org/', truncate: '', timeout: 60000, matchIndex: 0 }; ip.whatismyip(options, function(err, data){ if (err === null) { console.log('wan ip '+data.ip); printStrSingle(10, 'WAN '+data.ip); printStrSingle(19, 'MID BTN FOR SPEEDTEST'); } }); } printwanip(); } else { // NO WAN CONNECTIVITY printStrSingle(10, 'WAN XXX'); } } else {console.log ('*** 0:OFF ***')} }) // MID BUTTON FOR SPEEDTEST btn2.watch(function (err, value) { if (err) {throw err} if (value) { console.log ('*** 2:ON ***'); console.log ('SPEEDTESTING...'); printStrSingle(19, 'SPEEDTESTING...'); // PROGRESS DOTS let dots = 'TESTING'; printStrSingle(19, dots); const printDots = function () { printStrSingle(19, dots); dots = dots+'.'; } const tm3 = setInterval(printDots, 1000); // SPEEDTEST const { exec } = require('child_process') const prettyBytes = require('pretty-bytes'); exec('speedtest -s 1536 -f json', (err, stdout, stderr) => { if (err) {console.log(`stderr: ${stderr}`);} const spobj = JSON.parse(stdout); const strUP = prettyBytes(spobj.upload.bandwidth*8, {bits: true, maximumFractionDigits: 2}).replace('bit','bps'); const strDN = prettyBytes(spobj.download.bandwidth*8, {bits: true, maximumFractionDigits: 2}).replace('bit','bps'); console.log('SPEEDTESTED!'); console.log('UP '+strUP); console.log('DN '+strDN); console.log('RESULT_URL '+spobj.result.url); clearInterval(tm3); printStrSingle(19, 'SPEEDTESTED!'); printStrSingle(28, 'U P '+strUP); printStrSingle(37, 'DOWN '+strDN); console.log(`stdout: ${stdout}`) console.log(`stderr: ${stderr}`) }); } else {console.log ('*** 2:OFF ***')} }) // RIGHT BUTTON FOR SHUTDOWN btn3.watch(function (err, value) { if (err) {throw err} if (value) { const execSync = require('child_process').execSync; console.log ('*** 3:ON ***'); btn0.unexport(); btn2.unexport(); btn3.unexport(); oled.turnOffDisplay(); // oled.update(); // oled = null; const res = "" + execSync('shutdown -h now'); console.log (res); } else {console.log ('*** 3:OFF ***')} }) // CTL+C INTERRUPT process.on('SIGINT', function () { btn0.unexport(); btn2.unexport(); btn3.unexport(); oled.turnOffDisplay(); console.log('*** end ***'); }) // FUNC function printStrSingle (y, word) { //ERASE LINE AND PRINT oled.setCursor(3, y); oled.writeString(font, 1, ' ', 1, false); oled.setCursor(3, y); oled.writeString(font, 1, word, 1, false); } |
尚、これまでにインストールしたNode.jsライブラリは次の通りです。
1 2 3 4 5 6 7 8 9 10 11 12 13 |
$ npm ls pi@ /home/pi ├── i2c-bus@5.2.1 ├── oled-font-5x7@1.0.3 ├── oled-i2c-bus@1.0.12 ├── onoff@6.0.1 ├── ping@0.4.0 ├── pretty-bytes@5.6.0 └── whatismyip@1.1.4 $ sudo npm -g ls /usr/lib ├── npm@7.6.3 └── pm2@4.5.5 |
実際に仕事場の適当なLANポートに挿し計測してみました。フォント小さいのですがくっきり表示されるので、老眼でも意外と読めます。
自宅に持ち帰り、家庭向け1G Optical Fiberにぶら下がっているネットワーク機器の各計測点において測定してみました。
- ONU光終端器にWAN直結
- ルータのGbE LANポート
- ルータのGbEにぶら下がるGbEハブ下
以前より気になっていて、それがこのインターネットスピードテスタ製作の動機にもなっていたのですが、家庭向けとは言え、公称ギガビットの光を引き入れている割に遅い気がするのは、ルータがボトルネックになっているようです。GL-iNet GL-AR750S SlateのWAN-LANスループットはメーカーフォーラムページを見ても700M程度はあるそうなので、別途調べてみる必要がありそうです。