2013年4月28日 星期日

用 mutt 作為 command line 寄信程式

預設的 mail 不方便夾帶附加檔, 備忘一下用 mutt 做這事的指令:

mutt -s TITLE EMAIL -a ATTACHMENT < CONTENT

2013年4月19日 星期五

python BaseHTTPServer 速度緩慢的原因

在用 Bottle 的開發模式時, 發覺有時候速度會異常的慢。等到受不了以後, 按下 ctrl+c 看到以下的 backtrace:

Exception happened during processing of request from ('xxx.xxx.xxx.xxx', 37515)
Traceback (most recent call last):
File "/usr/lib/python2.7/SocketServer.py", line 284, in _handle_request_noblock
self.process_request(request, client_address)
File "/usr/lib/python2.7/SocketServer.py", line 310, in process_request
self.finish_request(request, client_address)
File "/usr/lib/python2.7/SocketServer.py", line 323, in finish_request
self.RequestHandlerClass(request, client_address, self)
File "/usr/lib/python2.7/SocketServer.py", line 638, in __init__
self.handle()
File "/usr/lib/python2.7/wsgiref/simple_server.py", line 121, in handle
self.rfile, self.wfile, self.get_stderr(), self.get_environ()
File "/usr/lib/python2.7/wsgiref/simple_server.py", line 85, in get_environ
host = self.address_string()
File "/usr/lib/python2.7/BaseHTTPServer.py", line 498, in address_string
return socket.getfqdn(host)
File "/usr/lib/python2.7/socket.py", line 137, in getfqdn
hostname, aliases, ipaddrs = gethostbyaddr(name)
KeyboardInterrupt

依照過去的經驗, 這個 gethostbyaddr 相當可疑。

作個簡單測試證實問題:

$ python -c 'import socket; print socket.gethostbyaddr("xxx.xxx.xxx.xxx")'
Traceback (most recent call last):
File "<string>", line 1, in <module>
socket.herror: [Errno 2] Host name lookup failure

再來用老招 ltrace 確認問題源頭:

$ ltrace  python -c 'import socket; print 
socket.gethostbyaddr("xxx.xxx.xxx.xxx")' 2>&1 | grep gethost
( ... 略 ... )
gethostbyaddr_r(0x7fffa5bbb834, 4, 2, 0x7fffa5bb77f0, 0x7fffa5bb7830) = 0

由 man gethostbyaddr 得知它會查 /etc/hosts, 最後在 /etc/hosts 加入一筆 "xxx.xxx.xxx.xxx xxx.xxx.xxx.xxx", 就立即有結果了:

$ python -c 'import socket; print socket.gethostbyaddr("xxx.xxx.xxx.xxx")'
('xxx.xxx.xxx.xxx', [], ['xxx.xxx.xxx.xxx'])

2013年4月16日 星期二

C++ 執行後掛在 __cxa_pure_virtual

今天遇到一個神奇的 bug, 程式掛掉後, 從 backtrace 看到其中一個函式名稱為 __cxa_pure_virtual, 從字面上的意思來看, 我呼叫了一個 pure virtual function, 所以程式掛了。但是 compiler 應該能抓到在 C++ 內呼叫 pure virtual function (Btw, Objective C++ 則否), 沒道理在 runtime 遇到這情況。

google 了一陣子, 看到這篇說有可能是呼叫 dangling pointer 的 virtual function, "Pure Virtual Function Called": An Explanation 則解釋各種可能的觸發原因。最後抓到兇手, 確實是呼叫 dangling pointer 的 virtual function。

依文章的解釋, destructor 會改變 virtual method table pointer 指向的位置, 最後會指向 abstract base class 的 virtual table, 裡面的 virtual function 則指向某個表示 pure virtual function 的指標 (gcc 用__cxa_pure_virtual)。所以, 若刪除指標後剛好記憶體內容保持原樣, 執行後就會呼叫到 __cxa_pure_virtual

依管理記憶體的方式, 刪除指標時可能會保留原狀, 也可能填入特殊值故意觸發 crash。呼叫 dangling 可能會有不同結果。

2013年4月5日 星期五

在 linux 和 iOS 上抓 memory leak 和 heap profiling 心得

問題描述

首先要定義一下什麼是「memory leak」。它的正式定義是指「程式弄丟了未釋放空間的位置」, 之後無法使用這塊空間也無法釋放它。另一方面, 程式沒有弄丟位置, 但是配置的空間愈用愈大, 或是用完後沒有歸還, 導致記憶體不足。若要應付第一種情況, 可用 memory leak 作為關鍵字找相關討論; 第二種情況要用 memory profiler 或 heap profiler 了解記憶體用去那裡, ( 感謝 Scott 告知 )。

就我自己少量的經驗來說, 第二種比較棘手, 第一種通常是粗心, 找到後補上 free/delete 即可。但第二種可能和程式配置和運用記憶體的策略有關。我遇到的問題都是第二種。若程式有針對不同區塊自己管記憶體的話, 比較方便日後植入統計程式找出瓶頸。順道一提, 如同 TDD 的精神, 在設計程式架構時就考慮測試, 會讓程式更易測試、維護和除錯。在設計架構時就考慮除錯, 也會讓程式更易維護和除錯。

在 Linux 上找 memory leak 和 profile heap

在 Linux 上試了兩套工具 mtracevalgrind。mtrace 滿簡單的, 可用來抓 C 的 memory leak, 但不適用於 C++, 原因是 mtrace 在 malloc/free 動手腳, 可以找出那一行呼叫 malloc 後沒呼叫 free。但是 C++ 的情況是 libstdc++ 呼叫 malloc, 而我們有興趣的是誰呼叫了 new。

後來改試 valgrind 效果還不錯。valgrind 是多個除錯工具的集合體, 我試了兩個工具:

  • memcheck: 抓 memory leak 和記憶體讀寫錯誤。
  • massif: profile heap 的用量, 可看到不同時間 heap 由各函式配置的比例。

使用 valgrind 的代價是程式會慢個 20 倍左右。但是用工具協助檢測問題, 99% 的情況會比自己憑空猜想來得準。還是有些耐性使用 valgrind 會比較快找到問題源頭。

memcheck

$ valgrind --tool=memcheck --leak-check=full PROG PROG_ARG ...

跑完後會輸出結果在 console。建議先有些耐心看完官網文件。對 memcheck 的報表有疑問的話, 可參考 "Understanding Valgrind memory leak reports"。重點是 possibly leak 表示 valgrind 無法判斷它是 still reachable 或 definitely leak。still reachable 可能是某些函式庫配置自己一塊 memory pool 來用, 程式離開時才會釋放, 所以嚴格說起來不是 memory leak。在這個階段, 我們關心的是 definitely leak。

massif

$ valgrind--tool=massif PROG PROG_ARG ...

跑完後會輸出結果在 massif.out.PID。然後執行 ms_print 看結果

$ ms_print massif.out.PID

建議先有些耐心看完官網文件。我試了許多參數想抓到程式離開前的 detailed snapshot 都徒勞無功。最後發覺用 vgdb 意外的簡單。

用法如下:

  1. 先執行 valgrind
  2. 在另一個 shell 下執行 vgdb detailed_snapshot /path/to/snapshot
  3. 閱讀當下的 snapshot: ms_print /path/to/snapshot

valgrind 也可以和 gdb 一起互動, 沒想到會在這個機會下試用到 gdb 的 remote debugging 功能。

經 Scott 告知, 除了用 ms_print 看文字報表外, 也可用 Massif Visualizer 看結果。這工具看起來滿不錯的, 先備忘在這裡。

另外補充一下 Linux 下記憶體用量的意思。平常我們是看 ps 回報的 VSZ 和 RSS, 但兩者其實都不夠準確, 用 pmap -d PID 看最後一行輸出的 private writable memory, 可能比較適合。

在 iOS 的找 memory leak 和 profile heap

就用 Mac OS X 附的 Instrutments, 使用 Leak 或 Allocations, 不用看文件即可輕鬆搞定! 全圖形介面操作, 還有詳細的報表。要看各 functions 用掉 heap 記憶體的比例的話, 在 Allocations 裡點 Statistics -> Call Tree。

同事用過 Instruments 後, 甚至說「真想將 Linux 的程式 port 到 Mac, 這樣才可以用 Instruments」, 由此可知 Instruments 有多麼地好用。

在 Fedora 下裝 id-utils

Fedora 似乎因為執行檔撞名,而沒有提供 id-utils 的套件 ,但這是使用 gj 的必要套件,只好自己編。從官網抓好 tarball ,解開來編譯 (./configure && make)就是了。 但編譯後會遇到錯誤: ./stdio.h:10...