Quantcast
Channel: あらびき日記
Viewing all 163 articles
Browse latest View live

[R][ESS][Emacs]ESS で快適 R ライフ 〜設定編〜(2012年度版)

$
0
0

最近 ESS のバージョンが 5.14 から一気に 12.03, 12.04 と上がったこともあり、設定を1から見直してみました。

今回は ESS を(私が)快適に使うための諸々のインストールと設定についてご紹介します。Emacs のバージョンは23.4.1です。


ESS のインストール

以下の手順で /usr/local 以下に ESS がインストールされます。

インストール先を変更したい場合は ess-12.04/Makeconf の DESTDIR の値を書き換えてください。

以降の作業は DESTDIR がデフォルトの /usr/local であることを前提としています。

$ wget http://ess.r-project.org/downloads/ess/ess-12.04.tgz
$ tar xvf ess-12.04.tgz
$ cd ess-12.04
$ make install

このままだと eldoc がバグってるので拙作のパッチを当てます。

$ wget https://raw.github.com/gist/2391663/ess-r-d.el.patch
$ patch /usr/local/share/emacs/site-lisp/ess/ess-r-d.el ess-r-d.el.patch
$ cd /usr/local/share/emacs/site-lisp/ess
$ emacs -batch -no-site-file -no-init-file -l ./ess-comp.el -f batch-byte-compile ./ess-r-d.el


auto-install のインストール

R を利用する上では必要ありませんが、快適な ESS 環境を構築するためにいろいろインストールする必要があるのであると便利です。

以降、インストールした *.el ファイルは ~/.emacs.d/site-lisp に保存することにします。

$ mkdir -p ~/.emacs.d/site-lisp
$ wget http://www.emacswiki.org/emacs/download/auto-install.el -O ~/.emacs.d/site-lisp/auto-install.el
$ emacs --batch -f batch-byte-compile ~/.emacs.d/site-lisp/auto-install.el

auto-complete のインストール

auto-complete をインストールすることで Emacs に IDE ライクな補完機能を導入できます*1

$ wget http://cx4a.org/pub/auto-complete/auto-complete-1.3.1.tar.bz2
$ tar xvfz auto-complete-1.3.1.tar.bz2
$ cd auto-complete-1.3.1
$ make
$ make install DIR=~/.emacs.d/site-lisp

anything のインストール

anything をインストールすれば Emacs の作業効率は劇的に改善されるでしょう。

まずは、次の内容で ~/.emacs.d/init.el を作成します。

(setq my-site-lisp (expand-file-name "~/.emacs.d/site-lisp"))
(add-to-list 'load-path my-site-lisp)
(require 'auto-install nil t)
(setq auto-install-directory (expand-file-name my-site-lisp))

次の作業は Emacs で作業します。

M-x auto-install-batch [RET] anything

1〜2分ぐらいで画面が変わって anything のコードが表示されるので、C-c C-c を押してください。こうすることでコンパイルされます。

15個程度のファイルがダウンロードされるので、全てのファイルに対して C-c C-c を押すことになります。


ESS の機能拡張

myuhe さん作の anything-R と ess-R-object-popup をインストールします。

anything-R をインストールすることで手軽に関数や値の確認ができるようになります。ess-R-object-popup は即座にオブジェクトの内容を確認するのに便利です。

anything 同様、Emacs で作業します。

M-x auto-install-from-emacswiki [RET] anything-R.el [RET] C-c C-c
M-x auto-install-from-gist [RET] 318365 [RET] C-c C-c ess-R-object-popup.el

anything-R はとても便利なんですが、そのままだと C-c r を押した時に最大200の候補を表示するのでちょっと重いです。

anything-for-R-list の値を変更して source を1つにするのもありですが、カスタマイズしやすいようにパッチを当てます。

$ wget https://raw.github.com/gist/2391813/anything-R.el.patch
$ patch ~/.emacs.d/site-lisp/anything-R.el anything-R.el.patch
$ emacs -batch -L ~/.emacs.d/site-lisp -f batch-byte-compile ~/.emacs.d/site-lisp/anything-R.el

.emacs の内容

自分の利用形態に合わせていろいろ検討した結果、次のような内容にしました。

Emacs 23.4 & ESS 12.04 用です。後方互換性はある程度保証されると思いますが、逆に ESS 5.14 とかだと所望の挙動になりません。


R と ESS の設定に関しては完全に自分好みのものにしていますが、基本的な設定の部分については当たり障りのない程度にとどめています。

できるだけ詳細にコメントを加えたつもりなので、自分好みに変更してみてください。

なお、Emacs は普段 no window で使っているので、グラフを Emacs 上にプロットするなどといったことは想定していません。


次回は ESS(Emacs) の使い方について簡単に紹介したいと思います。



参考

最終的に myuhe さんの「これからEmacsでR使う人のための設定まとめ」とあまり変わらない内容になってしまいました・・・

*1:auto-complete も auto-install でインストールしようとしましたがうまく行きませんでした


[R][ESS][Emacs]ESS で快適 R ライフ 〜操作編〜(2012年度版)

$
0
0

前回は ESS のインストールと設定についてご紹介しました。

今回は簡単な操作方法についてご紹介します。

前回ご紹介した設定を適用していることを前提とします。init.el の内容が少し変わっているので、ご注意ください。


サンプルコード

次のようなサンプルコードを使うことにします。

# ess_sample.R
library(MASS)

compareModels <- function(model1, model2) {
    plot(data)
    abline(model1, col = "blue")
    abline(model2, col = "red")
}

set.seed(1)
x <- runif(100, max = 100)
y <- 2 * x + rnorm(x, sd = 5)
data <- data.frame(x = c(x, 150), y = c(y, 1000))

model1 <- lm(y ~ x, data)
model2 <- rlm(y ~ x, data)
compareModels(model1, model2)

Emacs を起動します。

$ emacs ess_sample.R

次のような画面になったかと思います。

f:id:a_bicky:20120422173258p:image


iESS の操作方法

まずは C-c C-y を押して *R* バッファに移動して iESS を使ってみます。

補完機能

現在のディレクトリを system("pwd") で確認しようと sys まで入力してみます。

すると次のような状態になります。

f:id:a_bicky:20120422173303p:image


sys で始まる補完候補が表示され、特定の関数で1.5秒(デフォルト設定の場合)静止するか M-h を押すと黄色い背景でその関数に対するドキュメントが表示されます。これが auto-complete の機能です。

超便利ですね!!


<down>, <up> や C-p, C-n を押すことで補完候補を選択できますし、C-s で候補を絞ることも可能です。

ここでは system が選択されている状態なので TAB や Enter を押して system の補完を完了します。


なお、ドキュメントが表示されている間は C-M-n, C-M-p でドキュメントをスクロールすることができます。

補完候補が表示されるまでに時間がかかりますが、C-M-i を押すことで即座に候補を表示することも可能です。

関数の引数を確認する(Eldoc)

先ほどの画面で "(" を入力すると次のような画面になるかと思います。

f:id:a_bicky:20120422173154p:image


引数に関する補完機能も提供されていて auto-complete が凄まじく便利なことはおわかりいただけると思うのですが、注目していただきたいのはエコーエリア(最下部)に system 関数の引数の情報が表示されていることです。

こちらは Eldoc によって提供されているもので、デフォルト引数が何なのかなど知りたい時に便利です。

関数の名前や引数の部分にキャレットを持って行くと表示されるので、補完機能とは違って入力中以外にも確認できます。


スマートコンマを利用する

スマートコンマは ESS 12 から導入された機能です。

プロンプト(">")の直後にカンマを入力すると次のような画面になります。

f:id:a_bicky:20120422173256p:image


左の画面は anything によるもので、コマンドを選択する際にはスペース区切りで AND 検索をしたり、正規表現を使ったりすることができます。


スマートコンマにはいくつかのコマンドが用意されており、例えば change-directory が選択されている状態で Enter を押すと、どこのディレクトリに移動するか聞かれるので、移動先のディレクトリを入力して Enter を押します。

もう一度 system("pwd") を実行すると指定したディレクトリに移動していることが確認できると思います。

なお、change-directory は作業ディレクトリとカレントディレクトリ両方を変更します。getwd() を実行すると、同時に作業ディレクトリも変わっていることが確認できます。


ちなみにスマートコンマを利用しないでも

M-x ess-change-directory

で同じことができます。


スマートコンマの他のコマンドとして、install.packages を選択すると CRAN のパッケージリストを取得して、どれをインストールするか一覧を提示してくれたり、library を選択するとどのパッケージをロードするかインストール済みのパッケージ一覧を提示してくれたりと、かなり便利です。

いろいろ試してみると良いと思います。


コマンド履歴を利用する

M-p で実行したコマンドの履歴を遡ることができます。M-n はその逆です。また、M-r でインクリメンタルサーチを行うこともできます。


rdired を利用する

rdired は dired のようなインタフェースで R のオブジェクトを確認できる機能です。

個人的に全然使わないのですが、一応紹介しておきます。


M-x ess-rdired

を実行することで *R dired* というバッファが開き、現在存在するオブジェクトの一覧が表示されます。

中身を確認したいオブジェクトにキャレットを持って行き、v を押すとオブジェクトの内容を確認することができます。

他にもオブジェクトを削除したり、プロットしたりできるので、興味のある方は *R dired* バッファで ? を押してヘルプをご確認ください。


ドキュメントを確認する

C-c C-v で関数のドキュメントを確認することができます。

例えば seq.Date のドキュメントを確認したい場合は C-c C-v seq [Space] da [Enter] を押すだけで確認できます。

f:id:a_bicky:20120422173156p:image


anything-R を導入しているので、C-c r でも同じようなことができます。

C-c r と C-c C-v の違いは、C-c C-v はヘルプを参照することしかできませんが、C-c r だとヘルプの確認以外のこともできることです。

試しに C-c C-v seq [SPACE] da [TAB] を押すと次のような画面になります。

f:id:a_bicky:20120422173157p:image


ここで help を選択すればドキュメントが表示されますし、view source を選択すると関数の定義を確認することができます。



ヘルプバッファでの操作

ドキュメントのバッファ(ヘルプバッファ)は特殊なキーが割り当てられているので覚えておくと何かと便利です。

比較的使いそうなものだけ以下に示します。

基本操作
キー説明
?ヘルプの表示
q現在のバッファを閉じる(kill はしない)
x現在のバッファを kill して R のバッファに戻る

移動系のキー
キー説明
n次のセクションへ移動
p前のセクションへ移動
s dDescription へ移動
s DDetails へ移動
s uUsage へ移動
s aArguments へ移動
s vValues へ移動
s sSee Also へ移動
s rReferences へ移動
s eExample へ移動

Exampleの実行
キー説明
l現在の行を実行して次の行へ移動
rリージョンを実行する


スクリプトの編集

iESS の操作でご紹介した機能は、スマートコンマや履歴機能以外であればスクリプトの編集においても利用可能です。

スクリプトを実行する

現在編集中のスクリプトの一部分を実行するコマンドが用意されています。

個人的によく使うものは次のとおりです。

コマンド説明
C-c C-j現在の行を実行
C-c C-f現在の関数を定義
C-c C-r現在のリージョンを実行
C-c C-b現在のバッファを実行

C-c M-j、C-c M-f のように後のキーの Ctrl を Meta に変えるとコマンド実行後に R のバッファに移動します。



データや関数の定義にジャンプする(imenu)

ESS における imenu は少し特殊です。


M-x imenu

を実行すると次のように Data と Functions が用意されていることが確認できます。

f:id:a_bicky:20120422173200p:image



Data を選択すると、スクリプト内で data.frame や as.data.frame によって定義されたデータの一覧が表示され、定義部分にジャンプすることができます。

f:id:a_bicky:20120422173201p:image


Functions を選択すると、スクリプト内で定義されている関数の一覧が表示され、定義部分にジャンプすることができます。

f:id:a_bicky:20120422173202p:image


その場でオブジェクトの内容を確認する(ess-R-object-popup)

C-c C-g を押すことで現在のキャレット上のオブジェクトに対する概要などを確認することができます。

次のキャプチャ画像はキャレットが library 上にある状態で C-c C-g を押した状態の画像です。

f:id:a_bicky:20120422173204p:image

ところが、キャレットが x の上にある状態で C-c C-g を押しても中身が確認できません。

これは、ess_sample.R と結び付けられている R プロセスに x というオブジェクトが存在しないからです。

x の代入までを実行した後であれば 次のようにオブジェクトの中身を確認することができます。

f:id:a_bicky:20120422173205p:image



補足

ess-R-object-popup の説明で、R のプロセスに x が定義されていない状態で C-c C-g を押しても x の中身を確認できないことを述べました。

これは auto-complete や eldoc にも言えることで、ESS では R のプロセスで何らかのコマンドを実行して、その結果を利用して便利な機能を提供しています。

例えば auto-complete にしても library(MASS) を実行する前だと rl を入力しても rlm は補完の候補として出てきません。

f:id:a_bicky:20120422173158p:image


library(MASS) を実行した後であれば補完候補に挙がります。

f:id:a_bicky:20120422173207p:image


そして ESS では .Last.value がほとんど意味を成さないことも注意が必要です。

auto-complete や eldoc で R のプロセスに対して何らかの処理を行った時点で .Last.value にはその結果が入ります。




以上、ESS の操作についてでした!

ESS をダウンロードして解凍した際にできる ess-12.04/doc/refcard/refcard.pdf はチートシートのようなものなので、これを印刷しておくと便利かもしれません。ただし一部は昔のものと互換性がなくなって使えないコマンドがありますが・・・。


また、ESS 12 から導入された ess-tracebug は開発効率をかなり向上できる可能性のある機能なのですが、個人的に微妙だなぁと思うところがあるので紹介しませんでした。もう少し良くなったら紹介するかもしれません。




ではでは快適な R ライフを!

[R]R の apply 徹底解説

$
0
0

「R で for 文使っても良いのは小学生までだよね」と R に慣れてきた人は揃って口にしますよね。*1(えっ

いわゆる apply 族と呼ばれる関数の筆頭にあるのが apply 関数なわけですが、R を勉強し始める人にとっては躓きポイントかと思います。


というわけで apply についての解説記事を書いてみました。



apply の基礎

apply の引数は次のようになっています。

> args(apply)
function (X, MARGIN, FUN, ...) 
NULL

ドキュメントには引数の意味もちゃんと書いてあるのですが、理解していないと意味不明な気がします。あ、日本語訳は適当です。

引数名説明
X行列、データフレーム、配列などのオブジェクト
MARGIN繰り返し処理においてインデックスを変化させる次元の番号(行列の場合、1が行、2が列)、または次元の名前
FUN抽出した要素に適用する関数
...FUN に渡す引数

MARGIN の説明がわかりにくいと思うので具体例を挙げてみます。

X として次のような3次元配列を使用します。

> (a <- array(1:24, c(3, 4, 2)))
, , 1

     [,1] [,2] [,3] [,4]
[1,]    1    4    7   10
[2,]    2    5    8   11
[3,]    3    6    9   12

, , 2

     [,1] [,2] [,3] [,4]
[1,]   13   16   19   22
[2,]   14   17   20   23
[3,]   15   18   21   24


X に対して、1つ目の次元のインデックスが同じ要素を全て足し合わせるコードを書くとします。

C 言語的に書くと次のようになるでしょう。

> d <- dim(a)
> sum <- numeric(d[1])  # sum という配列を初期化
> for (i in seq_len(d[1])) {
+     for (j in seq_len(d[2])) {
+         for (k in seq_len(d[3])) {
+             sum[i] <- sum[i] + a[i, j, k]
+         }
+     }
+ }
> sum
[1]  92 100 108

ただ、R では sum という関数が用意されていますし、インデックスを指定しない次元はその次元に関して全ての要素が抽出されるので、次のように書けます。

> d <- dim(a)
> sum <- numeric(d[1])  # sum という配列を初期化
> for (i in seq_len(d[1])) {
+     sum[i] <- sum[i] + sum(a[i, , ])
+ }
> sum
[1]  92 100 108

ここで注目すべきは、a の1番目の次元に対してのみインデックスを指定して抽出した要素に対して sum という関数を適用していることです。

この1番目というのが apply の MARGIN に対応し、sumFUN に対応します。

よって、apply で書くと次のようになります。

> apply(a, 1, sum)
[1]  92 100 108


もう1つ例を挙げてみます。次は、1つ目の次元と3つ目の次元のインデックスが同じ要素を足し合わせるコードを書くとします。

C 言語的に書くと次のようになります。

> d <- dim(a)
> sum <- matrix(0, d[1], d[3])  # sum という行列を初期化
> for (i in seq_len(d[1])) {
+     for (j in seq_len(d[2])) {
+         for (k in seq_len(d[3])) {
+             sum[i, k] <- sum[i, k] + a[i, j, k]
+         }
+     }
+ }
> sum
     [,1] [,2]
[1,]   22   70
[2,]   26   74
[3,]   30   78

R 的に書くと次のようになります。

> d <- dim(a)
> sum <- matrix(0, d[1], d[3])  # sum という行列を初期化
> for (i in seq_len(d[1])) {
+     for (k in seq_len(d[3])) {
+         sum[i, k] <- sum[i, k] + sum(a[i, , k])
+     }
+ }
> sum
     [,1] [,2]
[1,]   22   70
[2,]   26   74
[3,]   30   78

for 文では a の1番目と3番目の次元に対してインデックスを指定しているので、apply の MARGIN は1と3になります。関数として sum を使っているので sumFUN です。

よって、apply で書くと次のようになります。

> apply(a, c(1, 3), sum)
     [,1] [,2]
[1,]   22   70
[2,]   26   74
[3,]   30   78

とにかく覚えていただきたいのは、apply は MARGIN で指定した次元のインデックスを変化させて繰り返し処理を行うということです。

for 文を使うよりも apply を使った方がすっきり書くことができるので、なんか R を使いこなしてる感がありますよね!


ただ、apply は面倒な処理をブラックボックス的にやってくれるのですっきり書くことができますが、その分最適な形で書いた for 文よりは遅くなります。

apply を使うことで高速化に繋がると勘違いしている人もチラホラいるので要注意です。*2と、昔の自分に言ってやりたいです。



練習問題

適当に練習問題を作ってみました。理解の助けになれば幸いです。解答は一番最後に載せておきました。

練習問題1

次のコードは2つ目と3つ目の次元のインデックスが同じ要素の最小値と最大値を算出するものです。

このコードを apply を使って書き換えてみてください。

a <- array(1:24, c(3, 4, 2))
d <- dim(a)
ans <- array(0, c(2, 4, 2))
for (j in seq_len(d[2])) {
    for (k in seq_len(d[3])) {
        ans[, j, k] <- range(a[, j, k])
    }
}

練習問題2

次のコードは1つ目の次元のインデックスが同じ要素(行列)の列ごとの総和と行ごとの総和を算出するものです。

このコードを for 文を使って書き換えてみてください。

a <- array(1:24, c(3, 4, 2))
apply(a, 1, function(x) {
    return(list(colsums = colSums(x), rowsums = rowSums(x)))
})


apply を読み解く

apply の挙動について理解するには、関数の定義を確認するのが一番手っ取り早いでしょう。


本質的な処理とは関係ないなぁと思った処理を省いた上で少し書き換えると apply 関数は次のような関数です。

apply <- function (X, MARGIN, FUN, ...) {
    # 諸々初期化
    dl <- length(dim(X))
    d <- dim(X)
    ds <- seq_len(dl)
    s.call <- ds[-MARGIN]
    s.ans <- ds[MARGIN]
    d.call <- d[-MARGIN]
    d.ans <- d[MARGIN]

    # イメージとしては for (i in seq_len(dim(X)[MARGIN[1]]) for (i in seq_len(dim(X)[MARGIN[2]]) ...
    # なので prod(dim(X)[MARGIN])、つまり d2 だけ繰り返しが発生する
    d2 <- prod(d.ans)
    # 繰り返し処理の必要なものは後の次元に移動させる
    newX <- aperm(X, c(s.call, s.ans))
    # newX を2次元に変換する。これによって各列の要素に対して FUN を適用することになる
    dim(newX) <- c(prod(d.call), d2)

    # FUN の結果は ans (長さ d2 の list) に格納する
    ans <- vector("list", d2)
    if (length(d.call) < 2L) {
        # 指定されてない次元が1以下の場合の処理。つまり抽出した要素がベクトルになる場合
        for (i in 1L:d2) {
            # 列に対して FUN を適用
            tmp <- FUN(newX[, i], ...)
            if (!is.null(tmp))
                ans[[i]] <- tmp
        }
    } else {
        # 指定されてない次元が2以上の場合の処理。つまり抽出した要素が行列や配列になる場合
        for (i in 1L:d2) {
            # 列の要素を行列や配列に戻した上で FUN を適用
            tmp <- FUN(array(newX[, i], d.call), ...)
            if (!is.null(tmp))
                ans[[i]] <- tmp
        }
    }
    # FUN の返す値は list-like な値かどうか?
    ans.list <- is.recursive(ans[[1L]])
    l.ans <- length(ans[[1L]])
    # FUN の結果が list-like でも長さが同じだったら ans.list は FALSE にする
    if (!ans.list)
        ans.list <- any(unlist(lapply(ans, length)) != l.ans)
    if (ans.list) {
        len.a <- d2
    } else {
        # ans を書き換えるのでメモリのコピーが発生
        ans <- unlist(ans, recursive = FALSE)
        len.a <- length(ans)
    }

    # FUN の返す値が list-like だったり長さ1のベクトルを要素とするリストだった場合
    if (len.a == d2) {
        if (length(MARGIN) == 1L) {
            return(ans)
        } else {
            # array に変換するのでメモリのコピーが発生
            return(array(ans, d.ans))
        }
    }

    # FUN の返す値が長さ一定のベクトル(行列や配列はベクトルになる)の場合
    if (len.a && len.a %% d2 == 0L) {
        # array に変換するのでメモリのコピーが発生
        return(array(ans, c(len.a %/% d2, d.ans)))
    }

    # どれにも属さない場合は list のまま(ここには到達しない気がする・・・)
    return(ans)
}

ヘタな書き方をするよりも高速になるポイントは ans という list を初期化した上で FUN の結果を格納しているところです。

繰り返し数は普通に for 文を書く場合と変わりません。


例えば次のような例はその差が顕著です。

> system.time({ ans <- c(); for(i in 1:100000) ans[i] <- i })
   user  system elapsed 
 19.708   1.295  21.010 
> system.time({ ans <- vector("list", 100000); for(i in 1:100000) ans[[i]] <- i; ans <- unlist(ans) })
   user  system elapsed 
  0.238   0.005   0.243 

前者はループごとにメモリを確保するので遅いですが、後者は一度にメモリを確保しているので高速です。


apply の場合は FUN がどのような値を返すか事前にわからないので list を使いますが、予め最終結果の型がわかっているのであればその型で初期化した方が高速です。unlist によるメモリのコピーも発生しませんし。

> system.time({ ans <- numeric(100000); for(i in 1:100000) ans[i] <- i })
   user  system elapsed 
  0.204   0.002   0.206 


まとめ

以上、apply について解説しました。

とりあえず次の2点さえ押さえておけば apply 関数に関しては OK かと思います!

  • apply は MARGIN で指定した次元のインデックスを変化させて繰り返し処理を行う
  • apply はすっきりした書き方ができるが、余計な処理をしている分最適な形で書いた for 文よりも遅くなる


練習問題解答

練習問題1
a <- array(1:24, c(3, 4, 2))
apply(a, c(2, 3), range)

練習問題2
a <- array(1:24, c(3, 4, 2))
d <- dim(a)
ans <- vector("list", d[1])
for (i in seq_len(d[1])) {
    x <- a[i, , ]
    ans[[i]] <- list(colsums = colSums(x), rowsums = rowSums(x))
}

*1:もちろん for 文を使った方が良い場面・使わないといけない場面もありますよ!

*2:ヘタな書き方をするよりは速くなりますが・・・

[Firefox][Mac]Mac 版 Firefox の Vimium でもリンク先を新規タブで開けるようにしてみた

$
0
0

Mac 版 Firefox の Vimium では F (follow link in the new tab) が効きません。ということで効くように修正してみました。

Emacs などで直接アーカイブの内容を変更してもいいですが、一応 unzip してから編集する手順を以下に記します。


まずアドオンのディレクトリに移動します。

$ cd ~/Library/Application Support/Firefox/Profiles/${PROFILE_NAME}/extensions/
$ unzip minoru-kun@inbox.ru.xpi content/overlay.js -d minoru-kun@inbox.ru

※${PROFILE_NAME} は自分の環境に合わせて変更してください


っで、minoru-kun@inbox.ru/content/overlay.js の285行目の

evt.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, newTab, false, false, false, 0, null);

という部分を次のように変更します。

evt.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, newTab, 0, null);

あまり意味はないと思いますが、次のように書いた方がお行儀が良いかもしれません。

if (navigator.platform.toLowerCase().indexOf("mac") == -1) {
evt.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, newTab, false, false, false, 0, null);
} else {
evt.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, newTab, 0, null);
}

変更を反映させます。

$ cd minoru-kun@inbox.ru
$ zip -f ../minoru-kun@inbox.ru.xpi content/overlay.js

Firefox を再起動します。これで F を押してリンクを選択すれば新しいタブで開けるはずです。

ではでは快適な Firefox 生活を!!

[Emacs][Node.js][JavaScript]Emacs から Node.js REPL を快適に使うための Emacs Lisp を作成しました

$
0
0

Node.js REPL は Node.js (JavaScript) の挙動の確認を手軽にできて便利なんですが、Emacs 上で使うと変なエスケープシーケンスが表示されたりタブ補完が効かなかったりと不便です。

また、端末上で使っても履歴のインクリメンタルサーチができないのは痛いです。


ということで作りました。

abicky/nodejs-mode.el

Node.js 0.4.12, 0.6.17、Emacs 23.4.1 で動作確認していますが、同じ環境でも PC のスペックなどによっては補完時に無限ループに陥るかもしれません・・・


提供する機能

  • タブによるトークンの補完
  • 文字列内でのファイル名補完
  • 履歴のインクリメンタルサーチ

インストール

$ cd /path/to/emacs/lisp
$ wget https://raw.github.com/abicky/nodejs-mode.el/master/nodejs-mode.el
$ emacs -batch -f batch-byte-compile nodejs-mode.el

.emacs に次の1行を追加してください。

(require 'nodejs-mode)

使い方

M-x nodejs で REPL を起動します。


補完候補は元の Node.js REPL と同じものになります(なるはずです)。

例えば re と入力した時点でタブを入力すると次のように require と return が補完候補として挙がります。

f:id:a_bicky:20120514083621p:image


require("f の状態でタブを入力すると f で始まるモジュールが補完候補として挙がります。

f:id:a_bicky:20120514083622p:image

require("./ の状態でタブを入力するとカンレントディレクトリ以下のファイルが補完候補として挙がります。これは元の Node.js REPL と違う点です。が、こちらの方が望ましいと思います。

f:id:a_bicky:20120514083624p:image


文字列内でタブを入力するとファイル名を補完しようとします。

f:id:a_bicky:20120514083625p:image



comint-mode をベースにしているので、履歴を遡る場合は M-p、その逆は M-n です。

M-r で履歴のインクリメンタルサーチも可能です。




というわけで、よかったらお使いください!

反響がそれなりにある場合は現在の行やリージョンを Node.js REPL に送って評価するマイナーモードとかも作るかもです。

[JSX]JSX 私的チュートリアル

$
0
0

先日 JSX がリリースされて話題を呼んでいますね。

JSX - a faster, safer, easier alternative to JavaScript


JSX の謳い文句として faster, safer, easier とありますが、要は JavaScript で涙ぐましいチューニングをしなくても、それ相当、あるいはそれ以上の速度で実行できる JavaScript が生成され (faster)、静的型付けでしかも型安全、さらにはコンパイル時にエラーが検出できる(safer)、そして JavaScript のようなプロトタイプ型のオブジェクト指向ではなく Java ライクなオブジェクト指向を採用しているのでわかりやすい(easier)ということかと思います。


なので、サクッと個人で JavaScript を書きたい場合には向かなくて、大規模開発や実行速度が求められる場合にありがたみがあるんでしょうね。


縁あって JSX はリリース前から試用させていただいていたので、個人的なチュートリアルを作成してみました。


インストール

Github から clone します。Node.js + npm が入っていない場合は事前にインストールしておきます。

$ git clone git://github.com/jsx/JSX.git
$ cd JSX
$ make setup  # または npm install

make setup では JSX の Web インタフェース を起動するための設定も行いますが、必要ない場合は npm install だけでも大丈夫です。



基本事項

主な組み込みの型 & クラス
型名概要
boolean真偽値
int整数
number浮動小数点数(JavaScript の number と同じ)
string文字列
variantどの型・クラスにも成り得る
MayBeUndefined.<T>T の型・クラス、または undefined
Array.<T>T の型・クラスの配列(T[] と表記することも可能)
Map.<T>T の型・クラスの連想配列
DateJavaScript の Date と同様
RegExpJavaScript の RegExp と同様

特殊な型に MayBeUndefined があります。

例えば、配列 a : number[ ] に対して a[10] としても a[10] は undefined かもしれません。なので、a の型は number[ ] ですが、a[10] の型は MayBeUndefined.<number> になり、undefined との比較ができます。


演算子

JavaScript との相違点は次のとおりかと思います。

  • typeof は variant 型の変数にしか適用できない
  • + 演算子は異なる型に適用できないので文字列の結合に使うためには数値をキャスト(as string) する必要がある
  • 論理演算(&&, ||)は論理値にしか適用できない
  • JavaScript の a = b || c; (b が真であれば b、偽であれば c を代入する)は a = b ?: c; という書き方で代用できる(b が undefined, 0, "" などの場合に c が代入される)
  • ===, !== 演算子は存在しない(同じ型同士の比較しかしないので)

if 文は真偽値しか使えない

JSX の if 文では真偽値しか使えません。

なので、JavaScript で数値 a に対して

if (!a) {
    ...
}

のような if 文と同じ条件にしようと思うと、JSX では

if (a == 0 || a == undefined) {
    ...
}

とする必要があります。null の場合、空文字列の場合なども考慮するとさらに条件が必要になります。


配列と連想配列

配列は JavaScript と同様に使えますが、中身は同一の型でなければなりません。異なる型を利用したい場合は variant[] で宣言します。

var array1 = [1, 2];                // number の配列
var array2 = [1, "2"] : variant[];  // 数値と文字列が混在しているので variant の配列にしなければならない

連想配列も同様です。JavaScript の違いとして、ドットでアクセスすることができません。

var map1 = { a: 1, b: 2 };                    // number の Map。a にアクセスするには map1["a"] とする。map1.a ではアクセスできない。
var map2 = { a: 1, b: "2" } : Map.<variant>;  // 数値と文字列が混在しているので variant の Map にしなければならない

ただし、variant は何でも代入できるがキャストしなければほとんど何もできない型なので、次の例のように毎回キャストする必要があります。

var map = {
    a: "a",
    b: 1,
    c: [1, 2, 3, 4, 5],
    d: { a: "a", b: "b"}
} : Map.<variant>;

log "a is " + map["a"] as string;                    // a is a
log 1 + map["b"] as int;                             // 2
log (map["c"] as int[]).join(", ");                  // 1, 2, 3, 4, 5
log "a of d is " + (map["d"] as Map.<string>)["a"];  // a of d is a

Hello World!

先に例を出した方がわかりやすいと思うので Hello Wrold! の例です。JSX Tutorial からの抜粋です。

class _Main {
    static function main(args : string[]) : void {
        log "Hello, world!";
    }
}

_Main クラスの main(:string[]) : void がエントリーポイントになります。JSX では Java のように何をするにしてもクラスを定義する必要があります。


実行方法

これも JSX Tutorial からの抜粋ですが、次のように --run オプションを付けて実行します。

$ bin/jsx --run example/hello.jsx
Hello, world!

あるいは次のように --executable オプション付きで JS を生成して、その JS を実行するということもできます。

$ bin/jsx --executable --output hello.js example/hello.jsx
$ node hello.js
Hello, world!


クラスの作成

JSX では abstract class, interface, mixin が提供されていますが、基本的なクラスの定義方法と継承方法についてのみ説明します。

abstract class や interface については JSX Tutorial の Classes and Interfaces を見るのが良いと思います。mixin の定義方法・利用方法は interface とほぼ同様です。

なお、private, public, protected のようなアクセス修飾子は存在しません。


インスタンス変数・クラス変数の定義の定義

記法は ActionScript の記法に近いです。

インスタンス変数の定義方法は次のとおりです。

var variableName : type;

例えば foo という変数が number の配列の場合は

var foo : number[];

となります。

変数が関数の場合は少し特殊で、

var foo : function(:string) : void;

のように、どんな型の引数を取る関数で、返り値が何かを指定する必要があります。引数名は指定しません。


変数宣言と同時に初期化を行うこともできます。初期化によって型が定まる場合は型を指定する必要がありません。

次の例では、foo は自動的に number[] となります。

var foo = [1, 2, 3, 4, 5];

メソッド内での変数宣言も同様です。クラス変数の場合は var の手前に static をつけます。

static var foo = [1, 2, 3, 4, 5];

メソッド内でこれらの変数にアクセスするには、インスタンス変数の場合は this.variableName、クラス変数の場合は ClassName.variableName とします。


インスタンスメソッド・クラスメソッドの定義

変数同様、ActionScript に近い記法です。

function([arg1:typ1[, arg2:type2[, ...]) : type {
    ...
}

また、オーバーロードがサポートされているので、同じメソッド名でも引数の数や型が違えば異なるメソッドを呼ぶことも可能です。


例えば getFoo という、number の配列 foo を返すメソッドだと

function getFoo() : number[] {
    return this.foo;
}

となります。


setFoo という、foo に値をセットするメソッドだと

function setFoo(newFoo : number[]) : void {
    this.foo = newFoo;
}

となります。

クラスメソッドの場合は function の手前に static を付けます。


なお、コンストラクタは次のように constructor というメソッドを定義するような形になります。メソッドの返り値は指定しません。

function constructor([arg1:type1[, arg2:type2[, ...]) {
}

クラスの例

以上を踏まえて Foo クラスを定義すると次のようになります。

class Foo {
    var foo : number[];

    function constructor() {
        this.foo = [1, 2, 3, 4, 5];
    }

    function getFoo() : number[] {
        return this.foo;
    }

    function setFoo(newFoo : number[]) : void {
        this.foo = newFoo;
    }
}

クラス変数・クラスメソッドの場合は次のようになります。

class Foo {
    static var foo = [1, 2, 3, 4, 5];

    static function getFoo() : number[] {
        return Foo.foo;
    }

    static function setFoo(newFoo : number[]) : void {
        Foo.foo = newFoo;
    }
}

継承

サブクラスのコンストラクタの最初にスーパークラスのコンストラクタを呼ばなければなりません。その際には super の形で呼び出します。明示的に呼ばなければ super() が呼び出されます。

スーパークラスのメソッドをオーバーライドする場合は必ず override 修飾子が必要です。オーバーライドしたスーパークラスのメソッドを呼ぶには super.methodName という形で呼びます。


以上の内容を詰め込んだのが次の例です。

class Human {
    var name : string;

    function constructor() {
        this.name = "noname";
    }

    function constructor(name : string) {
        this.name = name;
    }

    function introduce() : void {
        log "My name is " + this.name + ".";
    }
}

class Programmer extends Human {
    var language : string;

    function constructor(name : string, language : string) {
        super(name);  // 必ず最初にスーパークラスのコンストラクタを呼び出す必要がある
        this.language = language;
    }

    function constructor(language : string) {
        // super() が呼び出されて this.name が "noname" になる
        this.language = language;
    }

    override function introduce() : void {
        super.introduce();  // スーパークラスのメソッドを呼び出す
        log "I use " + this.language + ".";
    }
}


class _Main {
    static function main(args : string[]) : void {
        var noname = new Programmer("JSX");
        noname.introduce();
        // My name is noname.
        // I use JSX.

        var abicky = new Programmer("arabiki", "R");
        abicky.introduce();
        // My name is arabiki.
        // I use R.
    }
}


モジュールのロード

Python ライクなモジュールのロード機構があります。

例えば次のような classes.jsx があったとします。

// classes.jsx
class A {
    static function call() {
        log "a";
    }
}

class B {
    static function call() {
        log "b";
    }
}

classes.jsx で定義されたクラスを利用するには import でロードします。相対パスは読み込み元のスクリプトからの相対パスです。

import "./classes.jsx";

class _Main {
    static function main(args : string[]) : void {
        A.call();  // a
    }
}

同名のクラスの衝突を避けたい場合は into を使うことで回避できます。

import "./classes.jsx" into classes;  // classes という変数に import

class _Main {
    static function main(args : string[]) : void {
        classes.A.call();  // a
    }
}

一部のクラスだけロードしたい場合は from を使います。into と併用もできます。

import B from "./class.jsx";  // クラス B のみインポート

class _Main {
    static function main(args : string[]) : void {
        A.call();  // compile error: local variable 'A' is not declared
    }
}


その他の Tips

ブラウザ上で JSX から生成された JavaScript を実行する

foo.jsx という次のスクリプトを利用することにします。

// foo.jsx
class Foo {
    function constructor() {
        log "foo";
    }
}

まずは次のコマンドで JavaScript のコードを生成します。

$ jsx --output foo.js foo.jsx

JavaScript 側で new Foo を実行したければ

var foo = new JSX.require("foo.jsx").Foo$();

という形で呼び出します。

Foo の後にドルマークが付いていますが、JSX ではこれによってオーバーロードを実現しています。

例えば第1引数に number の変数を持つコンストラクタであれば Foo$N のように、引数の型を表す文字が入ります。今回は引数がないので Foo$ です。


次のような HTML ファイルを作成してアクセスすると、コンソールログに foo という文字が表示されます。

<html>
<script type="text/javascript" src="foo.js"></script>
<script type="text/javascript">
window.onload = function() {
    new JSX.require("foo.jsx").Foo$();
};
</script>
</html>

JavaScript の変数にアクセスする

js.global という Map オブジェクトを使うと JavaScript のグローバル変数にアクセスすることができます。

例えば console.warn を使いたい場合は次のようになります。

import "js.jsx";

class _Main {
    static function main(args : string[]) : void {
        var console = js.global["console"] as Map.<function(:string) : void>;
        console["warn"]("test");
    }
}




以上、簡単かつ極めて個人的なチュートリアルでした。

書き方でわからないことがあれば JSX/example, JSX/t, JSX/lib/js あたりを grep してみるといいと思います!

[JSX][Emacs]JSX の Emacs 環境を整備してみた 〜 jsx-mode.el 0.1.0 をリリースしました 〜

$
0
0

「JSX 書くなら Emacs だよね」というのが当たり前になる明るい未来を目指して jsx-mode.el を修正しました!

jsx/jsx-mode.el


今回の大きな変更点は次のとおりです。

  • ドキュメントの整備
  • flymake のサポート
  • インデントの修正


最初のリリースに比べるとだいぶ品質が上がったのではと自己満足しているところです。

順に簡単に説明していきます。


ドキュメントの整備

最初のバージョンでは「カスタマイズしたければソースコード読んで、変数いじるなりショートカットキー割り当てるなりしてちょうだい!」な感じだったんですが、ドキュメントにデフォルトで割り当てられているキーの説明を加えたり、サンプルを加えたりしました。


デフォルトでは次のキーが割り当てられています。

C-c C-c     comment-region (Comment or uncomment each line in the region)
C-c c       jsx-compile-file (Compile the current buffer)
C-c C       jsx-compile-file-async (Compile the current buffer asynchronously)
C-c C-r     jsx-run-buffer (Run the current buffer)

実行結果をその場で確認したい場合に C-c C-r は便利です!!


次のコードは同梱されている init.el.example のものです。これを参考に自分好みの設定にしちゃってください!

(add-to-list 'load-path (expand-file-name "~/.emacs.d/site-lisp/"))

(add-to-list 'auto-mode-alist '("\\.jsx\\'" . jsx-mode))
(autoload 'jsx-mode "jsx-mode" "JSX mode" t)

;; You can edit user-customizable variables by typing the following command.
;;     M-x customize-group [RET] jsx-mode
(custom-set-variables
 '(jsx-indent-level 2)
 '(jsx-cmd-options '("--add-search-path" "/path/to/search-path"))
 '(jsx-use-flymake t)
 '(jsx-syntax-check-mode "compile"))

(defun jsx-mode-init ()
  (define-key jsx-mode-map (kbd "C-c d") 'jsx-display-popup-err-for-current-line)
  (when (require 'auto-complete nil t)
    (auto-complete-mode t)))

(add-hook 'jsx-mode-hook 'jsx-mode-init)

というわけで、前よりはだいぶとっつきやすくなったのではと思っています。



flymake のサポート

デフォルトではオンになっていないので、jsx-use-flymake を t にしておくか、jsx-flymake-on を実行する必要があります。


例えば JSX に同梱されている example/hello.jsx を開くと次のような画面になりますが、

f:id:a_bicky:20120617165310p:image


ここに import "foo.jsx" を加えると、エラーの原因になっている行が強調され・・・

f:id:a_bicky:20120617165309p:image


jsx-display-popup-err-for-current-line を実行するとポップアップウィンドウにエラーの内容が表示されます。*1

f:id:a_bicky:20120617165308p:image


ポップアップウィンドウはうざいからエコーエリアの方が良い!という場合は jsx-display-err-for-current-line をお使いください。

f:id:a_bicky:20120617165307p:image


シンタックスチェックのモードとして "parse" と "compile" がありますが、前者は構文的に正しいかどうかだけをチェックし、後者は実際にコンパイルする場合のエラーをチェックします。

import "foo.jsx" は構文的には正しいので、"compile" にしないと存在チェックまでされません。jsx-syntax-check-mode をいじってお好みの方をお使いください。デフォルトは "parse" です。


インデントの修正

けっこう悲惨な状態だったので修正しました。

例えば今回のバージョンで次のようにインデントされるコードは・・・

class Foo {
    function foo(a : string,
                 b : string) : function (:string,
                                         :string) : void {
        return function(c : string, d : string) : void {
            if (true)
                log a + b + c + d;
        };
    }
}

前のバージョンだとこんな感じになってしまいます><

class Foo {
    function foo(a : string,
        b : string) : function (:string,
        :string) : void {
        return function(c : string, d : string) : void {
            if (true)
            log a + b + c + d;
        };
    }
}

というわけでインデントもだいぶましになっています。




以上、jsx-mode.el 0.1.0 の説明でした!


JSX ではエディタが補完候補を取得できるような仕組みを提供しようとしているので、正式にサポートされたら auto-complete との連携をサポートしようと思います。

外部プログラムを利用した補完機能の実装は nodejs-mode.el を作成する際に散々苦しんだから比較的簡単に導入できるはず・・・



何はともあれ、JSX 書くなら Emacs ですよね!!

*1:popup.el のインストールが必要です

[PRML][Book]これで PRML も怖くない!「パターン認識と機械学習の学習」

$
0
0

先日 @takesako さんより「パターン認識と機械学習の学習 ベイズ理論に挫折しないための数学」を献本していただきました!*1どうもありがとうございます。

【PRML同人誌】パターン認識と機械学習の学習 ベイズ理論に挫折しないための数学 (光成 滋生 著)


どういう本かというと、みなさんご存知「パターン認識と機械学習」(通称「PRML」、「ぷるむる」)を読み解くのに大変役立つ本です。

PRML では式展開が自明のものとして省略されていたり、演習問題に回されていたりする箇所がけっこうあります。そのような箇所を丁寧に式展開して解説しているのが本書です。

私自身、よほど難しそうでない限りはこまめに式展開して確認するタイプなので、そのような人には非常にありがたい本ですね。*2


目次は次のとおりです。

  • 第1章 「序論」のための確率用語
  • 第2章 「確率分布」のための数学
  • 第3章 「線形回帰モデル」のための数学
  • 第4章 「線形識別モデル」のための数学
  • 第9章 「混合モデルと EM」の数式の補足
  • 第10章「近似推論法」の数式の補足
  • 第11章「サンプリング法」のための物理学

内容は著者の光成さんが GitHub で公開しているものに、第1章の『「序論」のための確率用語』と第11章の『「サンプリング法」のための物理学』が加わった感じです。*3

第1章は測度論的な観点から「確率変数は写像である」ということを解説していて、測度論の知識が皆無な私は「1.1.3 確率変数 X」を読んでも意味不明で泣きそうになりましたが、1.1.3を理解できなくても「確率変数は写像である」という結論部分は理解できた気がしたのでおもしろかったです。ちなみに、あとの章には影響がないので読み飛ばしても問題ありません。

第2章〜第10章は、非常に丁寧にわかりやすく解説してあるので、初歩的な線形代数の知識があれば、PRML を読んでもよくわからなかった部分でも理解できることと思います。

第11章は PRML の「11.5.1 力学系」を物理学的な観点から別の解説をしています。



そんなわけで、PRML をしっかり理解したい人にはオススメの1冊です!



こちらも参考にどうぞ

*1:NLP 2012 の懇親会で話題になっていた本で、出版されたら絶対買おうと思っていたところ、思いもよらず献本がありました

*2:高額な PRML をわざわざ買う人はほとんどそういうタイプの人だと思いますが!

*3:他の章で差分があるかどうかは確認していません


[JavaScript][HTML5][Canvas]HTML5 の Canvas で getTransform が使えるようにしてみた

$
0
0

CanvasRenderingContext2D には setTransform があるのに getTransform はありません。

transform, rotate, save, restore などを駆使して変換しまくってると、現在どのような変換行列が適用されているのかよくわからなくなり、表示が乱れていてもデバッグしにくいこと極まりないです。

実際、「canvas gettransform」でググると「getTransform 欲しいよね」とか、「どうやったら現在の変換行列の状態が取得できるか?」とか、出てきます。その割に誰も getTransform を実装している気配がありません。


というわけで実装してみました。

------ 2012-07-24 追記 --------

行列の掛け算が逆になっているという悲惨な状態だったので修正しました。ご指摘ありがとうございます。


このスクリプトをロードすることで CanvasRenderingContext2D#getTransform が使えるようになります。

現在の変換行列の状態を長さ6の配列で返します。配列の各要素の順番は CanvasRenderingContext2D#setTransform の引数の順番に対応しています。



サンプル

こちらのサンプルを拝借して途中の変換行列の状態を表示してみます。

<html>
<head>
<script type="text/javascript" src="canvas_get_transform.js"></script>
<script type="text/javascript">
function draw() {
    var ctx = document.getElementById('canvas').getContext('2d');
    ctx.translate(75,75);

    for (var i=1;i<6;i++){ // Loop through rings (from inside to out)
        ctx.save();
        ctx.fillStyle = 'rgb('+(51*i)+','+(255-51*i)+',255)';

        for (var j=0;j<i*6;j++){ // draw individual dots
            ctx.rotate(Math.PI*2/(i*6));
            ctx.beginPath();
            ctx.arc(0,i*12.5,5,0,Math.PI*2,true);
            ctx.fill();
            console.log(ctx.getTransform());
        }
        ctx.restore();
    }
}
</script>
</head>
<body onload="draw()">
</body>
<canvas id="canvas"></canvas>
</html>

表示結果は次のようになります。

f:id:a_bicky:20120724043342p:image


・・・パッと見ても合ってるのか間違ってるのかよくわかりませんが、とりあえず変換行列の状態が取得できてるっぽいです。



精度の問題で微妙にずれたり、canvas のサイズをいじった時のことを考慮できてなかったりしますが、デバッグ用途としてはそれなりに機能するのではないでしょうか。


というわけで、よかったらお使いください!

[Regex][R]仕様に割りと忠実に URL をパースする正規表現を書いてみた

$
0
0

実用的なものであれば URL をパースする正規表現なんてすぐ書けるんですけど、仕様に忠実に書こうと思うとどうなるのかなぁと思って書いてみました。


URI の定義

まず、RFC 3986 には URI が次のように ABNF で定義されています。

URI         = scheme ":" hier-part [ "?" query ] [ "#" fragment ]

hier-part   = "//" authority path-abempty
             / path-absolute
             / path-rootless
             / path-empty

query         = *( pchar / "/" / "?" )
fragment      = *( pchar / "/" / "?" )

pchar         = unreserved / pct-encoded / sub-delims / ":" / "@"
unreserved  = ALPHA / DIGIT / "-" / "." / "_" / "~"
pct-encoded = "%" HEXDIG HEXDIG
sub-delims  = "!" / "$" / "&" / "'" / "(" / ")"
                  / "*" / "+" / "," / ";" / "="

今回対象にするのは http, https, ftp scheme に限定することにするので、hier-part は "//" authority path-abempty になります(たぶん)。

次に authority ですが、次のように定義されています。*1

authority   = [ userinfo "@" ] host [ ":" port ]
userinfo    = *( unreserved / pct-encoded / sub-delims / ":" )
host        = IP-literal / IPv4address / reg-name
port        = *DIGIT

host はちゃんと見てないんですが、RFC 1035RFC 1123 に書かれている有効なホスト名だけを対象とすることにします。IP アドレスは考慮しません。

有効なホスト名は、各ラベル(ドットで区切られる文字列)がアルファベットか数字で始まり、アルファベット、数字、ハイフンで構成され、ハイフンで終わらなく、63文字以内であり、ホスト名全体で255文字以内のものです。ホスト名が全体で255文字以内という条件は正規表現で表現できそうにないので無視します。あと、現在 TLD に数字やハイフンを含むものがないのでそのようなホスト名は除外します。


残りの path-abempty, query, fragment ですが、

path                = path-abempty    ; begins with "/" or is empty
                    / path-absolute   ; begins with "/" but not "//"
                    / path-noscheme   ; begins with a non-colon segment
                    / path-rootless   ; begins with a segment
                    / path-empty      ; zero characters

path-abempty  = *( "/" segment )

segment       = *pchar

となっています。今回対象にしているのは path-abempty ですが、これはスラッシュで始まるか空文字列と定義されています。


パースしてみる

文字列処理のための言語といえば、そう、R ですよね!というわけで R でパースしてみました。*2

library(gsubfn)

regex <- "(?ix)
    # scheme
    ( https? | ftp )
    ://
    # authority
    (
        (?:
            # userinfo
            (
                (?: [-.~\\w!$&'()*+,;=] | %[0-9a-f]{2} )+
                :
                (?: [-.~\\w!$&'()*+,;=] | %[0-9a-f]{2} )+
            )
            @
        )?
        # host (the last label is one of TLDs and currently consists of alphabets and dot)
        ( (?: [0-9a-z][-0-9a-z]{0,62}(?<!-)\\. )+ [a-z]+ )
        # port (0-65535)
        (?: :(\\d{1,5}) )?
    )
    # path-abempty
    ( (?: / (?: [-.~\\w!$&'()*+,;=:@] | %[0-9a-f]{2} )* )* )
    (?:
        \\?
        # query
        ( (?: [-.~\\w!$&'()*+,;=:@/] | %[0-9a-f]{2} )* )
    )?
    (?:
        \\#
        # fragment
        ( (?: [-.~\\w!$&'()*+,;=:@/] | %[0-9a-f]{2} )* )
    )?"

url <- "http://user:password@example.com:8080/path/to/file?date=1342460570#fragment"
print(strapply(url, regex, c, perl = TRUE, backref = 8, simplify = c))

これを実行すると次のような結果になります。

[1] "http://user:password@example.com:8080/path/to/file?date=1342460570#fragment"
[2] "http"                                                                       
[3] "user:password@example.com:8080"                                             
[4] "user:password"                                                              
[5] "example.com"                                                                
[6] "8080"                                                                       
[7] "/path/to/file"                                                              
[8] "date=1342460570"                                                            
[9] "fragment"                                                                   

上から順に、マッチした文字列、scheme、authority、userinfo、host、port、path-abempty、query、fragment になってますよね!


ネタなのでテストとか書いてませんし、パフォーマンスも考慮してないですし、誤りもあるかもしれませんがご了承ください・・・。

誤りは指摘していただけると嬉しいです!

*1:余談ですが、file scheme は authority が空なので、file:///path となります

*2:Perl だと文字列内で変数展開もできるのでもっと楽に書けますが・・・

[JavaScript]MobileSafari における Web SQL Database の実質利用可能な容量を調べてみた

$
0
0

MobileSafari の Web SQL Database では最大 50 MB の容量が利用可能です。これはデータベースを初期化する際に(初めて openDatabase を実行する際に) 25 MB 以上のサイズを指定すると最大 50 MB の容量を使用することを許可するか確認するダイアログが出て、50 MB を超える値を指定するとエラーになることから確認できます。*1


っで、この 50 MB は DB を利用する側にとって実質どの程度使えるのか知りたかったので調べてみました。検証端末は iPhone 4S (iOS 5.1.1) です。*2


検証コード

キーとバリューを持つようなレコードをエラーになるまで挿入するコードです。

各レコード、キー・バリュー合わせて 1 kB, 2 kB, ... 1024 kB になる場合でそれぞれ調べました。

------- 2012/8/14 追記 -------

JavaScript は ASCII 文字も含め全ての文字に関して 16 bit のメモリを消費するみたいなので、2 kB, 4 kB, ..., 2048 kB になっています。

var db = openDatabase("test", "", "", 50 * 1024 * 1024);

var createTable = function(table) {
    db.transaction(
        function(tx) {
            tx.executeSql("CREATE TABLE IF NOT EXISTS " + table + " (key UNIQUE, value)");
        },
        function(error) {
            console.error("error: " + error.message);
        },
        function() {
            console.log("success: create table " + table);
        }
    );
};

var dropTable = function(table) {
    db.transaction(
        function(tx) {
            tx.executeSql("DROP TABLE IF EXISTS " + table);
        },
        function(error) {
            console.error("error: " + error.message);
        },
        function() {
            console.log("success: drop table " + table);
        }
    );
};

var repeatStr = function(str, n) {
    return new Array(n + 1).join(str);
};

var main = function(size) {
    var table = "test";
    dropTable(table);
    createTable(table);

    var key = repeatStr("0", 24);          // 24 bytes
    var str = repeatStr("0", 1024);        // 1 kB
    var value = repeatStr(str, size - 1);  // (size-1) kB
    value += repeatStr("0", 1000);         // (size-1) kB + 1000 bytes

    var nextKey = function(key) {
        key++;
        return repeatStr("0", 24 - (key + "").length) + key;
    };

    var insertData = function(key, value) {
        db.transaction(
            function(tx) {
                tx.executeSql("INSERT OR REPLACE INTO " + table + " (key, value) VALUES (?, ?)", [key, value]);
            },
            function(error) {
                console.error("size: " + size + "kB, key: " + key);
                console.error("error: " + error.message);
                if (size > 1) {
                    main(size / 2);
                }
            },
            function() {
                //console.log("success: insert " + key );
                insertData(nextKey(key), value);
            }
        );
    };
    insertData(key, value);
};

main(1024);

結果

1レコード 1 kB の場合のみが特殊で、あとは全レコードのサイズが約 25 MB に到達するところでエラーになるみたいです。

レコードの内容はランダムな文字列の方が適切かもしれませんが、画像を base64 エンコードした値を挿入する場合の容量上限と感覚的にずれはないので妥当な結果に思われます。

------- 2012/8/14 追記 -------

1レコードのサイズは1文字1バイトで計算しているので、実際は2倍のサイズです

1レコードのサイズ(kB)挿入可能なレコード数
112554
211200
45968
83085
161568
32790
64396
128198
25698
51248
102423

ちなみに 1 kB の場合はこちらでも検証されていて、同じような結果になっています。


ところで、実際に複数のレコードを挿入する場合は今回のように1つ前のレコードの挿入が成功した場合のコールバック関数で次のレコードを挿入することはないと思いますが、 その場合コンソール画面を表示した状態だと容量制限を超えても成功した場合のコールバック関数しか呼ばれず、一見挿入が成功したように見えるので注意が必要です。これを知らないとハマります。


試しに main 関数の最後の内容を次の内容に変えて、コンソール画面を表示した上で実行すると 1024 kB のデータが 1000 件挿入できたように見えます。全ての処理が終わるまでコンソール画面を表示しなければ 24 件目以降エラーメッセージが出続けます。エラーメッセージが出てきたところでコンソール画面を表示するとログ上は再び挿入に成功し出します。

    var insertData = function(key, value) {
        db.transaction(
            function(tx) {
                tx.executeSql("INSERT OR REPLACE INTO " + table + " (key, value) VALUES (?, ?)", [key, value]);
            },
            function(error) {
                console.error("size: " + size + "kB, key: " + key);
                console.error("error: " + error.message);
            },
            function() {
                console.log("success: insert " + key );
            }
        );
    };

    for (var i = 0; i < 1000; i ++) {
        insertData(key, value);
        key = nextKey(key);
    }



以上、MobileSafari の Web SQL Database で実質利用可能な容量を調べました。「MobileSafari では約 <de>2550 MB までデータが保存できる」と覚えておくと良いかもしれません。あと、小さなデータを大量に保存する場合には向かないかもしれませんね。

*1:初期化の際に小さなサイズを指定しても実際の利用状況が 5 MB を超える時点で最大 10 MB、10 MB を超える時点で最大 25 MB、25 MB を超える時点で 最大 50 MB の容量を使用することを許可するか確認するダイアログが出ます

*2:実はもっと前に1レコード 100 kB になるようにして調べたことがあったのですが、何故か1文字 2 bytes で計算していたので再度検証した感じです・・・

[JSX]JSX で Function#apply を使う

$
0
0

JSX の built-in.jsx の Function の定義には

Unlike JavaScript, JSX does not provide Function#call() or Function#apply() since it is a statically-typed language.

と記述されており、Function#apply はサポートしない方針であることが伺えます。

と言っても、既存のコードで可変長引数の関数に対して長さ不明の引数を渡すために Function#apply が使われている場合は移植に困ります。


例えば次のようなコードです。

function foo(array1, array2) {
    Array.prototype.push.apply(array1, array2);
    return String.fromCharCode.appply(null, array1);
}

Array#push はどうも Array#concat を使った方が Android 標準ブラウザや Chrome で圧倒的に速いようなので*1、concat 使えばいいんですが、String.fromCharCode を for ループで書き直すと処理に時間がかかってしまってしまいます*2


実は JSX には apply を使うために用意されている関数(static メソッド)があるんです!

js.jsx を見てみると js.invoke という関数が定義されていることがわかります。

final class js {

static var global : Map.<variant>;

static native function invoke(obj : variant, funcName : string, args : Array.<variant>) : variant;

}

js.invoke を使って先ほどの foo 関数のようなメソッドを定義すると次のようになります。

import "js.jsx";

class _Main {
    static function foo(array1 : number[], array2 : number[]) : string {
        js.invoke(array1, "push", array2 as __noconvert__ variant[]);
        return js.invoke(String, "fromCharCode", array1 as __noconvert__ variant[]) as string;
    }

    static function main(args : string[]) : void {
        var str1 = "ABCDEFG";
        var str2 = "HIJKLMN";
        var chars1 = [] : number[];
        var chars2 = [] : number[];
        for (var i = 0; i < str1.length; i++) {
            chars1[i] = str1.charCodeAt(i);
            chars2[i] = str2.charCodeAt(i);
        }

        log _Main.foo(chars1, chars2);
    }
}

foo メソッドの変換結果を見ると次のように apply が使われていることがわかります。

_Main.foo$ANAN = function (array1, array2) {
(function (o, p, a) { return o[p].apply(o, a); }(array1, "push", array2));
return (function (o, p, a) { return o[p].apply(o, a); }(String, "fromCharCode", array1)) + "";
};


以上、あまり推奨されるやり方ではないでしょうが(そしてもっと良い方法が提供される可能性がありますが)、とりあえず Function#apply も使おうと思えば使えますよというお話でした!

*1:cf. http://jsperf.com/array-push-vs-array-concat-vs-for-loop 最初は配列の定義を setup のところに書いていて、push によってテストの度に要素数が増えてしまってるのか?と思いましたが毎回定義し直すようにしても結果はあまり変わりませんでした。

*2:cf. http://jsperf.com/apply-vs-for-loop-as-for-string-fromcharcode

[JSX]JSX の遅延評価にご注意

$
0
0

ハマることがあるかもなぁと思ったのでメモがてら書き残しておきます。

遅延評価はどの言語でもハマる人がいると思うんですが、実は JSX にも遅延評価が存在します。


例えば次のスクリプトを実行するとどんな値が出力されるでしょう?

class _Main {
    static var a = 1;
    static var b = _Main.a;

    static function main(args : string[]) : void {
        _Main.a = 2;
        log _Main.b;  // ??
    }
}



答えはなんと 2 です!



解説

_Main クラスの変換結果は次のようになっています。

/**
 * class _Main extends Object
 * @constructor
 */
function _Main() {
}

_Main.prototype = new Object;
/**
 * @constructor
 */
function _Main$() {
};

_Main$.prototype = new _Main;

/**
 * @param {Array.<undefined|!string>} args
 */
_Main.main$AS = function (args) {
_Main.a = 2;
console.log(_Main.b);
};

var _Main$main$AS = _Main.main$AS;

_Main.a = 1;
$__jsx_lazy_init(_Main, "b", function () {
return _Main.a;
});

最後の $__jsx_lazy_init の部分が曲者で、static 変数に対してリテラル以外で初期化すると $__jsx_lazy_init によって初期化されるようです。名前から想像がつくと思いますが、これによってこの変数が使われるときに初めて初期化が行われます。


$__jsx_lazy_init の定義は次のとおりです。

/**
 * defers the initialization of the property
 */
function $__jsx_lazy_init(obj, prop, func) {
function reset(obj, prop, value) {
delete obj[prop];
obj[prop] = value;
return value;
}

Object.defineProperty(obj, prop, {
get: function () {
return reset(obj, prop, func());
},
set: function (v) {
reset(obj, prop, v);
},
enumerable: true,
configurable: true
});
}

Object.defineProperty の getter を使うことで JavaScript で遅延評価を実現しています。今回の例では、_Main の b プロパティにアクセスがあった時に初めて _Main.a が評価され、その値が _Main.b にセットされるようになっています。

よって、_Main.b にアクセスする前に _Main.a の値を変えると、_Main.b にアクセスした時には変更後の _Main.a を使って初期化が行われることになります。



わざわざ $__jsx_lazy_init を使っているのは、おそらく2つのクラスがお互いの static 変数を参照して初期化するような場合に対処するためだと思います。が、それだと static 変数を初期化するフェーズを main の実行前に設ければ良いだけの気もします。


何はともあれ、遅延評価には気を付けましょう!

[Favmemo]Introduction to Favmemo for Immature Engineers

$
0
0

エンジニア目線で Favmemo を紹介するスライドを作ってみました。

Favmemo を作った経緯とか、フロントエンドの特徴とか、開発環境とか、今後について書いてあります。

「これから Web サービスを作りたい!」って人にとってはちょっとぐらいは参考になるかもです。

そんなわけで「for Immature Engineers」になってます。



[HTML5][Canvas]SWF 研究会#2 で「HTML5 Canvas で学ぶアフィン変換」を発表しました

$
0
0

昨日行われた SWF 研究会#2 に参加してきました。


「一通り聞いて理解すれば、簡単な Flash Player が自作出来る位の内容を目指します。」と書いてあるとおり、バイナリのパースから始まり、シェイプの描画、AS の実行処理系、Flash Player の作り方(のハマりどころ)とキレイに話がつながっていてとても勉強になりました。

LT の方も本発表と同じぐらい濃い発表が多かったです。


縁あって LT 枠を頂いたので、初心者向けにライトな内容を話そうと、アフィン変換について簡単に発表しました。



5分のライトニングトークと聞いていたんですが、5分の枠を超えて濃い内容を発表している人が多く、かなり浮いた発表になりましたが・・・。

○○カジュアルに参加したらガチな発表ばかりだった時の感覚ですね。


今回の発表用にデモを作ったんですが、明らかに5分を超えそうだったため省略したんでここに貼り付けときます。


しかし発表しておいてなんですが、未だに Canvas の変換行列を掛ける順序についてはわかったようなわかってないような、な感じです。

変換後の座標系でさらに変換が行われるから、最初の変換はその後に適用される変換全てに影響するって意味で一番左に来るのはなんとなく理解できるんですが。


[Ganglia]Ganglia 3分インストール @ Debian

$
0
0

MySQL のチューニングの効果検証のため、ディスク I/O とかちゃんと監視しないとなぁと思ったんで、名前を聞いたことがあるというだけの理由で Ganglia をインストールしてみました。

何もこだわらなければ APT ですんなり入るのでかなり楽です。


ただし Gangla のアーキテクチャについての理解がないとハマりにハマります。Ganglia のインストール方法についてはたくさん見つかるんですが、自分の理解度ですんなりインストールできる説明がなかったんでまとめてみました。


構成

Ganglia は一般的に次の3つから構成されるみたいです。

cf. Ganglia (software) - Wikipedia, the free encyclopedia


  • Ganglia Monitoring Daemon (gmond)

監視対象のクライアントにインストールされるデーモンで、クライアントの状態をマルチキャストやユニキャストで送信したり受信したりします。また、gmetad からのリクエストに対して XML を返します。

  • Ganglia Meta Daemon (gmetad)

管理サーバにインストールされるデーモンで、定期的に gmond にリクエストを送り、XML を取得したら RRD にデータを保存します。

  • Ganglia PHP Web Frontend

RRD のデータをブラウザ経由で見やすく表示します。


というわけで、今回は次のような構成にします。

172.16.199.3 が管理サーバで、172.16.199.5 が監視対象のクライアントです。いずれも Debian squeeze の 64bit 版です。

どこが TCP/IP でどこが UDP/IP かを理解しておけば iptables の設定も簡単です。

f:id:a_bicky:20121008123513p:image:w500


普通は管理サーバ自体も監視対象にするみたいですが今回は監視しません。


監視対象クライアントのセットアップ

以下、172.16.199.5 での作業です。

gmond のインストール
# aptitude install ganglia-monitor
# gmond --version
gmond 3.1.7
# telnet localhost 8649  # XML が表示されることを確認

gmond の設定

今回はマルチキャストではなくユニキャストを使うことにします。

/etc/ganglia/gmond.conf の udp_send_channel と udp_recv_channel を次のように書き換えます。*1

udp_send_channel {
  host = 172.16.199.5
  port = 8649
  ttl = 1
}

udp_recv_channel {
  port = 8649
}

設定を反映します。

# /etc/init.d/ganglia-monitor restart

最初 host を管理サーバである 172.16.199.3 にしていたので全然うまくいきませんでした。

今回は監視対象となるクライアントが1台しかないのでわかりにくいですが、本来であれば複数台からなるクラスタごとにリーダーとなるクライアントがいて、そいつが gmetad からの問い合わせに全クライアントの分をまとめて答えるって仕組みみたいです。

なので、172.16.199.5 は監視対象クライアント兼リーダーです。監視対象に 172.16.199.6 を追加した場合、全く同じ設定をして、172.16.199.5 に 172.16.199.6 の情報を送ることになります。


iptables の設定

他の部分の設定にもよりますが、例えば次のように、管理サーバである 172.16.199.3 から TCP の 8649 ポートに対して来たパケットを通すようにします。

iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT -i eth0 -p tcp --dport 8649 -s 172.16.199.3 --sport 1024: -m state --state NEW -j ACCEPT

とにかく 172.16.199.3 からの TCP の 8649 ポートに対するリクエストに返答できれば OK です。

また、自身の UDP の 8649 ポートから自身の UDP の 8649 ポートへの通信も許可されている必要があるはずです。


管理サーバのセットアップ

以下、172.16.199.3 での作業です。

諸々インストール
# aptitude install rrdtool gmetad ganglia-webfrontend
# gmetad --version
gmetad 3.1.7
# telnet localhost 8651  # XML が表示されることを確認

ここでもし XML が表示されなければ、そもそも gmetad が起動していないと思われます。デバッグオプションを付けて gmetad を起動してみてください。

# gmetad start --debug=1
Please make sure that /var/lib/ganglia/rrds is owned by nobody

上記エラーが出れば /etc/ganglia/gmetad.conf の setuid_username を変更するか、/var/lib/ganglia/rrds の owner を変える必要があります。


gmetad の設定

/etc/ganglia/gmetad.conf の data_source を次のように監視対象クライアント兼リーダーに変更します。

data_source "my cluster" 172.16.199.5

設定を反映します。

# /etc/init.d/gmetad restart

iptables の設定

監視対象クライアント兼リーダーである 172.16.199.5 の TCP の 8649 ポートへアクセスできるようにします。また、Web Frontend のため 80 番ポートを空けます。

例えば次のようになります。

iptables -A INPUT  -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A OUTPUT -m state --state ESTABLISHED,RELATED -j ACCEPT
iptables -A INPUT  -i eth0 -p tcp --dport 80 --sport 1024: -m state --state NEW -j ACCEPT
iptables -A OUTPUT -o eth0 -p tcp --dport 8649 --sport 1024: -m state --state NEW -j ACCEPT

設定の確認

ちゃんとデータを取得できるか確認します。

# telnet 172.16.199.5 8649

"Tring 172.16.199.5..." と表示されたままで XML が返ってこなければ iptables の設定が間違っている可能性が考えられます。


Web Frontend の設定

次の作業を行うことで http://172.16.199.3/ganglia/ にアクセスした時にフロントエンドが表示されるようになります。

# cp -p /etc/ganglia-webfrontend/apache.conf /etc/apache2/conf.d/ganglia.conf
# /etc/init.d/apache2 restart

http://172.16.199.3/ganglia/ にアクセスしてみて "Hosts up: 1" となっていればインストールは終了です。


"Hosts down: 1" になっていた場合、gmond の設定に不具合があるかもしれません。

試しに、監視対象クライアントである 172.16.199.5 で

# gmetric -n test -v 1 -t int8
There was an error sending to 1 of the send channels.

となった場合は host の指定が間違ってるとかマルチキャストの設定になっているとかが考えられます。



以上、全然カスタマイズをしない Ganglia のインストール方法でした!!


参考

主に以下の内容を参考にさせていただきました。

*1:ユニキャストの場合は globals の send_metadata_interval を 0 より大きな値にしろっていろんなところで見かけましたが、動いてるんでとりあえず気にしないことにします。

[Ganglia]Ganglia の mysqld プラグインを導入してみた

$
0
0

前回に引き続き Ganglia のお話です。

元々 MySQL のチューニングの効果検証のために導入したので、MySQL 関係のメトリックを追加します。


手軽にメトリックを追加しようと思うと、Ganglia 3.1 以降では ganglia/gmond_python_modules のものを使うのが良いみたいです。

MySQL 関係だと mysql と mysqld というのがあるんですが、graph.d があるというのと比較的最近に更新されているという理由で mysqld を使ってみることにしました。元々 Ganglia を導入して効果検証しようと思ったのは id:hirose31 さんによる「実録MySQLのチューニング 春の陣 - (ひ)メモ」がきっかけなので、mysql を使うべきかもしれませんが・・・。


mysqld プラグインのインストール

監視対象クライアントでのインストール

更新の度にアーカイブをダウンロードするのも何なんで、リポジトリをクローンします。

# mkdir /var/git
# cd /var/git/
# git clone https://github.com/ganglia/gmond_python_modules.git

次の内容で /etc/ganglia/conf.d/modpython.conf を作成します。/etc/ganglia/conf.d がない場合は作成します。

modules {
  module {
    name = "python_module"
    path = "/usr/lib/ganglia/modpython.so"
    params = "/usr/lib/ganglia/python_modules"
  }
}

include('/etc/ganglia/conf.d/*.pyconf')

必要なファイルを移動します。

# mkdir /usr/lib/ganglia/python_modules
# cp /var/git/gmond_python_modules/mysqld/python_modules/* /usr/lib/ganglia/python_modules/
# cp /var/git/gmond_python_modules/mysqld/conf.d/mysql.pyconf /etc/ganglia/conf.d/

mysql.pyconf に書いてあるように gmond 用の MySQL ユーザを作成します。

# mysql -uroot -p -e 'GRANT SUPER, PROCESS ON *.* TO health@localhost IDENTIFIED BY "hogehoge";'

/etc/ganglia/conf.d/mysql.pyconf の host と passwd をセットします。

modules {
  module {
    name = "mysql"
    language = "python"
    param host {
      value = 'localhost'
    }
    param user {
      value = 'health'
    }
    param passwd {
      value = 'hogehoge'
    }

必要な Python モジュールをインストールします。特にこだわらないので virtualenv + pip でインストールするとかじゃなくて APT でインストールします。

# aptitude install python-mysqldb

試しに起動してみます。

# /etc/init.d/ganglia-monitor stop
# gmond --debug=2

Python のバージョンの関係か、ここで次のようなエラーが出るかもしれません。

# gmond --debug=2
loaded module: core_metrics
loaded module: cpu_module
loaded module: disk_module
loaded module: load_module
loaded module: mem_module
loaded module: net_module
loaded module: proc_module
loaded module: sys_module
loaded module: python_module
udp_recv_channel mcast_join=NULL mcast_if=NULL port=8649 bind=NULL
tcp_accept_channel bind=NULL port=8649
udp_send_channel mcast_join=NULL mcast_if=NULL host=172.16.199.5 port=8649

Unable to find the metric information for 'mysql_innodb_pending_normal_aio_reads'. Possible that the module has not been loaded.

Unable to find the metric information for 'mysql_innodb_pending_log_writes'. Possible that the module has not been loaded.
(後略)

mysql_innodb_pending_normal_aio_reads とか定義されているけど、そんな情報取得できなかったよって意味です。

その場合は /usr/lib/ganglia/python_modules/mysql.py を次のように変更するとうまくいくかもしれません。変更したら忘れずに mysql.pyc を削除しましょう。

--- mysql.py.orig       2012-10-08 19:18:43.000000000 +0900
+++ mysql.py    2012-10-08 18:53:22.000000000 +0900
@@ -124,7 +124,7 @@
                if get_innodb:
                        cursor = conn.cursor(MySQLdb.cursors.Cursor)
                        cursor.execute("SHOW /*!50000 ENGINE*/ INNODB STATUS")
-                       innodb_status = parse_innodb_status(cursor.fetchone()[0].split('\n'))
+                       innodb_status = parse_innodb_status(cursor.fetchone()[2].split('\n'))
                        cursor.close()
                        logging.debug('innodb_status: ' + str(innodb_status))

ちなみに使用している Python はシステムに組み込みのもので古く、次のようになっています。

# python --version
Python 2.6.6
# python -c 'import MySQLdb; print MySQLdb.version_info'
(1, 2, 2, 'final', 0)

問題なく起動したら再起動します。

# /etc/init.d/ganglia-monitor restart

これで監視対象クライアントへのインストールは完了です。


管理サーバでのインストール

こちらでもクローンします。

# mkdir /var/git
# cd /var/git/
# git clone https://github.com/ganglia/gmond_python_modules.git

グラフを表示するためのファイルをコピーします。

# cp /var/git/gmond_python_modules/mysqld/graph.d/mysql_* /usr/share/ganglia-webfrontend/graph.d/

管理サーバは以上です!



あとは Web Frontend にアクセスして MySQL 関係のメトリックも追加されていることを確認してみてください。

以上、MySQL 関係のメトリックの追加方法でした!


追記

SHOW INNODB STATUS の Buffer pool hit rate とかも取りたかったので次のように変更しました。

  • /etc/ganglia/conf.d/mysql.pyconf
--- mysql.pyconf.orig2012-10-08 19:48:22.000000000 +0900
+++ mysql.pyconf2012-10-08 22:43:14.000000000 +0900
@@ -460,5 +460,22 @@
   metric {
     name = "mysql_aborted_clients"
   }
+
+  metric {
+    name = "mysql_innodb_pages_reads_per_sec"
+  }
+
+  metric {
+    name = "mysql_innodb_pages_creates_per_sec"
+  }
+
+  metric {
+    name = "mysql_innodb_pages_writes_per_sec"
+  }
+
+  metric {
+    name = "mysql_innodb_buffer_pool_hit_rate"
+  }
+
 }
  • /usr/lib/ganglia/python_modules/DBUtil.py
--- DBUtil.py.orig2012-10-08 19:46:19.000000000 +0900
+++ DBUtil.py2012-10-08 23:14:34.000000000 +0900
@@ -65,6 +65,7 @@
                                             dict.__repr__(self))
 
 import MySQLdb
+import re
 
 def longish(x):
 if len(x):
@@ -178,6 +179,14 @@
 innodb_status['pages_created'] = longish(istatus[4])
 innodb_status['pages_written'] = longish(istatus[6])
 
+elif re.match('.*? reads/s, .*? creates/s, .*? writes/s', line):
+innodb_status['pages_reads_per_sec'] = float(istatus[0])
+innodb_status['pages_creates_per_sec'] = float(istatus[2])
+innodb_status['pages_writes_per_sec'] = float(istatus[4])
+
+elif "Buffer pool hit rate" in line:
+innodb_status['buffer_pool_hit_rate'] = float(istatus[4]) / 1000
+
 # ROW OPERATIONS
 elif 'Number of rows inserted' in line:
 innodb_status['rows_inserted'] = longish(istatus[4])
  • /usr/lib/ganglia/python_modules/mysql.py
--- mysql.py.orig2012-10-08 19:50:56.000000000 +0900
+++ mysql.py2012-10-08 22:45:56.000000000 +0900
@@ -1068,6 +1068,30 @@
 'value_type':'uint',
 'units': 'txns',
 },
+
+innodb_pages_reads_per_sec = {
+'description': "",
+'value_type':'float',
+'units': 'reads/s',
+},
+
+innodb_pages_creates_per_sec = {
+'description': "",
+'value_type':'float',
+'units': 'creates/s',
+},
+
+innodb_pages_writes_per_sec = {
+'description': "",
+'value_type':'float',
+'units': 'writes/s',
+},
+
+innodb_buffer_pool_hit_rate = {
+'description': "Buffer pool hit rate",
+'value_type':'float',
+'units': '',
+},
 )
 
 update_stats(REPORT_INNODB, REPORT_MASTER, REPORT_SLAVE) 


参考

[R]Mac で RGtk2 を使ってみようとして挫折した

$
0
0

ふと RGtk2 を使ってみたいと思い、公式ページのサンプルを動かせるようにしようと思ったんですができませんでした。

ハマりポイントがたくさんありすぎてあとちょっとのところまで行くのでさえ大変だったんで、他の方がエラーメッセージでググった時に解決の糸口になればと思い備忘録を載せたいと思います。


環境

  • Mac OS X 10.6.8
  • R 2.15.0
  • gtk+ 2.24.6, 2.24.11
  • pango 1.30.1

インストール手順

gtk+ のインストール

Homebrew でインストールします。自分の場合、ここはすんなり行きました。(2.24.6 が既に入っていました)

$ brew install gtk+

次のコマンドを実行してエラーにならないことを確認します。

$ pkg-config gtk+-2.0 --libs --cflags
-D_REENTRANT -I/usr/X11/include/cairo ...

次のようにエラーになった場合は PKG_CONFIG_PATH の問題が考えられます。

$ pkg-config gtk+-2.0 --libs --cflags
Package cairo was not found in the pkg-config search path.
Perhaps you should add the directory containing `cairo.pc'
to the PKG_CONFIG_PATH environment variable
Package 'cairo', required by 'Pango Cairo', not found

/usr/X11/lib/pkgconfig を PKG_CONFIG_PATH に追加して再度実行してみてください。

$ export PKG_CONFIG_PATH=/usr/X11/lib/pkgconfig:$PKG_CONFIG_PATH

ちなみに、上記エラーを放置した状態で RGtk2 をインストールしようとすると gtk+ をインストールしているはずが次のように "checking for GTK... no" となってインストールに失敗します。

> install.packages("RGtk2")
trying URL 'http://cran.md.tsukuba.ac.jp/src/contrib/RGtk2_2.20.25.tar.gz'
Content type 'application/x-gzip' length 2784794 bytes (2.7 Mb)
opened URL
==================================================
downloaded 2.7 Mb

* installing *source* package ‘RGtk2’ ...
** package ‘RGtk2’ successfully unpacked and MD5 sums checked
checking for pkg-config... /usr/local/bin/pkg-config
checking pkg-config is at least version 0.9.0... yes
checking for INTROSPECTION... no
checking for GTK... no
configure: error: GTK version 2.8.0 required
ERROR: configuration failed for package ‘RGtk2’
* removing ‘/usr/local/Cellar/r/2.15.0/R.framework/Versions/2.15/Resources/library/RGtk2’
* restoring previous ‘/usr/local/Cellar/r/2.15.0/R.framework/Versions/2.15/Resources/library/RGtk2’

The downloaded source packages are in
‘/private/var/folders/PJ/PJZlSDgCGLaa7w4IKx3MxU+++TI/-Tmp-/Rtmprsju4V/downloaded_packages’
Warning message:
In install.packages("RGtk2") :
  installation of package ‘RGtk2’ had non-zero exit status

gtk+ を使ったサンプルが動くことを確認します。

// hellow_gtk.c
#include <gtk/gtk.h>

int main(int argc, char *argv[])
{
    GtkWidget *window;
    GtkWidget *label;

    gtk_init(&argc, &argv);

    window = gtk_window_new(GTK_WINDOW_TOPLEVEL);
    label = gtk_label_new("Hello World!\n");
    gtk_container_add(GTK_CONTAINER(window), label);

    gtk_widget_show(label);
    gtk_widget_show  (window);

    gtk_main ();

    return(0);
}

コンパイル&実行します。

$ gcc -o hello_gtk hello_gtk.c $(pkg-config gtk+-2.0 --libs --cflags)
./hello_gtk
Xlib:  extension "RANDR" missing on display "/tmp/launch-tHpkdy/org.x:0".


X11 が起動して "Hello World!" が表示されていれば成功です。

変な warning が気になりますが、https://trac.macports.org/wiki/FAQ#randr:ここ]にあるように、Snow Leopard の X11 server ではこの warning が出るのは仕方ないらしいです。


ここでもし次のようなエラーが出れば、文字通り /usr/local/lib/libpixman-1.0.dylib が存在しないのが原因なのでリンクを張る必要があります。

$ ./hello_gtk 
dyld: Library not loaded: /usr/local/lib/libpixman-1.0.dylib
  Referenced from: /usr/local/opt/cairo/lib/libcairo.2.dylib
  Reason: image not found

次のコマンドを実行すればリンクが張れます。

$ brew link pixman

ちなみに、上記エラーを放置した状態で RGtk2 をロードしようとすると gtk+ のインストールを促されます。

> library(RGtk2)
Error in dyn.load(file, DLLpath = DLLpath, ...) : 
  unable to load shared object '/usr/local/Cellar/r/2.15.0/R.framework/Versions/2.15/Resources/library/RGtk2/libs/RGtk2.so':
  dlopen(/usr/local/Cellar/r/2.15.0/R.framework/Versions/2.15/Resources/library/RGtk2/libs/RGtk2.so, 6): Library not loaded: /usr/local/lib/libpixman-1.0.dylib
  Referenced from: /usr/local/opt/cairo/lib/libcairo.2.dylib
  Reason: image not found
Loading Tcl/Tk interface ... done


ところで、リンクを張っていない(ファイルが存在しない)状態で Homebrew で cairo をインストールしても次のように /usr/local/lib/libpixman-1.0.dylib にリンクされているのが甚だ疑問なんですが・・・そういうものなんですか?

$ otool -L /usr/local/opt/cairo/lib/libcairo.2.dylib
/usr/local/opt/cairo/lib/libcairo.2.dylib:
/usr/local/opt/cairo/lib/libcairo.2.dylib (compatibility version 11203.0.0, current version 11203.2.0)
/usr/local/lib/libpixman-1.0.dylib (compatibility version 27.0.0, current version 27.2.0)
/usr/X11/lib/libfontconfig.1.dylib (compatibility version 6.0.0, current version 6.3.0)
/usr/X11/lib/libfreetype.6.dylib (compatibility version 14.0.0, current version 14.2.0)
/usr/X11/lib/libpng12.0.dylib (compatibility version 47.0.0, current version 47.0.0)
/usr/X11/lib/libXrender.1.dylib (compatibility version 5.0.0, current version 5.0.0)
/usr/X11/lib/libX11.6.dylib (compatibility version 9.0.0, current version 9.0.0)
/usr/lib/libz.1.dylib (compatibility version 1.0.0, current version 1.2.3)
/System/Library/Frameworks/ApplicationServices.framework/Versions/A/ApplicationServices (compatibility version 1.0.0, current version 38.0.0)
/usr/lib/libSystem.B.dylib (compatibility version 1.0.0, current version 125.2.11)
/System/Library/Frameworks/CoreFoundation.framework/Versions/A/CoreFoundation (compatibility version 150.0.0, current version 550.44.0)

最後に、RGtk2 をインストールします。

$ R
> install.packages("RGtk2")

以上のインストール手順で、RGtk2 を使って Hello World を実行することはできるはずです。demo も動くと思います。



cairoDevice が使えない!

目標は公式ページにある次のようなサンプルを動かすことだったんですが、結局できませんでした。

> library(RGtk2)
> library(cairoDevice)
> win = gtkWindow()
> da = gtkDrawingArea()
> win$add(da)
> asCairoDevice(da)
> plot(1:10)

最初に遭遇したクラッシュは次のような感じです。gtk+ のバージョンは 2.24.6 です。

$ ulimit -c unlimited
$ R
> library(RGtk2)
Xlib:  extension "RANDR" missing on display "/tmp/launch-tHpkdy/org.x:0".
> library(cairoDevice)
> win = gtkWindow()
> da = gtkDrawingArea()
> win$add(da)
> asCairoDevice(da)

(R:62460): Pango-CRITICAL **: pango_context_get_metrics: assertion `PANGO_IS_CONTEXT (context)' failed

(R:62460): Pango-CRITICAL **: pango_font_metrics_get_ascent: assertion `metrics != NULL' failed

(R:62460): Pango-CRITICAL **: pango_font_metrics_get_descent: assertion `metrics != NULL' failed

(R:62460): Pango-CRITICAL **: pango_font_metrics_get_approximate_char_width: assertion `metrics != NULL' failed
[1] TRUE
> plot(1:10)

 *** caught segfault ***
address 0x10, cause 'memory not mapped'

Traceback:
 1: axis(side = side, at = at, labels = labels, ...)
 2: Axis.default(...)
 3: Axis(...)
 4: localAxis(if (is.null(y)) xy$x else x, side = 1, ...)
 5: plot.default(1:10)
 6: plot(1:10)

Possible actions:
1: abort (with core dump, if enabled)
2: normal R exit
3: exit R without saving workspace
4: exit R saving workspace
Selection: 1
aborting ...
Segmentation fault (core dumped)

backtrace は次のような感じです。

$ gdb R /cores/core.62460
...
(gdb) bt
#0  0x0000000104fd3fce in cairo_get_target ()
#1  0x00000001027e9475 in pango_cairo_create_context ()
#2  0x00000001027e94ac in pango_cairo_create_layout ()
#3  0x000000010558a8cd in layoutText ()
#4  0x000000010558a935 in text_extents ()
#5  0x000000010558abaa in Cairo_StrWidth ()
#6  0x0000000100076160 in GEStrWidth ()
#7  0x00000001000af780 in Rf_GStrWidth ()
#8  0x0000000100106ad5 in do_axis ()
#9  0x00000001000d6447 in do_internal ()
#10 0x000000010008cc28 in bcEval ()
#11 0x0000000100093b0f in Rf_eval ()
#12 0x00000001000983d5 in Rf_applyClosure ()
#13 0x000000010008cc55 in bcEval ()
#14 0x0000000100093b0f in Rf_eval ()
#15 0x00000001000983d5 in Rf_applyClosure ()
#16 0x00000001000d9123 in Rf_usemethod ()
#17 0x00000001000d9456 in do_usemethod ()
#18 0x000000010008cf4d in bcEval ()
#19 0x0000000100093b0f in Rf_eval ()
#20 0x00000001000983d5 in Rf_applyClosure ()
#21 0x000000010008cc55 in bcEval ()
#22 0x0000000100093b0f in Rf_eval ()
#23 0x00000001000983d5 in Rf_applyClosure ()
#24 0x000000010008cc55 in bcEval ()
#25 0x0000000100093b0f in Rf_eval ()
#26 0x00000001000983d5 in Rf_applyClosure ()
#27 0x00000001000d9123 in Rf_usemethod ()
#28 0x00000001000d9456 in do_usemethod ()
#29 0x000000010008cf4d in bcEval ()
#30 0x0000000100093b0f in Rf_eval ()
#31 0x00000001000983d5 in Rf_applyClosure ()
#32 0x0000000100093f94 in Rf_eval ()
#33 0x00000001000c5e51 in Rf_ReplIteration ()
#34 0x00000001000c5fca in R_ReplConsole ()
#35 0x00000001000c648c in run_Rmainloop ()
#36 0x0000000100000e8b in main ()

cairo_get_target で落ちているようです。そもそも、asCairoDevice のところで出ている "pango_context_get_metrics: assertion `PANGO_IS_CONTEXT (context)' failed" という warning が気になります。

これは pango-context.c で定義されている pango_context_get_metrics で context としておかしな値が渡されたことを意味しています。g_return_val_if_fail のところです。

PangoFontMetrics *
pango_context_get_metrics (PangoContext                 *context,
   const PangoFontDescription   *desc,
   PangoLanguage                *language)
{
  PangoFontset *current_fonts = NULL;
  PangoFontMetrics *metrics;
  const char *sample_str;
  GList *items;

  g_return_val_if_fail (PANGO_IS_CONTEXT (context), NULL);

  if (!desc)
    desc = context->font_desc;

  if (!language)
    language = context->language;

  current_fonts = pango_font_map_load_fontset (context->font_map, context, desc, language);
  metrics = get_base_metrics (current_fonts);

  sample_str = pango_language_get_sample_string (language);
  items = itemize_with_font (context, sample_str, 0, strlen (sample_str), desc);

  update_metrics_from_items (metrics, language, sample_str, items);

  g_list_foreach (items, (GFunc)pango_item_free, NULL);
  g_list_free (items);

  g_object_unref (current_fonts);

  return metrics;
}

というわけで、cairoDevice が怪しいです。cairoDevice 単独で使ってみると見事に落ちました。

> library(cairoDevice)
Xlib:  extension "RANDR" missing on display "/tmp/launch-tHpkdy/org.x:0".
> Cairo()

(R:62510): Pango-CRITICAL **: pango_context_get_metrics: assertion `PANGO_IS_CONTEXT (context)' failed

(R:62510): Pango-CRITICAL **: pango_font_metrics_get_ascent: assertion `metrics != NULL' failed

(R:62510): Pango-CRITICAL **: pango_font_metrics_get_descent: assertion `metrics != NULL' failed

(R:62510): Pango-CRITICAL **: pango_font_metrics_get_approximate_char_width: assertion `metrics != NULL' failed
> plot(1:10)

 *** caught segfault ***
address 0x10, cause 'memory not mapped'

Traceback:
 1: axis(side = side, at = at, labels = labels, ...)
 2: Axis.default(...)
 3: Axis(...)
 4: localAxis(if (is.null(y)) xy$x else x, side = 1, ...)
 5: plot.default(1:10)
 6: plot(1:10)

Possible actions:
1: abort (with core dump, if enabled)
2: normal R exit
3: exit R without saving workspace
4: exit R saving workspace
Selection: 1
aborting ...
Segmentation fault (core dumped)

backtrace は次のように同じになりました。(アドレスまで一緒になるものなんですか?)

(gdb) bt
#0  0x0000000104a12fce in cairo_get_target ()
#1  0x00000001027da475 in pango_cairo_create_context ()
#2  0x00000001027da4ac in pango_cairo_create_layout ()
#3  0x00000001027ac8cd in layoutText ()
#4  0x00000001027ac935 in text_extents ()
#5  0x00000001027acbaa in Cairo_StrWidth ()
#6  0x0000000100076160 in GEStrWidth ()
#7  0x00000001000af780 in Rf_GStrWidth ()
#8  0x0000000100106ad5 in do_axis ()
#9  0x00000001000d6447 in do_internal ()
#10 0x000000010008cc28 in bcEval ()
#11 0x0000000100093b0f in Rf_eval ()
#12 0x00000001000983d5 in Rf_applyClosure ()
#13 0x000000010008cc55 in bcEval ()
#14 0x0000000100093b0f in Rf_eval ()
#15 0x00000001000983d5 in Rf_applyClosure ()
#16 0x00000001000d9123 in Rf_usemethod ()
#17 0x00000001000d9456 in do_usemethod ()
#18 0x000000010008cf4d in bcEval ()
#19 0x0000000100093b0f in Rf_eval ()
#20 0x00000001000983d5 in Rf_applyClosure ()
#21 0x000000010008cc55 in bcEval ()
#22 0x0000000100093b0f in Rf_eval ()
#23 0x00000001000983d5 in Rf_applyClosure ()
#24 0x000000010008cc55 in bcEval ()
#25 0x0000000100093b0f in Rf_eval ()
#26 0x00000001000983d5 in Rf_applyClosure ()
#27 0x00000001000d9123 in Rf_usemethod ()
#28 0x00000001000d9456 in do_usemethod ()
#29 0x000000010008cf4d in bcEval ()
#30 0x0000000100093b0f in Rf_eval ()
#31 0x00000001000983d5 in Rf_applyClosure ()
#32 0x0000000100093f94 in Rf_eval ()
#33 0x00000001000c5e51 in Rf_ReplIteration ()
#34 0x00000001000c5fca in R_ReplConsole ()
#35 0x00000001000c648c in run_Rmainloop ()
#36 0x0000000100000e8b in main ()

そもそも pango のサンプルが動かないんじゃないかと思って、こちらのページのサンプルコードを試してみました。

// cairosimple.c
#include <math.h>
#include <pango/pangocairo.h>

static void
draw_text (cairo_t *cr)
{
#define RADIUS 150
#define N_WORDS 10
#define FONT "Sans Bold 27"

  PangoLayout *layout;
  PangoFontDescription *desc;
  int i;

  /* Center coordinates on the middle of the region we are drawing
   */
  cairo_translate (cr, RADIUS, RADIUS);

  /* Create a PangoLayout, set the font and text */
  layout = pango_cairo_create_layout (cr);
  
  pango_layout_set_text (layout, "Text", -1);
  desc = pango_font_description_from_string (FONT);
  pango_layout_set_font_description (layout, desc);
  pango_font_description_free (desc);

  /* Draw the layout N_WORDS times in a circle */
  for (i = 0; i < N_WORDS; i++)
    {
      int width, height;
      double angle = (360. * i) / N_WORDS;
      double red;

      cairo_save (cr);

      /* Gradient from red at angle == 60 to blue at angle == 240 */
      red   = (1 + cos ((angle - 60) * G_PI / 180.)) / 2;
      cairo_set_source_rgb (cr, red, 0, 1.0 - red);

      cairo_rotate (cr, angle * G_PI / 180.);
    
      /* Inform Pango to re-layout the text with the new transformation */
      pango_cairo_update_layout (cr, layout);
    
      pango_layout_get_size (layout, &width, &height);
      cairo_move_to (cr, - ((double)width / PANGO_SCALE) / 2, - RADIUS);
      pango_cairo_show_layout (cr, layout);

      cairo_restore (cr);
    }

  /* free the layout object */
  g_object_unref (layout);
}

int main (int argc, char **argv)
{
  cairo_t *cr;
  char *filename;
  cairo_status_t status;
  cairo_surface_t *surface;

  if (argc != 2)
    {
      g_printerr ("Usage: cairosimple OUTPUT_FILENAME\n");
      return 1;
    }

  filename = argv[1];

  surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32,
2 * RADIUS, 2 * RADIUS);
  cr = cairo_create (surface);
  

  cairo_set_source_rgb (cr, 1.0, 1.0, 1.0);
  cairo_paint (cr);
  draw_text (cr);
  cairo_destroy (cr);
  
  status = cairo_surface_write_to_png (surface, filename);
  cairo_surface_destroy (surface);

  if (status != CAIRO_STATUS_SUCCESS)
    {
      g_printerr ("Could not save png to '%s'\n", filename);
      return 1;
    }

  return 0;
}

コンパイル&実行してみます。

$ gcc -g -o cairosimple cairosimple.c $(pkg-config pangocairo --cflags --libs)
$ ./cairosimple text.png
Segmentation fault (core dumped)

落ちました・・・。R と同じところで落ちてるみたいです。

(gdb) bt
#0  0x000000010026dfce in cairo_get_target ()
#1  0x0000000100004475 in pango_cairo_create_context ()
#2  0x00000001000044ac in pango_cairo_create_layout ()
#3  0x000000010000095b in draw_text (cr=0x10070ddf0) at cairosimple.c:21
#4  0x0000000100000c5d in main (argc=2, argv=0x7fff5fbfecf8) at cairosimple.c:80

デバッグオプションを付けて pango をビルドしたいと思ったんで、Homebrew と同じバージョンのものを自前で同じような感じでビルドしてインストールしてみました。

$ wget http://ftp.gnome.org/pub/GNOME/sources/pango/1.30/pango-1.30.1.tar.xz
$ gtar xf pango-1.30.1.tar.xz 
$ ./configure --enable-debug=yes --disable-dependency-tracking --disable-introspection --with-x
...
$ make
$ make install

再度サンプルを実行してみます。

$ gcc -g -o cairosimple cairosimple.c $(pkg-config pangocairo --cflags --libs)
$ ./cairosimple text.png

正常に出力されたようです・・・。意味不明です。

これなら R でもうまく動くかなぁと思って試してみます。

> library(cairoDevice)
Xlib:  extension "RANDR" missing on display "/tmp/launch-tHpkdy/org.x:0".
> Cairo()

(R:77993): Pango-CRITICAL **: pango_context_get_metrics: assertion `PANGO_IS_CONTEXT (context)' failed

(R:77993): Pango-CRITICAL **: pango_font_metrics_get_ascent: assertion `metrics != NULL' failed

(R:77993): Pango-CRITICAL **: pango_font_metrics_get_descent: assertion `metrics != NULL' failed

(R:77993): Pango-CRITICAL **: pango_font_metrics_get_approximate_char_width: assertion `metrics != NULL' failed
> plot(1:10)

クラッシュしませんが、次のように Index という文字しか表示されません・・・。

f:id:a_bicky:20121014134348p:image:w300


なんかよくわからなかったんで gtk+ のバージョンを上げたら直るかな?と思い、Homebrew を update して 2.24.11 に上げたら次のような感じでクラッシュします。

> library(cairoDevice)
Xlib:  extension "RANDR" missing on display "/tmp/launch-tHpkdy/org.x:0".
> Cairo()

 *** caught segfault ***
address 0x375000001a0, cause 'memory not mapped'

Traceback:
 1: .C("do_Cairo", as.numeric(width), as.numeric(height), as.numeric(pointsize),     as.character(surface_info), PACKAGE = "cairoDevice")
 2: Cairo()

Possible actions:
1: abort (with core dump, if enabled)
2: normal R exit
3: exit R without saving workspace
4: exit R saving workspace
Selection: 1
aborting ...
Segmentation fault (core dumped)

backtrace は次のようになっていました。

(gdb) bt
#0  0x0000000104274341 in _cairo_gstate_save ()
#1  0x0000000104271048 in cairo_save ()
#2  0x00000001027ac167 in initDevice ()
#3  0x00000001027ac1db in realize_event ()
#4  0x000000010441d9e6 in g_closure_invoke ()
#5  0x000000010442c0de in signal_emit_unlocked_R ()
#6  0x000000010442d03d in g_signal_emit_valist ()
#7  0x000000010442d62d in g_signal_emit ()
#8  0x0000000103ddbe7d in gtk_widget_realize ()
#9  0x0000000103ddc9aa in gtk_widget_map ()
#10 0x0000000103de74e1 in gtk_window_map ()
#11 0x000000010441d874 in _g_closure_invoke_va ()
#12 0x000000010442ca81 in g_signal_emit_valist ()
#13 0x000000010442d62d in g_signal_emit ()
#14 0x0000000103ddc9bc in gtk_widget_map ()
#15 0x0000000103de7431 in gtk_window_show ()
#16 0x000000010441d874 in _g_closure_invoke_va ()
#17 0x000000010442ca81 in g_signal_emit_valist ()
#18 0x000000010442d62d in g_signal_emit ()
#19 0x0000000103ddc55d in gtk_widget_show ()
#20 0x00000001027ae2b5 in createCairoDevice ()
#21 0x00000001027ab7c5 in initCairoDevice ()
#22 0x00000001027aba37 in do_Cairo ()
#23 0x0000000100067b0f in do_dotCode ()
#24 0x0000000100093ed6 in Rf_eval ()
#25 0x00000001000955b2 in do_begin ()
#26 0x0000000100093d68 in Rf_eval ()
#27 0x00000001000983d5 in Rf_applyClosure ()
#28 0x0000000100093f94 in Rf_eval ()
#29 0x00000001000c5e51 in Rf_ReplIteration ()
#30 0x00000001000c5fca in R_ReplConsole ()
#31 0x00000001000c648c in run_Rmainloop ()
#32 0x0000000100000e8b in main ()

これは Homebrew でインストールした pango を使っても起こります。

RGtk2 のサンプルの方はというと、次のようにクラッシュします。

> library(RGtk2)
Xlib:  extension "RANDR" missing on display "/tmp/launch-tHpkdy/org.x:0".
> library(cairoDevice)
> win = gtkWindow()
> da = gtkDrawingArea()
> win$add(da)
> asCairoDevice(da)
[1] TRUE
> plot(1:10)
R(62131,0x7fff7090dcc0) malloc: *** error for object 0x4024000000000000: pointer being freed was not allocated
*** set a breakpoint in malloc_error_break to debug
Abort trap (core dumped)

解放するメモリがないのに解放しようとすると起きるエラーみたいです。実際 backtrace を見るとこんな感じです。

(gdb) bt
#0  0x00007fff84dc40b6 in __kill ()
#1  0x00007fff84e649f6 in abort ()
#2  0x00007fff84d7c195 in free ()
#3  0x000000010482ae8a in _cairo_stroke_style_fini ()
#4  0x00000001048171f7 in _cairo_gstate_fini ()
#5  0x0000000104812325 in cairo_destroy ()
#6  0x0000000104fce08c in initDevice ()
#7  0x0000000104fcfb9e in Cairo_NewPage ()
#8  0x00000001000b35be in Rf_GNewPlot ()
#9  0x00000001000f8459 in do_plot_new ()
#10 0x000000010008cd88 in bcEval ()
#11 0x0000000100093b0f in Rf_eval ()
#12 0x00000001000983d5 in Rf_applyClosure ()
#13 0x000000010008cc55 in bcEval ()
#14 0x0000000100093b0f in Rf_eval ()
#15 0x00000001000983d5 in Rf_applyClosure ()
#16 0x00000001000d9123 in Rf_usemethod ()
#17 0x00000001000d9456 in do_usemethod ()
#18 0x000000010008cf4d in bcEval ()
#19 0x0000000100093b0f in Rf_eval ()
#20 0x00000001000983d5 in Rf_applyClosure ()
#21 0x0000000100093f94 in Rf_eval ()
#22 0x00000001000c5e51 in Rf_ReplIteration ()
#23 0x00000001000c5fca in R_ReplConsole ()
#24 0x00000001000c648c in run_Rmainloop ()
#25 0x0000000100000e8b in main ()

この辺でさすがに心が折れました・・・。

何時間もかけてまで使ってみたいものじゃなかったんでこのまま放置します。

誰か「こんな感じで問題を調査すれば原因を特定できるよ!」とか「こうすればサンプルが動いたよ!」というエントリーを書いてくれることを期待してます!!

[Emacs]Emacs から PlantUML を使ってみた

$
0
0

先日、シーケンス図を書くためのツールとして PlantUML というのを教えていただきました。

その時は Eclipse プラグインを使ったんですが、Emacs ユーザの端くれとしては Emacs から使いたいよねってことで使えるようにしてみました。


Emacs と PlantUML の連携方法に関しては一応ここに載っているんですが、リンク先に目を通してすぐ使える人はあまりいないのではないかと思います。


PlantUML のインストール

まずは PlantUML をインストールします。jar ファイルはお好きなところに配置してください。私は /usr/local/jars というディレクトリを作成してそこに配置することにしました。

$ wget http://sourceforge.net/projects/plantuml/files/plantuml.jar/download -O plantuml.jar
$ mkdir /usr/local/jars
$ mv plantuml.jar /usr/local/jars/
$ java -jar /usr/local/jars/plantuml.jar -version
PlantUML version 7937 (Fri Oct 12 02:12:59 JST 2012)
Java(TM) SE Runtime Environment
Java HotSpot(TM) 64-Bit Server VM
1.6.0_35-b10-428-10M3811
Mac OS X

シーケンス図を書くだけであれば不要なんですが、Graphviz も必要みたいです。インストールしていない方はインストールしておくと良いかもしれません。Mac だと Homebrew からインストールできます。

$ brew install graphviz

試しに次の内容ファイルからシーケンス図を作成してみます。

sample.uml

@startuml
Alice -> Bob: Authentication Request
Bob --> Alice: Authentication Response
@enduml

次のコマンドを実行することでカレントディレクトリに sample.png が作成されるはずです。

$ java -jar /usr/local/jars/plantuml.jar sample.uml

sample.png

f:id:a_bicky:20121016032130p:image


org-mode でシーケンス図を書いてみる

Emacs 24.1 から ob-plantuml.el が組み込みになっているようなので、Emacs 24.1 以降を使っていればインストール不要です。

古い Emacs を使っている場合はここのコードをコピペして ob-plantuml.el を作成する必要があります。ちなみに Emacs 24.1 以降ではこちらのコードだと所望の結果が得られません*1。org-babel-execute:plantuml の返り値を nil にする必要があります。


org-mode の設定として最低限次のような内容を記述しておけば動くと思います。

;; org-plantuml-jar-path は plantuml.jar へのパス
(setq org-plantuml-jar-path "/usr/local/jars/plantuml.jar")
(defun org-mode-init ()
  (org-babel-do-load-languages
   'org-babel-load-languages
   (add-to-list 'org-babel-load-languages '(plantuml . t))))
(add-hook 'org-mode-hook 'org-mode-init)

では早速 Emacs を起動してシーケンス図を書いてみます。Emacs を起動したら M-x org-mode を実行して org-mode にします。

PlantUML のコードを #+BEGIN_SRC plantuml と #+END_SRC の間に記述します。#+BEGIN_SRC plantuml の後には出力ファイル名を :file に、PlantUML の実行時の追加オプションを :cmdline に記述します。コードが書き終わったらコードブロック内で C-c C-c を押して PlantUML を実行します。この辺の説明が意味不明な方は org-babel で調べてください。


例えば次のような内容を UTF-8 で記述した場合は sample2.png が作成されるはずです。

--no-window-system で起動しなければ Emacs 内に画像が表示されるのではないかと思いますが、普段 --no-window-system で使ってるし、そうでない場合は何故か Emacs がすぐに落ちるんで確認できていません・・・

#+BEGIN_SRC plantuml :file sample2.png :cmdline -charset UTF-8
  アリス -> ボブ: Authentication Request
  ボブ --> アリス: Authentication Response
#+END_SRC

sample2.png

f:id:a_bicky:20121016035753p:image


org-babel ではコードブロック内で C-c ' を押すことで現在記述している言語のメジャーモードで編集することができます。PlantUML 用のメジャーモードとして plantuml-mode も存在するので、色付けなどしてほしい方は導入すると良いかもしれません。


plantuml-mode でシーケンス図を書いてみる

org-mode でもいいんですが、PlantUML だけを使いたいのにいちいち org-mode を使うのもなんかあれですし、何よりも #+BEGIN_SRC とかいうわけのわからないコードが入ると他の人とコードを共有しにくくなるので plantuml-mode をちょっと拡張してみました。


次のコードを init.el に記述することで、拡張子が uml のファイルを plantuml-mode で開いて、C-c C-c を押すことで編集中のファイルに対して PlantUML を実行することになります。適当に書いてるんで、好みに合わせて修正してください。

ちなみに Emacs 内に画像を表示する機能はありません・・・

(add-to-list 'auto-mode-alist '("\\.uml\\'" . plantuml-mode))
(autoload 'plantuml-mode "plantuml-mode" "PlantUML mode" t)

(defun plantuml-execute ()
  (interactive)
  (when (buffer-modified-p)
    (map-y-or-n-p "Save this buffer before executing PlantUML?"
                  'save-buffer (list (current-buffer))))
  (let ((code (buffer-string))
        out-file
        cmd)
    (when (string-match "^\\s-*@startuml\\s-+\\(\\S-+\\)\\s*$" code)
      (setq out-file (match-string 1 code)))
    (setq cmd (concat
               "java -jar " plantuml-java-options " "
               (shell-quote-argument plantuml-jar-path) " "
               (and out-file (concat "-t" (file-name-extension out-file))) " "
               plantuml-options " "
               (buffer-file-name)))
    (message cmd)
    (shell-command cmd)
    (message "done")))

(setq plantuml-jar-path "/usr/local/jars/plantuml.jar")
(setq plantuml-java-options "")
(setq plantuml-options "-charset UTF-8")
(setq plantuml-mode-map
      (let ((map (make-sparse-keymap)))
        (define-key map (kbd "C-c C-c") 'plantuml-execute)
        map))

PlantUML の書き方をもっと知りたい!

次の情報源が良さそうです。

各図に対してとっつきやすく説明してある気がします

やっぱり公式ドキュメントを読むのが一番だと思います




以上、Emacs 内に画像を表示できないとあまり意味ない気もしますが Emacs と PlantUML を連携するお話でした!

*1:かと言って Emacs 23 だとそのままで動くかは未確認です

[JSX]JSX のプロファイラが素晴らしい!

$
0
0

個人的にとても便利だと思っているけどあまり注目されていない JSX の機能にプロファイラがあります。


PC ブラウザであれば今時のブラウザはプロファイラがあるので、使うことはあまりないかもしれませんが、iOS5 の MobileSafari とかでプロファイルを取りたい場合には重宝すると思います。*1

今までは独自のプロファイラを使って、処理の重そうな関数に対してコール回数と処理時間(関数全体を処理するのにかかった時間と関数内で呼ばれた関数の処理時間を除いた時間)を測定していましたが、JSX のプロファイラを使うようになって全ての関数に対して詳細な情報を得られるようになったので劇的に作業効率が上がりました。


というわけで、Box2D.jsx のサンプルを使ってプロファイラの使い方を解説したいと思います。


Box2D.jsx のプロファイルを取ってみる

box2djsx のリポジトリを clone してプロファイラを有効にしてコンパイルするまでの流れは次のとおりです。コンパイルオプションには --profile を付けます。

ここで注意したいのは、必ず --release オプションも一緒に付けることです。--release オプションを付けないとインライン展開がされない上に、余計な処理が入るのでプロファイル結果としてはあまり参考にならない結果になります。

$ git clone https://github.com/tkihira/box2djsx.git
$ cd box2djsx/
$ git checkout kazuho/optimize
$ sed -i '' 's/kazuho/jsx/' .gitmodules  # JSX の URL を変更
$ git submodule update --init
$ cd JSX
$ git checkout master
$ cd ..
$ JSX/bin/jsx --add-search-path src --release --profile src/box2d.jsx > sample/box2d.js

プロファイラ用のサーバを起動します。自前でプロファイル結果を受け取るサーバを用意しなくて良いので楽ですね。

$ cd JSX
$ make server
node web/server.js
Open http://localhost:5000/


sample/box2djsx.html を次のように書き換えて、タッチイベントによってプロファイル結果をサーバに対して送信するようにします。サーバは make server を実行したサーバを指定するので、HTML ファイルのあるサーバと make server を実行したサーバが違えば JSX.postProfileResults の引数は変える必要があります。

<!doctype html>
<html><head><title>box2d sample</title>
<meta name="viewport" content="width=320px" />
<script src="box2d.js"></script>
<script>
window.addEventListener("load", function(e) {
var sample = JSX.require("src/box2d.jsx");
sample._Main.main$();
});

window.addEventListener("touchstart", function(e) {
if (JSX.profilerIsRunning()) {
JSX.postProfileResults(location.origin + ":5000/post-profile");
}
});
</script>
</head>
<body style="margin:0;position:relative;"><canvas id="canvas" width="320" height="400"></canvas>
<div id="fps" style="position:absolute;top:0;left:0;color:#f00">test</div>
</body>
</html>

sample ディレクトリを移動するなりシンボリックリンクを張るなりしてブラウザからアクセスできるようにします。

MobileSafari から sample/box2djsx.html にアクセスして、プロファイリングを終了したいところでタッチすれば結果がサーバに送信されます。

結果が送信されたら、http://localhost:5000/web/profiler.html にアクセスして、Results から結果を選択します。

私が iPhone 4S (iOS 5.1.1) の MobileSafari で測定したら次のようになりました。

画像じゃないのでいろいろクリックしてみるとおもしろいと思います。


結果の見方ですが、Count はその関数が呼ばれた回数、Inclusive はその関数全体の処理にかかった時間、Exclusive は Inclusive から他の関数の処理時間を除いた時間です。

例えば、処理単位でボトルネックを調べる場合は Mode を "Call Tree" にして Inclusive に注目すれば良いでしょう。

関数単位でボトルネックを調べる場合は Mode を "Functions" にして Exclusive でソート(「Exclusive(ms)」をクリック)すれば簡単に調べられます。


ボトルネックがわかったら、あの手この手でチューニングして高速化することになります。


プロファイル結果のリセットの仕方

--profile オプションを付けると最初からプロファイルが開始されますが、特定の箇所に対してプロファイルを取りたい場合もあるかと思います。そんな時は JSX.resetProfileResults を使います。

例えば次のように書くと、1回目のタッチでプロファイラをリセットして、2回目のタッチでプロファイル結果を送信することができます。

(function() {
var profilerIsRunning = false;
window.addEventListener("touchstart", function(e) {
if (JSX.profilerIsRunning()) {
if (profilerIsRunning) {
JSX.postProfileResults(location.origin + ":5000/post-profile");
console.log("profile end");
} else {
console.log("profile start");
JSX.resetProfileResults();
}
profilerIsRunning = !profilerIsRunning;
}
});
})();

実は JSX.resetProfileResults は私の要望がきっかけでできた機能なんですが、この機能がなかったらいろいろ破綻してただろうなぁと思うぐらい個人的に重宝している機能です。




以上、JSX のプロファイラの利用方法でした!

*1:iOS6 からは Web インスペクタを利用することでプロファイルを取ることができるようになりましたが使い勝手はあまり良くない気がします

Viewing all 163 articles
Browse latest View live