GraphvizRでCocoaReplをグラフ化してみる
びっずびずに(以下略)
・・・
興味本位でCocoaReplのグラフ化してみた話。
ソースを見るとReplControllerにインスタンス変数がたくさんあって楽しげ。でもインスタンスをどこからたどるかわからなかったのでObjectSpaceから無理矢理探してみた。
# 環境変数とかの準備 $: << "~/work/ruby" require 'vizviz/vizviz' ENV['PATH'] += ":/opt/local/bin" Dir.chdir ENV['HOME'] + "/work/ruby/vizviz" # 全オブジェクトから、ReplControllerを探す ObjectSpace.each_object do |o| p vv(o) if o.class == ReplController end
これをCocoaReplにつっこんでみたところスタックがあふれることが判明。たぶん循環参照っぽいから最大レベルを指定できるようにしてみたらこんな感じになった。
ObjectSpace.each_object do |o| p vv(o, 4) if o.class == ReplController end
vizviz.rb
require 'rubygems' require 'graphviz_r' require 'pp' class Object VIZ_MAX_LEVEL = 10 def vis_node(g, level = VIZ_MAX_LEVEL) if vis_elements.empty? g[vis_id, [:label => inspect]] else vis_node_instance(g, level) end end def vis_elements instance_variables end def vis_element(e) instance_eval(e) end def vis_each vis_elements.sort.each do |e| yield e end end def vis_node_instance(g, level) g[vis_id, [:shape => :record, :label => vis_label]] vis_each do |e| vis_element(e).vis_node(g, level - 1) end if level > 0 end def vis_label a = [self.class.inspect] vis_each do |e| a << "<#{vis_port(e)}>#{e}" end a.join '|' end def vis_edge(g, level = VIZ_MAX_LEVEL) unless vis_elements.empty? vis_each do |e| v = vis_element(e) g[vis_id, vis_port(e)] >> g[v.vis_id] v.vis_edge(g, level - 1) end if level > 0 end end def vis_id "id#{__id__}".to_sym end def vis_port(e) vis_element(e).vis_id end end class Hash def vis_elements keys end def vis_element(e) self[e] end end class Array def vis_elements self end def vis_element(e) self[e] end def vis_each (0...size).each do |e| yield e end end end def vv(obj, level) gvr = GraphvizR.new 'vv' gvr.graph[:rankdir => 'LR'] obj.vis_node(gvr, level) obj.vis_edge(gvr, level) puts gvr.to_dot gvr.output end
うーん。中身が長いときの対策とかネストが深い時の対策とか、スタティックにグラフ化しようとすると限界があるなあ。