2012年12月5日

Riak コンパイルメモ

概要

Erlang で実装されたNoSQL系のデータベース「Riak」の先端をコンパイルする手順メモ。

手順

コンパイルする場合 Erlang と Git コマンドが必須なのでパスに存在するか確認しておく。

Riak はビルドに「rebar」を利用している。最新である必要は特にないが、これもコンパイルしておく。

git cline git://github.com/basho/rebar.git
cd rebar
./bootstrap

Mac OS Xでコンパイルしているが、環境変数に UNICODE が含まれていると認識されコンパイルエラーになった。
Support environment vars with unicode characters · 61c353d · l4u/rebar-1 · GitHub」のパッチをあてたら通過した。

Riak 本体をコンパイルする。rebar のコピーを忘れないようにする。

git clone git://github.com/basho/riak.git
cd riak
cp /path/to/rebar .
make rel

関連ライブラリが Git で clone されてコンパイルされるので、環境によっては時間かかる。
途中でコンパイルエラーにたまになったので、rebarのバイナリコピーしてから再度 make するとちゃんと継続してコンパイルしてくれた。
「rel」ディレクトリの下にコンパイルされた。

使い方は確認中なので、また後で書く。

2012年12月4日

XML ファイルから PO ファイル を作成する

概要

XML ファイルを翻訳することがそれなりの頻度である。翻訳するなら PO ファイルを生成するのが便利。
XML から PO ファイルを生成するには「xml2po」か、「ITS Tool」を使う。両方ともGnomeのドキュメント国際化でも使われている Python で作成されたツール。
両方とも XML から PO を生成したり、PO から XML に戻したりできる。
ITS Tool の方が新しい。

xml2po

sudo port install gnome-doc-utils
xml2po --version

POファイルの作成と、XMLへの戻しは以下のようにする。

xml2po -o sample.pot sample.xml
cp sample.pot ja.po
mkdir ja
xml2po -p ja.po sample.xml > ja/sample.xml

ITS Tool

# 依存ライブラリインストール
pip install ftp://xmlsoft.org/libxml2/python/libxml2-python-2.6.21.tar.gz
# ダウンロード
curl -O http://files.itstool.org/itstool/itstool-1.2.0.tar.bz2
tar xvfz itstool-1.2.0.tar.bz2
cd itstool-1.2.0
./configure
sudo make install
# バージョン確認
itstool --version

POファイルの作成と、XMLへの戻しは以下のようにする。

itstool sample.xml -o sample.pot
cp sample.pot ja.po
msgfmt -o ja.mo ja.po
mkdir ja
itstool -m ja.mo -o ja/ sample.xml

まとめ

翻訳対象の XML にもよるだろうが、自分は xml2po の方を普段は利用している。

2012年12月3日

Erlang を Emacs でコーディングするための設定

概要

プログラミング言語Erlangを Emacs でコーディングするための設定。

Erlangインストール

Mac の場合は dmg、MacPorts、Homebrew でインストールする方法がある。

dmgの場合

あまり知られていないみたいだが、ちゃんと dmg によるバイナリが配布されている。
以下からダウンロード可能。
https://www.erlang-solutions.com/downloads/download-erlang-otp
アクセスするとブラウザの設定で自動でOperating Systemが選択されるはずだが、Operating Systemが適切でない場合は選択しなおせば dmg がダウンロードできる。
バイナリは通常は 64bit を選択すれば良い。

MacPortsの場合

sudo port install erlang

Homebrewの場合

brew install erlang

動作確認

ターミナルから動作するか確認する。「erl」コマンドで Erlang Shell が起動する。

% erl
1 > 2 + 4.
6
2 > halt().

「.」を忘れないように。「halt()」で終了する。

Emacsの設定

erlang-modeとdistelを設定する。
erlang-mode は Erlang と同時にインストールされる。

MacPorts だと「/opt/local/lib/erlang/lib/tools-*/emacs/」に存在する。

distelは補完とか、その他いろいろ便利なので設定している。

git clone git://github.com/jixiuf/distel.git
cd distel
make

init.el の設定は以下。path 関連の設定が必要だけど、略。

(setq erlang-root-dir "/opt/local/lib/erlang")
(require 'erlang-start)
(require 'erlang-flymake)

(require 'distel)
(distel-setup)

Erlang でのプログラム経験がかなり少ないので、ほとんど設定してない。

2012年11月19日

Cassandra 1.1 簡易メモ

概要

Apache Cassandra」の 1.1.6 をとりあえず試すためのメモ。
ネット上の記事が結構古い物しかない印象なので、公開しておく。
触りだけで、深い所までは書いてない。

設置から起動

curl -O http://ftp.riken.jp/net/apache/cassandra/1.1.6/apache-cassandra-1.1.6-bin.tar.gz
tar xvf apache-cassandra-1.1.6-bin.tar.gz
cd apache-cassandra-1.1.6

# conf の中で利用しているディレクトリを作成
sudo mkdir -p /var/log/cassandra
sudo chown -R `whoami` /var/log/cassandra
sudo mkdir -p /var/lib/cassandra
sudo chown -R `whoami` /var/lib/cassandra

# 起動
bin/cassandra -f

デフォルトでは Messaging Service が 7000 ポート、thrift server が 9160 ポートを利用する。

アクセス

標準クライアントは依然から存在する「bin/cassandra-cli」か、新しい「bin/cqlsh」を利用する。
ネットや README だと cassandra-cli を利用しているが、cqlsh の方が使いやすいのでそちらを利用する。

bin/cqlsh localhost

keyspaceの作成

最初に RDB のデータベース相当 である keyspace を作成する。

CREATE KEYSPACE Keyspace1
  WITH strategy_class = 'SimpleStrategy' 
   AND strategy_options:replication_factor = 1;

keyspace に移動

cqlsh> use Keyspace1;
cqlsh:Keyspace1>

column family の作成

RDB のテーブル相当である column family を作成する。cqlshを利用すると、「CREATE COLUMNFAMILY」、「CREATE TABLE」のいずれでも作成できる。
「CREATE TABLE」を利用すると 従来の RDB 的に column も同時に作成できる。とりあえずこちらを利用したサンプル。
Snappy による圧縮を指定して作成する場合は以下のようにする。

CREATE TABLE Users (
  user_id int PRIMARY KEY,
  user_name varchar,
  age int
  )
  WITH comparator=LongType
   AND compression_parameters:sstable_compression='SnappyCompressor'
   AND compression_parameters:chunk_length_kb=128;

確認は「DESCRIBE」コマンド。

DESCRIBE KEYSPACE Keyspace1
DESCRIBE COLUMNFAMILIE 
DESCRIBE COLUMNFAMILY Users

データ挿入

データを挿入してみる。

SELECT * FROM Users;
INSERT INTO Users (user_id, user_name, age) VALUES (1, 'John Smith', 42);
INSERT INTO Users (user_id, user_name, age) VALUES (2, '日本語 挿入', 33);
SELECT * FROM Users;

INSERT は UPDATE と同じ動作をするので、prymary key が重複するデータを挿入すると上書きする。

データ参照

SELECT文を発行する。
まず count で行数を取得してみる。古いバージョンでは count は Row 中の column 数を表示している時もあったが、現在は Row の数を返すようになっている。

select count(*) from Users;

普通に SELECT文が発行できる。暗黙に「limit = 10000」が付与される。

select user_name, age from Users where user_id = 1 ;

データ削除

DELETE文を発行する。

delete from Users where user_id = 1;

「PRIMARY KEY」を設定していると、「PRIMARY KEY」以外の値が消えるだけで「PRIMARY KEY」の値が残るので、論理削除とかにした方が良い場合もある。

ソート

ORDER BY したい場合は、PRIMARY KEY を複合キーで作る必要があるので、PRIMARY KEY を複合キーで作成できる場合にしか使えない。

参考サイト

2012年11月13日

Emacs で Python の補完を強化する jedi を設定してみた

概要

Emacs の Python 補完はいろいろあるが、どれもいまいち遅い。「emacs-jedi」は結構高速に補完してくれるみたいなので、設定してみた。

設定

Python のライブラリをインストール

pip install epc
pip install jedi

Emacs のライブラリを取得。

git clone git://github.com/kiwanami/emacs-deferred.git
git clone git://github.com/kiwanami/emacs-ctable.git
git clone git://github.com/kiwanami/emacs-epc.git
git clone git://github.com/tkf/emacs-jedi.git

init.el の設定

(require 'jedi)
(add-hook 'python-mode-hook 'jedi:ac-setup)

これで auto-complete を利用して補完が可能。eldoc が有効になっていると、関数の引数説明も表示される。
結構高速に動作してくれるのでしばらく利用してみる。

Emacs で似ている文字列を一度に選択して一括編集可能な mark-multiple.el の導入

概要

簡単に複数の同じ様な文字列を選択して、一括編集可能な「mark-multiple.el」を導入してみた。
機能に関しては、「説明動画」参照。

設定

git clone git://github.com/magnars/mark-multiple.el.git

init.el には以下を設定。

(require 'mark-more-like-this)
(global-set-key (kbd "C-<") 'mark-previous-like-this)
(global-set-key (kbd "C->") 'mark-next-like-this)

「C-<」で前方一致選択。「C->」で後方一致選択。

矩形選択の機能もあるので設定しておくと便利。

(require 'inline-string-rectangle)
(global-set-key (kbd "C-x r t") 'inline-string-rectangle)

2012年11月12日

Emacs の region 関連機能をカスタマイズできる expand-region.el の導入

概要

Emacs で範囲選択をいろいろカスタマイズできる、「expand-region.el」を導入してみた。
機能に関しては文章読むより、「解説動画」を見た方が良い。

設定

比較的変更が頻繁にはいっているので、git でソースを clone する方がよさそう。

git clone git://github.com/magnars/expand-region.el.git

init.el への設定は以下。

;; 選択ができるようにしておく
(transient-mark-mode t)

(require 'expand-region)
;; リージョンを広げる
(global-set-key (kbd "C-@") 'er/expand-region)
;; リージョンを狭める
(global-set-key (kbd "C-M-@") 'er/contract-region)

「C-@」で選択範囲を広げる。「C-M-@」で選択範囲を狭める。
その他選択中に「C-Shift-p」で選択範囲の上移動、「C-Shift-n」で選択範囲の下移動、「C-Shift-x」で選択範囲の先頭、末尾移動が可能。

動作をカスタマイズしたい場合は、言語ごとの「-expansions.el」のファイルが沢山はいっているので、参考にすればとりあえず作成できる。

参考サイト

2012年11月7日

Python の pip コマンドでライブラリの更新状況を確認する

概要

Python の pip コマンド最新バージョン 1.2.1 では、ライブラリの更新状況が確認できない。
確認する方法をメモする。

コマンドの設定

pip コマンドは「site-packages/pip/commands/」以下にコマンドが存在し、このディレクトリにコマンドプログラムを配置することで、様々な拡張が可能。

listコマンドを追加する「list.py」が公開されているので、ダウンロードして、配置する。
利用可能か確認したり、利用方法を確認するには、以下のようにする。

pip list --help

ライブラリの更新状況を確認する場合は以下。

pip list -o

インストールしているライブラリの量にもよるが、結構時間がかかるので注意。

2012年11月6日

アスキーアートロゴを作成してみる

概要

現在「tweepy のAPI 1.1 対応フォーク」を作成している。
変な所が無いか知人に見てもらった時、いくつかの指摘の中で、READMEファイルのアスキーアートロゴに関して質問があった。
そこで、このようなアスキーアートロゴの作成方法に関してメモしておく。

bannerコマンド

Mac OS Xで標準的に利用できるコマンドだと「banner」が存在する。「-w」オプションで幅を指定する。

banner -w 30 Hello

「banner」コマンドは自由度がほとんど無いので、あまり便利ではない。

FIGlet

FIGlet」はこの分野でかなり利用されていると思われる。MacPorts や Homebrew で簡単にインストールできる。

sudo port install figlet

brew install figlet

使い方は簡単。

figlet Hello

フォントの変更も可能なので、参考サイト参照。

FIGletのPython実装「pyfiglet」が存在する。

pip install pyfiglet

pyfigletはモジューで提供されて、コマンドがインストールされない。
以下のようにするとコマンドでも利用できるが、普通は import して利用する物だと思われる。

/path/to/pyfiglet/__init__.py "Hello"

TOIlet

色付きでアスキーアートロゴが生成できる。名前やサイトデザインが結構酷いが、品質に問題はないようだ。
これも MacPorts や Homebrew で簡単にインストールできる。

sudo port install TOIlet

brew install TOIlet

使い方は FIGlet とほとんど同じで、色を付けるパターンが存在している。

参考サイト

以下のサイトが詳しい。

2012年11月5日

pyobjc 2.4 のインストール

概要

Python の Objective-C ブリッジライブラリ「pyobjc」の 2.4 が 2012/11/01 にリリースされたのでインストールメモ。

インストール

以下は pip でのインストール手順。core を先にインストールしないと依存性が解消されないので注意。

pip install -U pyobjc-core
pip install -U pyobjc

ドキュメント

各種ドキュメントは以下にある。

サンプルソース

pip でインストールするとサンプルがインストールされないので、以下のURLから取得する。

サンプルの内容は Mac での Objective-C の知識が無いと意図が理解はできないと思われる。
Python から Objective-C の API が簡単に利用できるというのが便利。

2012年11月1日

Python モジュールバージョン番号フォマットに関してのメモ

概要

Python モジュールを開発する際のバージョン番号のフォーマットに関してのメモ。

ドキュメント

PEPが存在する。

形式

形式は以下。

N.N[.N]+[{a|b|c|rc}N[.N]+][.postN][.devN]
  • major
  • minor
  • micro
  • releaselevel
    • dev
    • alpha
    • beta
    • candidate
    • final
  • serial

バージョン番号表示

setup.py でバージョン番号が表示できるように作成する。

python setup.py --version

関連プログラム

2012年10月31日

とりあえず10月も毎日更新してみた

ほぼ前日の予約投稿だけれども、10月も毎日更新してみた。
やはり多少の時間がかかるのと、記事の内容が薄くなったりしてしまう。
11月は、更新毎日はやめる事にして、気が向いた時に更新してみる。
更新に利用していた時間を開発に回す予定。

2012年10月30日

Twitter 関連のライブラリを API 1.1 に対応させるのは結構大変

Python から Twitter API を扱うライブラリに「tweepy」があるが、これは API 1.1 に対応していない。
そこで、API 1.1 の調査を兼ねていろいろ変更してみている「sakito / tweepy — Bitbucket」。

単純に endpoint を 1.1 にするだけでも大丈夫そうな感じがするが、細かい所まで対応しようとするとかなり大変。
使う機能以外を気にすると時間がかかりすぎる気がするので主要な機能だけ、徐々に対応してみようと思う。

2012年10月29日

Python の in 演算子

概要

Python の in 演算子に関して詳細に記述してある「python `in` operator use cases」という記事を見つけた。
日本語を使うとどうなるかとりあえず試してみた。

ソース

いつも通り unittest で記述した。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import unittest


class TestIn(unittest.TestCase):

    def test_list(self):
        """
        list
        """
        lst = ['あ', 'い', 'う', 'え', 'お']
        self.assertTrue('あ' in lst)
        self.assertFalse('か' in lst)

    def test_nested_list(self):
        """
        nested list
        """
        lst = [
            ['あ', 'い', 'う', 'え', 'お'],
            ['さ', 'し', 'す', 'せ', 'そ'],
            [],
        ]
        self.assertFalse('あ' in lst)
        self.assertTrue(['あ', 'い', 'う', 'え', 'お'] in lst)
        self.assertTrue([] in lst)

    def test_dict(self):
        """
        dict
        """
        dct = {'名前': 'サンプル', '国': '日本', 'OS': 'Mac',
               '言語': {'web': 'python'}}
        self.assertTrue('名前' in dct)
        self.assertFalse('web' in dct)

    def test_dict_class(self):
        """
        dict class
        """
        class Person(dict):
            pass

        p = Person()
        self.assertEqual({}, p)

        p['name'] = '名前'
        self.assertTrue('name' in p)

    def test_set(self):
        """
        set
        """
        stlst = {'foo', 'bar', 'foo'}
        self.assertEqual(set(['foo', 'bar']), stlst)
        self.assertTrue('foo' in stlst)

    def test_generator(self):
        """
        generator
        """
        self.assertTrue(2, xrange(4))

    def test_string(self):
        """
        string
        """
        msg = 'Python はシンプルで強力な言語です'
        self.assertTrue('Python' in msg)
        self.assertTrue('強力' in msg)
        self.assertEqual(0, msg.find('Python'))
        self.assertEqual(25, msg.find('強力'))

if __name__ == '__main__':
    unittest.main()

まとめ

当然のように日本語でも特に問題はない。in はそれほど複雑ではないが、いろいろと応用が効いて便利。

2012年10月28日

多言語対応テンプレート言語 mustache を試してみた

概要

{{ mustache }}」は多言語対応のテンプレート言語。各種エディタの plugin も提供されていて便利そうなので、試してみた。

ドキュメント

文法は「mustache」を参照。

Python で試してみる

とりあえずPython で試してみる。「Pystache」を使う。

pip install Pystache

「pystache」コマンドがインストールされるので、簡単なサンプルを試してみる。

pystache 'Hello {{name}}' '{"name": "World"}'

普通はテンプレートファイルを作成する。以下のような内容で「sample.txt」として作成。

<html>
<head>
<title>こんにちは {{name}}</title>
</head>
<body>
サンプル
{{#lists}}
  <b>{{name}}</b>
{{/lists}}
</body>
</html>

これを利用するソースは以下。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import unittest

from pystache.loader import Loader
from pystache import Renderer


class TestPystache(unittest.TestCase):

    def test_file(self):
        DATA_DIR = './data'
        loader = Loader(search_dirs=[DATA_DIR, ],
                        file_encoding='utf-8',
                        extension='txt')
        template = loader.load_name('sample')

        context = {'name': 'World',
                   'lists': [
                       {'name': 'a'},
                       {'name': 'b'},
                       {'name': 'c'},
                   ],
                   }

        renderer = Renderer()
        actual = renderer.render(template, context)

        self.assertEqual('<html>\n'
                         '<head>\n'
                         '<title>こんにちは World</title>\n'
                         '</head>\n'
                         '<body>\n'
                         'サンプル\n'
                         '  <b>a</b>\n'
                         '  <b>b</b>\n'
                         '  <b>c</b>\n'
                         '</body>\n'
                         '</html>\n'.decode('utf-8'),
                         actual)

if __name__ == '__main__':
    unittest.main()

まとめ

多言語対応なのは利点。
Python だと Jinja2 という強力なテンプレートエンジンがあるので、それほど利点がないかもしれない。
テンプレートエンジンの選択肢が少ないような言語だとかなり便利かもしれない。

2012年10月27日

Python 標準ロギングモジュール logging の使い方メモ

概要

logging を利用してみる。

ドキュメント

モジュールの使い方は、公式ドキュメント「logging — Logging facility for Python」に概要が書いてある。 また「Logging HOWTO」が具体的な使い方に関して書いてある。

使い方

以下が一番簡単な例。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import logging


def main():
    logging.warning('わーにんぐ')
    logging.info('いんふぉ')

if __name__ == '__main__':
    main()

実行すると以下のようになる。これはデフォルトレベルが「WARNING」以上だから。

WARNING:root:わーにんぐ

ログのレベルは以下のようになる。

レベル 関数 数値 概要
CRITICAL logging.critical() 50 停止してしまうような致命的な問題用
ERROR logging.error() 40 重大な問題用
WARNING logging.warning() 30 実行機能で問題が発生した場合用
INFO logging.info() 20 動作情報表示用
DEBUG logging.debug() 10 詳細な情報表示用
NOTSET 0 全てを出力。基本的に設定用の値

ファイルへの出力

エラーをファイルに出したい場合があるが以下のようにする。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import logging


def main():
    logging.basicConfig(filename='example.log', level=logging.DEBUG)
    logging.debug('でばっぐ')
    logging.info('いんふぉ')
    logging.warning('わーにんぐ')

if __name__ == '__main__':
    main()

「sample.log」ファイルに以下のように出力される。

DEBUG:root:でばっぐ
INFO:root:いんふぉ
WARNING:root:わーにんぐ

ログ出力形式の指定

ログの出力形式はいろいろ変更できる。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import logging


def main():
    logging.basicConfig(format='%(asctime)s:%(levelname)s:%(message)s',
                        level=logging.DEBUG)
    logging.debug('でばっぐ')
    logging.info('いんふぉ')
    logging.warning('わーにんぐ')

if __name__ == '__main__':
    main()

以下のように出力される。

2012-10-01 11:21:46,999:DEBUG:でばっぐ
2012-10-01 11:21:46,999:INFO:いんふぉ
2012-10-01 11:21:47,000:WARNING:わーにんぐ

使えるフォーマットは「LogRecord attributes」に一覧がある。主な物は以下。

フォーマット 概要
%(asctime)s 実行時刻
%(filename)s ファイル名
%(funcName)s 関数名
%(levelname)s DEBUG、INFO等のレベル名
%(lineno)d 行番号
%(name)s 呼びだしたログの定義名
%(module)s モジュール名
%(message)s ログメッセージ
%(process)d プロセスID
%(thread)d スレッドID

設定ファイルの利用

フォーマットやログレベルの設定は、通常は設定ファイルを書いて利用する。設定ファイルのファイル名な何でも良い。ここでは以下の内容で「logging.conf」として作成する。
以下の例だと、コンソールとファイルに「DEBUG」レベルでログを出力する。

[loggers]
keys=root, logExample

[handlers]
keys=consoleHandler, fileHandler

[formatters]
keys=logFormatter

[logger_root]
level=DEBUG
handlers=consoleHandler

[logger_logExample]
level=DEBUG
handlers=consoleHandler, fileHandler
qualname=logExample
propagate=0

[handler_consoleHandler]
class=StreamHandler
level=DEBUG
formatter=logFormatter
args=(sys.stdout,)

[handler_fileHandler]
class=handlers.RotatingFileHandler
level=DEBUG
formatter=logFormatter
args=('example.log',)

[formatter_logFormatter]
format=%(asctime)s - %(name)s - %(levelname)s - %(message)s
datefmt=

利用するソースは以下。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import logging
import logging.config


def main():
    logging.config.fileConfig('logging.conf')
    logger = logging.getLogger('logExample')

    logger.debug('でばっぐ')
    logger.info('いんふぉ')
    logger.warning('わーにんぐ')

if __name__ == '__main__':
    main()

Handlerの利用

上記のサンプルだと「RotatingFileHandler」を利用している。これはファイルが一定量になると回転するハンドラ。
標準のハンドラでも十分な場合もあるが、PyPIで探すと他にもいろいろなハンドラが存在している。
以下とか便利かもしれない。

存在しない Handler は上記を参考に自作してみると良いかもしれない。

まとめ

logging は障害対処時に非常に有効。多用することになるので、使い方に慣れておくと良い。

2012年10月26日

Python で標準添付の Debuggerのpdb を利用してデバッグするメモ

概要

pdb を利用してみる。

ドキュメント

公式ドキュメント「pdb — The Python Debugger」に概要が書いてある。

使い方

スクリプトを直接起動してデバッグする方法と、インタラクティブshell で起動する方法があるが、ここでは、スクリプトを起動してデバッグする方法で書く。
以下がデバッグするスクリプトのサンプル「even.py」。
自分の場合、スクリプトを直接デバッグすることがあまりなく、デバッグするのはライブラリとかをunittest経由でデバッグすることがが多いのでサンプルは unittest で書いている。
以下の例はクラス内の関数が偶数だけ返す所でバグがあり、奇数を返すようになっている。そんなに良い例ではないかも。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import unittest


class Sample(object):
    """
    テスト対象のクラス
    """
    def to_even(self, lst):
        """
        リストを渡すと、偶数だけにして返す
        """

        # 意図的にバグらせていて、奇数しか返さない
        evenlst = [v for v in lst if v % 2 == 1]
        return evenlst


class TestSample(unittest.TestCase):
    """
    テストクラス
    """

    def test_main(self):
        clazz = Sample()

        #  0 から 10 のリストを生成 [0, 1, 2, .....]
        lst = xrange(10)
        evenlst = clazz.to_even(lst)

        # 偶数かテストする
        for item in evenlst:
            num = item % 2
            self.assertEqual(0, num)


if __name__ == '__main__':
    unittest.main()

とりあえず実行してみると以下のようにエラーになる。

> python even.py 
F
======================================================================
FAIL: test_main (__main__.TestSample)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "even.py", line 33, in test_main
    self.assertEqual(0, num)
AssertionError: 0 != 1

----------------------------------------------------------------------
Ran 1 test in 0.000s

FAILED (failures=1)

pdbを起動してみる。起動は以下のようにする。

python -m pdb even.py

起動すると pdb プロンプトが起動する。

> /path/to/even.py(3)<module>()
-> import unittest
(Pdb) 

上記の出力から「3行目」の「import」で実行が停止しているのがわかる。
「list」で停止位置の周辺ソースが表示される。

(Pdb) list
  1     #!/usr/bin/env python
  2     # -*- coding: utf-8 -*-
  3  -> import unittest
  4  
  5  
  6     class Sample(object):
  7         """
  8         テスト対象のクラス
  9         """
 10         def to_even(self, lst):
 11             """
(Pdb) 

行数とともに、現在の停止場所に「->」がある。

「list」のようにデバッグに利用できるコマンドの主な物は以下になる。コマンドには省略形が存在する物もある。

コマンド 省略形 概要
help h コマンドのヘルプを表示。
「help」でコマンド一覧、
「help コマンド名」でコマンドのヘルプ
が表示される。
where w スタックトレースを表示
down d スタックトレース中に1レベル下げる
up u スタックトレース中に1レベル上げる
step s 現在行を実行し、次の行に進む
次の行が関数の場合、関数内で停止
next n 現在行を実行し、次の行に進む
次の行が関数でも、関数内では停止しない
return r 現在の関数から抜ける
list l 現在行周辺のソースを表示
デフォルトは11行表示する
args a 現在関数の引数一覧を表示
p 式 式内容を表示
pp 式 式内容を pprint で表示
quit q デバッガ終了

現在行が「import」なので「n(ext)」で進める。
間違えて「s(tep)」すると「import」を実行する Python のライブラリ内に入る。その場合は「r(eturn)」とかすると戻ってこれる。

(Pdb) n
> /path/to/even.py(6)<module>()
-> class Sample(object):
(Pdb) 

class の行になるが、実行されるわけではない。
再度「n」するが、この「n」は一度実行すると次回は「リターンキー」で同じ事を実行する。

(Pdb) 
> /path/to/even.py(18)<module>()
-> class TestSample(unittest.TestCase):
(Pdb) 

「c(ontinue)」すると、停止するまで実行する。

(Pdb) c
F
======================================================================
FAIL: test_main (__main__.TestSample)
----------------------------------------------------------------------
Traceback (most recent call last):
  File "even.py", line 33, in test_main
    self.assertEqual(0, num)
AssertionError: 0 != 1

----------------------------------------------------------------------
Ran 1 test in 0.000s

FAILED (failures=1)
The program exited via sys.exit(). Exit status:  True
> /path/to/even.py(3)<module>()
-> import unittest

ブレークポイント

通常 step 実行とかしないで、ブレークポイントを利用する。ブレークポイント関連のコマンドは以下。

コマンド 省略形 概要
break 行数 or 関数名 b 指定行数にブレークポイントを付ける
tbreak 行数 or 関数名 一時的ブレークポイントを付ける
一回通過すると消える
clear 行数 or bp番号 cl ブレークポイントを削除
disable bp番号 ブレークポイントを停止
enable bp番号 ブレークポイントを有効化
ignore bp番号 回数 ブレークポイントを回数分無視する
condition bp番号 ブレークポイントの状態を確認
commands bp番号 ブレークポイントを表示
continue c ブレークポイントで停止してる場合、実行を継続

「33行目」にブレークポイントを設定し「c」して「l」で確認してみる。

(Pdb) b 33
Breakpoint 1 at /path/to/even.py:33
(Pdb) c
> /path/to/even.py(33)test_main()
-> for item in evenlst:
(Pdb) l
 28             #  0 から 10 のリストを生成 [0, 1, 2, .....]
 29             lst = xrange(10)
 30             evenlst = clazz.to_even(lst)
 31  
 32             # 偶数かテストする
 33 B->         for item in evenlst:
 34                 num = item % 2
 35                 self.assertEqual(0, num)
 36  
 37  
 38     if __name__ == '__main__':
(Pdb) 

ここで「evenlst」の内容を表示してみる。

(Pdb) p evenlst
[1, 3, 5, 7, 9]
(Pdb) 

バグっている事がわかる。

「n」を2回して「l」して「p」してみる。「p」で複数表示の場合はカンマで区切る。

(Pdb) p item,num
(1, 1)
(Pdb) 

バグ原因が判明したら「q」などで終了すれば良い。

まとめ

他者の作成したライブラリ等はソース修正するとまずいので、 print を入れることができない場合が多く、そういう場合は pdb は便利。
本当はライブラリ内で Exception が発生したりするので、結構デバッグは大変だったりする。
自作のスクリプトの場合は、普通は pdb を使うよりも、いかに良くテストを書くかの方が重要な気がしている。pdbを使わなければいけないのはテストが下手だと思った方が良い。

2012年10月24日

pyregexp を利用して Emacs の検索置換に Python の正規表現を利用してみる

概要

Emacs の正規表現は結構使いずらくて、覚えられないので、「pyregexp」を利用して、Python の正規表現を利用してみる。

pyregexpに関して

Python を利用して Emacs 内の検索・置換の処理で Python の正規表現を利用できるようにする Emacs Lisp。動作には Python が必要。

設定

ソースを取得して、pathの通った所に配置。

git clone git://github.com/benma/pyregexp.git

init.el 等で以下のように設定する。

(require 'pyregexp)
(define-key global-map (kbd "C-c r") 'pyregexp-replace)
(define-key global-map (kbd "C-c q") 'pyregexp-query-replace)
(define-key esc-map (kbd "C-r") 'pyregexp-isearch-backward)
(define-key esc-map (kbd "C-s") 'pyregexp-isearch-forward)

使い方

公式サイト「pyregexp」の一部をそのまま引用する。

以下のようなテキストを用意。これの重複した文字を検索して、重複を削除してみる。

We we can delete double double words
by by using backreferences.

「C-c r」して入力を開始すると、「Regexp」になるので、「(?i)(\w+) \1」を入力。一致箇所がハイライトされるる。3箇所一致しているはず。一番最後の文字は「いち」なので間違わないように。
置換する場合は「enter」をすると「Replace」になるので、「\1」を入力。置換結果が「We we => We」のように表示される。問題なければ、enter することで置換される。

Python の式を利用することも可能。

以下のようなファイルを用意。

hoge
foo
bar

「C-c r」して「^」を入力すると、文字列の先頭が一致する。enter して Replace にして「C-c C-c」すると「Replace (using expression)」になるので「str(i+1) + ": "」を入力すると、先頭に「1: 2: 3:」のような文字が入力される。

str だけでなく、int等も利用できるのでかなり便利。

「wdired-change-to-wdired-mode」でも利用可能。かなり高度な置換ができる。

まとめ

まだ使いはじめたばかりだが、自分は Python に慣れているので、かなり便利に利用できている。

2012年10月23日

Python の lambda メモ

概要

仕事の関係で Python の lambda に関して最近質問された。lambda は結構読みずらいようで質問の回数が多い気がする。毎回似たような質問に答えるのが面倒なので、回答用に概要をメモしておく。

Python の lambda

lambda は無名関数を作成するための機能。

def a(b):
    return c

と同じ物をが、lambda だと以下のように記述できる。

a = lambda b: c

簡単なサンプル

とりあえず簡単なサンプルが以下。時々 lambda は改行ができないと思っている人がいるみたいだが、括弧でかこめば普通に改行できる。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import unittest

class TestLambda(unittest.TestCase):

    def test_basic(self):

        # def を利用
        def a(b):
            return b * 10

        self.assertEqual(20, a(2))

        # lambda を利用
        a = lambda b: b * 2

        self.assertEqual(4, a(2))

        # 括弧を利用すると途中改行が可能
        a = (lambda b:
             b * 4)

        self.assertEqual(8, a(2))

if __name__ == '__main__':
    unittest.main()

まとめ

確かに lambda を使うべき場面もあるが、普通はlambda はそんなに多用しない方が良いと思う。利用しても、意味ない場合も多い。
変な所で、変なテクニック使ってもおもしろくともなんとも無いので、普通に読みやすく書くのが一番良い。
あと、書いたは良いが、質問される内容はもっと高度になる場合が多いので、このエントリは自分の手抜き用には役に立たないかもしれない。

2012年10月22日

Mercurial(hg) を使って GitHub に Pull Request する手順

概要

分散バージョン管理ツールとして自分は「Mercurial」を利用している。
自分にとって Git は難しすぎるので、できれば使いたくなかったので、Git を利用しないで GitHub に Pull Request する方法を調べた。

Pull Request が初めての場合

Pull Request が初めての場合、GitHub の公式マニュアルに従って練習すると良い。Pull Requestのマニュアルは「Fork A Repo · github:help」にある。

GitHub のマニュアルに従って、「https://github.com/octocat/Spoon-Knife」を Fork して練習する。失敗しても誰も怒ったりしないので、自由にやるのが良い。

hg-git のインストールと設定

hg で GitHub のソースを clone するには「hg-git」を利用する。
hg-git」は Git のバイナリがなくても、単体で Git レポジトリを扱うことができるライブラリ。pip 等で簡単にインストールできる。

pip install hg-git

hgrcに以下を設定する

[extensions]
hggit=

hg で GitHub のソースを clone する

hg-git を利用して GitHub のレポジトリを clone する。
例えばGitHub で「git@github.com:username/Spoon-Knife.git」のようなレポジトリの場合、以下のように指定すると clone できる。

hg clone git+ssh://github.com/username/Spoon-Knife.git

Pull Request用に開発

master でそのまま開発すると、fork 元の変更に追随することができなくなるので、開発用の branch を作成する。

cd Spoon-Knife
hg branch 'develop'
# コミット
hg ci -m"develop"
# 確認
hg branches

そして、開発用の branch から pull request 用の branch を作成する。

hg branch 'feature/samplespike'
# コミット
hg ci -m"feature/samplespike"
# 確認
hg branches

pull request 用の branch で開発する。

push 用の bookmark 作成

hg-git では hg の bookmark が Git の branch になる。push を本当にする場合、bookmark を作成する。

hg bookmark -r feature/samplespike pullrequest/samplespike

pull requestのために、Commit を一つに纏める

開発によって、Commit が複数に分割されている場合があるが、分割されたpull request はしない方が良い。
Git の場合 rebase でこれを実施するが、hg では「Histedit Extension」を利用する。Mercurial 2.3以降は標準添付なので以下のようにhgrcに設定する。

[extensions]
histedit=

利用しているバージョンが Mercurial 2.3 以下の場合は「histedit」からソースを取得して設定する必要がある。

hg clone https://bitbucket.org/durin42/histedit

ソースからの場合は hgrc に以下のように設定する。

[extensions]
histedit = /path/to/hg_histedit.py

histedit の使い方は「hg help -e histedit」でhg の help を見ると和訳されている。
英語だが公式のマニュアルを見ても良い「Histedit Extension」。

hg histedit まとめたいバージョン

push の前に確認する

「hg log」や「hg glog」を利用して意図した通りになっているか十分確認する。push するとhisteditでの変更ができない。

Hg-Gitプラグインは「--HG--」のようなコメントを挿入してしまう。これを抑止するにはソースを直接修正するしかない。
「hggit/git_handler.py」に該当部分が存在するので、気になる場合は、削除かコメントアウトしておくと良い。

push して pull request

hg-git でpush するのは hg で push するのと何も変らない。

hg push

あとは GitHub のWeb から該当の branch を pull request すれば良い。

本家に追随する

本家が自分のpull requestを取り込みしてくれたら、追随する必要がある。

hg pull -r master git+ssh://github.com/octocat/Spoon-Knife.git
hg up

これで取り込める。master に commit していると面倒なことになるので、master では開発しないようにした方が良い。

まとめ

これでGitHub の pull request が hg で可能になる。

2012年10月21日

munin 2 の作成した rrd ファイルを確認する

概要

munin 2 がどのような rrd ファイルを作成しているのか確認してみる。

データファイル確認

munin 2 のデータファイルはデフォルトでは「/var/lib/munin/localdomain」以下に作成されている。
rrdtool コマンド で内容を確認することが可能。

rrdtool info nginx_request-request-d.rrd

python で見る場合は以下のようにすると見れる。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import rrdtool


def main():
    info = rrdtool.info('nginx_request-request-d.rrd')
    for key in sorted(info.keys()):
        print '{0} = {1}'.format(key, info[key])

if __name__ == '__main__':
    main()

設定された値はだいたいわかるが、なぜその値なのかは、もうすこし調べてみる。

2012年10月20日

munin の plugin 作成メモ

概要

Munin」の plugin 作成の手順とサンプルをメモしておく。

ドキュメント

公式ドキュメントは結構ちらばっていてやや読みずらいが、plugin を作成したいならちゃんと読んだ方が良い。

plugin サンプル

とりあえず、サンプルを見て作成するのが速い。

参考に nginx の稼動を記録する plugin を作成してみる。nginx の status を利用して監視をするので、 status は有効にしておく。
nginx の稼動確認 plugin は公式の munin plugin としてすでに存在しているが、存在している plugin を作成することで、自作の plugin が既存の plugin と同じ動作をすることが確認できるので、最初に作成する plugin はすでに存在している plugin を作成してみるのが良い。

  • /usr/share/munin/plugins/nginx_request
  • /usr/share/munin/plugins/nginx_status

munin 標準の nginx plugin は Perl で作成されており、「LWP::UserAgent」が必要な plugin になっている。動作させる場合は、「/etc/munin/plugins」に「ln -s」する。動作させて nginx を監視したグラフを生成しておく。

munin の plugin はプログラム言語を問わないので、とりあえず自分は Python で実装してみたのが以下。「nginx_request.py」の名前で作成した。
以下の plugin は正規表現を「https://github.com/samuel/python-munin/blob/master/munin/nginx.py」の物を利用して、実質30から40分程度で作成したので、あまり最適化してないし、エラー処理もいいかげん。またバグがあるかもしれないので、ちゃんと検証してから利用すること。一応 Python 2.6 と 2.7 で動作するはず。
自作の plugin も動作させる場合は、「/etc/munin/plugins」に「ln -s」すれば良い。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
import os
import re
import sys
import urllib


def get_env():
    """
    get env
    """
    url = os.environ.get('url')
    if url is None:
        url = 'http://localhost/nginx_status'
    return url


def get_fields():
    """
    fields
    """
    return ('request',)


def get_server_status():
    """
    server status
    """
    # https://github.com/samuel/python-munin/blob/master/munin/nginx.py
    status_re = re.compile(
        r'Active connections:¥s+(?P<active>¥d+)¥s+'
        r'server accepts handled requests¥s+'
        r'(?P<accepted>¥d+)¥s+(?P<handled>¥d+)¥s+(?P<requests>¥d+)¥s+'
        r'Reading: (?P<reading>¥d+) Writing: (?P<writing>¥d+) Waiting: (?P<waiting>¥d+)')
    try:
        status = status_re.search(urllib.urlopen(get_env()).read()).groupdict()
    except Exception, e:
        status = None
    return status


def do_data():
    """
    data
    """
    request = get_server_status()['requests']
    fields = get_fields()
    for field in fields:
        print '{0}.value {1}'.format(field, request)


def do_autoconfig():
    """
    autoconfig
    """
    if get_server_status() is None:
        print 'no (no nginx status on {0})'.format(get_env())
    else:
        print 'yes'


def do_config():
    """
    config
    """
    print 'graph_title nginx Requests'
    print 'graph_args --base 1000 -l 0'
    print 'graph_vlabel Requests per second'
    print 'graph_category nginx'

    fields = get_fields()
    for field in fields:
        print '{0}.label requests'.format(field)
        print '{0}.min 0'.format(field)
        print '{0}.type DERIVE'.format(field)
        print '{0}.draw LINE2'.format(field)


if __name__ == '__main__':
    if len(sys.argv) > 1:
        if sys.argv[1] == 'config':
            do_config()
            sys.exit(0)
        if sys.argv[1] == 'autoconfig':
            do_autoconfig()
            sys.exit(0)
    else:
        do_data()

上記の plugin は 外部設定に対応している。「/etc/munin/plugin-conf.d」に「nginx」みたいな名前で以下のようなファイルを作成すると、監視 URL を変更できる。

[nginx_*]
env.url http://domain/nginx_status

plugin のデバッグ

基本的には「munin-run」を利用するとデバッグがしやすい。
上記の plugin をデバッグする場合は、以下のようにする。

sudo munin-run nginx_request autoconfig
sudo munin-run nginx_request config
sudo munin-run nginx_request

今後の課題

munin の plugin 作成の設定値を全部認識しているわけではないので、徐々に確認していく。

2012年10月19日

Ubuntu に munin 2 をインストールする手順メモ

概要

Munin」はサーバリソースグラフツール。
最新版は2.0.7だが、Ubuntu だと munin 1.x 系しかインストールできない模様なので、インストール手順をメモする。

muninの概要

munin は サーバのリソースをグラフ化するツール。似たようなツールは cacti 等が存在する。

muninの利点は以下

  • MySQL 等のデータベース不要
  • データ記録に RRDtool を利用
  • RRDtool で生成したデータファイルは、容量が初期生成時から増えることはないので、サーバの容量管理が容易
  • Perl で記述されているので、多くの環境で動作する
  • plugin の作成のためのプログラム言語はなんでも良く、書き方も比較的容易

munin は「master」と「node」が存在する。ここでは一つのサーバに「master」と「node」をインストールしているが、通常は「node」しかインストールしないサーバ等も存在することになる。

インストール

インストール手順を書いた fabfile は以下。「fab install」でインストールできる。
この記事を書いた時点では、deb ファイルがまだ 2.0.6 しかなかったので、2.0.6 をインストールしている。

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from fabric.decorators import task
from fabric.api import (
    sudo,
    run,
    cd,
    env,
)


@task
def install():
    """
    munin 2.0 インストール
    """
    # 依存ファイルのインストール
    sudo('apt-get update')
    sudo('apt-get -y install librrds-perl '
         'rrdtool '
         'liblog-log4perl-perl '
         'ttf-dejavu '
         'libdate-manip-perl '
         'libcgi-fast-perl '
         'libfile-copy-recursive-perl '
         'libio-socket-inet6-perl '
         'librrd4 '
         'libfcgi-perl '
         'libyaml-syck-perl '
         'libsocket6-perl '
         'liblist-moreutils-perl '
         'libnet-server-perl '
         'libio-multiplex-perl '
         'libnet-cidr-perl '
         'libnet-snmp-perl '
         'libhtml-template-perl '
         'liburi-perl '
         'gawk ')

    etc_dict = {
        'urlpath': 'http://ftp.jp.debian.org/debian/pool/main/m/munin/',
        'ver': '2.0.6-1',
    }
    with cd(env.path):
        deb_tpl = (
            'munin-common_{ver}_all.deb'.format(**etc_dict),
            'munin_{ver}_all.deb'.format(**etc_dict),
            'munin-plugins-core_{ver}_all.deb'.format(**etc_dict),
            'munin-node_{ver}_all.deb'.format(**etc_dict),
        )
        # ダウンロード
        for deb in deb_tpl:
            run('wget {urlpath}/{deb}'.format(deb=deb, **etc_dict))

        # インストール
        for deb in deb_tpl:
            sudo('dpkg -i {deb}'.format(deb=deb))

起動

基本的には自動起動するはず。

「/etc/init.d/munin-node」および、「/etc/cron.d/munin」、「/etc/cron.d/munin-node」を確認する。

ディレクトリ

ディレクトリを確認しておく必要がある。

  • 設定ファイル:/etc/munin
  • 記録ファイル:/var/lib/munin
  • HTMLファイル:/var/cache/munin/www
  • ログ:/var/log/munin
  • pid:/var/run/munin

細かい設定は、設定ファイルで実施するが、とりあえずこの記事ではインストールまでとする。

nginxの設定

http 経由でグラフを見えるように設定する。
以下のような設定を nginx の設定に追加すれば、「http://ip/munin/」でグラフを見ることが可能になる。

# munin
location ^~ /munin/ {
    alias   /var/cache/munin/www/;
}

以上

2012年10月18日

nginx で自己署名証明書(オレオレ証明書、self signed certificate)を設定する

概要

ngin で 自己署名証明書(オレオレ証明書)を設定する。自己署名証明書でも暗号化はされるので、自分で利用する分には問題ない。

証明書作成

fabfile の該当箇所は以下。コピペして実行しても良い。途中でパスワードなど聞かれるが、全部空で良い。

@task
def setup_ssl():
    """
    オレオレ証明書の作成
    """
    work_dir = '/opt/nginx/conf/'
    with cd('{0}'.format(work_dir)):
        # pass hoge
        sudo('openssl genrsa -des3 -out server.key 1024')
        sudo('openssl req -new -key server.key -out server.csr')
        sudo('cp server.key server.key.org')
        sudo('openssl rsa -in server.key.org -out server.key')
        sudo('openssl x509 -req -days 365 -in server.csr -signkey server.key -out server.crt')

nginxの設定

設定ファイルは以下のようになる。

daemon off;
worker_processes  5;

events {
    worker_connections  1024;
}

http {
    include       mime.types;
    default_type  application/octet-stream;

    log_format  main  '$remote_addr - $remote_user [$time_local] "$request" '
                      '$status $body_bytes_sent "$http_referer" '
                      '"$http_user_agent" "$http_x_forwarded_for"';
    error_log /opt/nginx/var/log/nginx-error.log debug;

    sendfile        on;

    keepalive_timeout  65;

    gzip  on;

    gzip_comp_level 2;
    gzip_proxied    any;
    gzip_types      text/plain text/css application/x-javascript text/xml application/xml application/xml+rss text/javascript;

    server {
        listen       80;
        # 443 ポートを有効にする
        listen       443;
        server_name  localhost;

        # SSL を有効にして、証明書のパスを指定
        ssl          on;
        ssl_certificate /opt/nginx/conf/server.crt;
        ssl_certificate_key /opt/nginx/conf/server.key;

        charset  utf-8;

        location / {
            root   /opt/www/html;
            index  index.html index.htm index.php;
        }

        error_page   500 502 503 504  /50x.html;
        location = /50x.html {
            root   html;
        }

        # nginx status
        location ^~ /nginx_status {
            stub_status on;
            access_log  off;
        }

    }
}

ファイアーウォールの設定等している場合は 443 ポートを開放する。
あとは再起動して、https でアクセスすれば良い。

以上

2012年10月17日

プロセス管理に daemontools ではなく Supervisor を利用してみませんか

概要

サーバでは様々なプロセスを実行する必要があるが、これをどう管理するかは悩み所になる。
ここでは強力で柔軟なプロセス管理ツール「Supervisor」に関して記述する。

Supervisorに関して

Supervisor」は Python で記述された、プロセス管理ツール。
プロセス管理ツールと言うと「upstart」や「daemontools」が利用されていると思われる。
「upstart」は強力ではあるが、柔軟性がまだそれほど無いため、高度なプロセス管理はまだ難しい状況だと思われる。
「daemontools」は ノウハウが蓄積されており、強力で柔軟性が高いツールだが、2001年以降公式では更新されておらず、動作が不安定にってしまう環境も存在している。パッチを当てれば良いが、環境によって動作が変化してしまう場合もあり、安心して利用できない状況になってしまった。
Supervisor」は積極的に開発され、非常に安定したツールとなっている。
機能的には「daemontools」とほぼ同様の事が可能。
Python で記述されることで、Unix互換システムにおいて、快適に動作する。当然 Mac でも動作する。
現在の Linux サーバは Python はほぼ必須でインストールされている、という状況なので、ツールが Python で記述されていることは利点の一つになりえる。

Supervisorの利点は以下となる。全機能はマニュアル「Supervisor」を参照。

  • 積極的に開発されている
  • 設定ファイルが比較的簡単
  • プロセスのグループ化が容易
  • プロセスの状態により処理を実行することが可能
  • サブプロセスの管理も可能
  • ログを高度に処理できる
  • Webインターフェースが存在する
  • XMP-RPCインターフェースが存在する

インストール

Supervisorをインストールする場合、通常virtualenvにより環境を構築する。
参考に、以下インストール手順を示した fabfile を配置した。以下のスクリプトはインストールディレクトリは「/opt/python」で、作業ディレクトリは「/opt/supervisor」として記述してある、また、この fabfile はあまり、汎用的には記述していない、あくまで参考である。

実行は「fab install」で実行可能

#!/usr/bin/env python
# -*- coding: utf-8 -*-
from fabric.decorators import task
from fabric.api import (
    sudo,
    run,
    cd,
    put,
    env,
)
from fabric.contrib.files import exists


@task
def install():
    """
    Supervisor
    """
    setup_venv()
    install_supervisor()
    setup_bin()
    setup_conf()
    setup_upstart()


@task
def setup_venv():
    """
    virtualenv による Python 環境構築
    """
    with cd(env.path):
        run('wget https://raw.github.com/pypa/virtualenv/master/virtualenv.py')
        sudo('python virtualenv.py --distribute /opt/python')


@task
def install_supervisor():
    """
    Supervisor インストール
    """
    run('source /opt/python/bin/activate')
    sudo('/opt/python/bin/pip install Supervisor')


@task
def setup_bin():
    """
    実行ファイル設定
    """
    work_dir = '/opt/supervisor/bin'
    if not exists('{0}'.format(work_dir)):
        sudo('mkdir -p {0}'.format(work_dir))
    put('./fabfile{0}/*'.format(work_dir),
        '{0}/'.format(work_dir), use_sudo=True)
    sudo('chown root:root {0}/*'.format(work_dir))
    sudo('chmod 755 {0}/*'.format(work_dir))


@task
def setup_conf():
    """
    設定ファイル
    """
    work_dir = '/opt/supervisor'
    if not exists('{0}'.format(work_dir)):
        sudo('mkdir -p {0}/etc'.format(work_dir))
        sudo('mkdir -p {0}/var/log'.format(work_dir))
        sudo('mkdir -p {0}/var/run'.format(work_dir))

    work_dir = '/opt/supervisor/etc'
    conf_file = '{0}/supervisord.conf'.format(work_dir)
    if not exists('{0}'.format(work_dir)):
        sudo('mkdir -p {0}'.format(work_dir))
    put('./fabfile{0}'.format(conf_file),
        '{0}'.format(conf_file), use_sudo=True)

    work_dir = '/opt/supervisor/etc/supervisor.d'
    conf_file = '{0}/*.conf'.format(work_dir)
    if not exists('{0}'.format(work_dir)):
        sudo('mkdir -p {0}'.format(work_dir))
    put('./fabfile{0}'.format(conf_file),
        '{0}'.format(conf_file), use_sudo=True)


@task
def setup_upstart():
    """
    upstart 設定
    """
    conf_file = '/etc/init/supervisor.conf'
    put('./fabfile{}'.format(conf_file),
        '{0}'.format(conf_file), use_sudo=True)
    sudo('chown root:root {0}'.format(conf_file))

管理用スクリプトの配置

Supervisor をインストールすると、インストール場所の「bin」以下に「supervisord」と「supervisorctl」の2ファイルが生成される。
これを直接利用しても良いが、自分は、一つのサーバで複数のSupervisorを起動する要件が発生する場合があるため、以下のようにスクリプトを作成している。
このスクリプトは実行する Python の指定と、設定ファイルの指定をしているだけだが、用途によっては便利だろう。

以下が「supervisord」と同じ機能のファイル

#!/opt/python/bin/python
# -*- coding: utf-8 -*-
import os
import sys

SRC_DIR=os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
sys.argv.extend(['-c', '{0}/etc/supervisord.conf'.format(SRC_DIR)])

import supervisor.supervisord

if __name__ == '__main__':
    supervisor.supervisord.main()

以下が「supervisorctl」と同じ機能のファイル。

#!/opt/python/bin/python
# -*- coding: utf-8 -*-
import os
import sys

SRC_DIR=os.path.abspath(os.path.join(os.path.dirname(__file__), '..'))
sys.argv[1:1] = ['-c', '{0}/etc/supervisord.conf'.format(SRC_DIR)]

import supervisor.supervisorctl

if __name__ == '__main__':
    supervisor.supervisorctl.main(sys.argv[1:])

設定ファイル

設定ファイルの雛形は「echo_supervisord_conf」プログラムによって生成可能。
とりあえず以下のように記述すると最低限の動作はする。「%(here)s」は設定ファイルのあるディレクトリを示す。
「include」の「file」は現在のバージョンではまだ「%(here)s」が使えない。

[supervisord]
childlogdir = %(here)s/../var/log
logfile = %(here)s/../var/log/supervisor.log
logfile_maxbytes = 50MB
logfile_backups = 10
loglevel = info
pidfile = %(here)s/../var/run/supervisor.pid
umask = 022
nodaemon = false
nocleanup = false

[inet_http_server]
port = 127.0.0.1:9001
username =
password =

[supervisorctl]
serverurl = http://127.0.0.1:9001
username =
password =

[rpcinterface:supervisor]
supervisor.rpcinterface_factory=supervisor.rpcinterface:make_main_rpcinterface

[include]
files = /opt/supervisor/etc/supervisor.d/*.conf

upstartの設定

Supervisor自身をSupervisorで管理することはできないので、Supervisorは upstartで管理する。
以下のようなファイルを「/etc/init」以下に「supervisor.conf」として配置する。

description     "supervisor"

start on runlevel [2345]
stop on runlevel [!2345]

respawn

chdir /opt/supervisor
exec sudo -i /opt/python/bin/python /opt/supervisor/bin/supervisord -n -d /opt/supervisor

pre-stop exec sudo -i /opt/python/bin/python /opt/supervisor/bin/supervisorctl stop all

プロセス管理設定

これで Supervisor は起動している。「supervisorctl status」を実行するとまだ管理対象が存在しないことがわかる。
とりあえず、nginx の管理を設定してみる。

管理対象のプロセスはフォアグラウンドで起動させる必要がある。nginx の設定を変更する。

daemon off;

Supervisor の設定ファイルで「include」の「files」で指定したディレクトリに以下のようなファイルを「nginx.conf」のような名前で配置する。

[program:nginx]
command=/opt/nginx/sbin/nginx -c /opt/nginx/conf/nginx.conf
redirect_stderr=true
stdout_logfile=%(here)s/../var/log/%(program_name)s_stdout.log
stderr_logfile=%(here)s/../var/log/%(program_name)s_stderr.log
autostart=true
autorestart=true

ここで注意すべきなのは「%(here)s」は、元設定ファイルのカレントディレクトリになるということ。パスには注意する。
以下のようにすることで、有効になる。

# 設定読み込み
supervisorctl reread
# 設定反映
supervisorctl update
# 確認
supervisorctl status

設定を反映するまでは、有効にならない。

以下のように、起動、終了、再起動が可能。

supervisorctl stop nginx
supervisorctl start nginx
supervisorctl restart nginx

「autostart=true」を設定しているので、Supervisor が起動すると自動起動する。
「autorestart=true」を設定しているため、異状終了しても管理対象プロセスは復旧してくる。kill などして、確認してみると良い。

Webインターフェース、XML-RPC

SupervisorはHTTPのWebインターフェエースをもっている。また XML-RPC による管理も可能。
HTTPSを利用したい場合は、nginx などの HTTPサーバを経由させれば良い。
上の設定ファイルを利用している場合はポート9001になる。

その他機能

他の機能はマニュアルを読むのが良い。機能は高機能なので、いろいろできる。