roby 0.7

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 (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
+