fairy 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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
+