最近はあまり流行らないかもしれませんが、自宅のPCをルータとして使っています。常に電源が入っていますので、以前から消費電力を定期的に調べたいと思っていました。
つい先日、部屋に転がっていた古いグラフィックカードをルータとして使っているPCに挿しました。GPUは電力消費が激しいそうなので、ちょうど良い機会だと思い、消費電力がどれ位増加したのか調べてみる事にしました。調べるだけではつまらないので、データを自動で取得しグラフ化する所までを目指してみる事にします。
消費電力を調べる測定器を探してみると「ワットチェッカー」「ワットメーター」という名称で製品がいくつか売られています。ただし、ほとんどの製品は今現在の消費電力を表示するだけで、PCからデータが吸える個人向けの測定器というとあまり種類がなくて、あっても1万円前後と高めの価格設定です。
PCからデータを取得できて、「安い」という条件で探してみると秋月電子で USB接続 ACクランプメータ e-meter 8870という機器を見つけました。ぼくの用途ですと精度は必要無い為これくらいでも良いのです。
※ クランプメータは、電流を測定する測定機器です。
というわけで、機械を購入しデータをグラフ化する所までをまとめてみました。
想定機器
測定対象 | AC電源接続PC |
OS | Linux(debian) |
クランプメータ | e-meter 8870 |
ラインセパレータ | 自作 |
グラフ化ツール | cacti 1.x系列 |
クランプメーターとは
細かい事は考えないことにして電力を測るのに必要なデータは、電圧と電流ですよね。小学校で習いました。
\it{W} = \it{E} \it{I}
電圧は家庭電源を使っていればぼくの必要な精度では変動しないものと見なせますから、必要なのは電流値ということになります。電流を調べるには間に電流計を挿入すれば良いのです。
電流計というのは間に直列に入れる必要があり、稼働中の機器の電流を測定するには向いていません。測定するたびに電源を落とし間に挿入するなんて非現実的です。ではどうしたらよいのでしょうか?
小学校で習った、電磁誘導を覚えていますか?コイルに鉄芯を出し入れすると電圧が発生するというアレです。多感な小学生が大好きな実験ですね。発生した電圧を測定器内のトランスで電流検出を行う測定器がクランプメータ(Clamp meter)です。
ここで変動する交流(AC)なら上の原理で計れそうだけど、直流(DC)は無理じゃない?と思うかもしれません。上の原理では直流電流は計る事ができません。が、しかし、直流測定も可能なクランプメータもあって、それは別な原理を利用しているそうです。
さて、この電磁誘導を利用しているクランプメータには一つ弱点があります。それは
「交流電源の2本の線を一緒に挟むと相殺されて 測定値が 0 になる」
というものです。つまり、普段使っている電源ケーブルを1本ずつに分けなければ測定できないのです。※別な原理を使うクランプメータでは線を分ける必要はありません。
ラインセパレータ
24時間動作使う事もあり、本当は市販品を使いたかったのですがe-meter 8870は、電線を挟む部分が小さくて市販品のケーブルセパレータを使えませんでした。仕方が無いので、自作しました。
e-meter 8870
中国製USB接続のクランプメータです。秋月電子で購入しました。
USB接続 ACクランプメータ e-meter 8870
上で説明したように2芯を挟むと見事に0.0A(ゼロ)が出力されます。仕事でクランプメータを使う事もありますので(仕事で使う機器はさすがに高いだけあって2本をはさんで普通に測定できる)、なにも考えずに線を挟んでみると見事にずっと0が出力されておかしいなぁ?としばし悩みました。
マニュアルを読むとL or N Lineをはさんでねって書いてありました。
PCと接続
マニュアルによればOS側から見るとUSB Serialデバイスとして認識されるようです。PCにLoginしてからe-mter 8870を接続してみます。
環境にもよるかと思いますが、dmesgあたりにUBSデバイスを認識したログが出力されると思います。
1 2 3 4 5 6 7 8 9 10 |
kernel: [93147.468456] usb 4-1.3: new full-speed USB device number 3 using ehci-pci kernel: [93147.580473] usb 4-1.3: New USB device found, idVendor=04d8, idProduct=8870 kernel: [93147.580477] usb 4-1.3: New USB device strings: Mfr=1, Product=2, SerialNumber=0 kernel: [93147.580479] usb 4-1.3: Product: USB SERIES kernel: [93147.580480] usb 4-1.3: Manufacturer: AVIOSYS INC.Taipei TAIWAN mtp-probe: checking bus 4, device 3: "/sys/devices/pci0000:00/0000:00:1d.0/usb4/4-1/4-1.3" mtp-probe: bus: 4, device: 3 was not an MTP device kernel: [93147.686922] cdc_acm 4-1.3:1.0: ttyACM0: USB ACM device kernel: [93147.687397] usbcore: registered new interface driver cdc_acm kernel: [93147.687398] cdc_acm: USB Abstract Control Model driver for USB modems and ISDN adapters |
このログから ttyACM0 として認識された事がわかります。
データを取得してみる
無事にデバイスが認識されたので、接続を行いどのようなデータが取得できるのか確認します。説明書に19200bpsで接続するように書かれているので19200bpsでデバイスを開いてみます。
1 2 3 4 5 6 |
% cu -s 19200 -l /dev/ttyACM0 Command: read:electric current auto on/off:automatic/manually ver:version |
ふむふむ。
1 2 3 4 5 6 7 8 9 10 |
% cu -s 19200 -l /dev/ttyACM0 Connected. Command: read:electric current auto on/off:automatic/manually ver:version z>00.0A ~. Disconnected. |
データがちゃんと吸えることが確認できました。
deviceが開けない場合
もしここで、device in use. や permission denied. とエラーが表示された場合は、/dev/ttyACM0への読み書き権限があるか確認しましょう。
1 2 3 |
% ls -l /dev/ttyACM0 crw-rw---- 1 root plugdev 166, 0 Apr 18 23:26 /dev/ttyACM0 |
オーナーとグループに読み書き権限が付いています。なので、自分をplugdevグループに追加してあげればいいのですね。
1 |
% sudo usermod -G plugdev ユーザー名 |
グループに自分を追加したら、一度Logoutを行いLoginし直しましょう。これでdeviceに対して読み書きができるはずです。
googleで検索するとdeviceに対して0666を設定すれば良いと書かれているページがあります。確かに、chmod o+rw する事で読み書きできるようになりますが、それはUNIX的に正しくない解決方法だと思います。
device名を固定する
さて、このUSB接続Clamp meterのデバイス名 (ttyACM0)ですが、USBデバイスを刺すタイミングによっては、ttyACM1などとなる可能性があります。人間が手で操作する場合はいいのですが、定期的にデータを吸い出すようなスクリプトを動かす場合、デバイス名が変るのはあまりうれしい事ではありません。そこで、好きな名前に固定してしまいましょう。
デバイス名を固定するには、udevという仕組みを利用します。
udevは、デバイスが追加された時に得られた情報を元にして様々なアクションを定義できます。今回は、特定のベンダーIDとプロダクトIDが接続された時にそのデバイスへ固定名のSymlinkを張らせるように設定を書きました。デバイス名は、”usb-clamp”としました。
1 2 3 4 |
% cat /etc/udev/rules.d/70-usb-clamp.rules # SUBSYSTEMS=="usb" , ACTION=="add" , ATTRS{idVendor}=="04d8" , ATTRS{idProduct}=="8870" , MODE="0660" , GROUP="plugdev" , SYMLINK+="usb-clamp" |
ルールファイルを作成したらudevプロセスをリロードします。
1 |
% sudo /etc/init.d/udev reload |
USB接続ClampMeterを抜いて差し直せば、今設定したudevルールが正しく動作している事が確認できます。
1 2 3 4 |
% ls -l /dev/ | grep -E "(usb-clamp|ACM)" crw-rw---- 1 root plugdev 166, 0 Apr 21 22:44 ttyACM0 lrwxrwxrwx 1 root root 7 Apr 21 22:14 usb-clamp -> ttyACM0 |
データを読み出す(不完全)
デバイス側はこれで準備完了です。次はデータを読み出すスクリプトを書きましょう。先に試した時のように cu コマンドを使い1行読み出すshell scriptでも良いですしライブラリが豊富な高級言語を使って書くのも良いでしょう。
ぼくは最近pythonに慣れたいと思っているので、pythonで書いてみました。
recv8870.py というファイル名にしました。
※ このスクリプトはうまく動かないので後で書き直します
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
#!/usr/bin/env python #coding: utf-8 # for python3.x # pip install pyserial import serial as ser s = ser.Serial('/dev/usb-clamp', 19200, timeout=10, parity=ser.PARITY_EVEN, rtscts=1) msg = s.readline() s.close() if msg != '': print(str(msg.strip())[2:-2]) |
今回グラフ化するにあたり、昔からの定番ツールであるcactiを利用します。cactiがデータを取得する方法はいくつかあって、一番お手軽なのはlocalhostでスクリプトを実行し返値を取り込む事です。今回、電力測定を行うのはcactiを実行しているhostなのでcactiにスクリプトを実行させても目的は達成できそうです。
が、しかし、リモートホストのデータを取得したくなった時に困りますから、汎用性の高い方法を採用してみます。
スクリプトの返値をsnmp経由で取得する
リモートホストからデータを取得する方法はいろいろあると思いますが、cactiに代表されるようなツールではsnmpを利用するのが一般的です。オープンソースの世界で広く利用されているnet-snmpは、スクリプトを実行しその返値をsnmp経由で返答させる事ができます。
net-snmpのextend機能を利用します。
/etc/snmp/snmpd.conf に以下の行を追加し、snmpdの再起動を行います。
1 |
extend pmeasure /usr/local/bin/get_current.sh |
/usr/local/bin/get_current.sh は、virtualenvでpython3に切り替えてから先のrecv8870.pyを実行するラッパーシェルです。例えばこんな感じ。
1 2 3 4 5 6 |
%cat /usr/local/bin/get_current.sh #!/bin/bash source ${HOME}/virtualenv/p3-serial/bin/activate /usr/local/bin/recv_8870.py |
debianのsnmp MIBsについて
debianではMIBが初期状態ではほとんどのMIBがインストールされないので必要ならば”snmp-mibs-downloader”をインストールしておきましょう。またdefaultではMIBを読み込まないようになっているので、/etc/default/snmpd を編集しておくことも忘れずに。
snmpdをrestart後、正しくスクリプトからの返値をsnmp経由で取得できるか確認します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
% snmpwalk -c public -v2c localhost nsExtensions NET-SNMP-EXTEND-MIB::nsExtendNumEntries.0 = INTEGER: 1 NET-SNMP-EXTEND-MIB::nsExtendCommand."pmeasure" = STRING: /usr/local/bin/get_current.sh NET-SNMP-EXTEND-MIB::nsExtendArgs."pmeasure" = STRING: NET-SNMP-EXTEND-MIB::nsExtendInput."pmeasure" = STRING: NET-SNMP-EXTEND-MIB::nsExtendCacheTime."pmeasure" = INTEGER: 5 NET-SNMP-EXTEND-MIB::nsExtendExecType."pmeasure" = INTEGER: exec(1) NET-SNMP-EXTEND-MIB::nsExtendRunType."pmeasure" = INTEGER: run-on-read(1) NET-SNMP-EXTEND-MIB::nsExtendStorage."pmeasure" = INTEGER: permanent(4) NET-SNMP-EXTEND-MIB::nsExtendStatus."pmeasure" = INTEGER: active(1) NET-SNMP-EXTEND-MIB::nsExtendOutput1Line."pmeasure" = STRING: 0.5 NET-SNMP-EXTEND-MIB::nsExtendOutputFull."pmeasure" = STRING: 0.5 NET-SNMP-EXTEND-MIB::nsExtendOutNumLines."pmeasure" = INTEGER: 1 NET-SNMP-EXTEND-MIB::nsExtendResult."pmeasure" = INTEGER: 0 NET-SNMP-EXTEND-MIB::nsExtendOutLine."pmeasure".1 = STRING: 0.5 |
OIDで表示したい場合は、-Onオプションをつけて実行します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
%snmpwalk -On -c public -v2c localhost nsExtensions .1.3.6.1.4.1.8072.1.3.2.1.0 = INTEGER: 1 .1.3.6.1.4.1.8072.1.3.2.2.1.2.8.112.109.101.97.115.117.114.101 = STRING: /usr/local/bin/get_current.sh .1.3.6.1.4.1.8072.1.3.2.2.1.3.8.112.109.101.97.115.117.114.101 = STRING: .1.3.6.1.4.1.8072.1.3.2.2.1.4.8.112.109.101.97.115.117.114.101 = STRING: .1.3.6.1.4.1.8072.1.3.2.2.1.5.8.112.109.101.97.115.117.114.101 = INTEGER: 5 .1.3.6.1.4.1.8072.1.3.2.2.1.6.8.112.109.101.97.115.117.114.101 = INTEGER: exec(1) .1.3.6.1.4.1.8072.1.3.2.2.1.7.8.112.109.101.97.115.117.114.101 = INTEGER: run-on-read(1) .1.3.6.1.4.1.8072.1.3.2.2.1.20.8.112.109.101.97.115.117.114.101 = INTEGER: permanent(4) .1.3.6.1.4.1.8072.1.3.2.2.1.21.8.112.109.101.97.115.117.114.101 = INTEGER: active(1) .1.3.6.1.4.1.8072.1.3.2.3.1.1.8.112.109.101.97.115.117.114.101 = STRING: 0.5 .1.3.6.1.4.1.8072.1.3.2.3.1.2.8.112.109.101.97.115.117.114.101 = STRING: 0.5 .1.3.6.1.4.1.8072.1.3.2.3.1.3.8.112.109.101.97.115.117.114.101 = INTEGER: 1 .1.3.6.1.4.1.8072.1.3.2.3.1.4.8.112.109.101.97.115.117.114.101 = INTEGER: 0 .1.3.6.1.4.1.8072.1.3.2.4.1.2.8.112.109.101.97.115.117.114.101.1 = STRING: 0.5 |
snmp経由でうまく数値が取得でき、グラフが生成される事が確認できました。
データの欠落問題
グラフをよく見るとデータに欠落があるのがわかると思います。かなりの頻度で値の取得に失敗しているようなグラフになっています。実際はどうなっているのか、rrdファイルをdumpして確認します。
1 2 3 4 5 6 7 8 9 10 11 12 |
% rrdtool dump /usr/local/cacti/rra/pooh_clampmeter_238.rrd (略) <!-- 2018-04-22 00:45:00 JST / 1524325500 --> 5.0000000000e-01 <!-- 2018-04-22 00:50:00 JST / 1524325800 --> 5.0000000000e-01 <!-- 2018-04-22 00:55:00 JST / 1524326100 --> 5.9933333333e-01 <!-- 2018-04-22 01:00:00 JST / 1524326400 --> 6.0000000000e-01 <!-- 2018-04-22 01:05:00 JST / 1524326700 --> 6.0000000000e-01 <!-- 2018-04-22 01:10:00 JST / 1524327000 --> NaN <!-- 2018-04-22 01:15:00 JST / 1524327300 --> NaN <!-- 2018-04-22 01:20:00 JST / 1524327600 --> 5.0000000000e-01 (略) |
Valueに”NaN”が入っているということは、データの取得に失敗したということです。
データ欠落の原因
原因を調べる為に’/dev/usb-clamp’を開いていろいろと試してみるとclient側からデータ取得タイミングは制御できないという事が分かりました。つまり、clientは、デバイスをopenした後e-meter側から測定データが送られてくるのを待つことしかできないのです。
snmpdがスクリプトを叩き、スクリプトから返値を受け取る際、タイムアウトしている事が分かりました。
データ収集方法の変更
スクリプト実行時、即時にデータが届かないのならば、e-meter8870側からのデータをバッファに溜めておき、リクエストを受けた時に持っているデータ中で最新の値を返せば良さそうです。そんな事をしてくれるpythonスクリプトを作成してみました。
設計方針
- 親プロセスはfork()してデーモン化する
- デーモン化した子プロセスは、共有キューを作成し下記2つのプロセスを起動する
- プロセス(1) /dev/usb-clampをopen後データが届いたら共有キューにput()
- プロセス(2) UNIXドメインソケットを作成し、接続待ちに入る。socketがopenされたら共有キューの最新データをsend()しclose()を呼び出し切断する
- 共有キューの最大長を超えた場合は古い物から削除していく
- systemdから起動し、再起動後やプロセスダウン時も自動的に起動を行う
慣れないpythonスクリプトで少しはまった箇所もありますが、なんとかデータ欠損なく動作するスクリプトが完成しました。cactiのデータ収集間隔を1分にしてもこの通り、欠損無くデータ収集ができました。
GPU有り/無しでの消費電力比較グラフ
さて、元々の目的であるGPUをインストールした後にどれくらい消費電力が増えるのか?を測定してみます。
GPUインストール前の消費電流グラフ
0.5A流れていますから、消費電力は50W程度という事になります。
SATA HDD 2本, 12GBメモリ, NIC増設している割に少なく感じます。
GPUをインストール後の消費電流
GPUをさすだけで0.5A増加したようです。インストール前と比べ約2倍消費電力になりました。アイドル状態でこれでは少し考えてしまいます。少し計算をさせてみて費用対効果が低いようならGPUは外した方が良さそうです。GPU温度が70度なのでこれからの暑い夏に向けて不安要素の一つです。
最後に
軽い気持ちで始めた消費電力の自動測定ですが、慣れないpythonでコードを書いたりと思ったより楽しめました。が、24時間消費電力を測定する意味はあるのか?と変化の少ないグラフを見て自問自答してしまいました。
ソースコード類
githubにあります。
https://github.com/acemomiage/recv8870
snmpdから実行させるスクリプト
- /usr/local/bin/get_current.sh に設置
- UNIXドメインソケットの操作にsocat(SOcket CAT)を利用しているのでsocatコマンドが必要です
systemdに登録するunitファイル
/etc/systemd/system/recv8870.service として配置後、enableしstartで実行。
もちろんその前にUSB Clampmeterを刺しておく必要があります。
1 2 3 |
% sudo systemctl enable recv8870.service % sudo systemctl start recv8870.service |
e-8870からデータを収集するスクリプト
-
- virtualenvで/usr/local/virtualenv/p3/以下にpython3環境を構築し、pyserialモジュールをインストールしています
- system globalなpython3を利用するのならば、systemdのunitファイルを適切なpathに変更する必要があります
- いろいろとエラー処理が甘くなっています
- pythonの流儀に沿っていない書き方をしている所があるかと