compact()を使って変数名付きのデバッグ出力を手軽に
ssh先のホスト名をscreenのウィンドウ名にする設定
.ssh/configで設定できる。
Host hoge PermitLocalCommand yes LocalCommand tty -s && [ x$TERM = "xscreen" ] && echo -ne "\ek"%n"\e\\" >`tty`
これだけではログアウトしたときウィンドウ名が元に戻らないので、.bashrcなどにこの設定を追加する。
if [ x$TERM = "xscreen" ]; then PROMPT_COMMAND='echo -ne "\ek`echo $HOSTNAME`\e\\"' fi
fluentdのin_tailプラグインでjsonを読む
in_tailプラグインでformatにjsonを指定すると、jsonをそのまま(文字列にせずに)読める。
このように設定しておいて
<source> type tail path /tmp/error.log pos_file /tmp/error.log.pos tag debug.error format json </source> <match debug.**> type stdout </match>
ログファイルにjsonを書き込んで、
echo '{"hoge":"fuga"}' >>/tmp/error.log
fluentdの標準出力を見ると、オブジェクトのまま表示される。
2012-09-11 20:20:46 +0900 debug.error: {"hoge":"fuga"}
jsonとして不正な文字列を渡すと無視される。
fluent-agent-liteでタグにホスト名を使う
/etc/fluent-agent-lite.conf の
LOGS=$(cat <<"EOF" ... EOF )
を
LOGS=$(cat <<EOF debug.`hostname` /var/log/... EOF )
のように書き換えればOK
fluentdでunix domain socketを使う
fluentdでunix domain socketを使うには、設定ファイルに下記を追加する。
<source> type unix </source>
UNIXドメインソケットのパスは環境変数 FLUENT_SOCKET で指定する。
指定しなければ /var/run/fluent/fluent.sock に作られる。
env FLUENT_SOCKET=sock/fluent.sock fluentd -c fluent.conf
試しに投げてみる
echo '{"hoge":"fuga"}' | fluent-cat debug.piyo -u -s /var/run/fluent/fluent.sock
Luaをタイムアウト付きで実行する
環境
- Lua 5.1.4
- LuaJIT 2.0.0-beta9
動機
MySQLのUDFの中でLuaを実行したい。
このとき、無限ループするようなコードを実行すると、killクエリを投げても止まらないスレッドになってしまい、mysqldをkill -9するしかなくなる。
そのため、無限ループしてても時間がたてば強制終了されるように、タイムアウトを設定したい。
実装
startclock = os.clock() timeout = startclock + 3 debug.sethook(function () if os.clock() >= timeout then error('timeout') end end, '', 100000) while true do end
debug.sethookの第2引数に'c', 'r', 'l'以外を渡して、第3引数を渡すと、カウントフックを設定できる。
第3引数に渡した値がカウント値として設定される。
カウント値はLuaが命令を実行するたびにデクリメントされ、0になったときにカウントフックが呼ばれる。
一度カウントフックが呼ばれると、カウント値は第3引数の値にリセットされ、再度0になるまでデクリメントされる。
リファレンスマニュアルに書いてある、
count がゼロでなければ、フックは count 命令が実行されるたびに、その直後に呼ばれる。
With a count different from zero, the hook is called after every count instructions.
この説明は間違っているので注意。
パフォーマンス
カウント値が0でないときは、命令実行のたびにデクリメントが走るだけなので、それほど重くない。
debug.sethook関数に渡す第3引数を十分大きな値にしておけば、数%程度の速度低下ですむはず。(厳密には測ってないけど。)
MySQLのHandler APIを使ってタイムラインを取得するサンプルコードでは、1%~2%程度の速度低下で済んだ。
ただし、カウント値を大きくすればするほど、タイムアウト時刻の精度が落ちるので、注意。
最後に
実際に動くコードはここにあります。
https://github.com/atsumu/mylua
LuaJITのメモリアロケータを入れ替える(気をつけないとメモリリークする)
環境
- Lua 5.1.4
- LuaJIT 2.0.0-beta9
動機
実装
#include "lua.h" #include "lauxlib.h" #include "lualib.h" #include "luajit.h" typedef struct st_mydata { lua_Alloc old_allocf; void *old_allocd; size_t memory_usage; size_t memory_limit; } MYDATA; void *myallocf(void *ud, void *ptr, size_t osize, size_t nsize) { MYDATA *mydata = (MYDATA *)ud; mydata->memory_usage += nsize - osize; if (nsize != 0 && nsize > osize && mydata->memory_usage > mydata->memory_limit) { return NULL; } return mydata->old_allocf(mydata->old_allocd, ptr, osize, nsize); } int main() { MYDATA mydata = {0}; lua_State *lua = luaL_newstate(); if (lua); else return 1; mydata.old_allocf = lua_getallocf(lua, &mydata.old_allocd); lua_setallocf(lua, myallocf, &mydata); // ... lua_close(lua); return 0; }
myallocf関数が、自前で実装した、容量制限付きのメモリアロケータ。
仕組みは単純で、制限容量を超える場合にはNULLを返し、超えない場合には処理系組み込みのアロケータを呼び出すだけ。
Lua 5.1.4では正しく動作するけど、LuaJIT 2.0.0-beta9ではメモリリークする。
便宜上main関数になってるけど、実際にはUDFの中(=MySQLの1つのスレッドの中)でLuaインスタンスを生成・削除するので、メモリリークが問題になる。
原因
lj_state.cの155行目
static void close_state(lua_State *L) { // ... #ifndef LUAJIT_USE_SYSMALLOC if (g->allocf == lj_alloc_f) lj_alloc_destroy(g->allocd); else #endif g->allocf(g->allocd, G2GG(g), sizeof(GG_State), 0); }
allocf(とallocd)を差し替えたままlua_closeすると、元のallocdが開放されないので、メモリリークになる。
単純に、lua_closeする前に、元のallocfに戻しておけばいい。
getallocfやsetallocfは例外を投げない。
int main() { MYDATA mydata = {0}; lua_State *lua = luaL_newstate(); if (lua); else return 1; mydata.old_allocf = lua_getallocf(lua, &mydata.old_allocd); lua_setallocf(lua, myallocf, &mydata); // ... lua_setallocf(lua, mydata.old_allocf, mydata.old_allocd); // 追加 lua_close(lua); return 0; }
最後に
MySQLに対して、Luaのコードを投げてHandler APIを叩けるUDFを作ってます。
クエリのサンプルはこんな感じ。
SELECT mylua(' local t = {}; mylua.init_table("localuser", "timeline", "user_id", "user_id", "status_id"); mylua.index_read_map(mylua.HA_READ_KEY_OR_NEXT, mylua.arg.uid, mylua.arg.sid); table.insert(t, { mylua.val_int("user_id"), mylua.val_int("status_id") }); mylua.index_next(); table.insert(t, { mylua.val_int("user_id"), mylua.val_int("status_id") }); return t; ', '{"uid":3,"sid":32}');
まだ本番では使っていませんが、だいぶ安定してきたので、興味がある方はどうぞ。githubで公開しています。
https://github.com/atsumu/mylua