roby 0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (240) hide show
  1. data/.gitignore +29 -0
  2. data/History.txt +4 -0
  3. data/License-fr.txt +519 -0
  4. data/License.txt +515 -0
  5. data/Manifest.txt +245 -0
  6. data/NOTES +4 -0
  7. data/README.txt +163 -0
  8. data/Rakefile +161 -0
  9. data/TODO.txt +146 -0
  10. data/app/README.txt +24 -0
  11. data/app/Rakefile +8 -0
  12. data/app/config/ROBOT.rb +5 -0
  13. data/app/config/app.yml +91 -0
  14. data/app/config/init.rb +7 -0
  15. data/app/config/roby.yml +3 -0
  16. data/app/controllers/.gitattributes +0 -0
  17. data/app/controllers/ROBOT.rb +2 -0
  18. data/app/data/.gitattributes +0 -0
  19. data/app/planners/ROBOT/main.rb +6 -0
  20. data/app/planners/main.rb +5 -0
  21. data/app/scripts/distributed +3 -0
  22. data/app/scripts/generate/bookmarks +3 -0
  23. data/app/scripts/replay +3 -0
  24. data/app/scripts/results +3 -0
  25. data/app/scripts/run +3 -0
  26. data/app/scripts/server +3 -0
  27. data/app/scripts/shell +3 -0
  28. data/app/scripts/test +3 -0
  29. data/app/tasks/.gitattributes +0 -0
  30. data/app/tasks/ROBOT/.gitattributes +0 -0
  31. data/bin/roby +210 -0
  32. data/bin/roby-log +168 -0
  33. data/bin/roby-shell +25 -0
  34. data/doc/images/event_generalization.png +0 -0
  35. data/doc/images/exception_propagation_1.png +0 -0
  36. data/doc/images/exception_propagation_2.png +0 -0
  37. data/doc/images/exception_propagation_3.png +0 -0
  38. data/doc/images/exception_propagation_4.png +0 -0
  39. data/doc/images/exception_propagation_5.png +0 -0
  40. data/doc/images/replay_handler_error.png +0 -0
  41. data/doc/images/replay_handler_error_0.png +0 -0
  42. data/doc/images/replay_handler_error_1.png +0 -0
  43. data/doc/images/roby_cycle_overview.png +0 -0
  44. data/doc/images/roby_replay_02.png +0 -0
  45. data/doc/images/roby_replay_03.png +0 -0
  46. data/doc/images/roby_replay_04.png +0 -0
  47. data/doc/images/roby_replay_event_representation.png +0 -0
  48. data/doc/images/roby_replay_first_state.png +0 -0
  49. data/doc/images/roby_replay_relations.png +0 -0
  50. data/doc/images/roby_replay_startup.png +0 -0
  51. data/doc/images/task_event_generalization.png +0 -0
  52. data/doc/papers.rdoc +11 -0
  53. data/doc/styles/allison.css +314 -0
  54. data/doc/styles/allison.js +316 -0
  55. data/doc/styles/allison.rb +276 -0
  56. data/doc/styles/jamis.rb +593 -0
  57. data/doc/tutorials/01-GettingStarted.rdoc +86 -0
  58. data/doc/tutorials/02-GoForward.rdoc +220 -0
  59. data/doc/tutorials/03-PlannedPath.rdoc +268 -0
  60. data/doc/tutorials/04-EventPropagation.rdoc +236 -0
  61. data/doc/tutorials/05-ErrorHandling.rdoc +319 -0
  62. data/doc/tutorials/06-Overview.rdoc +40 -0
  63. data/doc/videos.rdoc +69 -0
  64. data/ext/droby/dump.cc +175 -0
  65. data/ext/droby/extconf.rb +3 -0
  66. data/ext/graph/algorithm.cc +746 -0
  67. data/ext/graph/extconf.rb +7 -0
  68. data/ext/graph/graph.cc +529 -0
  69. data/ext/graph/graph.hh +183 -0
  70. data/ext/graph/iterator_sequence.hh +102 -0
  71. data/ext/graph/undirected_dfs.hh +226 -0
  72. data/ext/graph/undirected_graph.hh +421 -0
  73. data/lib/roby.rb +41 -0
  74. data/lib/roby/app.rb +870 -0
  75. data/lib/roby/app/rake.rb +56 -0
  76. data/lib/roby/app/run.rb +14 -0
  77. data/lib/roby/app/scripts/distributed.rb +13 -0
  78. data/lib/roby/app/scripts/generate/bookmarks.rb +162 -0
  79. data/lib/roby/app/scripts/replay.rb +31 -0
  80. data/lib/roby/app/scripts/results.rb +15 -0
  81. data/lib/roby/app/scripts/run.rb +26 -0
  82. data/lib/roby/app/scripts/server.rb +18 -0
  83. data/lib/roby/app/scripts/shell.rb +88 -0
  84. data/lib/roby/app/scripts/test.rb +40 -0
  85. data/lib/roby/basic_object.rb +151 -0
  86. data/lib/roby/config.rb +5 -0
  87. data/lib/roby/control.rb +747 -0
  88. data/lib/roby/decision_control.rb +17 -0
  89. data/lib/roby/distributed.rb +32 -0
  90. data/lib/roby/distributed/base.rb +440 -0
  91. data/lib/roby/distributed/communication.rb +871 -0
  92. data/lib/roby/distributed/connection_space.rb +592 -0
  93. data/lib/roby/distributed/distributed_object.rb +206 -0
  94. data/lib/roby/distributed/drb.rb +62 -0
  95. data/lib/roby/distributed/notifications.rb +539 -0
  96. data/lib/roby/distributed/peer.rb +550 -0
  97. data/lib/roby/distributed/protocol.rb +529 -0
  98. data/lib/roby/distributed/proxy.rb +343 -0
  99. data/lib/roby/distributed/subscription.rb +311 -0
  100. data/lib/roby/distributed/transaction.rb +498 -0
  101. data/lib/roby/event.rb +897 -0
  102. data/lib/roby/exceptions.rb +234 -0
  103. data/lib/roby/executives/simple.rb +30 -0
  104. data/lib/roby/graph.rb +166 -0
  105. data/lib/roby/interface.rb +390 -0
  106. data/lib/roby/log.rb +3 -0
  107. data/lib/roby/log/chronicle.rb +303 -0
  108. data/lib/roby/log/console.rb +72 -0
  109. data/lib/roby/log/data_stream.rb +197 -0
  110. data/lib/roby/log/dot.rb +279 -0
  111. data/lib/roby/log/event_stream.rb +151 -0
  112. data/lib/roby/log/file.rb +340 -0
  113. data/lib/roby/log/gui/basic_display.ui +83 -0
  114. data/lib/roby/log/gui/chronicle.rb +26 -0
  115. data/lib/roby/log/gui/chronicle_view.rb +40 -0
  116. data/lib/roby/log/gui/chronicle_view.ui +70 -0
  117. data/lib/roby/log/gui/data_displays.rb +172 -0
  118. data/lib/roby/log/gui/data_displays.ui +155 -0
  119. data/lib/roby/log/gui/notifications.rb +26 -0
  120. data/lib/roby/log/gui/relations.rb +248 -0
  121. data/lib/roby/log/gui/relations.ui +123 -0
  122. data/lib/roby/log/gui/relations_view.rb +185 -0
  123. data/lib/roby/log/gui/relations_view.ui +149 -0
  124. data/lib/roby/log/gui/replay.rb +327 -0
  125. data/lib/roby/log/gui/replay_controls.rb +200 -0
  126. data/lib/roby/log/gui/replay_controls.ui +259 -0
  127. data/lib/roby/log/gui/runtime.rb +130 -0
  128. data/lib/roby/log/hooks.rb +185 -0
  129. data/lib/roby/log/logger.rb +202 -0
  130. data/lib/roby/log/notifications.rb +244 -0
  131. data/lib/roby/log/plan_rebuilder.rb +470 -0
  132. data/lib/roby/log/relations.rb +1056 -0
  133. data/lib/roby/log/server.rb +550 -0
  134. data/lib/roby/log/sqlite.rb +47 -0
  135. data/lib/roby/log/timings.rb +164 -0
  136. data/lib/roby/plan-object.rb +247 -0
  137. data/lib/roby/plan.rb +762 -0
  138. data/lib/roby/planning.rb +13 -0
  139. data/lib/roby/planning/loops.rb +302 -0
  140. data/lib/roby/planning/model.rb +906 -0
  141. data/lib/roby/planning/task.rb +151 -0
  142. data/lib/roby/propagation.rb +562 -0
  143. data/lib/roby/query.rb +619 -0
  144. data/lib/roby/relations.rb +583 -0
  145. data/lib/roby/relations/conflicts.rb +70 -0
  146. data/lib/roby/relations/ensured.rb +20 -0
  147. data/lib/roby/relations/error_handling.rb +23 -0
  148. data/lib/roby/relations/events.rb +9 -0
  149. data/lib/roby/relations/executed_by.rb +193 -0
  150. data/lib/roby/relations/hierarchy.rb +239 -0
  151. data/lib/roby/relations/influence.rb +10 -0
  152. data/lib/roby/relations/planned_by.rb +63 -0
  153. data/lib/roby/robot.rb +7 -0
  154. data/lib/roby/standard_errors.rb +218 -0
  155. data/lib/roby/state.rb +5 -0
  156. data/lib/roby/state/events.rb +221 -0
  157. data/lib/roby/state/information.rb +55 -0
  158. data/lib/roby/state/pos.rb +110 -0
  159. data/lib/roby/state/shapes.rb +32 -0
  160. data/lib/roby/state/state.rb +353 -0
  161. data/lib/roby/support.rb +92 -0
  162. data/lib/roby/task-operations.rb +182 -0
  163. data/lib/roby/task.rb +1618 -0
  164. data/lib/roby/test/common.rb +399 -0
  165. data/lib/roby/test/distributed.rb +214 -0
  166. data/lib/roby/test/tasks/empty_task.rb +9 -0
  167. data/lib/roby/test/tasks/goto.rb +36 -0
  168. data/lib/roby/test/tasks/simple_task.rb +23 -0
  169. data/lib/roby/test/testcase.rb +519 -0
  170. data/lib/roby/test/tools.rb +160 -0
  171. data/lib/roby/thread_task.rb +87 -0
  172. data/lib/roby/transactions.rb +462 -0
  173. data/lib/roby/transactions/proxy.rb +292 -0
  174. data/lib/roby/transactions/updates.rb +139 -0
  175. data/plugins/fault_injection/History.txt +4 -0
  176. data/plugins/fault_injection/README.txt +37 -0
  177. data/plugins/fault_injection/Rakefile +18 -0
  178. data/plugins/fault_injection/TODO.txt +0 -0
  179. data/plugins/fault_injection/app.rb +52 -0
  180. data/plugins/fault_injection/fault_injection.rb +89 -0
  181. data/plugins/fault_injection/test/test_fault_injection.rb +84 -0
  182. data/plugins/subsystems/README.txt +40 -0
  183. data/plugins/subsystems/Rakefile +18 -0
  184. data/plugins/subsystems/app.rb +171 -0
  185. data/plugins/subsystems/test/app/README +24 -0
  186. data/plugins/subsystems/test/app/Rakefile +8 -0
  187. data/plugins/subsystems/test/app/config/app.yml +71 -0
  188. data/plugins/subsystems/test/app/config/init.rb +9 -0
  189. data/plugins/subsystems/test/app/config/roby.yml +3 -0
  190. data/plugins/subsystems/test/app/planners/main.rb +20 -0
  191. data/plugins/subsystems/test/app/scripts/distributed +3 -0
  192. data/plugins/subsystems/test/app/scripts/replay +3 -0
  193. data/plugins/subsystems/test/app/scripts/results +3 -0
  194. data/plugins/subsystems/test/app/scripts/run +3 -0
  195. data/plugins/subsystems/test/app/scripts/server +3 -0
  196. data/plugins/subsystems/test/app/scripts/shell +3 -0
  197. data/plugins/subsystems/test/app/scripts/test +3 -0
  198. data/plugins/subsystems/test/app/tasks/services.rb +15 -0
  199. data/plugins/subsystems/test/test_subsystems.rb +71 -0
  200. data/test/distributed/test_communication.rb +178 -0
  201. data/test/distributed/test_connection.rb +282 -0
  202. data/test/distributed/test_execution.rb +373 -0
  203. data/test/distributed/test_mixed_plan.rb +341 -0
  204. data/test/distributed/test_plan_notifications.rb +238 -0
  205. data/test/distributed/test_protocol.rb +516 -0
  206. data/test/distributed/test_query.rb +102 -0
  207. data/test/distributed/test_remote_plan.rb +491 -0
  208. data/test/distributed/test_transaction.rb +463 -0
  209. data/test/mockups/tasks.rb +27 -0
  210. data/test/planning/test_loops.rb +380 -0
  211. data/test/planning/test_model.rb +427 -0
  212. data/test/planning/test_task.rb +106 -0
  213. data/test/relations/test_conflicts.rb +42 -0
  214. data/test/relations/test_ensured.rb +38 -0
  215. data/test/relations/test_executed_by.rb +149 -0
  216. data/test/relations/test_hierarchy.rb +158 -0
  217. data/test/relations/test_planned_by.rb +54 -0
  218. data/test/suite_core.rb +24 -0
  219. data/test/suite_distributed.rb +9 -0
  220. data/test/suite_planning.rb +3 -0
  221. data/test/suite_relations.rb +8 -0
  222. data/test/test_bgl.rb +508 -0
  223. data/test/test_control.rb +399 -0
  224. data/test/test_event.rb +894 -0
  225. data/test/test_exceptions.rb +592 -0
  226. data/test/test_interface.rb +37 -0
  227. data/test/test_log.rb +114 -0
  228. data/test/test_log_server.rb +132 -0
  229. data/test/test_plan.rb +584 -0
  230. data/test/test_propagation.rb +210 -0
  231. data/test/test_query.rb +266 -0
  232. data/test/test_relations.rb +180 -0
  233. data/test/test_state.rb +414 -0
  234. data/test/test_support.rb +16 -0
  235. data/test/test_task.rb +938 -0
  236. data/test/test_testcase.rb +122 -0
  237. data/test/test_thread_task.rb +73 -0
  238. data/test/test_transactions.rb +569 -0
  239. data/test/test_transactions_proxy.rb +198 -0
  240. metadata +570 -0
@@ -0,0 +1,279 @@
1
+ require 'roby/distributed/protocol'
2
+ require 'roby/log'
3
+ require 'tempfile'
4
+ require 'fileutils'
5
+
6
+ module Roby
7
+ module LoggedPlan
8
+ attr_accessor :layout_level
9
+ def all_events(display)
10
+ known_tasks.inject(free_events.dup) do |events, task|
11
+ if display.displayed?(task)
12
+ events.merge(task.events.values.to_value_set)
13
+ else
14
+ events
15
+ end
16
+ end
17
+ end
18
+
19
+ attr_reader :dot_id
20
+ def to_dot(display, io, level)
21
+ @layout_level = level
22
+ id = io.layout_id(self)
23
+ @dot_id = "plan_#{id}"
24
+ io << "subgraph cluster_#{dot_id} {\n"
25
+ (known_tasks | finalized_tasks | free_events | finalized_events).
26
+ each do |obj|
27
+ obj.to_dot(display, io) if display.displayed?(obj)
28
+ end
29
+
30
+ io << "};\n"
31
+
32
+ transactions.each do |trsc|
33
+ trsc.to_dot(display, io, level + 1)
34
+ end
35
+
36
+ relations_to_dot(display, io, TaskStructure, known_tasks)
37
+ end
38
+
39
+ def each_displayed_relation(display, space, objects)
40
+ space.relations.each do |rel|
41
+ next unless display.relation_enabled?(rel)
42
+
43
+ objects.each do |from|
44
+ next unless display.displayed?(from)
45
+ unless display[from]
46
+ Roby::Log.warn "no display item for #{from} in #each_displayed_relation"
47
+ next
48
+ end
49
+
50
+ from.each_child_object(rel) do |to|
51
+ next unless display.displayed?(to)
52
+ unless display[to]
53
+ Roby::Log.warn "no display item for child in #{from} <#{rel}> #{to} in #each_displayed_relation"
54
+ next
55
+ end
56
+
57
+ yield(rel, from, to)
58
+ end
59
+ end
60
+ end
61
+ end
62
+
63
+ def relations_to_dot(display, io, space, objects)
64
+ each_displayed_relation(display, space, objects) do |rel, from, to|
65
+ from_id, to_id = from.dot_id, to.dot_id
66
+ if from_id && to_id
67
+ io << " #{from_id} -> #{to_id}\n"
68
+ else
69
+ Roby::Log.warn "ignoring #{from}(#{from.object_id} #{from_id}) -> #{to}(#{to.object_id} #{to_id}) in #{rel} in #{caller(1).join("\n ")}"
70
+ end
71
+ end
72
+ end
73
+
74
+ def layout_relations(positions, display, space, objects)
75
+ each_displayed_relation(display, space, objects) do |rel, from, to|
76
+ display.task_relation(from, to, rel, from[to, rel])
77
+ end
78
+ end
79
+
80
+ # The distance from the root plan
81
+ attr_reader :depth
82
+
83
+ # Computes the plan depths and max_depth for this plan and all its
84
+ # children. +depth+ is this plan depth
85
+ #
86
+ # Returns max_depth
87
+ def compute_depth(depth)
88
+ @depth = depth
89
+ child_depth = transactions.
90
+ map { |trsc| trsc.compute_depth(depth + 1) }.
91
+ max
92
+ child_depth || depth
93
+ end
94
+
95
+ def apply_layout(bounding_rects, positions, display, max_depth = nil)
96
+ max_depth ||= compute_depth(0)
97
+
98
+ if rect = bounding_rects[dot_id]
99
+ item = display[self]
100
+ rect[2] *= 1.2
101
+ rect[3] *= 1.2
102
+ item.z_value = Log::PLAN_LAYER + depth - max_depth
103
+ item.set_rect *rect
104
+ else
105
+ Roby::Log.warn "no bounding rectangle for #{self} (#{dot_id})"
106
+ end
107
+
108
+
109
+ (known_tasks | finalized_tasks | free_events | finalized_events).
110
+ each do |obj|
111
+ obj.apply_layout(positions, display)
112
+ end
113
+
114
+ transactions.each do |trsc|
115
+ trsc.apply_layout(bounding_rects, positions, display, max_depth)
116
+ end
117
+ layout_relations(positions, display, TaskStructure, known_tasks)
118
+ end
119
+ end
120
+
121
+ module LoggedPlanObject
122
+ attr_reader :dot_id
123
+
124
+ def dot_label(display); display_name(display) end
125
+
126
+ # Adds the dot definition for this object in +io+
127
+ def to_dot(display, io)
128
+ return unless display.displayed?(self)
129
+ @dot_id ||= "plan_object_#{io.layout_id(self)}"
130
+ io << " #{dot_id}[label=\"#{dot_label(display).split("\n").join('\n')}\"];\n"
131
+ end
132
+
133
+ # Applys the layout in +positions+ to this particular object
134
+ def apply_layout(positions, display)
135
+ return unless display.displayed?(self)
136
+ if p = positions[dot_id]
137
+ raise "no graphics for #{self}" unless graphics_item = display[self]
138
+ graphics_item.pos = p
139
+ else
140
+ STDERR.puts "WARN: #{self} has not been layouted"
141
+ end
142
+ end
143
+ end
144
+
145
+ class PlanObject::DRoby
146
+ include LoggedPlanObject
147
+ end
148
+
149
+ class TaskEventGenerator::DRoby
150
+ def dot_label(display); symbol.to_s end
151
+ def dot_id; task.dot_id end
152
+ end
153
+
154
+ module LoggedTask
155
+ include LoggedPlanObject
156
+ def dot_label(display)
157
+ event_names = events.values.find_all { |ev| display.displayed?(ev) }.
158
+ map { |ev| ev.dot_label(display) }.
159
+ join(" ")
160
+
161
+ own = super
162
+ if own.size > event_names.size then own
163
+ else event_names
164
+ end
165
+ end
166
+ end
167
+
168
+ module Log
169
+ class Layout
170
+ @@bkpindex = 0
171
+
172
+ def layout_id(object)
173
+ id = Object.address_from_id(object.object_id).to_s
174
+ object_ids[id] = object
175
+ id
176
+ end
177
+
178
+ attribute(:object_ids) { Hash.new }
179
+ attr_reader :dot_input
180
+
181
+ def <<(string); dot_input << string end
182
+ def layout(display, plan)
183
+ @@index ||= 0
184
+ @@index += 1
185
+
186
+ # Dot input file
187
+ @dot_input = Tempfile.new("roby_dot")
188
+ # Dot output file
189
+ dot_output = Tempfile.new("roby_layout")
190
+
191
+ dot_input << "digraph relations {\n"
192
+ display.layout_options.each do |k, v|
193
+ dot_input << " #{k}=#{v};\n"
194
+ end
195
+ plan.to_dot(display, self, 0)
196
+
197
+ # Take the signalling into account for the layout
198
+ display.propagated_events.each do |_, sources, to, _|
199
+ sources.each do |from|
200
+ from_id, to_id = from.dot_id, to.dot_id
201
+ if from_id && to_id
202
+ dot_input << " #{from.dot_id} -> #{to.dot_id}\n"
203
+ end
204
+ end
205
+ end
206
+
207
+ dot_input << "\n};"
208
+ dot_input.flush
209
+
210
+ # Make sure the GUI keeps being updated while dot is processing
211
+ FileUtils.cp dot_input.path, "/tmp/dot-input-#{@@index}.dot"
212
+ system("#{display.layout_method} #{dot_input.path} > #{dot_output.path}")
213
+ #pid = fork do
214
+ # exec("#{display.layout_method} #{dot_input.path} > #{dot_output.path}")
215
+ #end
216
+ #while !Process.waitpid(pid, Process::WNOHANG)
217
+ # if Qt::Application.has_pending_events
218
+ # Qt::Application.process_events
219
+ # else
220
+ # sleep(0.05)
221
+ # end
222
+ #end
223
+ FileUtils.cp dot_output.path, "/tmp/dot-output-#{@@index}.dot"
224
+
225
+ # Load only task bounding boxes from dot, update arrows later
226
+ current_graph_id = nil
227
+ bounding_rects = Hash.new
228
+ object_pos = Hash.new
229
+ lines = File.open(dot_output.path) { |io| io.readlines }
230
+ full_line = ""
231
+ lines.each do |line|
232
+ line.chomp!
233
+ full_line << line
234
+ if line[-1] == ?\\
235
+ full_line.chop!
236
+ next
237
+ end
238
+
239
+ case full_line
240
+ when /((?:\w+_)+\d+) \[.*pos="(\d+),(\d+)"/
241
+ object_pos[$1] = Qt::PointF.new(Integer($2), Integer($3))
242
+ when /subgraph cluster_(plan_\d+)/
243
+ current_graph_id = $1
244
+ when /graph \[bb="(\d+),(\d+),(\d+),(\d+)"\]/
245
+ bb = [$1, $2, $3, $4].map do |c|
246
+ c = Integer(c)
247
+ end
248
+ bounding_rects[current_graph_id] = [bb[0], bb[1], bb[2] - bb[0], bb[3] - bb[1]]
249
+ end
250
+ full_line = ""
251
+ end
252
+
253
+ graph_bb = bounding_rects.delete(nil)
254
+ bounding_rects.each_value do |coords|
255
+ coords[0] -= graph_bb[0]
256
+ coords[1] = graph_bb[1] - coords[1] - coords[3]
257
+ end
258
+ object_pos.each do |id, pos|
259
+ pos.x -= graph_bb[0]
260
+ pos.y = graph_bb[1] - pos.y
261
+ end
262
+
263
+ @display = display
264
+ @plan = plan
265
+ @object_pos = object_pos
266
+ @bounding_rects = bounding_rects
267
+
268
+ ensure
269
+ dot_input.close! if dot_input
270
+ dot_output.close! if dot_output
271
+ end
272
+
273
+ attr_reader :bounding_rects, :object_pos, :display, :plan
274
+ def apply
275
+ plan.apply_layout(bounding_rects, object_pos, display)
276
+ end
277
+ end
278
+ end
279
+ end
@@ -0,0 +1,151 @@
1
+ require 'roby/log/data_stream'
2
+
3
+ module Roby
4
+ module Log
5
+ # This class is a logger-compatible interface which read event and index logs,
6
+ # and may rebuild the task and event graphs from the marshalled events
7
+ # that are saved using for instance FileLogger
8
+ class EventStream < DataStream
9
+ def splat?; true end
10
+
11
+ # The event log
12
+ attr_reader :logfile
13
+
14
+ # The index of the currently displayed cycle in +index_data+
15
+ attr_reader :current_cycle
16
+ # The index of the first non-empty cycle
17
+ attr_reader :start_cycle
18
+ # A [min, max] array of the minimum and maximum times for this
19
+ # stream
20
+ def range; [start_time, logfile.range.last] end
21
+
22
+ def initialize(basename, file = nil)
23
+ super(basename, "roby-events")
24
+ if file
25
+ @logfile = file
26
+ reinit!
27
+ end
28
+ end
29
+ def open
30
+ @logfile = Roby::Log.open(name)
31
+ reinit!
32
+ self
33
+ end
34
+ def close; @logfile.close end
35
+
36
+ def index_data; logfile.index_data end
37
+
38
+ # True if the stream has been reinitialized
39
+ def reinit?
40
+ @reinit ||= (!index_data.empty? && logfile.stat.size < index_data.last[:pos])
41
+ end
42
+
43
+ # Reinitializes the stream
44
+ def reinit!
45
+ prepare_seek(nil)
46
+
47
+ super
48
+
49
+ start_cycle = 0
50
+ while start_cycle < index_data.size && index_data[start_cycle][:event_count] == 4
51
+ start_cycle += 1
52
+ end
53
+ @start_cycle = start_cycle
54
+ @current_cycle = start_cycle
55
+ end
56
+
57
+ # True if there is at least one sample available
58
+ def has_sample?
59
+ logfile.update_index
60
+ !index_data.empty? && (index_data.last[:pos] > logfile.tell)
61
+ end
62
+
63
+ # Seek the data stream to the specified time.
64
+ def prepare_seek(time)
65
+ if !time || !current_time || time < current_time
66
+ clear
67
+
68
+ @current_time = nil
69
+ @current_cycle = start_cycle
70
+ logfile.rewind
71
+ end
72
+ end
73
+
74
+ def start_time
75
+ return if start_cycle == index_data.size
76
+ Time.at(*index_data[start_cycle][:start])
77
+ end
78
+
79
+ # The current time
80
+ def current_time
81
+ return if index_data.empty?
82
+ time = Time.at(*index_data[current_cycle][:start])
83
+ if index_data.size == current_cycle + 1
84
+ time += index_data[current_cycle][:end]
85
+ end
86
+ time
87
+ end
88
+
89
+ # The time we will reach when the next sample is processed
90
+ def next_time
91
+ return if index_data.empty?
92
+ if index_data.size > current_cycle + 1
93
+ Time.at(*index_data[current_cycle + 1][:start])
94
+ end
95
+ end
96
+
97
+ # Reads a sample of data and returns it. It will be fed to
98
+ # decoders' #decode method.
99
+ #
100
+ # In this stream, this is the chunk of the marshalled file which
101
+ # corresponds to a cycle
102
+ def read
103
+ if reinit?
104
+ reinit!
105
+ end
106
+
107
+ start_pos = index_data[current_cycle][:pos]
108
+ end_pos = if index_data.size > current_cycle + 1
109
+ index_data[current_cycle + 1][:pos]
110
+ else
111
+ logfile.stat.size
112
+ end
113
+
114
+ logfile.seek(start_pos)
115
+ logfile.read(end_pos - start_pos)
116
+
117
+ ensure
118
+ @current_cycle += 1
119
+ end
120
+
121
+ # Unmarshalls a set of data returned by #read_all and yield
122
+ # each sample that should be fed to the decoders
123
+ def self.init(data)
124
+ io = StringIO.new(data)
125
+ while !io.eof?
126
+ yield(Marshal.load(io))
127
+ end
128
+ rescue EOFError
129
+ end
130
+
131
+ # Unmarshalls one cycle of data returned by #read and feeds
132
+ # it to the decoders
133
+ def self.decode(data)
134
+ Marshal.load(data)
135
+ end
136
+
137
+ # Read all data read so far in a format suitable to feed to
138
+ # #init_stream on the decoding side
139
+ def read_all
140
+ end_pos = if index_data.size > current_cycle + 1
141
+ index_data[current_cycle + 1][:pos]
142
+ else
143
+ logfile.stat.size
144
+ end
145
+ logfile.rewind
146
+ logfile.read(end_pos)
147
+ end
148
+ end
149
+ end
150
+ end
151
+
@@ -0,0 +1,340 @@
1
+ require 'roby/log/logger'
2
+ require 'roby/distributed'
3
+ require 'tempfile'
4
+ require 'fileutils'
5
+
6
+ module Roby::Log
7
+ class Logfile < DelegateClass(File)
8
+ attr_reader :event_io
9
+ attr_reader :index_io
10
+ attr_reader :index_data
11
+ attr_reader :basename
12
+ def range
13
+ [Time.at(*index_data.first[:start]),
14
+ Time.at(*index_data.last[:start]) + index_data.last[:end]]
15
+ end
16
+
17
+ def initialize(file, allow_old_format = false, force_rebuild_index = false)
18
+ @event_io = if file.respond_to?(:to_str)
19
+ @basename = if file =~ /-events\.log$/ then $`
20
+ else file
21
+ end
22
+
23
+ File.open("#{basename}-events.log")
24
+ else
25
+ @basename = file.path.gsub(/-events\.log$/, '')
26
+ file
27
+ end
28
+
29
+ if !allow_old_format
30
+ FileLogger.check_format(@event_io)
31
+ end
32
+
33
+ index_path = "#{basename}-index.log"
34
+ if force_rebuild_index || !File.file?(index_path)
35
+ rebuild_index
36
+ else
37
+ @index_io = File.open(index_path)
38
+ if @index_io.stat.size == 0
39
+ rebuild_index
40
+ end
41
+ end
42
+
43
+ super(@event_io)
44
+
45
+ @index_data = Array.new
46
+ update_index
47
+ rewind
48
+ end
49
+
50
+ # Reads as much index data as possible
51
+ def update_index
52
+ begin
53
+ pos = nil
54
+ loop do
55
+ pos = index_io.tell
56
+ length = index_io.read(4)
57
+ raise EOFError unless length
58
+ length = length.unpack("N").first
59
+ index_data << Marshal.load(index_io.read(length))
60
+ end
61
+ rescue EOFError
62
+ index_io.seek(pos, IO::SEEK_SET)
63
+ end
64
+
65
+ return if index_data.empty?
66
+ end
67
+
68
+ def rewind
69
+ @event_io.rewind
70
+ Marshal.load(@event_io)
71
+ @index_io.rewind
72
+ @index_data.clear
73
+ update_index
74
+ end
75
+
76
+ def rebuild_index
77
+ STDOUT.puts "rebuilding index file for #{basename}"
78
+ @index_io.close if @index_io
79
+ @index_io = File.open("#{basename}-index.log", 'w+')
80
+ FileLogger.rebuild_index(@event_io, @index_io)
81
+ end
82
+
83
+ def self.open(path)
84
+ io = new(path)
85
+ if block_given?
86
+ begin
87
+ yield(io)
88
+ ensure
89
+ io.close unless io.closed?
90
+ end
91
+ else
92
+ io
93
+ end
94
+ end
95
+ end
96
+
97
+ # A logger object which marshals all available events in two files. The
98
+ # event log is the full log, the index log contains only the timings given
99
+ # to Control#cycle_end, along with the corresponding position in the event
100
+ # log file.
101
+ #
102
+ # You can use FileLogger.replay(io) to send the events back into the
103
+ # logging system (using Log.log), for instance to feed an offline display
104
+ class FileLogger
105
+ # The current log format version
106
+ FORMAT_VERSION = 3
107
+
108
+ @dumped = Hash.new
109
+ class << self
110
+ attr_reader :dumped
111
+ end
112
+
113
+ # The IO object for the event log
114
+ attr_reader :event_log
115
+ # The IO object for the index log
116
+ attr_reader :index_log
117
+ # The set of events for the current cycle. This is dumped only
118
+ # when the +cycle_end+ event is received
119
+ attr_reader :current_cycle
120
+ # StringIO object on which we dump the data
121
+ attr_reader :dump_io
122
+
123
+ def initialize(basename)
124
+ @current_pos = 0
125
+ @dump_io = StringIO.new('', 'w')
126
+ @current_cycle = Array.new
127
+ @event_log = File.open("#{basename}-events.log", 'w')
128
+ event_log.sync = true
129
+ FileLogger.write_header(@event_log)
130
+ @index_log = File.open("#{basename}-index.log", 'w')
131
+ index_log.sync = true
132
+ end
133
+
134
+ attr_accessor :stats_mode
135
+ def splat?; false end
136
+
137
+ def dump_method(m, time, args)
138
+ if m == :cycle_end || !stats_mode
139
+ current_cycle << m << time.tv_sec << time.tv_usec << args
140
+ end
141
+ if m == :cycle_end
142
+ info = args.first
143
+ info[:pos] = event_log.tell
144
+ info[:event_count] = current_cycle.size
145
+ Marshal.dump(current_cycle, event_log)
146
+
147
+ dump_io.truncate(0)
148
+ dump_io.seek(0)
149
+ Marshal.dump(info, dump_io)
150
+ index_log.write [dump_io.size].pack("N")
151
+ index_log.write dump_io.string
152
+ current_cycle.clear
153
+ end
154
+
155
+ rescue
156
+ puts "failed to dump #{m}#{args}: #{$!.full_message}"
157
+ args.each do |obj|
158
+ unless (Marshal.dump(obj) rescue nil)
159
+ puts "there is a problem with"
160
+ pp obj
161
+ end
162
+ end
163
+ end
164
+
165
+ Roby::Log.each_hook do |klass, m|
166
+ define_method(m) { |time, args| dump_method(m, time, args) }
167
+ end
168
+
169
+ # Creates an index file for +event_log+ in +index_log+
170
+ def self.rebuild_index(event_log, index_log)
171
+ event_log.rewind
172
+ # Skip the file header
173
+ Marshal.load(event_log)
174
+
175
+ current_pos = event_log.tell
176
+ dump_io = StringIO.new("", 'w')
177
+
178
+ loop do
179
+ cycle = Marshal.load(event_log)
180
+ info = cycle.last.last
181
+ info[:pos] = current_pos
182
+ info[:event_count] = cycle.size
183
+
184
+ dump_io.truncate(0)
185
+ dump_io.seek(0)
186
+ Marshal.dump(info, dump_io)
187
+ index_log.write [dump_io.size].pack("N")
188
+ index_log.write dump_io.string
189
+
190
+ current_pos = event_log.tell
191
+ end
192
+
193
+ rescue EOFError
194
+ ensure
195
+ event_log.rewind
196
+ index_log.rewind
197
+ end
198
+
199
+
200
+ def self.log_format(input)
201
+ input.rewind
202
+ format = begin
203
+ header = Marshal.load(input)
204
+ case header
205
+ when Hash: header[:log_format]
206
+ when Symbol
207
+ if Marshal.load(input).kind_of?(Array)
208
+ 0
209
+ end
210
+ when Array
211
+ 1 if header[-2] == :cycle_end
212
+ end
213
+ rescue
214
+ end
215
+
216
+ unless format
217
+ raise "#{input.path} does not look like a Roby event log file"
218
+ end
219
+ format
220
+
221
+ ensure
222
+ input.rewind
223
+ end
224
+
225
+ def self.check_format(input)
226
+ format = log_format(input)
227
+ if format < FORMAT_VERSION
228
+ raise "this is an outdated format. Please run roby-log upgrade-format"
229
+ elsif format > FORMAT_VERSION
230
+ raise "this is an unknown format version #{format}: expected #{FORMAT_VERSION}. This file can be read only by newest version of Roby"
231
+ end
232
+ end
233
+
234
+ def self.write_header(io)
235
+ header = { :log_format => FORMAT_VERSION }
236
+ Marshal.dump(header, io)
237
+ end
238
+
239
+ def self.from_format_0(input, output)
240
+ current_cycle = []
241
+ while !input.eof?
242
+ m = Marshal.load(input)
243
+ args = Marshal.load(input)
244
+
245
+ current_cycle << m << args
246
+ if m == :cycle_end
247
+ Marshal.dump(current_cycle, output)
248
+ current_cycle.clear
249
+ end
250
+ end
251
+
252
+ unless current_cycle.empty?
253
+ Marshal.dump(current_cycle, output)
254
+ end
255
+ end
256
+
257
+ def self.from_format_1(input, output)
258
+ # The only difference between v1 and v2 is the header. Just copy
259
+ # data from input to output
260
+ output.write(input.read)
261
+ end
262
+
263
+ def self.from_format_2(input, output)
264
+ # In format 3, we did two things:
265
+ # * changed the way exceptions were dumped: instead of relying on
266
+ # DRbRemoteError, we are now using DRobyModel
267
+ # * stopped dumping Time objects and instead marshalled tv_sec
268
+ # and tv_usec directly
269
+ Exception::DRoby.class_eval do
270
+ def self._load(str)
271
+ Marshal.load(str)
272
+ end
273
+ end
274
+
275
+ new_cycle = []
276
+ input_stream = EventStream.new("input", Logfile.new(input, true))
277
+ while !input.eof?
278
+ new_cycle.clear
279
+ begin
280
+ m_data = input_stream.read
281
+ cycle_data = Marshal.load(m_data)
282
+ cycle_data.each_slice(2) do |m, args|
283
+ time = args.shift
284
+ new_cycle << m << time.tv_sec << time.tv_usec << args
285
+ end
286
+
287
+ stats = new_cycle.last.first
288
+ reftime = stats[:start]
289
+ stats[:start] = [reftime.tv_sec, reftime.tv_usec]
290
+ new_cycle.last[0] = stats.inject(Hash.new) do |new_stats, (name, value)|
291
+ new_stats[name] = if value.kind_of?(Time) then value - reftime
292
+ else value
293
+ end
294
+ new_stats
295
+ end
296
+
297
+ Marshal.dump(new_cycle, output)
298
+ rescue Exception => e
299
+ STDERR.puts "dropped cycle because of the following error:"
300
+ STDERR.puts " #{e.full_message}"
301
+ end
302
+ end
303
+ end
304
+
305
+ def self.to_new_format(file, into = file)
306
+ input = File.open(file)
307
+ log_format = self.log_format(input)
308
+
309
+ if log_format == FORMAT_VERSION
310
+ STDERR.puts "#{file} is already at format #{log_format}"
311
+ else
312
+ if into =~ /-events\.log$/
313
+ into = $`
314
+ end
315
+ STDERR.puts "upgrading #{file} from format #{log_format} into #{into}"
316
+
317
+ input.rewind
318
+ Tempfile.open('roby_to_new_format') do |output|
319
+ write_header(output)
320
+ send("from_format_#{log_format}", input, output)
321
+ output.flush
322
+
323
+ input.close
324
+ FileUtils.cp output.path, "#{into}-events.log"
325
+ end
326
+
327
+ File.open("#{into}-events.log") do |event_log|
328
+ File.open("#{into}-index.log", 'w') do |index_log|
329
+ puts "rebuilding index file for #{into}"
330
+ rebuild_index(event_log, index_log)
331
+ end
332
+ end
333
+ end
334
+
335
+ ensure
336
+ input.close if input && !input.closed?
337
+ end
338
+ end
339
+ end
340
+