compact()を使って変数名付きのデバッグ出力を手軽に

PHPデバッグ出力をいくつも表示するとき、どの値がどの変数のものなのかわかるように、こんなふうに書く。(よね?)

var_export(array("long_var_name1" => $long_var_name1, ...));

ここでcompact()を使うと、

var_export(compact("long_var_name1", ...));

のように、タイプ数をかなり減らせる。うれしい。


参考: http://jp2.php.net/manual/ja/function.compact.php

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として不正な文字列を渡すと無視される。

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するしかなくなる。
そのため、無限ループしてても時間がたてば強制終了されるように、タイムアウトを設定したい。

実装

Luaのフックを使えば、タイムアウトを実現できる。

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%程度の速度低下で済んだ。
ただし、カウント値を大きくすればするほど、タイムアウト時刻の精度が落ちるので、注意。

制限

LuaJITの場合、タイムアウトされないことがあった。
JITコンパイルされたコードの中ではカウントフックが呼ばれないのかも。

最後に

実際に動くコードはここにあります。
https://github.com/atsumu/mylua

LuaJITのメモリアロケータを入れ替える(気をつけないとメモリリークする)

環境

  • Lua 5.1.4
  • LuaJIT 2.0.0-beta9

動機

MySQLのUDFの中でLuaを実行したい。
問題は2つ。

  • 1コネクションにつきLuaインスタンスを1つ作るので、max_connectionsで設定した数だけLuaインスタンスが同時に立ち上がる可能性がある。
  • 1つメモリを食いつぶすクエリがあるだけで、他のすべてのクエリまで影響を受けるのは避けたい。

ということで、Luaインスタンス毎にメモリ使用量の上限を設定したい。

実装

#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