A Log In The Life.

May The Code Be With You !

Tick Tuples の Clojure DSL での書き方

どうも、Storm 初心者のまさおりです。Storm のトポロジーを書いていて「10秒間隔で Bolt からデータ流したいなー」とか思ったものの、よくわからなかったので調べてみましたー。

Tick Tuples

It’s common to require a bolt to “do something” at a fixed interval, like flush writes to a database. Many people have been using variants of a ClockSpout to send these ticks. The problem with a ClockSpout is that you can’t internalize the need for ticks within your bolt, so if you forget to set up your bolt correctly within your topology it won’t work correctly. 0.8.0 introduces a new “tick tuple” config that lets you specify the frequency at which you want to receive tick tuples via the “topology.tick.tuple.freq.secs” component- specific config, and then your bolt will receive a tuple from the system component and tick stream at that frequency.

Tick Tuples in Clojure DSL

フムフム、Tick Tuple 受け取る側で “topology.tick.tuple.freq.secs” を config に設定すればいいのね。

で、それ Clojure DSL でどうやって書くの?と探すと、ML に流れてました。

ML によると defbolt のオプションに :conf {"topology.tick.tuple.freq.secs", 2} みたいに渡せば良さそうです。

(defbolt mybolt ["myfield"] {:prepare true :conf {"topology.tick.tuple.freq.secs", 2}} [conf tuple collector]

Tick?

実際に使うには 『Tick Tuple が来たときだけ、次の Bolt に Tuple 渡す』みたいな処理も必要で、受け取った Tuple が Tick Tuple が判別する必要があります。

(defn tick? [tuple]
  (= (.getSourceStreamId tuple) (Constants/SYSTEM_TICK_STREAM_ID)))

※ これデフォルトで用意してありそうな気がするんですよー。ないんですかねー?詳しい方教えて下さい:D

Example

まとめるとこんな感じです。

(ns example.bolts
  (:require [backtype.storm [clojure :refer [emit-bolt! defbolt ack! bolt]]])
  (:import [backtype.storm Constants]))

(defn tick? [tuple]
  (= (.getSourceStreamId tuple) (Constants/SYSTEM_TICK_STREAM_ID)))

(defbolt tick-tack-bolt ["tack"]  {:prepare true :conf {"topology.tick.tuple.freq.secs", 10}} [conf tuple collector]
  (let [tack (atom 0)]
    (bolt
      (execute [tuple]
                    (if (tick?  tuple)
                      (do
                        (emit-bolt! collector [@tack] :anchor tuple)
                        (reset! tack 0))
                      (do
                         (swap! tack inc)
                         (ack! collector tuple)))))))
  • tupletick tuple の場合だけ emit-bolt! して、そうじゃない場合は単に ack! してるだけですー

その他

翻訳環境 2013 Later

はじめに

Apache Traffic Server のドキュメントの日本語訳をはじめました。

( Apache Traffic Server 4.1.x documentation 詳しくはそのうち:p )

手元の翻訳環境をとりあえず整えたので、メモ

各種ツール

Sphinx

翻訳もとのドキュメントが Sphinx なので、手元でもビルドできるように Sphinx を入れました。

$ pip install sphinx

sphinx-intl

翻訳のフローは Sphinx の国際化のドキュメントに従うので、sphinx-intl もいれました。

$ pip install sphinx

gettext

Sphinx の国際化のドキュメントにもあるとおり、 gettext の msgfmg コマンドが必要なので、gettext を Homebrew 経由で入れました。

$ brew install gettext

Emacs po-mode

gettext utilities のなかに emacs 用の po-mode があっので、これを使うことにしました。

Homebrew 経由で gettext-0.18.3.1 を入れたので、次のディレクトリに入っていました。 (Melpa とかに置いてあると管理しやすいんですが、見当たらなかったです。)

/usr/local/Cellar/gettext/0.18.3.1/share/emacs/site-lisp

とりあえず、直接 load するようにしてしまいました。

  • init.el
(load "/usr/local/Cellar/gettext/0.18.3.1/share/emacs/site-lisp/po-compat.el")
(load "/usr/local/Cellar/gettext/0.18.3.1/share/emacs/site-lisp/po-mode.el")
(load "/usr/local/Cellar/gettext/0.18.3.1/share/emacs/site-lisp/start-po.el")

使いこなすまでにはちょっと時間がかかりそうですが、とりあえずは n, u, C-j, RET, C-c C-c を覚えておけばなんとかなりそうです。

Emacs po-wrap

po-mode 入れるといろいろ捗るんですが、うまいこと折り返してくれないので、きれいに整形方法を探していたら、Emacs Wiki に書いてありました。

この po-wrap は gettext の msgcat コマンドを使います。これは gettext の bin 下にあるので、パスを通しておきます。 例:

  • .zprofile
export PATH=$PATH:/usr/local/Cellar/gettext/0.18.3.1/bin

一通り翻訳して M-x po-mode 実行するといい感じに整形してくれます。

おわりに

とりあえずはこんな感じで翻訳はじめました。各種ツールのインストール方法はいろりろあると思うので、お好みでどうぞ。 もっといい感じの環境があれば教えてくださいー:D

Let's translate!

Try Storm-Starter

Storm

Storm が気になります!ってわけで、オライリーGetting Started with Storm を斜め読みして storm-starter を試してみました。

Getting Started With Storm

Getting Started With Storm

  • 作者: Jonathan Leibiusky,Gabriel Eisbruch,Dario Simonassi
  • 出版社/メーカー: Oreilly & Associates Inc
  • 発売日: 2012/09/17
  • メディア: ペーパーバック
  • この商品を含むブログを見る

What is storm-starter?

その名前の通り、Storm をお試しできるプロジェクトです。

必要な環境は Maven もしくは Leiningen のようです。僕は Leiningen の方が慣れてるので、 Lieiningen を選択しました。 (leiningen についてはこちら)

JavaClojure のコードが用意してありますが、もちろん Clojure 一択です!

Try storm-starter with Leiningen and Clojure

Install

storm-starter は leiningen プロジェクトになっているので、ダウンロードしてきて lein コマンドから依存関係を解決するだけで、動きます。楽チンですね!

$ git clone git@github.com:nathanmarz/storm-starter.git
$ cd storm-starter
$ lein deps
$ lein compile
$ lein run -m storm.starter.clj.word-count

実際に実行してみると、ローカルモードでも並列実行されていることがわかります。

Word Count Program

実行すると文章の単語を数えるプログラムが動きます。大まかに言うと次のような流れになっています。

  1. Spout から文が流れる
  2. Bolt で単語に分割する
  3. Bolt で単語毎に数える

実行されるコードは下記です。

Clojure DSL によって、Topology, Spout, Bolt が簡潔に記述されているのがわかりますね。 Clojure すごい。

短いコードなので少し見ていきましょう。

main

まずは main 関数です。さっきは引数なしで実行したので、run-local! 関数が実行されています。

(引数をつけて実行すると、それがトポロジー名になってクラスターに送られて、分散モードで実行されますが、分散モードの話はそのうち。)

(defn -main
  ([]
   (run-local!))
  ([name]
   (submit-topology! name)))

main 関数を追うと、トポロジーを作って、10000 ミリ秒実行して止まるようになっています。 その間に Spout から流れた文の単語を数えます。

mk-topology

トポロジーの定義です。

(defn mk-topology []

  (topology
   {"1" (spout-spec sentence-spout)
    "2" (spout-spec (sentence-spout-parameterized
                     ["the cat jumped over the door"
                      "greetings from a faraway land"])
                     :p 2)}
   {"3" (bolt-spec {"1" :shuffle "2" :shuffle}
                   split-sentence
                   :p 5)
    "4" (bolt-spec {"3" ["word"]}
                   word-count
                   :p 6)}))
  • “1”と"2" は Spout
  • "3"と"4"は Bolt
  • “2” が 2 並列、"3" が 5並列、"4" が 6並列
  • "1","2" から "3" に渡して、単語ごとに "4" に渡す
  • “4” は渡された単語ごとにインクリメントする

たぶんこんな感じです:p

実際に使うとなると、 "1","2" の Spout が Storm の外からデータ入れないといけないのと、"4"" から結果を集めてあげる必要がありそうです。

Multilang protocol

Storm は JVM 系の言語を使うこともできますが、JVMではない言語も使うことができます。 Storm-Starter には先ほどの Word-Count プログラムの split-sentence Bolt の Ruby版と Python版が用意してあります。 こうち Python 版を試してみました。

shell-bolt-spec

Topology を定義する際に、split-sentence Bolt を Python で書かれた Bolt を使うように定義します。

(defn mk-topology []

  (topology
   {"1" (spout-spec sentence-spout)
    "2" (spout-spec (sentence-spout-parameterized
                     ["the cat jumped over the door"
                      "greetings from a faraway land"])
                     :p 2)}
   {"3" (shell-bolt-spec {"1" :shuffle "2" :shuffle}
                    "python"
                 "splitsentence.py"
                 ["word"]
                 :p 5)
    "4" (bolt-spec {"3" ["word"]}
                   word-count
                   :p 6)}))

実行は先ほどと同じです。

$ lein compile
$ lein run -m storm.starter.clj.word-count

プロセスを監視していると、python のプロセスが 5つ立ち上がることがわかると思います。 Non-JVM な言語と Storm の通信は標準入出力でやりとりされるようです。

まとめ

動くとこまでは結構あっさり行きました。あとは分散モードの環境を作って、ローカルでテストしたトポロジーをデプロイして試していくと楽しそうです。 Storm に乗っけてしまえば、スケールしていきそうなので、アルゴリズムをどうトポロジーに落とし込んで行くかがポイントになりそうです。

あと、Clojure いいですね!Clojure!

Appendix

Install Clojure with Leiningen

install clojure with leinigen

Clojure の環境の作り方は 2 つあります。

  1. Clojure の jar ファイルをダウンロードして起動スクリプトを書く
  2. Leinigen で clojure 本体もインストールする

1 については以前書きました。 Install Clojure with Homebrew 今日は 2 についてです。

2つの方法の比較

clojure の repl を立ち上げるだけならば、1 でいいのですが、clojure-contrib やその他のいろいろなライブラリを利用しようとすると、少し面倒です。

一方、leinigen を利用した場合、project.clj に必要なライブラリを書くと、lein deps コマンドで必要なライブラリをダウンロードしてくれます。

他の言語のパッケージマネージャーと違い、clojure 自体さえもダウンロードしてくれます。

これは、プロジェクト毎に依存関係が独立しているので、プロジェクト毎に違うバージョンを用いたい場合などには便利です。

Install Leinigen

多くのパッケージマネージャーで用意してあるので、お使いのパッケージマネージャーでも利用できるでしょう。

Homebrew の場合はこんな感じです。

$ brew install leiningen

もしくは、インストールスクリプトをダウンロードしてインストールします。

$ wget https://raw.github.com/technomancy/leiningen/preview/bin/lein
$ chmod +x lein
$ ./lein self-install

Leinigen の使い方

チュートリアルがあるので、斜め読みするとだいたい把握できると思います。

Leinigen のコマンドは lein です。サブコマンドがいろいろとありますが、はじめに押さえておくべきなのはnew deps repl あたりでしょう。

プロジェクトをはじめるときには new サブコマンドを使います。

$ lein new my-project
Created new project in: /path/to/the/dir/my-project
Look over project.clj and start coding in my_project/core.clj

こんな感じでファイルが自動で作られます。

$ tree my-project
my-project
├── README
├── project.clj
├── src
│   └── my_project
│       └── core.clj
└── test
    └── my_project
        └── test
            └── core.clj

5 directories, 4 files

project.clj に必要なパッケージを書いて、lein deps を実行すると指定されたバージョンのパッケージがインストールされます。

たとえば、JSON encoder の Cheshire をいれてみましょう。

(defproject my-project "1.0.0-SNAPSHOT"
  :description "FIXME: write description"
  :dependencies [[org.clojure/clojure "1.4.0"]
                 [cheshire "4.0.4"]])

:dependencies に書くだけです。

$ cd my-project
$ lein deps
…
$ tree ./
./
├── README
├── lib
│   ├── cheshire-4.0.4.jar
│   ├── clojure-1.4.0.jar
│   ├── jackson-core-2.1.0.jar
│   └── jackson-dataformat-smile-2.1.0.jar
├── project.clj
├── src
│   └── my_project
│       └── core.clj
└── test
    └── my_project
        └── test
            └── core.clj

6 directories, 8 files

こんな感じで my-project/lib 下に必要な jarファイルがインストールされます。 ここで repl を立ち上げるとインストールしたパッケージが利用できます。

$ lein repl
REPL started; server listening on localhost port 32435
user=> (ns myns (:use [cheshire.core]))

とりあえず、インストールと簡単な使い方はこんなところです。

Appendix

Markdown

『ドキュメントは大事です』
えぇ、そうです。正しいです。きっと。
とは言うものの、文章を書くのは面倒だし、読みやすくするためにいろいろ書かなきゃいけないけど、ナントカ記法よくわっかんない...でも
『ドキュメントは大事です』
そうなんです。
というわけで、はななBlog も Markdown で書けると幸せになれそうですね。
いくつもの記法を使いこなすのもすばらしいですが、できれば少ない方が幸せになれそうです。

下書きをみていたら、昔こんなこと書いてました。(なぜか下書きのまま...)

はてな記法覚えるのがイヤで、はてなBlog 放置してたんですが、Markdown 対応したので再開します。

とりあえず、Posterous に書いてたエントリーを移しました。2つしかないけど。

結局、Markdown で書けようが書けまいが続かないんですけどね。

Writing Gauche Extension Module in C

はじめに

Gauche の拡張を C で書く際のメモです。 そもそもは『C のライブラリを Gauche Extention としてラップしたい』という動機があります。 その際に、あちこち躓いたので、整理を兼ねてまとめてみました。 ですので、いろいろと間違えていることもあると思います。どうぞご指摘ください:-)

環境

このメモは以下の環境を想定しています。API は将来変更になるかもしれません。 (おそらく変更されるでしょう)

Gauche/C API のドキュメント

いまのところ、Gauche 自体のドキュメントには拡張をの書き方や内部のインターフェイスに関するドキュメントはありません。おそらく、今後変わっていくためでしょう。 それでも、手がかりはいろいろとあります。

まず、次のブログエントリーがわかりやすかったです。

つぎに、FFI については practical-scheme にメモがあります。

さらに、Gauche のソースの examples 下の spigot と mqueue-cpp が参考になります。

  • spigot: C で書かれた拡張

    Gauche-0.9.3/example/spigot

  • mqueue-cpp: C++ で書かれた拡張

    Gauche-0.9.3/example/mqueue-cpp

スケルトン

基本的には gauche-package でスケルトンを作っていくことになります。 詳しくは "gauche-packageを利用したgaucheの拡張モジュールの作り方"" にある通りなので、ここでは手順だけメモしておきます。

$ gauche-package generate example
$ cd example
$ ls
DIST            Makefile.in     configure.ac    example.c       example.h       example.scm     examplelib.stub test.scm
$ ./DIST gen
$ ls
DIST            Makefile.in     autom4te.cache  configure       configure.ac    example.c       example.h       example.scm     examplelib.stub test.scm
$ ./configure
$ make
$ make check

これで一通りの Gauche Extention Module ができました。

ScmForeignPointer

Gauche:FFI にあるように C の構造体をラップするには ScmForeignPointer マクロを使います。

ScmForeignPointerオブジェクトは、(staticにではなく)初期化時にScmClass構造体を作るんで、 ヘッダで宣言しておくのはクラス構造体へのポインタだけでいい。 Cソース側では初期化ルーチンで Scm_MakeForeignPointerClass を呼ぶと 新しいScmClassへのポインタが帰ってくるのでそれを保存しとく。 SCM_CLASS_DECLとかは不要。boxerについてもScm_MakeForeignPointerが有る程度面倒を見てくれる。


Gauche:FFI より引用

構造体のラップ

実際に C の構造体をラップするときにはおそらく次のようなものが必要です。

  • C の構造体に対応する ScmClass と type
  • xx_print と xx_cleanup 関数
  • モジュールの初期化の設定

(※ 僕が勝手にこうするんじゃないかな?と推測したものなので、過不足があるかもしれません。)

次のように example 構造体があるとします。これを Gauche で扱えるようにラップしていきます。 説明は適当です:p

struct example {
  int x;
};

マクロ定義

ExampleClassstruct example を扱うマクロを定義します。 これらは下の examplelib.stub 内の define-type で使います。

example.h

extern ScmClass *ExampleClass;

#define EXAMPLE_P(obj)      SCM_XTYPEP (obj, ExampleClass)
#define EXAMPLE_UNBOX(obj)  SCM_FOREIGN_POINTER_REF (struct example*, obj)
#define EXAMPLE_BOX(obj)    Scm_MakeForeignPointer (ExampleClass, obj)

Gauche 内での表示とメモリ解放のための関数

たぶん、そんな感じの関数だと思います。これらの関数は下の Scm_MakeForeignPointerClass の第3引数と第4引数として渡します。

example.c

ScmClass *ExampleClass;

static void example_class_print (ScmObj obj, ScmPort* out, ScmWriteContext* ctx)
{
  struct example* e = EXAMPLE_UNBOX(obj);
  Scm_Printf(out, "#<example-class \"%p\">", e);
}

static void example_class_cleanup (ScmObj obj)
{
  struct example* e = EXAMPLE_UNBOX(obj);
  free(e);
}

タイプの定義

struct example*に対応する Gauche内の型(?)を定義します。

examplelib.stub

(define-type <example-class> "struct example*" "example class" "EXAMPLE_P" "EXAMPLE_UNBOX" "EXAMPLE_BOX")

初期化

example.c 内で 初期化のための関数Scm_Init_exampleというのがあります。 このなかに次のように ExampleClassGauche が扱うために必要なものを教えてあげます。

example.c

ExampleClass =
  Scm_MakeForeignPointerClass(mod,
                      "<example-class>",
                              example_class_print,
                              example_class_cleanup,
                              SCM_FOREIGN_POINTER_KEEP_IDENTITY | SCM_FOREIGN_POINTER_MAP_NULL);

まとめ

これで C の構造体をラップできたはずです。 あとはこれを Gauche から扱うための関数を書いてあげればいいでしょう:D

とは書いたものの、各関数やマクロが何をしているかなんとなくしかわかってません:p

本当にこれでメモリリークしてないのか?とか気になります...がひとまずこんなところで:p

Gist

これらをまとめたものは Gist にあげておきました。

https://gist.github.com/2730090

Install Clojure with Homebrew

homebrew で clojure の環境を整えます。

clojure and clojure-contrib

$ brew search clojure
clojure clojure-contrib

homebrew で clojure を探すと、clojureclojure-contrib が見つかります。

clojure は cojure 本体です。

http://clojure.org/downloads

clojure-contrib はユーザーコミュニティによるライブラリ集といったものです。

http://richhickey.github.com/clojure-contrib/index.html

Install

$ brew install clojure clojure-contrib

.zprofile などに次のように書いて、パスを通します。

export CLASSPATH=$CLASSPATH:/usr/local/Cellar/clojure-contrib/1.2.0/clojure-contrib.jar

これで、clj で repl が立ち上がります。

$ clj
Clojure 1.4.0
user=>

この clj コマンドですが、実態は以下のように shellscript です。

$ cat /usr/local/bin/clj
#!/bin/sh
# Clojure wrapper script.
# With no arguments runs Clojure's REPL.

# Put the Clojure jar from the cellar and the current folder in the classpath.
CLOJURE=$CLASSPATH:/usr/local/Cellar/clojure/1.4.0/clojure-1.4.0.jar:${PWD}

if [ "$#" -eq 0 ]; then
    java -cp "$CLOJURE" clojure.main --repl
else
    java -cp "$CLOJURE" clojure.main "$@"
fi

rlwrap

lisp の処理系などでよく使いますが、rlwrap を噛ましてあげると history の参照など使いやすくなります。 brew install rlwrap

alias clj='rlwrap clj'