NanoPi NEO2のシステム監視 RPi-Monitorとnetdata

Raspberry PiにしろNanoPi NEOにしろおもちゃなので常時稼働させるのはちょっと心配。ときどき稼働状況を見たいということがある筈。
あまり大がかりなのではなく簡単にシステムの稼働状況を見るツールとしてRPi-MonitorとnetdataをNanoPi NEO2に入れてみた。

RPi-Monitor

NanoPi NEO2とarmbianの組み合わせでは以前にも書いたがRPi-Monitor (rpimonitor)が簡単に使えるようになっている。RPi-Monitorインストール用コマンドがarmbianmonitorに用意されているので実行するだけ。

RPi-Monitorインストール

# armbianmonitor -r
中略
[ ok ] Restarting rpimonitor (via systemctl): rpimonitor.service.
Processing triggers for libc-bin (2.24-11+deb9u1) ...

Now you're able to enjoy RPi-Monitor at http://:8888
#

そんなに大したものではなさそう(失礼)なのにいろいろ展開されるので個人的にはrpimonitorは好きじゃない。
インストール後に自動的にサービス起動まで行うのでブラウザで開くだけの筈なんだけど、ネットワーク系のモニタ設定がワザと無効になっているので設定ファイルを変更する。

設定変更

/etc/rpimonitor/template/network.conf
 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
[code lang="plain"]
dynamic.10.name=net_received
dynamic.10.source=/sys/class/net/eth0/statistics/rx_bytes
dynamic.10.regexp=(.*)
dynamic.10.postprocess=$1*-1
dynamic.10.rrd=DERIVE
dynamic.10.max=0

dynamic.11.name=net_send
dynamic.11.source=/sys/class/net/eth0/statistics/tx_bytes
dynamic.11.regexp=(.*)
dynamic.11.postprocess=
dynamic.11.rrd=DERIVE
dynamic.11.min=0

web.status.1.content.8.name=eth0
web.status.1.content.8.icon=network.png
#web.status.1.content.8.line.1="To activate network monitoring, edit and customize <font color='#AA0000'><b>network.conf</b></font>"
#web.status.1.content.8.line.2="Help is available in man pages:"
#web.status.1.content.8.line.3="<font color='#AA0000'><b>man rpimonitord</b></font> or <font color='#AA0000'><b>man rpimonitord.conf</b></font>"
web.status.1.content.8.line.1="Ethernet Sent: <b>"+KMG(data.net_send)+"<i class='icon-arrow-up'></i></b> Received: <b>"+KMG(Math.abs(data.net_received)) + "<i class='icon-arrow-down'></i></b>"

web.statistics.1.content.2.name=eth0
web.statistics.1.content.2.graph.1=net_send
web.statistics.1.content.2.graph.2=net_received
web.statistics.1.content.2.graph_options.yaxis={ tickFormatter: function (v) { if (Math.abs(v) > 1048576) return (Math.round(v*10/1024/1024)/10) + " MiB/s" ; if (Math.abs(v) > 1024) return (Math.round(v*10/1024)/10) + " KiB/s" ; else return v + " B/s" }, }
web.statistics.1.content.2.ds_graph_options.net_send.label=Upload bandwidth (bytes)
web.statistics.1.content.2.ds_graph_options.net_send.lines={ fill: true }
web.statistics.1.content.2.ds_graph_options.net_send.color="#FF7777"
web.statistics.1.content.2.ds_graph_options.net_received.label=Download bandwidth (bytes)
web.statistics.1.content.2.ds_graph_options.net_received.lines={ fill: true }
web.statistics.1.content.2.7ds_graph_options.net_received.color="#77FF77"

基本的には設定ファイルのヘッダ部以外でコメント行を非コメントに、非コメント行をコメントに入れ替え、 web.status.1.content.数字.name=network を web.status.1.content.数字.name=eth0にすれば良い筈。

rpimonitorを再起動

# service rpimonitor restart

RPi-Monitorを見る

ウェブブラウザでrpimonitorを開く。
http://192.168.6.16:8888

上の例では192.168.6.16はNanoPi NEO2のIPアドレスとする。

rpimonitor 1
ステータス画面。わかりやすいけどこんなに広い表示エリアを使って表示する程のことじゃない。一番下のネットワークの状態は送受信速度ではなく送受信した通信量。

rpimonitor 2
Statistics画面。下側の小さなグラフで表示した範囲をマウスで選択すると上側の大きなグラフにその範囲が表示されるというのは良い機能かも。
上の画像ではCPUの温度を表示させている。右寄りで温度が下がって6度台になっているのはNanoPi NEO2のヒートシンクに風を当てたから。風を当てる前も15度前後という低い温度だが、これは室温が4度という寒い部屋だから。

netdata

netdataは以前にFreeBSD用に書いたけど、これもNanoPi NEO2とarmbianの組み合わせで動く(パッケージが用意されている)。

netdataインストール

# apt-get update
# apt-cache search netdata  ←netdataを検索
netdata - real-time charts for system monitoring       ←該当3つが表示されるが、これを入れたい
netdata-data - real-time charts for system monitoring (Data)
netdata-dbgsym - Debug symbols for netdata
# apt-get install netdata   ←netdataインストール
Reading package lists... Done
Building dependency tree       
Reading state information... Done
The following additional packages will be installed:
  fonts-font-awesome libjs-bootstrap libyaml-0-2 netdata-data python-yaml
Recommended packages:
  nodejs
The following NEW packages will be installed:
  fonts-font-awesome libjs-bootstrap libyaml-0-2 netdata netdata-data python-yaml
0 upgraded, 6 newly installed, 0 to remove and 0 not upgraded.
Need to get 2,091 kB of archives.
After this operation, 7,808 kB of additional disk space will be used.
Do you want to continue? [Y/n] y              ←確認を求められたらyを押す
後略

設定ファイルの変更

/etc/netdata/netdata.conf
1
2
3
4
5
6
7
[global]
    run as user = netdata
    web files owner = root
    web files group = root
    # Netdata is not designed to be exposed to potentially hostile
    # networks.See https://github.com/firehol/netdata/issues/164
    bind socket to IP = *

bind socket to IP が初期値は127.0.0.1になっていて外部から閲覧できないので * にでも変えておく。他は特に変更不要かと。

netdataを起動する

# service netdata start

netdataを見る

ウェブブラウザでnetdataを開く。
http://192.168.6.16:19999

上の例では192.168.6.16はNanoPi NEO2のIPアドレスとする。

netdata 1
netdataはリアルタイム性がウリなのでグラフやメーターがガンガン動く、だけじゃなくてグラフの表示範囲を弄り倒せる。そのせいで特にモバイル環境で表示すると例えばスクロールさせようとしてうっかりグラフに触れるとグラフ操作になったりというウザさもある。

netdata 2
ブラウザ画面の縮小表示。初期設定状態では表示項目が多すぎる。似たようなのが繰り返し表示されるので自分が見たいのがどれか混乱する。

RPi-Monitor、netdata共に稼働させてるホストで情報収集してそのホストで表示させるタイプなので別の監視用ホストに情報を集約させるようにはできていない。(ただし、カスタムDashboardを作ってその中に別のホストの「情報を表示すること」は可能。)
でも、難しさが皆無なので手軽に入れられるし、どちらもWebUIなので見るのも簡単なのよね。

関連記事:

Cisco 7961G電話機でCardDAVの連絡帳を利用する

この記事の題名は7961電話機となっているけどCiscoのIP電話ならたぶんそのまま利用できる筈。Cisco以外のIP電話機でもウェブサーバからXMLで電話帳を取る仕組みがある機種ならXMLを少し変えるだけで同じように利用できると思う。

Cisco 7961G電話機のサービスメニュー設定の記事で、電話帳としてXMLテキストファイルを書くなら32エントリーまでなので(少なすぎて使い物にならないので)普通はCGIなどでというようなことを書いたけど、せっかくなのでCGIの電話帳サンプルをブログ記事用に作ってみた。
巨大にならないよう必要最低限の「なんとか動くよね」レベルなのでこの記事のサンプルを流用してどうこうするよりは、ちょろっと見るだけ見て後は自分で1から作った方がまともなのが出来るんじゃないかな。

連絡帳のデータとしてCardDAVを利用することにしたのは個人・グループレベルであれば大掛かりな連絡帳データベースを作るほどじゃないし、既に利用しているCardDAVがあればそれをそのまま利用できる方が簡単じゃないかなと思ったから。
企業レベルであったり個人でも連絡先が膨大にあるということであればデータベースやLDAPで管理してそこからデータをひっぱるようにすりゃ良いけど個人ブログでやることじゃなさそうだから触れない。

Ciscoの電話機には一応文字検索用の機能が付いていて電話機のダイヤルボタンを携帯電話の文字打ちの様に押して文字を入力すれば検索できるんだけど、Ciscoということもあって英数字だけ。日本語が入力できないので日本語検索ができない。そこで最も単純に50音の行別(あ行・か行・さ行・・・)に分けてリスト表示して選んで貰うという方式にした。

おおまかな仕組みは、CardDAVサーバから連絡帳データをまるっと貰ってきてそれをひらがな50音の行別に分けてリスト表示。宛先・かけ先を選択したら電話番号を表示。
スクリプトは基本的にはURLの引数による出し分け。

URLの例
  • http://HOGE/directory.php ひらがな50音の行一覧リスト
  • http://HOGE/directory.php?search=xx 指定した行別リスト
  • http://HOGE/directory.php?search=xx&page=nn 指定した行リストに複数ページある場合
  • http://HOGE/directory.php?search=xx&order=nn 指定した行リストから特定の相手を選んだ場合(ページの有無関係なし)

CardDAVサーバからデータを取る部分は自作する気にならなかったのでChristian Putzke氏のCardDAV PHPを利用させて貰う。ファイル1つをGitHubから貰ってくるだけ。変更不要。

CardDAVというかvCardでは多くの種類のエレメントがあるがそれらを網羅することはすっぱり諦めて以下のエレメントだけを扱うことにする。

  • FN: フルネーム表示用
  • X-PHONETIC-LAST-NAME: ラストネーム(姓)のよみがな
  • TEL;TYPE=home: 家の電話番号
  • TEL;TYPE=cell: 携帯の電話番号
  • TEL;TYPE=work: 仕事用の電話番号
  • TEL;TYPE=other: その他の電話番号

例えばクルマ用電話番号のTEL;TYPE=carとか秘書用電話番号のTEL;TYPE=x-assistantとかはもちろん扱わない。(その他たくさん)
CardDAVの登録・編集アプリによっては家用の電話番号をTEL;TYPE=HOME,VOICEのようにおまけ付きで登録するのがあるが、この記事のスクリプトではTEL;TYPE=homeまでを(大文字小文字関係なく)見て家用と判断し、カンマから後ろは無視。あと、1人が複数の携帯番号を持っていて TEL;TYPE=cell:xxxxxxxx が複数ある場合、CardDAVで取得したデータの中で先に出現した番号は後に出現した番号に上書きされるので表示されない。(注意)

ラストネームの最初の文字で50音の行別振り分けを行うのでラストネームを登録していなかったりアルファベットで登録していると振り分けが「英数他」に入る筈。

行別の宛先名が10を超えるとリスト表示の前にページリストが表示される。メニュー表示のアイテム数の限度が不明だけどディレクトリリストと同じく32件が限度であれば行別で登録可能な総数は行別でそれぞれ320件となる。「か行」「さ行」は多いけど「ま行」「や行」は少ないなど偏りがあると「件数の多い○行は320件では足りない」ということがあるかも。もっと増やしたければ1ページ10件を32件に変更すれば1024件まで扱えるということになる。(あくまでも電話機が扱えるメニューリストの最大アイテム数が32であるとするならばだけど)

directory.php (ファイル名は任意)
  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
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
<?php
include_once ('./carddav.php');
//--------------------------------------------------------------
//CardDAVサーバ接続用設定
$davurl = 'https://dav.example.com/remote.php/dav/addressbooks/users/foobar/contacts/'; //CardDAVのURL
$david = 'foobar'; //CardDAVのアカウント
$davpw = 'secretpassword'; //CardDAVのパスワード
//--------------------------------------------------------------
$url = 'http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI'];

header('Content-Type: text/xml');  //共通 レスポンスヘッダ XMLコンテンツとする

if (empty ($_GET['search'])) {
        $top = new xmlMenuGen("CiscoIPPhoneMenu", "MenuItem", "Name", "URL");
        $top->addHead('電話帳','選択して下さい');
        $top->addItem('英数他', $url . '?search=an');
        $top->addItem('あ行',   $url . '?search=aa');
        $top->addItem('か行',   $url . '?search=ka');
        $top->addItem('さ行',   $url . '?search=sa');
        $top->addItem('た行',   $url . '?search=ta');
        $top->addItem('な行',   $url . '?search=na');
        $top->addItem('は行',   $url . '?search=ha');
        $top->addItem('ま行',   $url . '?search=ma');
        $top->addItem('や行',   $url . '?search=ya');
        $top->addItem('ら行',   $url . '?search=ra');
        $top->addItem('わ行',   $url . '?search=wa');
        $top->output();
        exit;
}

//CardDAVサーバ接続・認証
$carddav = new carddav_backend($davurl);
$carddav->set_auth($david, $davpw);

if (! $carddav->check_connection()){
        //DAVサーバに接続できなかったら
        echo phoneTextXML('Error','CardDAVサーバに接続できませんでした');
        exit;
}

//DAVサーバに接続できたらデータ取得
$dataRaw = $carddav->get();
$dataArr = simplexml_load_string($dataRaw);
//var_dump($dataArr); //取得したデータ確認用

//取得したデータを配列に入れる
$test = array();
$fn = array();
$ln = array();
$phm = array();
$pcl = array();
$pwk = array();
$pot = array();
$i = 0; //CardDAVレコード番号として
foreach($dataArr->element as $elem){
        foreach($elem->vcard as $vcard){
                $arvcard = explode("\n",$vcard);
                $fn[$i] = '';
                $ln[$i] = '';
                $phm[$i] = '';
                $pcl[$i] = '';
                $pwk[$i] = '';
                $pot[$i] = '';
                foreach($arvcard as $line){
                        if(preg_match('/^FN:/i', $line)){
                                $fn[$i] = preg_replace('/^FN:/i', '', $line);
                                $fn[$i] = str_replace(array("\r\n", "\r", "\n"), '', $fn[$i]);
                        }
                        if (preg_match('/^X-PHONETIC-LAST-NAME:/i', $line)){
                                $ln[$i]  = preg_replace('/^X-PHONETIC-LAST-NAME:/i', '', $line);
                        }
                        if(preg_match('/^TEL;TYPE=home/i', $line)){
                                $phm[$i] = preg_replace('/[^0-9*#]/', '', $line);
                        }
                        if(preg_match('/^TEL;TYPE=cell/i', $line)){
                                $pcl[$i] = preg_replace('/[^0-9*#]/', '', $line);
                        }
                        if(preg_match('/^TEL;TYPE=work/i', $line)){
                                $pwk[$i] = preg_replace('/[^0-9*#]/', '', $line);
                        }
                        if(preg_match('/^TEL;TYPE=other/i', $line)){
                                $pot[$i] = preg_replace('/[^0-9*#]/', '', $line);
                        }
                }
        $i++;
        }
}

//50音x行別リスト
$idx = $_GET['search'];

$kana_idx = array(
"aa" => "[ア-オあ-おア-オ]",
"ka" => "[カ-コか-こが-ごカ-コガ-ゴ]",
"sa" => "[サ-ソさ-そざ-ぞサ-ソザ-ゾ]",
"ta" => "[タ-トた-とだ-どタ-トダ-ド]",
"na" => "[ナ-ノな-のナ-ノ]",
"ha" => "[ハ-ホは-ほば-ぼぱ-ぽハ-ホバ-ボパ-ポ]",
"ma" => "[マ-モま-もマ-モ]",
"ya" => "[ヤ-ヨや-よヤ-ヨ]",
"ra" => "[ラ-ロら-ろラ-ロ]",
"wa" => "[ワ-ンわ-んワ-ン]",
"an" => "[a-zA-Z0-9]"
);

$kana_gyo = array(
"aa" => "あ行", "ka" => "か行", "sa" => "さ行", "ta" => "た行", "na" => "な行",
"ha" => "は行", "ma" => "ま行", "ya" => "や行", "ra" => "ら行", "wa" => "わ行",
"an" => "英数他"
);

//行別リスト全体取得
$lifn = array();
$liphm = array();
$lipcl = array();
$lipwk = array();
$lipot = array();
$k = 0; //行別レコード番号として
for ($j = 0; $j < $i; $j++) {
        foreach ($kana_idx as $kidx=>$ptn) {
                if (preg_match("/^" . $ptn . "/u", $ln[$j])) {
                        if ($idx == $kidx){
                                $lifn[$k] = $fn[$j];
                                $liphm[$k] = $phm[$j];
                                $lipcl[$k] = $pcl[$j];
                                $lipwk[$k] = $pwk[$j];
                                $lipot[$k] = $pot[$j];
                                $k++;
                        }
                }
        }
}

//ページ数
$maxPage = ceil(($k - 1) / 10);
$amrPage = $k % 10;

//かけ先番号表示
if ((! empty ($_GET['order']) and (! empty ( $_GET['search'])))) {
        $order =  $_GET['order'] -1;
        $dial =new xmlMenuGen("CiscoIPPhoneDirectory", "DirectoryEntry", "Name", "Telephone");
        $dial->addHead($lifn[$order], '選択して下さい');
        if ($liphm[$order]){
                $dial->addItem('家', $liphm[$order]);
        }
        if ($lipcl[$order]){
                $dial->addItem('携帯', $lipcl[$order]);
        }
        if ($lipwk[$order]){
                $dial->addItem('仕事', $lipwk[$order]);
        }
        if ($lipot[$order]){
                $dial->addItem('その他', $lipot[$order]);
        }
        $dial->output();
        exit;
}

//ページ表示
if ((empty ($_GET['order']) and (! empty ( $_GET['search'])))) {
        //ページ指定無し
        if (empty ( $_GET['page'])){
                if ($k == 0) {
                        echo phoneTextXML($kana_gyo[$idx], '登録がありません');
                        exit;
                } elseif ($maxPage > 1){
                    $pmenu = new xmlMenuGen("CiscoIPPhoneMenu", "MenuItem", "Name", "URL");
                    $pmenu->addHead($kana_gyo[$idx], '選択して下さい');
                        for ($p = 1; $p <= $maxPage; $p++) {
                                $purl = $url . '&page=' . "$p";
                                $pmenu->addItem("ページ $p", $purl);
                        }
                        $pmenu->output();
                        exit;
                }
        }

        //行別リスト表示
        if (empty ($_GET['page'])){
                $page = 1;
        } else {
                $page = $_GET['page'];
        }
        //1ページ10アイテム表示の計算
        $lst = $page * 10 - 10;
        if (($k - $lst) > 10){
                $len = $lst + 10;
        } else {
                $len = $lst + $amrPage;
        }
        $pmenu = new xmlMenuGen("CiscoIPPhoneMenu", "MenuItem", "Name", "URL");
        $pmenu->addHead($kana_gyo[$idx], '選択して下さい');
        for ($l = $lst; $l < $len; $l++) {
                $m = $l + 1;
                $url = preg_replace('/page=[0-9]/', '', $url);
                $ourl = $url .'&order=' . "$m";
                $pmenu->addItem($lifn[$l], $ourl);
        }
        $pmenu->output();
        exit;
}

//メッセージ用
function phoneTextXML($title, $body){
        $dom = new DomDocument('1.0', 'utf-8');
        $dom->formatOutput = true;
        $out = $dom->appendChild($dom->createElement('CiscoIPPhoneText'));
         $out->appendChild($dom->createElement('Title', $title));
         $out->appendChild($dom->createElement('Text', $body));
        return $dom->saveXML();
}

//メニュー/番号表示用クラス
class xmlMenuGen{
        public $xmlmenu;
        public $menuItem = array();

        function __construct($h1, $h2, $itmA, $itmB){
                $this->h1 = $h1;
                $this->h2 = $h2;
                $this->itmA = $itmA;
                $this->itmB = $itmB;
                $this->xmlmenu = new DOMDocument('1.0', 'UTF-8');
                $this->xmlmenu->formatOutput = true;
                $this->menuItem = $this->xmlmenu->appendChild( $this->xmlmenu->createElement($this->h1));
        }

        function addHead($title, $prompt){
                $this->menuItem->appendChild($this->xmlmenu->createElement('Title', $title));
                $this->menuItem->appendChild($this->xmlmenu->createElement('Prompt', $prompt));
        }

        function addItem($name, $url){
                $item = $this->menuItem->appendChild( $this->xmlmenu->createElement($this->h2));
                $item->appendChild($this->xmlmenu->createElement($this->itmA, $name));
                $item->appendChild($this->xmlmenu->createElement($this->itmB, $url));
        }

        function output(){
                echo $this->xmlmenu->saveXML();
        }
}
?>

ファイルは1つだけ。carddav.phpと同じディレクトリに置くことを想定している。

トップメニュー以外は表示の度にCardDAVサーバからデータを取得するので動作はトロい。それはないだろということなら例えばトップメニューを開いたときにCardDAVサーバからデータを取得し、テキストファイルとして保存して、後はそのテキストファイルを利用するというようなのもあると思うけど、閉じたネットワークに置くとしてもあまり良くないかなと思っている。

あと、少なくともこのスクリプトを置くウェブサーバはインターネットからはアクセスできないところにしてね。インターネット側に公開してURLがバレたら何の認証もなく連絡先がXMLで丸見えになる。
なるべく電話用のネットワークは完全に閉じてるのが望ましいかと。許してもPBXだけがSIPで外部に繋がる程度で。

関連記事:

Up