ksauzz weblog

technical note....

Erlangのデバッグツール Redbug

redbugはErlangコードのトレースに使用するデバッグ用ツールです。 eper というパフォーマンス関連ツールの一部として配布されています。 これを使うと、指定した関数のトレースがとても手軽に行えます。

redbugで出来る事

  • モジュール名、関数名、引数でのトレース対象指定(ワイルドカード可)
  • 引数の確認
  • 戻り値の確認
  • コールスタックの確認

Riakでの実行例

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
(riak1@127.0.0.1)81> redbug:start({riak_kv_vnode, put,[return, stack]},[{time,5000},{msgs,4},{max_msg_size,10000
000},{print_depth,10}]).
ok

20:17:34 <{riak_kv_put_fsm,init,1}> {riak_kv_vnode,put,
                                     [[{685078892498860742907977265335757665463718379520,
                                        'riak1@127.0.0.1'},
                                       {707914855582156101004909840846949587645842325504,
                                        'riak1@127.0.0.1'}],
                                      {<<"bucket">>,<<"key1">>},
                                      {r_object,<<"buck"...>>,<<...>>,...},
                                      87431492,63534539854,[]]}
  "proc_lib:init_p_do_apply/3 "
  "gen_fsm:handle_msg/7 "

.. 略 ..

20:17:34 <{riak_kv_put_fsm,init,1}> {riak_kv_vnode,put,6} -> ok
quitting: msg_count

動作確認環境

OSX 10.8.3 R15B03, CentOS 6.3 R15B01

導入方法

rebarを使っていればrebar.configのdepsにeperを追加するだけです。

1
2
3
{deps, [
        {eper, "0.61", {git, "git://github.com/basho/eper.git", {branch, master}}}
       ]}.

TODO: rebarを使わない方法

動作確認

sampleモジュールを例に、簡単に動作確認をしてみます。

1. erlの起動

まずはErlangシェルを起動します。

1
2
3
4
5
% erl -pa ebin/ deps/*/ebin
Erlang R15B03 (erts-5.9.3.1) [source] [64-bit] [smp:4:4] [async-threads:0] [kernel-poll:false] [dtrace]

Eshell V5.9.3.1  (abort with ^G)
1>

2. redbugの起動

redbug:start/1でトレース対象のモジュール名(sample)を指定し、redbugを起動します。 ※15sec(default)でトレース処理が終了してしまうので気をつけましょう。

1
2
1> redbug:start(sample).
ok

3. トレース対象のコードを実行

トレース対象のsampleモジュールを実行し、トレースの様子を確認します。 尚、ここで実行するsample:start_link/0は間接的にsample:init/1を呼び出すコードになっています。

1
2
2> sample:start_link().
{ok,<0.41.0>}

するとコンソールに次の内容が出力されます。

1
2
3
4
20:43:07 <{erlang,apply,2}> {sample,start_link,[]}

20:43:07 <sample> {sample,init,[[]]}
quitting: timeout

これがトレース結果です。簡単ですがsample:start_link()sample:init([])がコールされた事が示されています。

使用例

redbugはトレース条件、オプションを指定することで様々なトレースを行うことができます。ここではその一部を紹介します。

Usage
-----
redbug:start(Trc)                %% トレース条件のみ指定
redbug:start(Trc, Opts)          %% トレース条件, オプション指定
redbug:start(Timeout, Msgs, Trc) %% トレース条件, タイムアウト, トレース数指定(オプション指定不能)

サンプル

riak_kv_get_fsmモジュールの呼び出しをtrace

redbug:start(riak_kv_get_fsm).

riak_kv_get_fsm:initの呼び出しをtrace

redbug:start(riak_kv_get_fsm, init).
or
redbug:start({riak_kv_get_fsm, init}).

riak_kv_get_fsmの呼び出し、戻り値をtrace

redbug:start({riak_kv_get_fsm, '_', [return]}).

riak_kv_get_fsm:initの呼び出し、戻り値、コールスタックをtrace

redbug:start({riak_kv_get_fsm, init, [return, stack]}).

riak_kv_put_fsm:execute(pw, _, default)の呼び出し、戻り値をtrace (引数によるマッチング)

redbug:start({riak_kv_put_fsm, '_', [{pw,'_',default},return]}).

riak_kv_get_fsm:initの呼び出しをtrace、Timeout 1,000msec

redbug:start({riak_kv_get_fsm, init}, [{time, 1000}]).

riak_kv_get_fsm:initの呼び出しをtrace、Timeout 1,000msec

redbug:start({riak_kv_get_fsm, init}, [{time, 1000}, {msgs, 20}]).

その他オプションとそのデフォルト値

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
time         = 15000        % ms
msgs         = 10           % unit
proc         = all          % list of procs (or 'all')
target       = node()       % target node
cookie       = ''           % target node cookie
buffered     = no           % output buffering
arity        = false        % arity instead of args
print_call   = true         % print calls (see `return_only')
print_form   = "~s~n"       % format for printing
print_file   = ""           % file to print to (standard_io)
print_msec   = false        % print milliseconds in timestamps?
print_depth  = 999999       % Limit for "~P" formatting depth
print_re     = ""           % regexp that must match to print
max_queue    = 5000         % max # of msgs before suicide
max_msg_size = 50000        % max message size before suicide
file         = ""           % file to write trace msgs to
file_size    = 1            % file size (per file [Mb])
file_count   = 8            % number of files in wrap log

ざっくりですが以上です。

余談ですが、Riakはeperを含んでいるので、riak attachするだけでredbugで内部をトレースできます。redbugはRiakの勉強にも、もってこいですね。ではでは。

ひとりでやる(?)Riak Advent Calendar 2012 Day7 - Ruby Clientで遊んでみよう

ということでRiakのRubyライブラリをご紹介。RiakのProtocol Buffer/HTTPクライアントであるriak-ruby-clientとObject MapperであるRippleの2本立てです。Rippleに関してはRailsへの導入方法を簡単に説明します。

  • riak-ruby-clientの使い方
  • RailsへRippleを導入する

riak-ruby-clientの使い方

インストール

gemでサクッとインストールします。

1
2
3
% gem install riak-client
Successfully installed riak-client-1.1.0
1 gem installed

利用方法

1. Clientの生成

Procotol Bufferを使うか、HTTPを使うかをnewするときに指定できます。

1.1. HTTP
1
2
require 'riak'
client = Riak::Client.new
1.2. Protocol Buffer
1
2
require 'riak'
client = Riak::Client.new(:protocol => "pbc")
1.3. 複数ノードへの接続
1
2
3
4
5
6
require 'riak'
client = Riak::Client.new(:nodes => [
  {:host => '10.0.0.1'},
  {:host => '10.0.0.2', :pb_port => 7070},
  {:host => '10.0.0.3', :http_port => 8080}
])

2. Objectを保存する

生成したclientを使ってObjectを保存します。 やり方が3パターンほどあるので、それぞれサンプルをご紹介。 Riak::RObjectの生成方法が違うだけでやっていることは同じです。

2.1 シンプルに使う
1
2
3
4
5
bucket = client.bucket("doc")
object = bucket.get_or_new("index.html")
object.raw_data = "<html><body>Hello, world!</body></html>"
object.content_type = "text/html"
object.store
2.2 Riak::RObjectを明示的に利用
1
2
3
4
5
bucket = client.bucket("doc")
new_one = Riak::RObject.new(bucket, "index2.html")
object.raw_data = "<html><body>Hello, world!</body></html>"
object.content_type = "text/html"
object.store
2.3 ハッシュを使って
1
2
3
4
object = client['doc']['index.html']
object.raw_data = "<html><body>Hello, world!</body></html>"
object.content_type = "text/html"
object.store

保存したデータをhttpieで確認してみます。

1
2
3
4
5
6
7
8
9
10
11
12
13
% http localhost:8098/buckets/doc/keys/index.html
HTTP/1.1 200 OK
Content-Length: 39
Content-Type: text/html; charset=UTF-8
Date: Wed, 12 Dec 2012 16:55:44 GMT
ETag: "44tAJLzAUJTa20TtR8s5fJ"
Last-Modified: Wed, 12 Dec 2012 14:53:48 GMT
Link: </buckets/doc>; rel="up"
Server: MochiWeb/1.1 WebMachine/1.9.0 (someone had painted it blue)
Vary: Accept-Encoding
X-Riak-Vclock: a85hYGBgzGDKBVIcypz/fgacmLY0gymRKY+VoUbM9hRfFgA=

<html><body>Hello, world!</body></html>

はい、確認できました。 簡単ですがriak-ruby-clientの紹介はここまで。

RailsへRippleを導入する

Ripple - A rich Ruby modeling layer for Riak -

RippleはRiakのデータをオブジェクトにマッピングしてくれるライブラリです。ActiveRecordライクに利用できます。 Riakを使ってアプリケーションを作る際には、基本的にRippleやそれに類似したライブラリを使うことになる筈です。

それではRailsへRippleを導入する方法を簡単に紹介します。

なお、このサンプルではRails 3.2.8を使用しています。

1. Railsアプリケーションの生成

rails newでアプリケーションを生成します

1
2
3
4
5
6
7
% rails new ripple-example
      create
      create  README.rdoc
      create  Rakefile
      create  config.ru
...
% cd ripple-example

2. rippleをdependenciesへ追加

Gemfileに以下を追加します。 Railsの3.2.8を使っているのでrippleは1.0.0.beta2を使います。

1
gem "ripple", "~>1.0.0.beta2"

bundle installします。

1
bundle install

3. セットアップ

rails g rippleします。

1
2
3
4
5
6
7
8
% rails g ripple
      create  config/ripple.yml
      create  app/mapreduce
      create  app/mapreduce/contrib.js
      create  app/mapreduce/iso8601.js
      create  app/mapreduce/ripple.js
      insert  test/test_helper.rb
      insert  test/test_helper.rb

config/ripple.ymlを編集してRiakの設定を自分の環境に合わせます。 sourceにはローカルのriakコマンドがインストールされているパスを設定してください。

1
2
3
4
5
development:
  http_port: 8098
  pb_port: 8087
  host: 127.0.0.1
  source: /usr/local/bin  #Default for Homebrew.

それでは試しにmodelを生成します。 propertiesとしてname, ageを指定してみました。

1
2
3
4
5
% rails g ripple:model User name:string age:integer
      create  app/models/user.rb
      invoke  test_unit
      create    test/unit/user_test.rb
      create    test/fixtures/users.yml

生成したModelはこんな感じになります。

1
2
3
4
5
6
class User
  include Ripple::Document

  property :name, String
  property :age, Integer
end

4. 動作確認

consoleで動作確認してみます。 まずはデータを保存。

1
2
3
4
5
6
7
8
9
10
11
12
% rails console
Loading development environment (Rails 3.2.8)
irb(main):001:0> user = User.new
=> <User:[new] name=nil age=nil>
irb(main):002:0> user.name = 'たろう'
=> "たろう"
irb(main):003:0> user.age = 18
=> 18
irb(main):004:0> user.save
=> true
irb(main):005:0> user.key
=> "QB4nWCL1SpvCe5csclOUgmNX7XN"

保存後に取得したkeyの値がオブジェクトのIDとなります。 このIDを使ってデータが保存されたか確認してみます。

1
2
irb(main):006:0> User.find "QB4nWCL1SpvCe5csclOUgmNX7XN"
=> <User:QB4nWCL1SpvCe5csclOUgmNX7XN name="たろう" age=18>

はい、保存されていますね。

なお、オブジェクトのID(riakのkey名)は、以下のkey\_onをModel内で指定する事により、 自動生成された値でなく、特定のproperyを使用することができます。 下記の例だとkey_nameというpropertyがriakのkey名に使われます。

1
key_on :key_name # use :key_name as key name.

こんな感じでRiak+Railsのアプリケーションを作って遊んでみてはいかがでしょうか。 今日はここまで。

Vagrantメモ

create vagrant box using veewee.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
$ vagrant basebox templates
The following templates are available:
vagrant basebox define '<boxname>' 'CentOS-4.8-i386'
vagrant basebox define '<boxname>' 'CentOS-5.5-i386-netboot'
vagrant basebox define '<boxname>' 'CentOS-5.5-x86_64-netboot'
vagrant basebox define '<boxname>' 'CentOS-5.6-i386'
vagrant basebox define '<boxname>' 'CentOS-5.6-i386-netboot'
...
$ vagrant basebox define 'CentOS-6.2-x86_64-minimal'  'CentOS-6.2-x86_64-minimal'

  *customizing definitions*

$ vagrant basebox build CentOS-6.2-x86_64-minimal
$ vagrant basebox export CentOS-6.2-x86_64-minimal
$ vagrant box add CentOS-6.2-x86_64-minimal CentOS-6.2-x86_64-minimal.box

create and connect to VM.

1
2
3
$ vagrant init CentOS-6.2-x86_64-minimal
$ vagrant up
$ vagrant ssh

no-provision

1
vagrant up --no-provision

debugging

verbose logging.

1
VAGRANT_LOG=INFO vagrant up

check console with gui mode.

1
config.vm.boot_mode = :gui

default account

id/pass

1
2
vagrant/vagrant
root/vagrant

Erlの停止

erlangシェルを停止させるとき、q()だと停止に時間が掛かり、<Crt+G>, qだと即座に停止する。 この2つの違いを先日追いかけたので記録しておく。

q() の場合

まずはerlシェル上でq()を呼び出した場合の挙動。

1
2
3
Eshell V5.9.2  (abort with ^G)
1> q().
ok

マニュアルによるとこれはshell_default:q/0を呼び出している。

lib/stdlib/src/shell_default.erl
1
q()_____-> c:q().

最終的にinit:stop/0

stdlib/src/c.erl
1
2
q() ->
    init:stop().

init:stop/0はコードが負えなかったので、man initを読む。すると

All applications are taken down smoothly, all code is unloaded, and all ports are closed before the system terminates.

となっており、アプリケーションを正式な手順で停止させる雰囲気。

追記: たぶんerts/preloaded/src/init.erlから更に追える。

<Crl+G>, q の場合

次にCrl+G, qの場合。(JCLモードでq)

stdlib/src/c.erl
1
2
3
4
Eshell V5.9.2  (abort with ^G)
1>
User switch command
 --> q

User switch commandでgrepするとuser_drv.erlが使われている様子。

lib/kernel/src/user_drv.erl
1
2
3
4
5
6
7
8
switch_cmd({ok,[{atom,_,q}],_}, Iport, Oport, Gr) ->
    case erlang:system_info(break_ignored) of
    true ->_________________% noop
        io_request({put_chars,unicode,"Unknown command\n"}, Iport, Oport),
        switch_loop(Iport, Oport, Gr);
    false ->
        halt()
    end;

man erlangより

For integer Status the Erlang runtime system closes all ports and allows async threads to finish their operations before exiting.

まとめ

  • q() はapplicationの停止、コードのunloadをした上でruntimeを停止
  • <Crl+G>, qは単にruntimeを停止

諸々調べた後でマニュアルに停止方法が纏めてあることに気づいたが、 なぜかq()が紹介されていなかった。

4.4 How do I quit the Erlang shell?
To shut a system down cleanly, use init:stop().
Some quick ways are evaluating halt(). or Control+.
Control+C and Control+G give you access to menus.

そういえばerts/preloadedにはerlang, prim_inet, initモジュール等が入っているので追っかけてみると楽しいかもしれない。

PHP環境メモ

PHPを触ることになったので環境設定をメモ。

とりあえずは phpenv, php-build, composer, phpunit をチョイス。

PHP

install phpenv

1
2
3
4
curl https://raw.github.com/CHH/phpenv/master/bin/phpenv-install.sh | sh
echo export PATH = ~/.phpenv/bin:$PATH >> ~/.zshrc
echo eval "$(phpenv init - zsh)" ~/.zshrc
source ~/.zshrc

install php-build

1
2
3
4
5
mkdir -p ~/.phpenv/plugins
cd ~/.phpenv/plugins
git clone git://github.com/CHH/php-build.git
curl https://raw.github.com/hnw/php-build/plugin-to-chh-phpenv/bin/rbenv-install > php-build/bin/rbenv-install
chmod 755 php-build/bin/rbenv-install

install php

1
2
brew install libjpeg re2c mcrypt
phpenv install 5.4.8

Composer (Package manager)

install composer

1
curl -s https://getcomposer.org/installer | php && mv -v composer.phar /usr/local/bin/composer

create composer.json

1
2
3
4
5
6
7
8
9
10
11
{
    "config": {
        "bin-dir": "."
    },
    "require": {
        "xxx/xxxx": "2.*"
    },
    "require-dev": {
        "phpunit/phpunit": "3.7.*",
    }
}

install dependencies(with require-dev)

1
2
3
composer install --dev
        or
composer update --dev

PHPUnit

configuration file.

create phpunit.xml.dist

1
2
3
4
5
6
7
8
9
10
11
12
<phpunit
    bootstrap="vendor/autoload.php"
    processIsolation="false"
    verbose="true"
    strict="false"
    colors="true">
    <testsuites>
        <testsuite name="PHPUnit">
            <directory>test</directory>
        </testsuite>
    </testsuites>
</phpunit>

run test

1
./phpunit

参考

Installing Http trace on CentOS

コピペ用

1
2
3
4
wget http://nodejs.tchol.org/repocfg/el/nodejs-stable-release.noarch.rpm
yum localinstall --nogpgcheck -y nodejs-stable-release.noarch.rpm
yum install -y nodejs-compat-symlinks npm libpcap-devel
npm -g install http_trace

Octopressを別マシンでセットアップ

ご覧のとおり、このブログはOctopressを使っています。 最近、新しいマシンでブログを書こうとしたら、rake deployが上手く動かなかったよという話。 git cloneしただけではダメみたいです。

ちなみにOctopressのverisionは2.0

手順

  1. github上でデフォルトのブランチをsourceにする。
  2. git clone
  3. rake setup_github_pages

要するにrake setup_github_pagesしとけという話です。

1は必須ではないと思いますが、やっとくと楽です。 これで無事デプロイできるようになりました。ってまぁ、このエントリが公開されてることがその証明なわけです。

原因はデプロイ用の_deployディレクトリがorigin/masterに紐付いてなかったこと。

Rakefileを見ればわかりますが、setup_github_pagesの役割は元のoctopressのリポジトリをどけるだけでなく、 ローカルリポジトリの_deployディレクトリをmasterブランチに関連付ける役割もありました。

まぁ、そんだけです。

※追記

どうやらmasterブランチの歴史が書き換わる(初期化される)ので、 それが嫌なら自力で_deployディレクトリをセットアップするしかなさそうですね。

退職しました

ということで退職ブームにのるわけではありませんが、9月某日で退職しました。お世話になったみなさんありがとうございました。 2010年に入社してから2年弱お世話になったことになります。入社当時、数十名だったエンジニア組織も今では規模が数倍になり、気がつけば古株のようになっていた気がします。月日が流れるのは本当に早いものです。

前職について

前職は所謂Web屋さんでして、BtoCのサービスをそれなりに長い年月運営している会社です。ここでアプリケーションのエンジニアとして働かせていただきました。在職期間のほとんどは既存システムのリニューアルに費やしました。古いシステム資源を生かしつつ、新しい仕組みを如何に構築するか、という点では非常に苦労しましたが、その中で数多くの技術的課題へ自由に楽しく取り組ませて頂きました。

職場環境としては「安心して長く働ける環境」という点に配慮された、人にとても優しい会社でした。なにか課題が見つかればあまり放置せずに改善されていた印象です。ここまで職場環境の改善が徹底されている会社も珍しいのではないかと思います。そこで働く人も非常に暖かい人が多く、人としてアレな僕を受け入れてくれたことにはただただ感謝です。

さて、そんな恵まれた環境を去るにあたって、迷いが無かったわけではありません。ですが、迷っている自分の背中を押してくれる人がいたこともあり、今一度、新しい環境で自分を追い込んでみることに決めました。

今後について

既に新しい会社で働き始めています。いままでとはちょっと違った分野、環境で働くことになるのですが、仕事を紹介して頂いた某氏、快く送り出して頂いた前職の方々に対して、恥ずかしくないように頑張りたいと思います。

Erlangのビルド

OSXへErlangをソースからいれた時のメモ。 –enable-darwin-64bitつけないと後でトラブるよ。という事でメモ。

git clone git://github.com/erlang/otp.git
cd otp
git checkout -b OTP_R15B03 OTP_R15B03
./otp_build autoconf
./otp_build configure --disable-hipe --enable-smp-support --enable-threads --enable-kernel-poll  --enable-darwin-64bit --prefix=/usr/local/erlang/OTP_R15B03
make && make install

PREFIXはお好みで。

あとは適当にPATHとか足しとく

DTrace support

--with-dynamic-trace=dtraceをconfigureのオプションに追加する。

1
./otp_build configure --with-dynamic-trace=dtrace --disable-hipe --enable-smp-support --enable-threads --enable-kernel-poll  --enable-darwin-64bit --prefix=/usr/local/erlang/OTP_R15B03

デフォルトディレクトリ以外へのHomebrewインストール

Homebrewはデフォルトで /usr/local へインストールされるが、 これを /usr/local/homebrew へインストールしたときの手順

インストール

cd /usr/local
sudo git clone git://github.com/mxcl/homebrew.git homebrew
sudo chgrp -R staff homebrew bin include lib etc var
sudo chmod -R 775 homebrew bin include lib etc var
ln -s /usr/local/homebrew/bin/brew /usr/local/bin/brew

MANPATHを通しておく(.zshenv)

export MANPATH=$MANPATH:/usr/local/homebrew/share/man

Homebrewはドキュメントにもあるように /usr/local にシンボリックリンクを作らなければならないので、 /usr/local/homebrew/bin へPATHを通してはいけない。

Everything will install into ~/Developer, but your brew command is still in the path. NOTE that Homebrew will still need to create symlinks into /usr/local or nothing will work! But the actual files are installed to ~/Developer/Cellar.

デフォルトインストール時(/usr/local)のアンインストール手順

アンインストールの手段が提供されていないので、個別にインストールファイルを削除する。

gistにアンインストール用のスクリプトが公開されているので、これを使うと便利。

https://gist.github.com/1173223