PythonのGarbage Collector

前回のsnmpによるシステム監視でメモリとスワップメモリを使い果たしてハングアップすることがわかりました。思い当たることは自ら作ったPythonスクリプトです。理屈上は定義した変数以外はないのでメモリの消費は一定のはずですが実際はどんどん増えて使い切ってしまう現象に陥りました。いままで何事もなく動いていたので何か原因があるはずです。しかしながら数千行のコードを調べるのは時間の無駄なので別の方法を調べてみました。検索するとPythonにガーベッジコレクションの機能があり、検索で実例をみて当てはめてみました。やはりPythonでループしていると起こり得るケースです。import gcでモジュールを読みこみループ上にgc.collect()を配置します。Pythonのドキュメントを見るとgc.enable()という機能があり、自動的にメモリを開放してくれるそうですが今回は適用しません。

追記:

gc.collect()を使うと50%程度とかなり遅くなることが分かりました。シュミレーションで使う場合はgc.enable()を使ったほうが良さそうです。いろいろアプリケーションを整理したらCactiが正常に動くようになりました。

 

追記2

まだPythonのバグがFIXできなかったのでデバッグをしてみました。検索すると何件かヒットしたので最初のひとつを挑戦してみました。

pip install objgraph

python3 -m pdb ./{program}.py

(Pdb) continue
CTRL+C
(Pdb) import objgraph
(Pdb) objgraph.show_most_common_types(limit=20)
function                   18919
tuple                      8785
dict                       7327
weakref                    4017
builtin_function_or_method 3585
...
(Pdb) continue
CTRL+C
(Pdb) import objgraph
(Pdb) objgraph.show_most_common_types(limit=20)
function                   18903
tuple                      8957
dict                       7326
weakref                    4018
builtin_function_or_method 3585
...

tupleが増えてることが分かります。tupleはあまり使っててないでたぶんモジュールかなとか推測できますがそれ以上進めません。

次にtracemallocを使った方法で挑戦してみました。

import tracemalloc
tracemalloc.start()
# ... run your application ...
...

snapshot = tracemalloc.take_snapshot()
top_stats = snapshot.statistics('lineno')
print("[ Top 10 ]")
for stat in top_stats[:10]:
    print(stat)
[ Top 10 ]
/home/parvati/environments/test2_env/lib/python3.6/site-packages/mysql/connector/connection_cext.py:289: size=200 KiB, count=3633, average=56 B
/usr/lib/python3.6/tracemalloc.py:65: size=6336 B, count=88, average=72 B
/usr/lib/python3.6/tracemalloc.py:469: size=6224 B, count=91, average=68 B
...

/home/parvati/environments/test2_env/lib/python3.6/site-packages/mysql/connector/connection_cext.py:289: size=384 KiB, count=7019, average=56 B
/usr/lib/python3.6/tracemalloc.py:469: size=13.5 KiB, count=209, average=66 B
/usr/lib/python3.6/tracemalloc.py:462: size=10.1 KiB, count=208, average=50 B
...

connection_cext.pyのサイズが 200 KiBから384 KiBに増えていることがわかります。このプログラムを続けていると一定の割合で増加していきます。そこでonnection_cext.pyをエディタで開いてみました。

289行: row = self._cmysql.fetch_row()

とあります。class CMySQLConnection()内に定義された関数です。おそらくループでクラスで定義されたオブジェクトを消去せずオブジェクトを追加していったように推測できます。さらにGoogleで検索してみるとSELECT分はメモリに残るというようなコメントも見受けられます。となるとmysql-connector-pythonを編集するとなると手に負えないので、ほかpythonでMySQLが使えるモジュールを探してみてPyMySQLをインストールしてみました。

違いはmysql-connector-pythonはリストで返しますがPyMySQLはdict形式で返します。またmysql-connector-pythonに比べて半分くらいパーフォーマンスが落ちます。PyMySQLでプログラムを直してデバッグしてみます。

[ Top 10 ]
/usr/lib/python3.6/tracemalloc.py:469: size=16.1 KiB, count=251, average=66 B
/home/parvati/environments/test2_env/lib/python3.6/site-packages/pymysql/connections.py:1211: size=14.1 KiB, count=150, average=96 B
/usr/lib/python3.6/tracemalloc.py:462: size=12.1 KiB, count=250, average=50 B
/home/parvati/environments/test2_env/lib/python3.6/site-packages/pymysql/connections.py:1209: size=9672 B, count=403, average=24 B
/usr/lib/python3.6/tracemalloc.py:65: size=9000 B, count=125, average=72 B
./binance_histricalklinev2.py:144: size=6448 B, count=86, average=75 B
/home/parvati/environments/test2_env/lib/python3.6/site-packages/pymysql/cursors.py:407: size=5032 B, count=70, average=72 B
...

[ Top 10 ]
/usr/lib/python3.6/tracemalloc.py:469: size=16.0 KiB, count=250, average=66 B
/home/parvati/environments/test2_env/lib/python3.6/site-packages/pymysql/connections.py:1211: size=14.1 KiB, count=150, average=96 B
/usr/lib/python3.6/tracemalloc.py:462: size=12.1 KiB, count=249, average=50 B
/home/parvati/environments/test2_env/lib/python3.6/site-packages/pymysql/connections.py:1209: size=9696 B, count=404, average=24 B
/usr/lib/python3.6/tracemalloc.py:65: size=9000 B, count=125, average=72 B
./binance_histricalklinev2.py:144: size=7128 B, count=95, average=75 B
/home/parvati/environments/test2_env/lib/python3.6/site-packages/pymysql/cursors.py:407: size=5176 B, count=72, average=72 B

connections.py:1209cursors.py:407に若干増加が見られる程度となりました。これでしばらく様子見ようと思います。

 

参考:

 

SNMPをインストールする

最近、VPSの調子が悪くハングアップすることが1週間ごと起きました。原因は調べようがないので分かりませんがVPSのダッシュボードでNetworkとDisc IO とCPUの負荷状況を確認できるのでそれで判断するしかありません。しかし手の施しようのなくなるまえに何か手を打っておく方法といえばSNMPでシステムを監視することです。それでは早速インストールしてみます。snmpとかnet-snmpとかディストリビューションによって呼び方が異なりますが、基本は同じものです。Ubuntuやデビアンのインストールは次のようにします。

apt-get install snmp snmp-mibs-downloader snmpd

出力時に理解しやすいように次の行をコメントアウトします。またudp:161を使うのでファイヤーウォールの設定も必要です。
/etc/snmp/snmp.conf

#mibs :

/etc/snmp/snmpd.conf

agentAddress udp:161,udp6:[::1]:161
# 基本のシステム情報
rocommunity public  default    -V systemonly

# すべての情報
rocommunity public  default

出力例:(※注:VPN等で接続します)

snmpwalk -v1 {VPS IP} -c public UCD-SNMP-MIB::laTable

UCD-SNMP-MIB::laIndex.1 = INTEGER: 1
UCD-SNMP-MIB::laIndex.2 = INTEGER: 2
UCD-SNMP-MIB::laIndex.3 = INTEGER: 3
UCD-SNMP-MIB::laNames.1 = STRING: Load-1
UCD-SNMP-MIB::laNames.2 = STRING: Load-5
UCD-SNMP-MIB::laNames.3 = STRING: Load-15
UCD-SNMP-MIB::laLoad.1 = STRING: 0.05
UCD-SNMP-MIB::laLoad.2 = STRING: 0.15
UCD-SNMP-MIB::laLoad.3 = STRING: 0.18
UCD-SNMP-MIB::laConfig.1 = STRING: 12.00
UCD-SNMP-MIB::laConfig.2 = STRING: 10.00
UCD-SNMP-MIB::laConfig.3 = STRING: 5.00
UCD-SNMP-MIB::laLoadInt.1 = INTEGER: 5
UCD-SNMP-MIB::laLoadInt.2 = INTEGER: 15
UCD-SNMP-MIB::laLoadInt.3 = INTEGER: 18
UCD-SNMP-MIB::laLoadFloat.1 = Opaque: Float: 0.050000
UCD-SNMP-MIB::laLoadFloat.2 = Opaque: Float: 0.150000
UCD-SNMP-MIB::laLoadFloat.3 = Opaque: Float: 0.180000
UCD-SNMP-MIB::laErrorFlag.1 = INTEGER: noError(0)
UCD-SNMP-MIB::laErrorFlag.2 = INTEGER: noError(0)
UCD-SNMP-MIB::laErrorFlag.3 = INTEGER: noError(0)
UCD-SNMP-MIB::laErrMessage.1 = STRING: 
UCD-SNMP-MIB::laErrMessage.2 = STRING: 
UCD-SNMP-MIB::laErrMessage.3 = STRING:

snmpwalk -v1 {VPS IP} -c public UCD-SNMP-MIB::memory

UCD-SNMP-MIB::memIndex.0 = INTEGER: 0
UCD-SNMP-MIB::memErrorName.0 = STRING: swap
UCD-SNMP-MIB::memTotalSwap.0 = INTEGER: 1949692 kB
UCD-SNMP-MIB::memAvailSwap.0 = INTEGER: 1252456 kB
UCD-SNMP-MIB::memTotalReal.0 = INTEGER: 495188 kB
UCD-SNMP-MIB::memAvailReal.0 = INTEGER: 18744 kB
UCD-SNMP-MIB::memTotalFree.0 = INTEGER: 1271200 kB
UCD-SNMP-MIB::memMinimumSwap.0 = INTEGER: 16000 kB
UCD-SNMP-MIB::memShared.0 = INTEGER: 25880 kB
UCD-SNMP-MIB::memBuffer.0 = INTEGER: 23688 kB
UCD-SNMP-MIB::memCached.0 = INTEGER: 110540 kB
UCD-SNMP-MIB::memSwapError.0 = INTEGER: noError(0)
UCD-SNMP-MIB::memSwapErrorMsg.0 = STRING:

snmpwalk -cpublic -v1 {VPS IP} UCD-SNMP-MIB::dskTable

...
UCD-SNMP-MIB::dskPercent.1 = INTEGER: 56
UCD-SNMP-MIB::dskPercent.2 = INTEGER: 0
UCD-SNMP-MIB::dskPercent.3 = INTEGER: 56
UCD-SNMP-MIB::dskPercent.4 = INTEGER: 11
UCD-SNMP-MIB::dskPercent.5 = INTEGER: 0
UCD-SNMP-MIB::dskPercent.6 = INTEGER: 0
UCD-SNMP-MIB::dskPercent.7 = INTEGER: 0
UCD-SNMP-MIB::dskPercent.8 = INTEGER: 35
UCD-SNMP-MIB::dskPercent.9 = INTEGER: 0
...

ディスク情報は読み解くのが難しいですね。CPU負荷平均のlaTableとメモリのmemoryだけを見ておけばよいでしょう。

ほかシステム監視に使えそうなサブツリー

snmptranslate -Tpで調べる

UCD-SNMP-MIB::systemStats
UCD-SNMP-MIB::ucdExperimental
system

参考:

リモートPCのBoot & Suspend

最近ラップトップPCをサーバとして使うようにしてみました。使っていないときは電力の節約のためサスペンドしていますが、どうも毎回数m離れたところまで行っては操作するのが面倒になったので、メインPCからサスペンドしてみました。ちょっとしたことでも億劫になるナマケモノみたいになってますが、極力動かないで済むことに越したことないのでセットアップしてみました。ラップトップはwake-on LANが使えるようにBIOSをセットしておきます。

サスペンド:

sshでログインしてsudo pm-suspendを実行します。

ブートまたは電源ON:

wakeonlanをインストールします。

wakeonlan [HWアドレス]を実行します。

dm-cryptでrootパーティションを暗号化している場合、過去の記事を参考に設定します。

Ubuntu(18.04TLS)でssh経由でdm-cryptの解除する場合、debianと少し異なります。

パッケージ

apt install busybox-initramfs dropbear-initramfs
cp -a /usr/share/initramfs-tools/hooks/dropbear /etc/initramfs-tools/hooks

/etc/initramfs-tools/initramfs.conf

BUSYBOX=y
IP={IP ADDR}::{GW}:{NETMASK}::{IF}:off
例) IP=192.168.11.1::192.168.11.254:255.255.255.0::enp0s25:off

/etc/dropbear-initramfs

cd /etc/dropbear-initramfs
cat /etc/initramfs-tools/root/.ssh/id_rsa.pub.initramfs > authorized_keys

/etc/crypttabがないとdropbearはインストールされません。なければ空のファイルを作ります

touch /etc/crypttab

initramfsをアップデートします。

update-initramfs -v -u

おまけ:

LinuxのGrubをアップデートするとmacOSのブートローダより先に起動するようになります。毎回訂正するのは面倒なのでGrubからCloverを立ち上げる設定をします。EFIのUUIDを調べてgurbの設定ファイルに書き込みます。

例)

blkid /dev/sda1
/dev/sda1: LABEL="EFI" UUID="66AA-11BB" TYPE="vfat" PARTLABEL="EFI System Partition" PARTUUID="123456ab-123a-456e-78f6-e1234567880f"

例)/etc/grub.d/40_custom

#!/bin/sh
exec tail -n +3 $0
# This file provides an easy way to add custom menu entries.  Simply type the
# menu entries you want to add after this comment.  Be careful not to change
# the 'exec tail' line above.

menuentry 'macOS' {
insmod fat
insmod part_gpt
insmod chain
insmod search_fs_uuid
search --fs-uuid --no-floppy --set=root 66AA-11BB
chainloader /EFI/CLOVER/CLOVERX64.efi
}

Grubを更新します。
update-grub