Beautiful Visualization を読んでいて、ちょっと自分でもなにかやってみたくなる。
Ruby のリポジトリからなにか興味深い性質を可視化できるか考えて、あるファイルに短期間 (24時間) の中で 2人の committer が commit した回数を数えてみることを思いついた。そういう 2人は協調して開発している(あるいは後のほうの人が修正をしている)可能性が高いのではないだろうか。
% cat Makefile z10.png: z9.dot; neato -Tpng $< -o $@ z9.dot: z8.csv; erb t.erb > $@ z8.csv: z7.csv; tb grep --ruby '80 < _["count"].to_i' $< -o $@ z7.csv: z6.csv; tb group author_1,author_2 -a count $< -o $@ z6.csv: z5.csv; tb grep --ruby 'Time.parse(_["date_2"]) - Time.parse(_["date_1"]) < 60*60*24' $< -o $@ z5.csv: z4.csv; tb consecutive $< | tb grep --ruby '_["path_1"] == _["path_2"] && _["author_1"] != _["author_2"]' -o $@ z4.csv: z3.csv; tb sort -f path,rev $< -o $@ z3.csv: z2.csv; tb grep -f path /trunk/ $< | tb grep -v -f author '\Asvn\z' | tb grep -f path -v ChangeLog -o $@ z2.csv: z1.csv; tb cut rev,author,date,path $< -o $@ z1.csv:; tb svn-log -o $@ -- -v http://svn.ruby-lang.org/repos/ruby/trunk % cat t.erb % require 'tb' % t = Tb.load_csv("z8.csv") digraph g { % t.each {|rec| <%= rec["author_1"] %> -> <%= rec["author_2"] %> [ weight=<%= rec["count"] %>, label="<%= rec["count"] %>", fontsize=5 ]; % } } % make ...
で、できたのが以下である。条件に合致した commit の回数が、80回以上あった関係を矢印 (コミット間で時間が進む方向が矢印の方向になっている) にして、(GraphViz の) neato でレイアウトしてみた。
まあ予想通りというか、うん。
なお、生成は tb だらけだが、今回のために svn-log と consecutive サブコマンドを新しく作ってみた。
ImageMagick の convert の -level オプションの挙動をみてみる。
% ruby -e 'STDOUT.binmode; print "P5\n256 1\n255\n", (0..255).to_a.pack("C*")' > src.pgm % convert -level 20%,80%,0.5 src.pgm dst.pgm % tb csv dst.pgm|tb grep -f component V|tbplot -x x -y value
結果はこんなかんじ。
あきらかに変だろうということで報告してみた。
<URL:http://www.imagemagick.org/discourse-server/viewtopic.php?f=3&t=20010>
drbrain -> nobu が多すぎる、と指摘を受けたので、調べたところ、24時間以内のコミット対で複数のファイルが重なっているときに、ファイルの数だけ数えてしまっていた。ちゃんとコミット対の数を数えてみよう。
% cat t.erb % require 'tb' % t = Tb.load_csv("z8.csv") digraph g { % t.each {|rec| <%= rec["author_1"] %> -> <%= rec["author_2"] %> [ weight=<%= rec["count"].to_f %>, label="<%= rec["count"] %>", fontsize=5, minlen=2, len=2 ]; % } } % cat Makefile z10.png: z9.dot; circo -Tpng $< -o $@ z9.dot: z8.csv; erb t.erb > $@ z8.csv: z7.csv; tb grep --ruby '20 < _["count"].to_i' $< -o $@ z7.csv: z6.csv; tb group rev_1,author_1,rev_2,author_2 $< |tb group author_1,author_2 -a count -o $@ z6.csv: z5.csv; tb grep --ruby 'Time.parse(_["date_2"]) - Time.parse(_["date_1"]) < 60*60*24' $< -o $@ z5.csv: z4.csv; tb consecutive $< | tb grep --ruby '_["path_1"] == _["path_2"] && _["author_1"] != _["author_2"]' -o $@ z4.csv: z3.csv; tb sort -f path,rev $< -o $@ z3.csv: z2.csv; tb grep -f path /trunk/ $< | tb grep -v -f author '\Asvn\z' | tb grep -f path -v ChangeLog -o $@ z2.csv: z1.csv; tb cut rev,author,date,path $< -o $@ z1.csv:; tb svn-log -o $@ -- -v http://svn.ruby-lang.org/repos/ruby/trunk % make ...
なお、neato だと、ノードとエッジが重なってしまったので、circo を使ってみた。
他のプロジェクトは... えーと、Rails は git, Python は hg, Perl は git ... うぅむ、まず tb git-log とかを作るところから始めないといかんか。
しかし、git だとどういう表を生成するか悩むんだよな。
svn ならひとつのコミットに対して複数の情報が関連するのはファイルだけなんだけど、git だとファイルだけじゃなくて親コミットも複数あるから、選択しないといけない。
しばらく考えた結果、ネストさせるのがいいのではないか、という結論に達した。つまり、CSV の要素の中に CSV を入れる。
たとえば、次のような感じである。
commit,author,date,log,parents,paths commit-hash1,foo <bar@baz>,2011-01-01T00:00:00Z,msg,"parent parent-hash11 parent-hash12 ","path path11 path12 path13" commit-hash2,hoge <fuga@moge>,2010-01-01T00:00:00Z,msg,"parent parent-hash21 ","path path21 path22 "
ここでは、parents と paths フィールドにCSVな値が入っている。
調べてみると、RDB でも、nested table という名前で、このような形式がサポートされているものがあるようだ。
さて、CSV をネストさせることを思いつくより前に、XML を使うという案はすぐに思いついたのだが、これがなかなか難しい。というか、XML について考えた結果、自分が何をやっているか理解が深まった。
結局、CSV や XML の世界で考えるのでなく、それが表現しようとしているデータが (UML の) クラス図に従うと考えることで、CSV と XML の対応を考え、だいたい納得できた。
XML で CSV と同様な情報を表現するのはもちろん可能だが、tb grep みたいな機能は (可能だが) 不自然になると思う。
JSON は XML よりいいような気がするな。array と object が JSON という規格のレベルで分かれているのがいい。
オブジェクト構造を直接的に表現できるのがいいというなら、YAML のほうがいいのだろうか。
あるいは、究極的には、Marshal とか。
それはなんか違う気がする。うぅむ。
とりあえず、検討中のところはさておき、tb-0.2 を release する。
[latest]