profile(2)

『EXCELでやさしく学ぶ時系列』なんて本を読んでみた。副題が、明日を支配する時系列ですって。 これは、きっと金儲けにEXCELを使いましょって奴に違いないと思って、手に取ってみた次第。

例題が出生率ってのは、お飾りで、直ぐに平均株価とかTOPIXなんてのが出てきたよ。右肩上がりの神話 ってんで、正のトレンドとか負のトレンドが出て来る。直線なり曲線なり指数平均化なりARIMA なりで、未来を占いましょって言う調子の良い事が書かれている。 これは、EXCEL方眼紙を卒業して実業の世界へ足を突っ込むための指南書なんだな。

時系列データってのは、トレンドと季節変動と不規則変動の合成で現わされるらしい。遥か昔に、 こいうった事をRでやった記憶が有るな。

周期変動はスペクトラム分析って事になるんだけど、ずばり周期変動って事で、三角関数の ぐらふが出てきた。A列に0から23までの数列を作り、B列には、10*sin(PI()*A1/3)+2*sin(PI()*A0/6) とか入力して、サイン波の合成グラフを書きましょ。

これの周期成分ピリオグラムとかを求めましょ。どうやるかと思って興味深深でページをまくると、 EXCELじゃ大変なので、EXCELにAddonしたSPSSを使いましょですって。本の題名をEXCELと 謳っておきながら、これじゃ詐欺じゃないですか。金儲けをするには、事前に投資が必要 ですってのが、ぷんぷんと臭ってくるな。

申し訳程度に階差(前項との差分)を取ると、トレンドを消す事が出来ますって事と、移動平均を 取るとトレンドを抽出出来ますって、演習でやってたよ。

ラグを変えながが自己相関を取るとか、関連する数列でラグを取りながら交差相関係数を求めると 有効利用出来ますってさ。

後は、酔いどれの千鳥足グラフ、かっこよく言うと、ランダムウォークのグラフで遊んで みましょってさ。これは、競馬でおけらになって、やけ酒を飲んだ人の足取りを表して いるんだな。手回しの良い事。

どうやって書くかと言うと、関数RANDを使って0から1の間の数列を作る(A列)。もう一列は隣の列からから0.5を 引いた列を作る(B列)。C列の初項(C1)を0、次の項(C2)は、C1+B1後は、それをコピー。これで、千鳥足になるって。

最後は、x(t+1)=a*x(t)(1-x(t)) で、aが4の場合をカオスって言う。為替も株も競馬も競輪も 、誰にも予想が付かないカオスだよーって説明して、責任放棄してますなあ。まあ、余り真に 受けないようにってか。

EXCELを宗教的に嫌いな人は、 Rで計量時系列分析:はじめに覚えておきたいこと なんてのが、良いかも知れません。

折角なので、pureで書いた統計スクリプトを使って、いんちき自己相関をやってみる。 元データは、周期12のサイン波。

> let sw = [sin $ x * pi / 6 | x = (0..60)];

これで、サイン波が発生出来たはず。ラグ(時間のずれ)を変えながら、自己相関を取ってみる。

> rotl n xs = (drop n xs) + (take n xs);
> do (\n -> puts ((str n) +" "+ str (cor sw (rotl n sw)))) (0..25);
0 1.0
1 0.866025403784439
2 0.502083333333333
3 0.00513354503153706
4 -0.492633545031537
5 -0.858958102023161
6 -0.996650635094611
7 -0.869374768689828
8 -0.511233968427944
9 -0.017633545031537
10 0.480133545031536
11 0.84980746692855
12 0.993301270189222
13 0.872724133595217
14 0.520384603522555
15 0.0301335450315371
16 -0.467633545031536
17 -0.840656831833939
18 -0.989951905283833
19 -0.876073498500606
20 -0.529535238617167
21 -0.0426335450315371
22 0.455133545031537
23 0.831506196739328
24 0.986602540378444
25 0.879422863405995

上で出てきたrotlってのは、AKB48のヘビー・ローテーションならぬ、レフト・ローテーションです。 こういう工作は、大田区の下町の皆様が得意中の得意のはず。(いえね、今丁度、下町のボブスレー なんて本を読んでたんで、それを思い出したのさ)

原材料のxsと言うリストの左端から決められた長さnでカットして棄て、もう一つは、決められた長さで 左側を切り出す。それを溶接して一本にまとめれば完了。ね、鉄工所のおじさんが得意とする技ですよ。

12の倍数の所で、相関が最大(山)になってる。逆相の所では、負の相関が最大(谷)になってる。 これって、周期分析に使えるな。と、信号処理っぽい実験が出来ました。

更に注意してみると、pi/2(90度)だけずれた所(sinとcosの関係になるな)では、相関値が零になってる。 直交してるって事でいいですかね? 無関係な関係が、変(複)調に利用されてるんだな。えっと、あれだ。 ソフトウェア・ラジオの原理に通じてるのね。

ちゃんとした自己相関の定義は、 自己相関関数入門 とか 自己相関関数 とか C/C++言語で自己相関関数(ACF)により基本周波数解析を行うサンプルプログラム を、参照。

dtrace

前回から、profileなんてのに手を出している。究極のプロファイルって何だろう? そりゃ、プロファイルされてる事を相手に知られないでプロファイルする事じゃなかろうか。

現実世界で言うと、盗聴とか盗撮で、今日のパンツの色は...(いきなりそれかい)とか、監視カメラとか 、Nシステムとか(最近、警察物の推理小説に凝ってるので)いろいろあらーな。 まあ、不確定性定理 なんてのは、持ち出さなくてもいいけど。

つらつら考えていたら、dtraceを思い出したぞ。アプリに全く手を加えずに、挙動を収集しましょってやつ。 2010年に丁度同じ事をやってたな。流行は繰り返すのよって事で、進化の度合いを確認しとく。

今、OpenSolaris方面はどうなってるんですかね。dtraceの発祥の地ですからね。ボラクルに 吸収っされちゃってフェードアウトですかね。DNAは、MAC OSとかFreeBSDにしっかり受け継がれていたぞ。

dtraceって今は無きSUNで、zfsって言う究極のファイルシステムを開発してる時に生まれた そうだ。究極のっていう事なんで、OSの核になるカーネルとの密接な連携が必要。カーネルの 調整とかするには、中まで踏み込んでいかねばならない。それにはdebuggerさ。でも、そんなの 面倒。カーネルの要所々に、色々な観測点(プローブ)を仕込んでおいて、それを、外から 取り出して、見られたら便利ね。

で、dtraceってソフトがカーネル連携で開発された。dtraceは例えれば、オシロスコープか ロジアナだろう。観測対象は、勿論カーネル。どのプローブを有効にして、どんな風に取り出した データを加工・表示するかは、プログラミングして決める。使い易い道具を作るって、匠の 世界だなあ。その匠達は、ボラクルを辞して、色々な所へ散って行ったのかな。

FreeBSD 10になってからは、標準で入っているんで、カーネルの再コンパイルは不要になったぞ。 これを使って、rubyでも監視してみるかな。

その前に、dtraceをプチ復習。まずは、 Oracle's CM そして、某サイトでの解説記事 カーネル挙動を追尾する「DTrace」の実力 お次は、 D言語基礎文法最速マスター(DTraceのほう) 他にもいろいろあるけど、この辺でいいだろう。

でも究極は原典にあたるのが一番。いつかみたのが、FreeBSDのソースツリーにどっかりと鎮座 してたぞ。もう、dtraceの為にSolarisを入れる必要は無い。

[sakae@fb10 /usr/src/cddl/contrib/dtracetoolkit]$ ls
Apps            Java            Perl            User            install
Bin             JavaScript      Php             Version         iopattern
Code            Kernel          Proc            Zones           iosnoop
Cpu             License         Python          dexplorer       iotop
Disk            Locks           README          dtruss          opensnoop
Docs            Man             Ruby            dvmstat         procsystime
Examples        Mem             Shell           errinfo         rwsnoop
FS              Misc            Snippits        execsnoop       rwtop
Guide           Net             System          hotkernel       statsnoop
Include         Notes           Tcl             hotuser

基本観察対象はカーネルなんだけど、プローブの仕様が公開されているんで、それぞれの アプリにプローブを仕込んで、独自のデータを採取する事も可能。

実際にdtraceを使うには、それ用のモジュールをロードする。

[sakae@fb10 ~]$ sudo kldload dtraceall
[sakae@fb10 ~]$ kldstat
Id Refs Address    Size     Name
         :
 7    1 0xc6444000 2000     dtraceall.ko
 8    3 0xc6446000 4000     cyclic.ko
 9   12 0xc644a000 3000     opensolaris.ko
10   10 0xc644f000 31000    dtrace.ko
11    1 0xc6486000 2000     dtio.ko
12    1 0xc6488000 2000     dtmalloc.ko
13    1 0xc648c000 4000     dtnfscl.ko
14    1 0xc6490000 4000     fbt.ko
15    1 0xc6f62000 9000     fasttrap.ko
16    1 0xc7073000 2000     lockstat.ko
17    1 0xc7078000 3000     sdt.ko
18    1 0xc7090000 f000     systrace.ko
19    1 0xc709f000 3000     profile.ko

OSの起動直後から使いたかったら、/boot/loder.confに書いておけばOK。モジュールを ロードすると、デバイスが生えてくる。

[sakae@fb10 ~]$ ls /dev/dtrace
dtmalloc        fasttrap        helper          profile         systrace
dtrace          fbt             lockstat        sdt

それぞれのデバイスの説明書が有るかと思って調べてみたけど、用意されてはいなかった。 詳しくはsourceで事ですな。どんな種類のプローブが有るかは、dtrace -lで確認出来る。

root@fb10:~ # dtrace -l
   ID   PROVIDER            MODULE                          FUNCTION NAME
    1     dtrace                                                     BEGIN
    2     dtrace                                                     END
    3     dtrace                                                     ERROR
    4        fbt            kernel                camstatusentrycomp entry
    5        fbt            kernel                camstatusentrycomp return
    6        fbt            kernel            cam_compat_handle_0x17 entry
    7        fbt            kernel            cam_compat_handle_0x17 return
    8        fbt            kernel                     camperiphdone entry
    9        fbt            kernel                     camperiphdone return
    :
51184   dtmalloc                                          isofs_node malloc
51185   dtmalloc                                          isofs_node free
51186   dtmalloc                                         isofs_mount malloc
51187   dtmalloc                                         isofs_mount free
51188   dtmalloc                                              isadev malloc
51189   dtmalloc                                              isadev free
51190         io            kernel                                   start
51191         io            kernel                                   done
51192         io            kernel                                   wait-start
51193         io            kernel                                   wait-done

非常に大量にあるので、PROVIDERだけを抽出してみた。

root@fb10:~ # dtrace -l | sed -e 's/^ *//' | cut -f 2 -w | sort | uniq -c | sort -nr
48735 fbt
1072 syscall
 714 dtmalloc
 223 mac_framework
 172 nfscl
 158 vfs
  29 lockstat
  18 sched
  15 proc
  13 profile
  12 sctp
   8 tcp
   6 xbb
   4 io
   3 mac
   3 dtrace
   2 udp
   2 priv
   2 ip
   2 callout_execute
   1 PROVIDER

カーネルのあらゆる所に、プローブが仕込んであると思うぞ。

とまあ、dtraceの主目的はカーネルの観測なんだけど、普通のアプリだって簡単に 観測出来る。その為のカジュアルなコマンドが用意されてた。SUNの匠の道具なんだけどね。

[sakae@fb10 ~]$ lv /usr/sbin/dtruss
  :
# USAGE: dtruss [-acdeflhoLs] [-t syscall] { -p PID | -n name | command }
#
#               -p PID          # examine this PID
#               -n name         # examine this process name
#               -t syscall      # examine this syscall only
#               -a              # print all details
#               -c              # print system call counts
#               -d              # print relative timestamps (us)
#               -e              # print elapsed times (us)
#               -f              # follow children as they are forked
#               -l              # force printing of pid/lwpid per line
#               -o              # print on cpu times (us)
#               -s              # print stack backtraces
#               -L              # don't print pid/lwpid per line
#               -b bufsize      # dynamic variable buf size (default is "4m")
#  eg,
#               dtruss df -h    # run and examine the "df -h" command
#               dtruss -p 1871  # examine PID 1871
#               dtruss -n tar   # examine all processes called "tar"
#               dtruss -f test.sh       # run test.sh and follow children

このコマンド自体はshell scriptなんだけど、D語が埋め込まれていて、勉強にはうってつけだぞ。 匠の技をこっそり拝借しましょ。

dtrace for ruby

能書きはもういいんで、rubyにプローブを仕込んでみれ。昨年出たruby 2.1で試してみた。 組み込むには、--enable-dtraceて事でやったら、dtraceが見つからんと言われる。3秒考えて、 dtraceがイネーブルになってないと駄目と悟って、kldloadしてからやってみた。

minirubyの所でsegvしたよ。clangが悪さしてるんかなあって思って、emacsが引き連れてきたgccに切り替えてみた。 やり方は、CC=gcc46 ./configure ... だった。これでもsegv。

dtraceが無い環境にしてrubyを作ると、clangでもgcc46でも無事に2.1が動いた。はてな、ってんで、 rubyのheadを取ってきた。もう、2.2のtagが打たれていたぞ。OSSは開発が停止した所が死 です。常に自転車操業を強いられます。大変ですね。開発者の皆様。

戯言は兎も角、headをコンパイルするには、rubyの環境が必要なのね。しょがないので、pkgから、2.0を 入れて、ruby20をrubyってリンクしといた。これで、コンパイル。

  :
translating probes probes.d
dtrace -32 -o probes.h.tmp -h -C -I. -I.ext/include/i386-freebsd10.0 -I./include -I. -s probes.d
   :
dtrace -32 -G -C -I. -I.ext/include/i386-freebsd10.0 -I./include -I. -s ./probes
.d -o probes.o array.o  eval.o  gc.o  hash.o  load.o  object.o  parse.o  string.
o  vm.o
linking miniruby
cc -O0 -fno-fast-math -g -Wall -Wextra -Wno-unused-parameter -Wno-parentheses -W
no-long-long -Wno-missing-field-initializers -Wunused-variable -Werror=pointer-a
rith -Werror=write-strings -Werror=declaration-after-statement -Werror=shorten-6
4-to-32 -Werror=implicit-function-declaration -Werror=division-by-zero -Werror=d
eprecated-declarations -Werror=extra-tokens -std=iso9899:199409  -L. -fstack-pro
tector -rdynamic -fstack-protector -pie  main.o dmydln.o miniinit.o miniprelude.
o array.o  bignum.o  class.o  compar.o  complex.o  dir.o  dln_find.o  encoding.o
  enum.o  enumerator.o  error.o  eval.o  load.o  proc.o  file.o  gc.o  hash.o  i
nits.o  io.o  marshal.o  math.o  node.o  numeric.o  object.o  pack.o  parse.o  p
rocess.o  random.o  range.o  rational.o  re.o  regcomp.o  regenc.o  regerror.o
regexec.o  regparse.o  regsyntax.o  ruby.o  safe.o  signal.o  sprintf.o  st.o  s
trftime.o  string.o  struct.o  time.o  transcode.o  util.o  variable.o  version.
o  compile.o  debug.o  iseq.o  vm.o  vm_dump.o  vm_backtrace.o  vm_trace.o  thre
ad.o  cont.o  ascii.o us_ascii.o unicode.o utf_8.o  newline.o  close.o addr2line
.o dmyext.o probes.o  -lelf -lexecinfo -lthr -lcrypt -lm   -o miniruby
*** Signal 11

Stop.

上記はMakefileをちょいといじって、コンパイルの過程を詳細に表示するようにしたもの。 やっぱり落ちてるなあ。

Program terminated with signal 11, Segmentation fault.
#0  0x010ba28d in ?? ()
(gdb) bt
#0  0x010ba28d in ?? ()
#1  0x0133a790 in ?? ()
#2  0x21c1a140 in ?? ()
#3  0x0133a790 in ?? ()
#4  0xbfbfd4fc in ?? ()
#5  0x010b7c0f in ?? ()
#6  0x21c1a140 in ?? ()
#7  0x0133a790 in ?? ()
#8  0x21352800 in ?? ()
#9  0x21352a00 in ?? ()
#10 0x0133a790 in ?? ()
#11 0x0133a790 in ?? ()
#12 0x00000000 in ?? ()

おいらには、わけわかめ。諦めるか。ちょいと悔しいので、絵に描いた餅を晒しておきます。 おっと、るびまにも記事があったわい。

[sakae@fb10 ~/src/ruby/tool]$ ruby ./probes_to_wiki.rb ../probes.d | grep ===
=== ruby:::method-entry(classname, methodname, filename, lineno);
=== ruby:::method-return(classname, methodname, filename, lineno);
=== ruby:::cmethod-entry(classname, methodname, filename, lineno);
=== ruby:::cmethod-return(classname, methodname, filename, lineno);
=== ruby:::require-entry(requiredfile, filename, lineno);
=== ruby:::require-return(requiredfile, filename, lineno);
=== ruby:::find-require-entry(requiredfile, filename, lineno);
=== ruby:::find-require-return(requiredfile, filename, lineno);
=== ruby:::load-entry(loadedfile, filename, lineno);
=== ruby:::load-return(loadedfile, filename, lineno);
=== ruby:::raise(classname, filename, lineno);
=== ruby:::object-create(classname, filename, lineno);
=== ruby:::array-create(length, filename, lineno);
=== ruby:::hash-create(length, filename, lineno);
=== ruby:::string-create(length, filename, lineno);
=== ruby:::symbol-create(str, filename, lineno);
=== ruby:::parse-begin(sourcefile, lineno);
=== ruby:::parse-end(sourcefile, lineno);
=== ruby:::gc-mark-begin();
=== ruby:::gc-mark-end();
=== ruby:::gc-sweep-begin();
=== ruby:::gc-sweep-end();
=== ruby:::method-cache-clear(class, filename, lineno);

これ、本来なら、rubyに組み込まれるであろう、プローブの一覧です。直接probes.dって言う D言語を見るより、楽かと思いまして。。。 更に、どういう風にプローブが当てられて いるかは、RUBY_DTRACEでgrepすれば、出てくるぞ。

上のプローブを眺めていると、rubyの秘穴のように見えてくるのは、おいらだけ?

残念至極なので、pythonにプローブを組み込めないかと、お取り寄せしてみたけど、そんな オプションは無かったぞ。どこからかpatchを拾ってきて組み込めって事だな。 折角なので素直にコンパイルしてみたら、面白い事を抜かしおったわい。

[sakae@fb10 ~/MINE/bin]$ ./python3
Python 3.3.3 (default, Mar 18 2014, 06:18:43)
[GCC 4.2.1 Compatible FreeBSD Clang 3.3 (tags/RELEASE_33/final 183502)] on freebsd10

Clang 3.3 ってのは、Gcc 4.2.1相当品なのね。GCCとCLANGの追いかけっこってのも見ものだな。

systemtap

FreeBSDでdtraceを使って、rubyの挙動を調べるってのは、あえなく撃沈しちゃったけど、 dtrace相当品がLinuxに有るのを思い出した。

Linux のイントロスペクションと SystemTap あたりが正統な説明かな。そしてカーネルだけじゃなくて、アプリでも使えますぜってのの例が、 systemtapで動的追跡をしよう(1) だ。つらつら見てくと、stapなんてコマンドを使うのね。

もう少し探ってみるかってんで、stapをキーにぐぐってみると、あららtopには、 ネイチャー誌を賑わした理研の姫こと割烹着のおねーさんが出てきちゃったぞ。 STAP細胞関係のゲノムデータを解析してみた なんて楽しい記事も出てきたし、捏造関係もどったりと。

んな訳で、少し検索の語句を工夫して あげた所、 ruby 2.0 の dtrace featureを systemtap から使う方法 に行き着いた。

上記を参考にfedoraにsystemtapを入れ、ruby2.1をコンパイルしてみた。注意点として、config する時、--enable-debugを付ける事かな。(受け売りなんですけどね)

 ./configure --enable-dtrace --enable-debug --disable-install-doc --prefix=/home/sakae/MINE

ちょいと実験したかったんで、docは外したんだけど、しっかりtkとかのI/Fをコンパイル してくれて、結構時間がかかったぞ。

上のURLに則り

[sakae@fedora z]$ cat ruby.stp
probe method__entry {
        printf ("%s %s %d\n", sourcefile, methodname, sourceline)
    }

こういうDスクリプト相当品を用意して、以前にやった回文数を求めるスクリプトを走らせると

[sakae@fedora z]$ sudo stap -v -I.. ./ruby.stp -c "../ruby --disable-gems ./mil.rb"
Pass 1: parsed user script and 102 library script(s) using 30148virt/16596res/2604shr/14596data kb, in 180usr/80sys/270real ms.
Pass 2: analyzed script: 3 probe(s), 3 function(s), 1 embed(s), 0 global(s) using 41044virt/17748res/3140shr/15120data kb, in 10usr/0sys/12real ms.
Pass 3: translated to C into "/tmp/stapiwBxWZ/stap_92e06b647d3c4c79e18c903211c78247_4020_src.c" using 41056virt/18440res/3700shr/15132data kb, in 20usr/230sys/242real ms.
Pass 4: compiled C into "stap_92e06b647d3c4c79e18c903211c78247_4020.ko" in 3400usr/1530sys/5213real ms.
Pass 5: starting run.
./mil.rb mil 14
./mil.rb cp 1
./mil.rb ex 10
./mil.rb nx 6
./mil.rb cp 1
./mil.rb ex 10
./mil.rb nx 6
./mil.rb cp 1
./mil.rb ex 10
./mil.rb nx 6
./mil.rb cp 1
./mil.rb ex 10
./mil.rb nx 6
./mil.rb cp 1
Pass 5: run completed in 20usr/120sys/428real ms.

うん、動いているな。後は、stapのマニュアルをしっかり読んで創意工夫だな。 なお、上記URL中に出てきた、ruby-tapset.stpを作るスクリプトは、rubyのソースツリー中の toolの中に用意されてた。使う時は、スクリプト中に書いてあるruby_pathを適時修正の事。

dtruss

FreeBSDでのrubyが失敗しちゃったけど、そのうちにあの人が対応してくれるに違いない。 それまで塩漬けだな。このままでは、ちょっと悔しいのでdtrussをやってみる。

ruby -vした時、どんなシステムコールが行われているか?

[sakae@fb10 ~]$ sudo dtruss -c ruby -v
  :
write                                           2
getpid                                          3
read                                            3
cpuset_getaffinity                              4
ioctl                                           4
madvise                                         4
lstat                                           6
__sysctl                                        8
close                                          12
munmap                                         12
open                                           13
sigaction                                      13
access                                         18
fstat                                          19
mmap                                           43
sigprocmask                                    55

いろいろなファイルを開いていますなあ。どんなんが使われている?

[sakae@fb10 ~]$ sudo dtruss -t open -b 12m ruby -v
ruby 2.0.0p353 (2013-11-22 revision 43784) [i386-freebsd10]
SYSCALL(args)            = return
open("/etc/libmap.conf\0", 0x100000, 0xBFBFD37C)                 = 3 0
open("/usr/local/lib/libruby20.so.20\0", 0x100000, 0x0)          = 3 0
open("/usr/lib/libexecinfo.so.1\0", 0x100000, 0xBFBFCCF8)                = 3 0
open("/var/run/ld-elf.so.hints\0", 0x100000, 0x0)                = 3 0
open("/lib/libthr.so.3\0", 0x100000, 0xBFBFCCF8)                 = 3 0
open("/lib/libcrypt.so.5\0", 0x100000, 0xBFBFCCF8)               = 3 0
open("/lib/libm.so.5\0", 0x100000, 0xBFBFCCF8)           = 3 0
open("/lib/libc.so.7\0", 0x100000, 0xBFBFCCF8)           = 3 0
open("/usr/lib/libelf.so.1\0", 0x100000, 0xBFBFCCF8)             = 3 0
open("/lib/libgcc_s.so.1\0", 0x100000, 0xBFBFCCF8)               = 3 0
open("/usr/share/locale/ja_JP.UTF-8/LC_CTYPE\0", 0x100000, 0x1B6)               = 3 0
open("/dev/urandom\0", 0x108004, 0x0)            = 3 0
open("/lib/libgcc_s.so.1\0", 0x100000, 0x0)              = 7 0

返り値の3 0 とかは、ディスクリプターと成功/失敗を表すフラグかな。

まとめ

今まで、アプリの挙動観察って事で、いろいろやってきたけど、まとめのサイトが 有ったので、有り難く利用させてもらいましょ。 実行中のアプリケーションを外から観察するコマンド