GraphvizRでCocoaReplをグラフ化してみる

jitte2007-11-26


びっずびずに(以下略)

・・・

興味本位で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

うーん。中身が長いときの対策とかネストが深い時の対策とか、スタティックにグラフ化しようとすると限界があるなあ。