1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118
|
# -*- coding: utf-8 -*-
module Delayer::Deferred::Deferredable
=begin rdoc
graphvizによってChainableなDeferredをDOT言語形式でダンプする機能を追加するmix-in。
いずれかのノードに対して _graph_ メソッドを呼ぶと、再帰的に親子を全て辿り、digraphの断片の文字列を得ることが出来る。
== 出力例
20892180 [shape=egg,label="#<Class:0x000000027da288>.Promise\n(reserved)"]
20892480 [shape=box,label="test/thread_test.rb:53\n(connected)"]
20891440 [shape=diamond,label="test/thread_test.rb:56\n(fresh)"]
20892480 -> 20891440
20892180 -> 20892480
=end
module Graph
# この一連のDeferredチェインの様子を、DOT言語フォーマットで出力する
# ==== Args
# [child_only:]
# _true_ なら、このノードとその子孫のみを描画する。
# _false_ なら、再帰的に親を遡り、そこから描画を開始する。
# [output:]
# このオブジェクトに、 _<<_ メソッドで内容が書かれる。
# 省略した場合は、戻り値が _String_ になる。
# ==== Return
# [String] DOT言語によるグラフ
# [output:] 引数 output: に指定されたオブジェクト
def graph(child_only: false, output: String.new)
if child_only
output << "digraph Deferred {\n".freeze
Enumerator.new{ |yielder|
graph_child(output: yielder)
}.lazy.each{|l|
output << "\t#{l}\n"
}
output << '}'.freeze
else
ancestor.graph(child_only: true, output: output)
end
end
# Graph.graph の結果を内容とする一時ファイルを作成して返す。
# ただし、ブロックを渡された場合は、一時ファイルを引数にそのブロックを一度だけ実行し、ブロックの戻り値をこのメソッドの戻り値とする。
# ==== Args
# [&block] 一時ファイルを利用する処理
# ==== Return
# [Tempfile] ブロックを指定しなかった場合。作成された一時ファイルオブジェクト
# [Object] ブロックが指定された場合。ブロックの実行結果。
def graph_save(permanent: false, &block)
if block
Tempfile.open{|tmp|
graph(output: tmp)
tmp.seek(0)
block.(tmp)
}
else
tmp = Tempfile.open
graph(output: tmp).tap{|t|t.seek(0)}
end
end
# 画像ファイルとしてグラフを書き出す。
# dotコマンドが使えないと失敗する。
# ==== Args
# [format:] 画像の拡張子
# ==== Return
# [String] 書き出したファイル名
def graph_draw(dir: '/tmp', format: 'svg'.freeze)
graph_save do |dotfile|
base = File.basename(dotfile.path)
dest = File.join(dir, "#{base}.#{format}")
system("dot -T#{format} #{dotfile.path} -o #{dest}")
dest
end
end
# このノードとその子全てのDeferredチェインの様子を、DOT言語フォーマットで出力する。
# Delayer::Deferred::Deferredable::Graph#graph の内部で利用されるため、将来このメソッドのインターフェイスは変更される可能性がある。
def graph_child(output:)
output << graph_mynode
if has_child?
@child.graph_child(output: output)
output << "#{__id__} -> #{@child.__id__}"
end
if has_awaited?
awaited.each do |awaitable|
if awaitable.is_a?(Delayer::Deferred::Deferredable::Chainable)
awaitable.ancestor.graph_child(output: output)
else
label = "#{awaitable.class}"
output << "#{awaitable.__id__} [shape=oval,label=#{label.inspect}]"
end
output << "#{awaitable.__id__} -> #{__id__} [label = \"await\", style = \"dotted\"]"
end
end
nil
end
private
# このノードを描画する時の形の名前を文字列で返す。
# 以下のページにあるような、graphvizで取り扱える形の中から選ぶこと。
# http://www.graphviz.org/doc/info/shapes.html
def graph_shape
'oval'.freeze
end
# このノードの形などをDOT言語の断片で返す。
# このメソッドをオーバライドすることで、描画されるノードの見た目を自由に変更することが出来る。
# ただし、簡単な変更だけなら別のメソッドをオーバライドするだけで可能なので、このmix-inの他のメソッドも参照すること。
def graph_mynode
label = "#{node_name}\n(#{sequence.name})"
"#{__id__} [shape=#{graph_shape},label=#{label.inspect}]"
end
end
end
|