素朴な疑問、$0を書き換えてApacheが処理してるURLを可視化するテクについて

いろいろ調べて個人的には勉強になったけど、あまりに雑多なメモなので、大抵の人には参考にならないと思います。
PragDave: See Rails request paths in 'top'の技なんだけど、Rubyで書かれてるmongrel限定で、multi-threadの環境だったらダメなんだろうな。

で、pre-forkのApacheなら、PHPとかでもコレに類する技が使えるのだろうか?

それってmod_topでは?

そもそも、rubyが$0を書き換えるってのは、どうやって実現しているのだろう?

ちょっとそれを調べる前に、、、

いやいや、ローテクで行こう。ENVを書き換えればいいんだ。

Apacheのconfigで、requestのuriをENVの中に紛れ込ませれば、それを/proc/プロセス番号/environで参照できる。そして、プロセスのメモリサイズがぴょこんと変化したときのuriをログしておけば、、、いけるんじゃないかな。

Apacheの設定

SetEnvIfを利用

    SetEnvIf Request_URI ".php$" _MONITOR_URI=Request_URI
ps ax | awk '$5 ~ /httpd/ {print $1}' | while read pid
  do
    cat /proc/$pid/environ
  done | awk '

ところが、うまく行かない

  • SetEnvIfで設定される値は、文字列リテラルであってRequest_URIという変数の値ではない
  • Rubyで使える$0の書き換えの技も、/proc/プロセス番号/environには影響しない。おそらく、プロセスを起動するときに引き渡す値のリストが格納されている模様

そもそもRubyで$0を書き換えるのはどうやってるの?again

Ruyb-1.8.7-p72のソースを見た。
ruby.cにそれらしい部分がある。

void
ruby_prog_init()
{
    init_ids();

    ruby_sourcefile = rb_source_filename("ruby");
    rb_define_hooked_variable("$VERBOSE", &ruby_verbose, 0, verbose_setter);
    rb_define_hooked_variable("$-v", &ruby_verbose, 0, verbose_setter);
    rb_define_hooked_variable("$-w", &ruby_verbose, 0, verbose_setter);
    rb_define_variable("$DEBUG", &ruby_debug);
    rb_define_variable("$-d", &ruby_debug);
    rb_define_readonly_variable("$-p", &do_print);
    rb_define_readonly_variable("$-l", &do_line);

    rb_define_hooked_variable("$0", &rb_progname, 0, set_arg0);
    rb_define_hooked_variable("$PROGRAM_NAME", &rb_progname, 0, set_arg0);

    rb_define_readonly_variable("$*", &rb_argv);
    rb_argv = rb_ary_new();
    rb_define_global_const("ARGV", rb_argv);
    rb_define_readonly_variable("$-a", &do_split);
    rb_global_variable(&rb_argv0);

#ifdef MSDOS
    /*
     * There is no way we can refer to them from ruby, so close them to save
     * space.
     */
    (void)fclose(stdaux);
    (void)fclose(stdprn);
#endif
}

どうも、set_arg0で書き換えられそうな感じ

static void
set_arg0(val, id)
    VALUE val;
    ID id;
{
    VALUE progname;
    char *s;
    long i;
    int j;
#if !defined(PSTAT_SETCMD) && !defined(HAVE_SETPROCTITLE)
    static int len = 0;
#endif

    if (origargv == 0) rb_raise(rb_eRuntimeError, "$0 not initialized");
    StringValue(val);
    s = RSTRING(val)->ptr;
    i = RSTRING(val)->len;
#if defined(PSTAT_SETCMD)
    if (i >= PST_CLEN) {
        union pstun j;
        j.pst_command = s;
        i = PST_CLEN;
        RSTRING(val)->len = i;
        *(s + i) = '\0';
        pstat(PSTAT_SETCMD, j, PST_CLEN, 0, 0);
    }
    else {
        union pstun j;
        j.pst_command = s;
        pstat(PSTAT_SETCMD, j, i, 0, 0);
    }
    progname = rb_tainted_str_new(s, i);
#elif defined(HAVE_SETPROCTITLE)
    setproctitle("%.*s", (int)i, s);
    progname = rb_tainted_str_new(s, i);
#else
    if (len == 0) {
        len = get_arglen(origargc, origargv);
    }

    if (i >= len) {
        i = len;
    }
    memcpy(origargv[0], s, i);
    s = origargv[0] + i;
    *s = '\0';
    if (++i < len) memset(s + 1, ' ', len - i);
    for (i = len-1, j = origargc-1; j > 0 && i >= 0; --i, --j) {
        origargv[j] = origargv[0] + i;
        *origargv[j] = '\0';
    }
    progname = rb_tainted_str_new2(origargv[0]);
#endif
    rb_progname = rb_obj_freeze(progname);
}

PSTATもSETPROCTITLEもBSD系の機能らしい。えー、そうするとorigargvをいきなり書き変えて終わりってわけか。それってどこ?

$0関連のruby-devの古いメッセージとかが参考になる

ちなみにsetenvとかenviron(7)とか

CentOS5.2のmanpageのenviron(7)にはmulti-threadの環境ではenvironをいじるなとある。

              extern char **environ;

       is initialized as a pointer to an array of character  pointers  to  the
       environment strings. The argv and environ arrays are each terminated by
       a null pointer. The null pointer terminating  the  argv  array  is  not
       counted in argc.

       Conforming  multi-threaded applications shall not use the environ vari-
       able to access or modify  any  environment  variable  while  any  other
       thread  is  concurrently modifying any environment variable.  A call to
       any function dependent on any environment variable shall be  considered
       a use of the environ variable to access that environment variable.

Multi-thread版のApacheでは、いずれにせよ環境変数やコマンドラインをリクエスト毎にいじるのは危険っぽい

ではログにpid,メモリサイズを出すか?

それは出来るような気がする。直近のログとpsコマンドを組み合わせれば、かなりのことはわかるのではないかな。
mod_log_config フォーマット文字列 - Apache httpdによれば、以下の値が役に立ちそう。

  • %P リクエストを扱った子プロセスのプロセス ID
  • %{format}P リクエストを扱ったワーカーのプロセス ID かスレッド ID。 format として有効な値は pid, tid, hextid です。hextid を使うには APR 1.2.0 以降が必要です。
  • %D リクエストを処理するのにかかった時間、マイクロ秒単位
  • %X 応答が完了したときの接続ステータス
  • %T リクエストを扱うのにかかった時間、秒単位

%{FOOBAR}e 環境変数 FOOBAR の内容 ここに当該プロセスのメモリサイズが突っ込めると最高だが、それは難しそうだ。