fairy 0.6.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (186) hide show
  1. data/LICENSE +674 -0
  2. data/Makefile +116 -0
  3. data/README +15 -0
  4. data/bin/fairy +582 -0
  5. data/bin/fairy-cat +74 -0
  6. data/bin/fairy-cp +128 -0
  7. data/bin/fairy-rm +122 -0
  8. data/bin/subcmd/controller +41 -0
  9. data/bin/subcmd/inspector +81 -0
  10. data/bin/subcmd/master +43 -0
  11. data/bin/subcmd/node +47 -0
  12. data/bin/subcmd/processor +54 -0
  13. data/doc/programming-interface.html +240 -0
  14. data/doc/programming-interface.rd +300 -0
  15. data/etc/fairy.conf.tmpl +118 -0
  16. data/ext/simple_hash/extconf.rb +4 -0
  17. data/ext/simple_hash/simple_hash.c +42 -0
  18. data/fairy.gemspec +60 -0
  19. data/lib/fairy/client/addins.rb +20 -0
  20. data/lib/fairy/client/barrier.rb +29 -0
  21. data/lib/fairy/client/basic-group-by.rb +52 -0
  22. data/lib/fairy/client/cat.rb +41 -0
  23. data/lib/fairy/client/direct-product.rb +51 -0
  24. data/lib/fairy/client/equijoin.rb +79 -0
  25. data/lib/fairy/client/exec.rb +54 -0
  26. data/lib/fairy/client/filter.rb +62 -0
  27. data/lib/fairy/client/find.rb +35 -0
  28. data/lib/fairy/client/group-by.rb +194 -0
  29. data/lib/fairy/client/here.rb +84 -0
  30. data/lib/fairy/client/inject.rb +70 -0
  31. data/lib/fairy/client/input-file.rb +53 -0
  32. data/lib/fairy/client/input-iota.rb +49 -0
  33. data/lib/fairy/client/input-local-file.rb +188 -0
  34. data/lib/fairy/client/input-varray.rb +30 -0
  35. data/lib/fairy/client/input.rb +42 -0
  36. data/lib/fairy/client/io-filter.rb +26 -0
  37. data/lib/fairy/client/junction.rb +31 -0
  38. data/lib/fairy/client/map.rb +34 -0
  39. data/lib/fairy/client/merge-group-by.rb +71 -0
  40. data/lib/fairy/client/output-file.rb +64 -0
  41. data/lib/fairy/client/output-local-file.rb +60 -0
  42. data/lib/fairy/client/output-null.rb +47 -0
  43. data/lib/fairy/client/output-varray.rb +50 -0
  44. data/lib/fairy/client/output.rb +29 -0
  45. data/lib/fairy/client/roma-put.rb +62 -0
  46. data/lib/fairy/client/roma.rb +156 -0
  47. data/lib/fairy/client/seg-join.rb +61 -0
  48. data/lib/fairy/client/seg-map.rb +78 -0
  49. data/lib/fairy/client/seg-shuffle.rb +35 -0
  50. data/lib/fairy/client/seg-split.rb +27 -0
  51. data/lib/fairy/client/seg-zip.rb +60 -0
  52. data/lib/fairy/client/select.rb +38 -0
  53. data/lib/fairy/client/sort.rb +48 -0
  54. data/lib/fairy/client/sort18.rb +56 -0
  55. data/lib/fairy/client/sort19.rb +61 -0
  56. data/lib/fairy/client/there.rb +47 -0
  57. data/lib/fairy/client/top_n_into_roma.rb +34 -0
  58. data/lib/fairy/client/wc.rb +92 -0
  59. data/lib/fairy/controller.rb +1103 -0
  60. data/lib/fairy/logger.rb +107 -0
  61. data/lib/fairy/master/addins.rb +20 -0
  62. data/lib/fairy/master/atom.rb +17 -0
  63. data/lib/fairy/master/c-barrier.rb +283 -0
  64. data/lib/fairy/master/c-basic-group-by.rb +250 -0
  65. data/lib/fairy/master/c-cat.rb +159 -0
  66. data/lib/fairy/master/c-direct-product.rb +203 -0
  67. data/lib/fairy/master/c-exec.rb +68 -0
  68. data/lib/fairy/master/c-filter.rb +422 -0
  69. data/lib/fairy/master/c-find.rb +138 -0
  70. data/lib/fairy/master/c-group-by.rb +64 -0
  71. data/lib/fairy/master/c-here.rb +80 -0
  72. data/lib/fairy/master/c-inject.rb +119 -0
  73. data/lib/fairy/master/c-input-file.rb +46 -0
  74. data/lib/fairy/master/c-input-iota.rb +66 -0
  75. data/lib/fairy/master/c-input-local-file.rb +117 -0
  76. data/lib/fairy/master/c-input-varray.rb +53 -0
  77. data/lib/fairy/master/c-input.rb +24 -0
  78. data/lib/fairy/master/c-inputtable.rb +31 -0
  79. data/lib/fairy/master/c-inputtable18.rb +36 -0
  80. data/lib/fairy/master/c-inputtable19.rb +35 -0
  81. data/lib/fairy/master/c-io-filter.rb +28 -0
  82. data/lib/fairy/master/c-junction.rb +54 -0
  83. data/lib/fairy/master/c-map.rb +27 -0
  84. data/lib/fairy/master/c-merge-group-by.rb +241 -0
  85. data/lib/fairy/master/c-output-file.rb +84 -0
  86. data/lib/fairy/master/c-output-local-file.rb +19 -0
  87. data/lib/fairy/master/c-output-null.rb +45 -0
  88. data/lib/fairy/master/c-output-varray.rb +57 -0
  89. data/lib/fairy/master/c-output.rb +20 -0
  90. data/lib/fairy/master/c-seg-join.rb +141 -0
  91. data/lib/fairy/master/c-seg-map.rb +26 -0
  92. data/lib/fairy/master/c-seg-shuffle.rb +87 -0
  93. data/lib/fairy/master/c-seg-split.rb +110 -0
  94. data/lib/fairy/master/c-seg-zip.rb +132 -0
  95. data/lib/fairy/master/c-select.rb +27 -0
  96. data/lib/fairy/master/c-sort.rb +108 -0
  97. data/lib/fairy/master/c-there.rb +57 -0
  98. data/lib/fairy/master/c-wc.rb +232 -0
  99. data/lib/fairy/master/job-interpriter.rb +19 -0
  100. data/lib/fairy/master/scheduler.rb +24 -0
  101. data/lib/fairy/master.rb +329 -0
  102. data/lib/fairy/node/addins.rb +19 -0
  103. data/lib/fairy/node/p-barrier.rb +95 -0
  104. data/lib/fairy/node/p-basic-group-by.rb +252 -0
  105. data/lib/fairy/node/p-direct-product.rb +153 -0
  106. data/lib/fairy/node/p-exec.rb +30 -0
  107. data/lib/fairy/node/p-filter.rb +363 -0
  108. data/lib/fairy/node/p-find.rb +111 -0
  109. data/lib/fairy/node/p-group-by.rb +1534 -0
  110. data/lib/fairy/node/p-here.rb +21 -0
  111. data/lib/fairy/node/p-identity.rb +24 -0
  112. data/lib/fairy/node/p-inject.rb +127 -0
  113. data/lib/fairy/node/p-input-file.rb +108 -0
  114. data/lib/fairy/node/p-input-iota.rb +39 -0
  115. data/lib/fairy/node/p-input-local-file.rb +61 -0
  116. data/lib/fairy/node/p-input-varray.rb +26 -0
  117. data/lib/fairy/node/p-io-filter.rb +28 -0
  118. data/lib/fairy/node/p-map.rb +40 -0
  119. data/lib/fairy/node/p-merger-group-by.rb +48 -0
  120. data/lib/fairy/node/p-output-file.rb +104 -0
  121. data/lib/fairy/node/p-output-local-file.rb +14 -0
  122. data/lib/fairy/node/p-output-null.rb +32 -0
  123. data/lib/fairy/node/p-output-varray.rb +41 -0
  124. data/lib/fairy/node/p-seg-join.rb +82 -0
  125. data/lib/fairy/node/p-seg-map.rb +34 -0
  126. data/lib/fairy/node/p-seg-split.rb +61 -0
  127. data/lib/fairy/node/p-seg-zip.rb +79 -0
  128. data/lib/fairy/node/p-select.rb +40 -0
  129. data/lib/fairy/node/p-single-exportable.rb +90 -0
  130. data/lib/fairy/node/p-sort.rb +195 -0
  131. data/lib/fairy/node/p-task.rb +113 -0
  132. data/lib/fairy/node/p-there.rb +44 -0
  133. data/lib/fairy/node/p-wc.rb +266 -0
  134. data/lib/fairy/node.rb +187 -0
  135. data/lib/fairy/processor.rb +510 -0
  136. data/lib/fairy/share/base-app.rb +114 -0
  137. data/lib/fairy/share/block-source.rb +234 -0
  138. data/lib/fairy/share/conf.rb +396 -0
  139. data/lib/fairy/share/debug.rb +21 -0
  140. data/lib/fairy/share/encoding.rb +17 -0
  141. data/lib/fairy/share/fast-tempfile.rb +93 -0
  142. data/lib/fairy/share/file-place.rb +176 -0
  143. data/lib/fairy/share/hash-1.rb +20 -0
  144. data/lib/fairy/share/hash-md5.rb +28 -0
  145. data/lib/fairy/share/hash-murmur.rb +69 -0
  146. data/lib/fairy/share/hash-rb18.rb +20 -0
  147. data/lib/fairy/share/hash-simple-hash.rb +28 -0
  148. data/lib/fairy/share/inspector.rb +16 -0
  149. data/lib/fairy/share/lc/exceptions.rb +82 -0
  150. data/lib/fairy/share/lc/ja/exceptions.rb +81 -0
  151. data/lib/fairy/share/locale.rb +17 -0
  152. data/lib/fairy/share/log.rb +215 -0
  153. data/lib/fairy/share/pool-dictionary.rb +53 -0
  154. data/lib/fairy/share/port-marshaled-queue.rb +347 -0
  155. data/lib/fairy/share/port.rb +1697 -0
  156. data/lib/fairy/share/reference.rb +45 -0
  157. data/lib/fairy/share/stdout.rb +56 -0
  158. data/lib/fairy/share/tr.rb +16 -0
  159. data/lib/fairy/share/varray.rb +147 -0
  160. data/lib/fairy/share/vfile.rb +183 -0
  161. data/lib/fairy/version.rb +8 -0
  162. data/lib/fairy.rb +206 -0
  163. data/sample/grep.rb +46 -0
  164. data/sample/ping.rb +19 -0
  165. data/sample/sort.rb +102 -0
  166. data/sample/wordcount.rb +61 -0
  167. data/spec/README +12 -0
  168. data/spec/fairy1_spec.rb +31 -0
  169. data/spec/fairy2_spec.rb +42 -0
  170. data/spec/fairy3_spec.rb +126 -0
  171. data/spec/fairy4_spec.rb +63 -0
  172. data/spec/fairy5_spec.rb +45 -0
  173. data/spec/fairy6_spec.rb +52 -0
  174. data/spec/fairy7_spec.rb +58 -0
  175. data/spec/fairy8_spec.rb +48 -0
  176. data/spec/mkdat.rb +148 -0
  177. data/spec/run_all.sh +65 -0
  178. data/test/testc.rb +7111 -0
  179. data/tools/cap_recipe/Capfile +144 -0
  180. data/tools/cap_recipe/cluster.yml.sample +14 -0
  181. data/tools/fairy_perf_graph.rb +444 -0
  182. data/tools/git-tag +44 -0
  183. data/tools/log-analysis.rb +62 -0
  184. data/tools/svn-ls-diff +38 -0
  185. data/tools/svn-tags +37 -0
  186. metadata +298 -0
@@ -0,0 +1,144 @@
1
+
2
+ require 'yaml'
3
+
4
+ cluster = YAML.load_file(File.dirname(__FILE__) + "/cluster.yml")
5
+
6
+ server cluster["master"], :master
7
+ cluster["nodes"].each{|node| server node, :nodes }
8
+
9
+ if cluster["run_uid"]
10
+ set :user, cluster["run_uid"]
11
+ end
12
+
13
+
14
+ desc <<-DESC
15
+ Start fairy on a cluster specified by cluster.yml
16
+ DESC
17
+ task :start do
18
+ start_master
19
+ sleep(3)
20
+ start_nodes
21
+ sleep(3)
22
+ ps
23
+ end
24
+
25
+ task :start_master, :roles => [:master] do
26
+ run %{ mkdir -p /tmp/fairy/tmpbuf }
27
+ run %{ nohup fairy master > /tmp/fairy_master.log 2>&1 & }
28
+ end
29
+
30
+ task :start_nodes, :roles => [:nodes] do
31
+ run %{ mkdir -p /tmp/fairy/tmpbuf }
32
+ run %{ nohup fairy node > /tmp/fairy_node.log 2>&1 & }
33
+ end
34
+
35
+ desc <<-DESC
36
+ Show fairy processes on the cluster.
37
+ DESC
38
+ task :ps, :roles => [:master, :nodes] do
39
+ run %{ ps -ef | grep -E 'fairy (master|controller|node|processor) ' | grep -v 'grep' | tee /tmp/fairy_ps }
40
+ end
41
+
42
+ desc <<-DESC
43
+ Stop fairy.
44
+ DESC
45
+ task :stop, :roles => [:master, :nodes] do
46
+ ps
47
+ run %{ if [ -s /tmp/fairy_ps ]; then cat /tmp/fairy_ps | awk '{print $2}' | xargs kill -9; fi }
48
+ end
49
+
50
+ desc <<-DESC
51
+ Restart fairy.
52
+ DESC
53
+ task :restart do
54
+ stop
55
+ start
56
+ end
57
+
58
+ desc <<-DESC
59
+ Show some information of the cluster.
60
+ * Versions of Ruby and fairy.
61
+ * Values of $RUBYLIB/$FAIRY_HOME environment var.
62
+ * Path of fairy command.
63
+ DESC
64
+ task :showinfo, :roles => [:master, :nodes] do
65
+ run %{ ruby -v }
66
+ run %{ echo "RUBYLIB=$RUBYLIB" }
67
+ run %{ echo "FAIRY_HOME=$FAIRY_HOME" }
68
+ run %{ grep 'Version =' "$FAIRY_HOME"/lib/fairy/version.rb | sed -e 's/^ *//' }
69
+ run %{ which -a fairy }
70
+ end
71
+
72
+ desc <<-DESC
73
+ Truncate fairy log (master:/tmp/fairy/log).
74
+ DESC
75
+ task :clear_log, :roles => [:master] do
76
+ run %{ cat /dev/null > /tmp/fairy/log }
77
+ end
78
+
79
+
80
+
81
+
82
+ desc <<-DESC
83
+ Install fairy to all hosts in the cluster.
84
+ You need to grant "sudo" privilege to "run_uid" user.
85
+ If you put the following files in "cap_recipe" directory
86
+ before the execution, they will be distributed to the
87
+ cluster.
88
+ * fairy.conf -> $FAIRY_HOME/etc
89
+ * .fairyenv -> "run_uid" user's home directory
90
+ This task also creates "/tmp/fairy/tmpbuf" directory
91
+ on all hosts.
92
+ DESC
93
+ task :install, :roles => [:master, :nodes] do
94
+ pkg = "fairy"
95
+ if local_pkg
96
+ pkg = "/tmp/#{File.basename(local_pkg)}"
97
+ upload "#{local_pkg}", "#{pkg}"
98
+ end
99
+
100
+ if gem_cmd
101
+ sudo %{ #{gem_cmd} install #{pkg} }
102
+ else
103
+ sudo %{ gem install #{pkg} }
104
+ end
105
+
106
+ run %{ mkdir -p /tmp/fairy/tmpbuf }
107
+
108
+ if FileTest.exists? "fairy.conf"
109
+ sync_conf
110
+ end
111
+
112
+ if FileTest.exists? ".fairyenv"
113
+ sync_env
114
+ end
115
+ end
116
+
117
+ set :fairy_home, ENV['FAIRY_HOME'] unless exists? :fairy_home
118
+
119
+ task :sync_conf, :roles => [:master, :nodes] do
120
+ unless fairy_home
121
+ raise "fairy_home is not specified."
122
+ end
123
+ upload "fairy.conf", "/tmp/fairy.conf"
124
+ sudo %{ cp "/tmp/fairy.conf" #{fairy_home}/etc }
125
+ run %{ rm /tmp/fairy.conf }
126
+ end
127
+
128
+ task :sync_env, :roles => [:master, :nodes] do
129
+ upload ".fairyenv", ".fairyenv"
130
+ end
131
+
132
+ desc <<-DESC
133
+ Update fairy on all hosts in the cluster.
134
+ You need to grant "sudo" privilege to "run_uid" user.
135
+ DESC
136
+ task :update, :roles => [:master, :nodes] do
137
+ if gem_cmd
138
+ sudo %{ #{gem_cmd} update fairy }
139
+ else
140
+ sudo %{ gem update fairy }
141
+ end
142
+ end
143
+
144
+
@@ -0,0 +1,14 @@
1
+ #
2
+ # fairy servers
3
+ #
4
+ run_uid: fairy
5
+ master: fairy-xm07
6
+ nodes:
7
+ - fairy-xm01
8
+ - fairy-xm02
9
+ - fairy-xm03
10
+ - fairy-xm04
11
+ - fairy-xm05
12
+ - fairy-xm06
13
+
14
+
@@ -0,0 +1,444 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ # Log Analysis Data Visualizer for fairy
4
+ #
5
+ # This tool processes a output of log-analysis.rb
6
+ # and outputs a PNG image.
7
+ #
8
+ # by Hajime Masuda (Rakuten, Inc.)
9
+ #
10
+ # encoding: UTF-8
11
+
12
+ require 'time'
13
+ #require 'pp'
14
+
15
+ require 'rubygems'
16
+ require 'cairo'
17
+
18
+
19
+ module FairyPerformanceGraph
20
+
21
+ class Base
22
+ attr_reader :parent, :margin_top, :margin_button, :margin_between_elems, :margin_left, :margin_right
23
+
24
+ def initialize(parent)
25
+ @parent = parent
26
+
27
+ @margin_top = 0
28
+ @margin_button = 0
29
+ @margin_between_elems = 0
30
+ @margin_left = 0
31
+ @margin_right = 0
32
+ end
33
+
34
+ def height
35
+ init_val = @margin_top + @margin_button - @margin_between_elems
36
+ @contents.inject(init_val){|result,elem|
37
+ result += elem.height + @margin_between_elems
38
+ result
39
+ }
40
+ end
41
+ end
42
+
43
+ class Session < Base
44
+ attr_accessor :nodes, :scale, :start_at, :elapsed
45
+
46
+ def initialize(width=1200)
47
+ super(nil)
48
+
49
+ @contents = []
50
+
51
+ @margin_top = 25
52
+ @margin_button = 5
53
+ @margin_between_elems = 5
54
+ @margin_left = 5
55
+ @margin_right = 5
56
+
57
+ @width = width
58
+ @bgcolor = "WHITE"
59
+ @fgcolor = "BLACK"
60
+ @rounded_r = 10
61
+ @font_size = 18.0
62
+
63
+ @cache = {}
64
+ end
65
+
66
+ def index(name)
67
+ return @cache[name] if @cache[name]
68
+
69
+ idx = @contents.index{|node| node.name == name}
70
+ @cache[name] = idx if idx
71
+
72
+ return idx
73
+ end
74
+
75
+ def nodes
76
+ @contents
77
+ end
78
+
79
+ def max_child_width
80
+ @width - (@margin_left + @margin_right)
81
+ end
82
+
83
+ def draw(write_to)
84
+ @start_at = Time.now
85
+ @end_at = Time.at(0)
86
+
87
+ @contents.each{|node|
88
+ node.processors.each{|processor|
89
+ processor.filters.each{|filter|
90
+ @start_at = filter.start_at if @start_at > filter.start_at
91
+ @end_at = filter.end_at if @end_at < filter.end_at
92
+ }
93
+ }
94
+ }
95
+
96
+ max_filter_width = @contents[0].processors[0].filters[0].max_width
97
+ @scale = max_filter_width.to_f / (@end_at - @start_at)
98
+
99
+ @elapsed = @end_at - @start_at
100
+
101
+ format = Cairo::FORMAT_ARGB32
102
+ surface = Cairo::ImageSurface.new(format, @width, (h = height))
103
+ @context = Cairo::Context.new(surface)
104
+
105
+ # draw background
106
+ @context.set_source_color(@bgcolor)
107
+ @context.rectangle(0, 0, @width, h)
108
+ @context.fill
109
+
110
+ # draw nodes
111
+ height_total = 0
112
+ w = @width - (@margin_left + @margin_right)
113
+ @contents.each_with_index{|node,i|
114
+ off_y = @margin_top + (i * @margin_between_elems) + height_total
115
+ height_total += node.draw(@context, @margin_left, off_y, w)
116
+ }
117
+
118
+
119
+ # draw time lines
120
+ ml = @margin_left + @contents[0].margin_left + @contents[0].processors[0].margin_left
121
+ interval = max_filter_width / 20
122
+ y = h - @margin_button
123
+ @context.set_source_color("DARK_KHAKI")
124
+ @context.set_line_width(0.5)
125
+ @context.set_dash([2,2])
126
+ 21.times{|i|
127
+ @context.move_to(ml, @margin_top)
128
+ @context.line_to(ml, y)
129
+ @context.stroke
130
+ ml += interval
131
+ }
132
+ @context.set_dash(nil)
133
+
134
+ # draw text
135
+ @context.set_source_color(@fgcolor)
136
+ @context.move_to(5, @font_size)
137
+ @context.set_font_size(@font_size)
138
+ @context.show_text("START:#{@start_at} -- END:#{@end_at} (#{@elapsed.to_i} sec.)")
139
+
140
+ # output image
141
+ surface.write_to_png(write_to)
142
+ end
143
+ end
144
+
145
+ class Node < Base
146
+ attr_reader :name, :line_color, :bgcolor
147
+
148
+ def initialize(parent, name)
149
+ super(parent)
150
+
151
+ @name = name
152
+ @contents = []
153
+
154
+ @margin_left = 80
155
+
156
+ @bgcolor = "#FFFFDD"
157
+ @fgcolor = "BLACK"
158
+ @rounded_r = 10
159
+ @font_size = 14.0
160
+ @line_width = 0.5
161
+ end
162
+
163
+ def processors
164
+ @contents
165
+ end
166
+
167
+ def max_child_width
168
+ @parent.max_child_width - (@margin_left + @margin_right)
169
+ end
170
+
171
+ def draw(context, x, y, width)
172
+ context.set_source_color(@bgcolor)
173
+ context.rounded_rectangle(x, y, width, (h = height), @rounded_r, @rounded_r)
174
+ context.fill_preserve
175
+ context.set_source_color(@fgcolor)
176
+ context.set_line_width(@line_width)
177
+ context.stroke
178
+
179
+ context.move_to((x + 5), (y + @font_size + 5))
180
+ context.set_font_size(@font_size)
181
+ context.show_text(@name)
182
+
183
+ # draw processors
184
+ height_total = 0
185
+ off_x = x + @margin_left
186
+ w = width - @margin_left
187
+ @contents.each_with_index{|processor,i|
188
+ off_y = y + @margin_top + (i * @margin_between_elems) + height_total
189
+ height_total += processor.draw(context, off_x, off_y, w)
190
+ }
191
+
192
+ h
193
+ end
194
+ end
195
+
196
+ class Processor < Base
197
+ attr_reader :id
198
+
199
+ def initialize(parent, id)
200
+ super(parent)
201
+
202
+ @id = id
203
+ @contents = []
204
+
205
+ @margin_top = 10
206
+ @margin_button = 10
207
+ @margin_between_elems = 5
208
+ @margin_left = 40
209
+ @margin_right = 5
210
+
211
+ @fgcolor = "DARK_BLUE"
212
+ @line_width = 0.75
213
+ @dash = [5,2]
214
+ @font_size = 14.0
215
+ end
216
+
217
+ def filters
218
+ @contents
219
+ end
220
+
221
+ def max_child_width
222
+ @parent.max_child_width - (@margin_left + @margin_right)
223
+ end
224
+
225
+ def draw(context, x, y, width)
226
+ context.set_source_color(@fgcolor)
227
+ context.set_font_size(@font_size)
228
+ context.move_to((x + 5), (y + @font_size + 5))
229
+ context.show_text("P#"+id.to_s)
230
+
231
+ @contents.sort!{|a,b| a.start_at - b.start_at}
232
+
233
+ # draw filters
234
+ height_total = 0
235
+ off_x = x + @margin_left
236
+ @contents.each_with_index{|filter,i|
237
+ off_y = y + @margin_top + (i * @margin_between_elems) + height_total
238
+ height_total += filter.draw(context, off_x, off_y)
239
+ }
240
+
241
+ if @id == (@parent.processors.size - 1)
242
+ h = height
243
+ else
244
+ context.set_source_color(@fgcolor)
245
+ context.set_line_width(@line_width)
246
+ context.set_dash(@dash)
247
+ context.move_to(x, (y + (h = height)))
248
+ context.line_to((x + width), (y + h))
249
+ context.stroke
250
+ context.set_dash(nil)
251
+ end
252
+
253
+ h
254
+ end
255
+ end
256
+
257
+ class Filter < Base
258
+ IMPORT = 0
259
+ PROCESSING = 1
260
+ EXPORT = 2
261
+
262
+ TYPE_NAME2IDX = {
263
+ "IMPORT" => IMPORT,
264
+ "PROCESSING" => PROCESSING,
265
+ "EXPORT" => EXPORT
266
+ }
267
+
268
+ PTN_NAME = /\A(?:\w+::)*\w+\[((\d+)-\d+)(\[(\d+:\d+)\])?\]\z/
269
+
270
+ attr_reader :name, :type, :start_at, :end_at, :elapsed, :job_id, :task_id, :key_info
271
+ attr_accessor :elapsed_for_store
272
+
273
+ def initialize(parent, name, type, start_at, end_at, elapsed)
274
+ super(parent)
275
+
276
+ @name = name
277
+ @type = TYPE_NAME2IDX[type]
278
+ @start_at = Time.parse(start_at)
279
+ @end_at = Time.parse(end_at)
280
+ @elapsed = elapsed
281
+
282
+ @fgcolor = "BLACK"
283
+ @font_size = 8
284
+
285
+ @job_id, @task_id, @key_info = self.class.parse_name(@name)
286
+ end
287
+
288
+ def self.parse_name(name)
289
+ if m = name.match(PTN_NAME)
290
+ m[1..3]
291
+ else
292
+ []
293
+ end
294
+ end
295
+
296
+ def height
297
+ 10
298
+ end
299
+
300
+ def max_width
301
+ @parent.max_child_width
302
+ end
303
+
304
+ def draw(context, x, y)
305
+ case @type
306
+ when IMPORT
307
+ context.set_source_color("LIGHT_BLUE")
308
+ when PROCESSING
309
+ context.set_source_color("PINK")
310
+ when EXPORT
311
+ context.set_source_color("LIGHT_GREEN")
312
+ end
313
+
314
+ scale = @parent.parent.parent.scale
315
+ global_start_at = @parent.parent.parent.start_at
316
+
317
+ width = ((@end_at - @start_at) * scale).to_i
318
+ off_x = x + ((@start_at - global_start_at) * scale).to_i
319
+
320
+ context.rectangle(off_x, y, width, (h = height))
321
+ context.fill
322
+
323
+ if @type == IMPORT
324
+ context.set_source_color("CORNFLOWER_BLUE")
325
+ width_store = (@elapsed_for_store * scale).to_i
326
+ off_x_store = off_x + width - width_store
327
+ context.rectangle(off_x_store, y, width_store, h)
328
+ context.fill
329
+ end
330
+
331
+ context.set_source_color(@fgcolor)
332
+ context.move_to((off_x + 2), (y + @font_size))
333
+ context.set_font_size(@font_size)
334
+ context.show_text(@name)
335
+
336
+ global_elaped = @parent.parent.parent.elapsed
337
+ percentage = (@elapsed / global_elaped) * 100
338
+
339
+ context.set_source_color("RED")
340
+ context.move_to(off_x + 2 + ((@name.size * @font_size) * 0.7).to_i, (y + @font_size))
341
+ context.set_font_size(@font_size + 2.0)
342
+ context.show_text("%.1f sec. (%.1f%%)" % [@elapsed, percentage])
343
+
344
+
345
+ h
346
+ end
347
+ end
348
+
349
+ class LogParser
350
+ PTN_SEPARATOR = /, */
351
+ PTN_FIRST_RECORD = /\A([^ ]+) \[P\]#(\d+) (.+)\z/
352
+
353
+ attr_reader :io
354
+
355
+ def initialize(log)
356
+ @io = File.open(log, "r")
357
+ end
358
+
359
+ def each_log
360
+ @io.each_with_index{|ln,i|
361
+ ln.chomp!
362
+ ary = ln.split(PTN_SEPARATOR)
363
+ m = ary[0].match(PTN_FIRST_RECORD)
364
+
365
+ line_no = i+1
366
+ host_name = m[1]
367
+ processor_id = m[2].to_i
368
+ filter_name = m[3]
369
+ type = ary[1]
370
+ start_at = ary[2]
371
+ end_at = ary[3]
372
+ elasped = ary[4].to_f
373
+
374
+ yield(line_no, host_name, processor_id, filter_name, type, start_at, end_at, elasped)
375
+ }
376
+ end
377
+ end
378
+
379
+ #
380
+ # main
381
+ #
382
+ def run(input_from, output_to, width=nil)
383
+ if width
384
+ graph = Session.new(width)
385
+ else
386
+ graph = Session.new
387
+ end
388
+
389
+ log = LogParser.new(input_from)
390
+ log.each_log{|line_no, host_name, processor_id, filter_name, type, start_at, end_at, elapsed|
391
+
392
+ if type == "STORE"
393
+ node = graph.nodes.select{|node| node.name == host_name}[0] or next
394
+ processor = node.processors[processor_id] or next
395
+ filter = processor.filters.select{|filter| (filter.type == Filter::IMPORT) && (filter.job_id == Filter.parse_name(filter_name)[0])}[0] or next
396
+ filter.elapsed_for_store = elapsed
397
+ #$stderr.puts("set filter.elapsed_for_store (#{filter.name})")
398
+ next
399
+ end
400
+
401
+ if idx = graph.index(host_name)
402
+ node = graph.nodes[idx]
403
+ else
404
+ node = Node.new(graph, host_name)
405
+ graph.nodes << node
406
+ end
407
+
408
+ unless processor = node.processors[processor_id]
409
+ processor = Processor.new(node, processor_id)
410
+ node.processors << processor
411
+ end
412
+
413
+ processor.filters << Filter.new(processor, filter_name, type, start_at, end_at, elapsed)
414
+ }
415
+ #pp graph
416
+
417
+ graph.draw(output_to)
418
+ end
419
+ module_function :run
420
+
421
+ end
422
+
423
+
424
+ #
425
+ # boot strap
426
+ #
427
+ unless ARGV.size == 2 || ARGV.size == 3
428
+ $stderr.puts "Usage: #{File.basename($0)} INPUT_FROM OUTPUT_TO [IMAGE_WIDTH]"
429
+ exit(1)
430
+ end
431
+
432
+ INPUT_FROM = ARGV.shift
433
+ OUTPUT_TO = ARGV.shift
434
+ WIDTH = ARGV.shift
435
+
436
+ if WIDTH
437
+ FairyPerformanceGraph.run(INPUT_FROM, OUTPUT_TO, WIDTH.to_i)
438
+ else
439
+ FairyPerformanceGraph.run(INPUT_FROM, OUTPUT_TO)
440
+ end
441
+
442
+ exit(0)
443
+
444
+