Class: Roby::GUI::PlanRebuilderWidget

Inherits:
Qt::Widget
  • Object
show all
Defined in:
lib/roby/gui/plan_rebuilder_widget.rb

Overview

This widget displays information about the event history in a list, allowing to switch between the “important events” in this history

Defined Under Namespace

Classes: Snapshot

Constant Summary collapse

DEFAULT_REMOTE_POLL_PERIOD =
0.05

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(parent, plan_rebuilder) ⇒ PlanRebuilderWidget

Returns a new instance of PlanRebuilderWidget.



35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
# File 'lib/roby/gui/plan_rebuilder_widget.rb', line 35

def initialize(parent, plan_rebuilder)
    super(parent)
    @list    = Qt::ListWidget.new(self)
    @layout  = Qt::VBoxLayout.new(self)

    def list.mouseReleaseEvent(event)
        if event.button == Qt::RightButton
            event.accept

            menu = Qt::Menu.new
            inspect_cycle = menu.add_action("Step-by-step from there")
            if action = menu.exec(event.globalPos)
                cycle_index = currentItem.data(Qt::UserRole).toInt

                rebuilder_widget = self.parentWidget
                stepping = Stepping.new(
                    rebuilder_widget,
                    rebuilder_widget.current_plan,
                    rebuilder_widget.logfile.dup,
                    cycle_index)
                stepping.exec
            end
        end
    end

    @layout.add_widget(@btn_create_display)
    @history = Hash.new
    @logfile = nil # set by #open
    @plan_rebuilder = plan_rebuilder
    @current_plan = DRoby::RebuiltPlan.new
    @layout.add_widget(list)

    Qt::Object.connect(list, SIGNAL('currentItemChanged(QListWidgetItem*,QListWidgetItem*)'),
               self, SLOT('currentItemChanged(QListWidgetItem*,QListWidgetItem*)'))
end

Instance Attribute Details

#current_planObject (readonly)

The current plan managed by the widget



18
19
20
# File 'lib/roby/gui/plan_rebuilder_widget.rb', line 18

def current_plan
  @current_plan
end

#historyObject (readonly)

The history, as a mapping from the cycle index to a (time, snapshot, list_item) triple



14
15
16
# File 'lib/roby/gui/plan_rebuilder_widget.rb', line 14

def history
  @history
end

#last_cycleInteger (readonly)

The last processed cycle

Returns:

  • (Integer)


24
25
26
# File 'lib/roby/gui/plan_rebuilder_widget.rb', line 24

def last_cycle
  @last_cycle
end

#listObject (readonly)

The list used to display all the cycles in the history



11
12
13
# File 'lib/roby/gui/plan_rebuilder_widget.rb', line 11

def list
  @list
end

#logfileDRoby::Logfile::Reader (readonly)

The underlying log file



21
22
23
# File 'lib/roby/gui/plan_rebuilder_widget.rb', line 21

def logfile
  @logfile
end

#plan_rebuilderObject (readonly)

The PlanRebuilder object we use to process the log data



16
17
18
# File 'lib/roby/gui/plan_rebuilder_widget.rb', line 16

def plan_rebuilder
  @plan_rebuilder
end

Class Method Details

.analyze(plan_rebuilder, logfile, until_cycle: nil) ⇒ Object



213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
# File 'lib/roby/gui/plan_rebuilder_widget.rb', line 213

def self.analyze(plan_rebuilder, logfile, until_cycle: nil)
    start_time, end_time = logfile.index.range

    start = Time.now
    puts "log file is #{(end_time - start_time).ceil}s long"
    dialog = Qt::ProgressDialog.new("Analyzing log file", "Quit", 0, (end_time - start_time))
    dialog.setWindowModality(Qt::WindowModal)
    dialog.show

    while !logfile.eof? && (!until_cycle || !plan_rebuilder.cycle_index || plan_rebuilder.cycle_index < until_cycle)
        data = logfile.load_one_cycle
        plan_rebuilder.process_one_cycle(data)
        if block_given?
            needs_snapshot =
                (plan_rebuilder.has_structure_updates? ||
                 plan_rebuilder.has_event_propagation_updates?)
            yield(needs_snapshot, data) 
        end
        plan_rebuilder.clear_integrated
        dialog.setValue(plan_rebuilder.cycle_start_time - start_time)
        if dialog.wasCanceled
            Kernel.raise Interrupt
        end
    end
    dialog.dispose
    puts "analyzed log file in %.2fs" % [Time.now - start]
end

Instance Method Details

#add_missing_cycles(count) ⇒ Object



117
118
119
120
121
122
# File 'lib/roby/gui/plan_rebuilder_widget.rb', line 117

def add_missing_cycles(count)
    item = Qt::ListWidgetItem.new(list)
    item.setBackground(Qt::Brush.new(Qt::Color::fromHsv(33, 111, 255)))
    item.flags = Qt::NoItemFlags
    item.text = "[#{count} cycles missing]"
end

#analyze(until_cycle: nil) ⇒ Object



241
242
243
244
245
# File 'lib/roby/gui/plan_rebuilder_widget.rb', line 241

def analyze(until_cycle: nil)
    PlanRebuilderWidget.analyze(plan_rebuilder, logfile, until_cycle: until_cycle) do
        push_cycle
    end
end

#append_to_historyObject



126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/roby/gui/plan_rebuilder_widget.rb', line 126

def append_to_history
    snapshot = Snapshot.new plan_rebuilder.stats.dup,
        DRoby::RebuiltPlan.new
    snapshot.plan.merge(plan_rebuilder.plan)
    if @last_snapshot
        snapshot.plan.dedupe(@last_snapshot.plan)
    end
    @last_snapshot = snapshot

    cycle = snapshot.stats[:cycle_index]
    time = Time.at(*snapshot.stats[:start]) + snapshot.stats[:actual_start]

    item = Qt::ListWidgetItem.new(list)
    item.text = "@#{cycle} - #{Roby.format_time(time)}"
    item.setData(Qt::UserRole, Qt::Variant.new(cycle))
    history[cycle] = [time, snapshot, item]
    emit addedSnapshot(cycle)
end

#apply(snapshot) ⇒ Object



153
154
155
156
157
158
# File 'lib/roby/gui/plan_rebuilder_widget.rb', line 153

def apply(snapshot)
    @display_time = Time.at(*snapshot.stats[:start]) + snapshot.stats[:end]
    @current_plan.clear
    @current_plan.merge(snapshot.plan)
    emit appliedSnapshot(Qt::DateTime.new(@display_time))
end

#connect(client, options = Hash.new) ⇒ Object

Displays the data incoming from client

client is assumed to be a DRoby::Logfile::Client instance

update_period is, in seconds, the period at which the display will check whether there is new data on the port.



280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
# File 'lib/roby/gui/plan_rebuilder_widget.rb', line 280

def connect(client, options = Hash.new)
    options = Kernel.validate_options options,
        port: DRoby::Logfile::Server::DEFAULT_PORT,
        update_period: DEFAULT_REMOTE_POLL_PERIOD

    if client.respond_to?(:to_str)
        self.window_title = "roby-display: #{client}"
        emit sourceChanged

        begin
            hostname = client
            client = DRoby::Logfile::Client.new(client, options[:port])
        rescue Exception => e
            connection_failed(e, client, options)
            return false
        end
    end


    @client = client
    client.add_listener do |data|
        plan_rebuilder.clear_integrated
        plan_rebuilder.process_one_cycle(data)
        time = push_cycle
        emit liveUpdate(Qt::DateTime.new(time))

        cycle = plan_rebuilder.cycle_index
        time = plan_rebuilder.cycle_start_time
        emit info("@#{cycle} - #{time.strftime('%H:%M:%S.%3N')}")
    end
    @connection_pull = timer = Qt::Timer.new(self)
    timer.connect(SIGNAL('timeout()')) do
        begin
            client.read_and_process_pending(max: 0.1)
        rescue Exception => e
            disconnect
            emit warn("Disconnected: #{e.message}")
            puts e.message
            puts "  " + e.backtrace.join("\n  ")
            if hostname
                connect(hostname, options)
            end
        end
    end
    timer.start(Integer(options[:update_period] * 1000))
    return true
end

#connection_failed(e, client, options) ⇒ Object

Called when the connection to the log server failed, either because it has been closed or because creating the connection failed



250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
# File 'lib/roby/gui/plan_rebuilder_widget.rb', line 250

def connection_failed(e, client, options)
    @connection_error = e
    emit warn("connection failed: #{e.message}")
    if @reconnection_timer
        return
    end

    @reconnection_timer = Qt::Timer.new(self)
    @connect_client  = client.dup
    @connect_options = options.dup
    @reconnection_timer.connect(SIGNAL('timeout()')) do
        puts "trying to reconnect to #{@connect_client} #{@connect_options}"
        if connect(@connect_client, @connect_options)
            emit info("Connected")
            @reconnection_timer.stop
            @reconnection_timer.dispose
            @reconnection_timer = nil
        end
    end
    @reconnection_timer.start(1000)
end

#current_timeObject

The end time of the last received cycle



345
346
347
# File 'lib/roby/gui/plan_rebuilder_widget.rb', line 345

def current_time
    plan_rebuilder.current_time
end

#currentItemChanged(new_item, previous_item) ⇒ Object



148
149
150
151
# File 'lib/roby/gui/plan_rebuilder_widget.rb', line 148

def currentItemChanged(new_item, previous_item)
    data = new_item.data(Qt::UserRole).toInt
    apply(history[data][1])
end

#cycle_start_timeObject



335
336
337
# File 'lib/roby/gui/plan_rebuilder_widget.rb', line 335

def cycle_start_time
    plan_rebuilder.cycle_start_time
end

#disconnectObject



328
329
330
331
332
333
# File 'lib/roby/gui/plan_rebuilder_widget.rb', line 328

def disconnect
    @client.disconnect
    @connection_pull.stop
    @connection_pull.dispose
    @connection_pull = nil
end

#display_timeObject

The time of the currently selected snapshot



350
351
352
# File 'lib/roby/gui/plan_rebuilder_widget.rb', line 350

def display_time
    @display_time || start_time
end

#job_placeholder_of(task, cycle) ⇒ Object

Returns the job information for the given task in the given cycle



108
109
110
111
112
113
114
115
# File 'lib/roby/gui/plan_rebuilder_widget.rb', line 108

def job_placeholder_of(task, cycle)
    if task.kind_of?(Roby::Interface::Job)
        _, snapshot, * = history[cycle]
        task.
            enum_parent_objects(snapshot.relations[Roby::TaskStructure::PlannedBy]).
            first
    end
end

#open(filename) ⇒ Object

Opens filename and reads the data from there



201
202
203
204
205
206
207
208
209
# File 'lib/roby/gui/plan_rebuilder_widget.rb', line 201

def open(filename)
    @logfile = DRoby::Logfile::Reader.open(filename)
    self.window_title = "roby-display: #{filename}"
    emit sourceChanged
    analyze
    if !history.empty?
        apply(history[history.keys.sort.first][1])
    end
end

#push_cycle(snapshot: true) ⇒ Object



180
181
182
183
184
185
186
187
188
189
190
191
192
193
# File 'lib/roby/gui/plan_rebuilder_widget.rb', line 180

def push_cycle(snapshot: true)
    cycle = plan_rebuilder.stats[:cycle_index]
    if last_cycle && (cycle != last_cycle + 1)
        add_missing_cycles(cycle - last_cycle - 1)
    end
    needs_snapshot =
        (plan_rebuilder.has_structure_updates? ||
         plan_rebuilder.has_event_propagation_updates?)
    if snapshot && needs_snapshot
        append_to_history
    end
    @last_cycle = cycle
    Time.at(*plan_rebuilder.stats[:start]) + plan_rebuilder.stats[:actual_start]
end

#redraw(time = plan_rebuilder.current_time) ⇒ Object



196
197
198
# File 'lib/roby/gui/plan_rebuilder_widget.rb', line 196

def redraw(time = plan_rebuilder.current_time)
    emit appliedSnapshot(Qt::DateTime.new(time))
end

#seek(time) ⇒ Object



160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
# File 'lib/roby/gui/plan_rebuilder_widget.rb', line 160

def seek(time)
    # Convert from QDateTime to allow seek() to be a slot
    if time.kind_of?(Qt::DateTime)
        time = Time.at(Float(time.toMSecsSinceEpoch) / 1000)
    end

    result = nil
    history.each_value do |cycle_time, snapshot, item|
        if cycle_time < time
            if !result || result[0] < cycle_time
                result = [cycle_time, snapshot]
            end
        end
    end
    if result
        apply(result[1])
    end
end

#start_timeObject

The start time of the first received cycle



340
341
342
# File 'lib/roby/gui/plan_rebuilder_widget.rb', line 340

def start_time
    plan_rebuilder.start_time
end

#tasks_info(Set<Roby::Task>,Hash<Roby::Task,Roby::Task>)

Info about all tasks known within the stored history

Returns:

  • ((Set<Roby::Task>,Hash<Roby::Task,Roby::Task>))

    returns the set of all tasks stored in the history, as well as a mapping from job placeholder tasks to the corresponding job task



77
78
79
80
81
82
83
84
85
86
# File 'lib/roby/gui/plan_rebuilder_widget.rb', line 77

def tasks_info
    all_tasks = Set.new
    all_job_info = Hash.new
    history.each_key do |cycle_index|
        tasks, job_info = tasks_info_of_snapshot(cycle_index)
        all_tasks.merge(tasks)
        all_job_info.merge!(job_info)
    end
    return all_tasks, all_job_info
end

#tasks_info_of_snapshot(cycle) ⇒ Set<Roby::Task>

Returns the set of tasks that are present in the given snapshot

Returns:



91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
# File 'lib/roby/gui/plan_rebuilder_widget.rb', line 91

def tasks_info_of_snapshot(cycle)
    _, snapshot, * = history[cycle]
    tasks = snapshot.plan.tasks.to_set
    job_info = Hash.new
    tasks.each do |t|
        if t.kind_of?(Roby::Interface::Job)
            planned_by_graph = snapshot.plan.task_relation_graph_for(Roby::TaskStructure::PlannedBy)
            placeholder_task = planned_by_graph.enum_for(:each_out_neighbour, t).first
            if placeholder_task
                job_info[placeholder_task] = t
            end
        end
    end
    return tasks, job_info
end