Move files from gst-devtools into the "subprojects/gst-devtools/" subdir
[platform/upstream/gstreamer.git] / subprojects / gst-devtools / debug-viewer / GstDebugViewer / GUI / models.py
1 # -*- coding: utf-8; mode: python; -*-
2 #
3 #  GStreamer Debug Viewer - View and analyze GStreamer debug log files
4 #
5 #  Copyright (C) 2007 RenĂ© Stadler <mail@renestadler.de>
6 #
7 #  This program is free software; you can redistribute it and/or modify it
8 #  under the terms of the GNU General Public License as published by the Free
9 #  Software Foundation; either version 3 of the License, or (at your option)
10 #  any later version.
11 #
12 #  This program is distributed in the hope that it will be useful, but WITHOUT
13 #  ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
14 #  FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License for
15 #  more details.
16 #
17 #  You should have received a copy of the GNU General Public License along with
18 #  this program.  If not, see <http://www.gnu.org/licenses/>.
19
20 """GStreamer Debug Viewer GUI module."""
21
22 from array import array
23 from bisect import bisect_left
24 import logging
25
26 from gi.repository import GObject
27 from gi.repository import Gtk
28
29 from GstDebugViewer import Common, Data
30
31
32 class LogModelBase (Common.GUI.GenericTreeModel, metaclass=Common.GUI.MetaModel):
33
34     columns = ("COL_TIME", GObject.TYPE_UINT64,
35                "COL_PID", int,
36                "COL_THREAD", GObject.TYPE_UINT64,
37                "COL_LEVEL", object,
38                "COL_CATEGORY", str,
39                "COL_FILENAME", str,
40                "COL_LINE_NUMBER", int,
41                "COL_FUNCTION", str,
42                "COL_OBJECT", str,
43                "COL_MESSAGE", str,)
44
45     def __init__(self):
46
47         Common.GUI.GenericTreeModel.__init__(self)
48
49         # self.props.leak_references = False
50
51         self.line_offsets = array("I")
52         self.line_levels = []  # FIXME: Not so nice!
53         self.line_cache = {}
54
55     def ensure_cached(self, line_offset):
56
57         raise NotImplementedError("derived classes must override this method")
58
59     def access_offset(self, offset):
60
61         raise NotImplementedError("derived classes must override this method")
62
63     def iter_rows_offset(self):
64
65         ensure_cached = self.ensure_cached
66         line_cache = self.line_cache
67         line_levels = self.line_levels
68         COL_LEVEL = self.COL_LEVEL
69         COL_MESSAGE = self.COL_MESSAGE
70         access_offset = self.access_offset
71
72         for i, offset in enumerate(self.line_offsets):
73             ensure_cached(offset)
74             row = line_cache[offset]
75             # adjust special rows
76             row[COL_LEVEL] = line_levels[i]
77             msg_offset = row[COL_MESSAGE]
78             row[COL_MESSAGE] = access_offset(offset + msg_offset)
79             yield (row, offset,)
80             row[COL_MESSAGE] = msg_offset
81
82     def on_get_flags(self):
83
84         flags = Gtk.TreeModelFlags.LIST_ONLY | Gtk.TreeModelFlags.ITERS_PERSIST
85
86         return flags
87
88     def on_get_n_columns(self):
89
90         return len(self.column_types)
91
92     def on_get_column_type(self, col_id):
93
94         return self.column_types[col_id]
95
96     def on_get_iter(self, path):
97
98         if not path:
99             return
100
101         if len(path) > 1:
102             # Flat model.
103             return None
104
105         line_index = path[0]
106
107         if line_index > len(self.line_offsets) - 1:
108             return None
109
110         return line_index
111
112     def on_get_path(self, rowref):
113
114         line_index = rowref
115
116         return (line_index,)
117
118     def on_get_value(self, line_index, col_id):
119
120         last_index = len(self.line_offsets) - 1
121
122         if line_index > last_index:
123             return None
124
125         if col_id == self.COL_LEVEL:
126             return self.line_levels[line_index]
127
128         line_offset = self.line_offsets[line_index]
129         self.ensure_cached(line_offset)
130
131         value = self.line_cache[line_offset][col_id]
132         if col_id == self.COL_MESSAGE:
133             # strip whitespace + newline
134             value = self.access_offset(line_offset + value).strip()
135         elif col_id in (self.COL_TIME, self.COL_THREAD):
136             value = GObject.Value(GObject.TYPE_UINT64, value)
137
138         return value
139
140     def get_value_range(self, col_id, start, stop):
141
142         if col_id != self.COL_LEVEL:
143             raise NotImplementedError("XXX FIXME")
144
145         return self.line_levels[start:stop]
146
147     def on_iter_next(self, line_index):
148
149         last_index = len(self.line_offsets) - 1
150
151         if line_index >= last_index:
152             return None
153         else:
154             return line_index + 1
155
156     def on_iter_children(self, parent):
157
158         return self.on_iter_nth_child(parent, 0)
159
160     def on_iter_has_child(self, rowref):
161
162         return False
163
164     def on_iter_n_children(self, rowref):
165
166         if rowref is not None:
167             return 0
168
169         return len(self.line_offsets)
170
171     def on_iter_nth_child(self, parent, n):
172
173         last_index = len(self.line_offsets) - 1
174
175         if parent or n > last_index:
176             return None
177
178         return n
179
180     def on_iter_parent(self, child):
181
182         return None
183
184     # def on_ref_node (self, rowref):
185
186     # pass
187
188     # def on_unref_node (self, rowref):
189
190     # pass
191
192
193 class LazyLogModel (LogModelBase):
194
195     def __init__(self, log_obj=None):
196
197         LogModelBase.__init__(self)
198
199         self.__log_obj = log_obj
200
201         if log_obj:
202             self.set_log(log_obj)
203
204     def set_log(self, log_obj):
205
206         self.__fileobj = log_obj.fileobj
207
208         self.line_cache.clear()
209         self.line_offsets = log_obj.line_cache.offsets
210         self.line_levels = log_obj.line_cache.levels
211
212     def access_offset(self, offset):
213
214         # TODO: Implement using one slice access instead of seek+readline.
215         self.__fileobj.seek(offset)
216         return self.__fileobj.readline()
217
218     def ensure_cached(self, line_offset):
219
220         if line_offset in self.line_cache:
221             return
222
223         if len(self.line_cache) > 10000:
224             self.line_cache.clear()
225
226         self.__fileobj.seek(line_offset)
227         line = self.__fileobj.readline()
228
229         self.line_cache[line_offset] = Data.LogLine.parse_full(line)
230
231
232 class FilteredLogModelBase (LogModelBase):
233
234     def __init__(self, super_model):
235
236         LogModelBase.__init__(self)
237
238         self.logger = logging.getLogger("filter-model-base")
239
240         self.super_model = super_model
241         self.access_offset = super_model.access_offset
242         self.ensure_cached = super_model.ensure_cached
243         self.line_cache = super_model.line_cache
244
245     def line_index_to_super(self, line_index):
246
247         raise NotImplementedError("index conversion not supported")
248
249     def line_index_from_super(self, super_line_index):
250
251         raise NotImplementedError("index conversion not supported")
252
253
254 class FilteredLogModel (FilteredLogModelBase):
255
256     def __init__(self, super_model):
257
258         FilteredLogModelBase.__init__(self, super_model)
259
260         self.logger = logging.getLogger("filtered-log-model")
261
262         self.filters = []
263         self.reset()
264         self.__active_process = None
265         self.__filter_progress = 0.
266
267     def reset(self):
268
269         self.logger.debug("reset filter")
270
271         self.line_offsets = self.super_model.line_offsets
272         self.line_levels = self.super_model.line_levels
273         self.super_index = range(len(self.line_offsets))
274
275         del self.filters[:]
276
277     def __filter_process(self, filter):
278
279         YIELD_LIMIT = 10000
280
281         self.logger.debug("preparing new filter")
282         new_line_offsets = array("I")
283         new_line_levels = []
284         new_super_index = array("I")
285         level_id = self.COL_LEVEL
286         func = filter.filter_func
287
288         def enum():
289             i = 0
290             for row, offset in self.iter_rows_offset():
291                 line_index = self.super_index[i]
292                 yield (line_index, row, offset,)
293                 i += 1
294         self.logger.debug("running filter")
295         progress = 0.
296         progress_full = float(len(self))
297         y = YIELD_LIMIT
298         for i, row, offset in enum():
299             if func(row):
300                 new_line_offsets.append(offset)
301                 new_line_levels.append(row[level_id])
302                 new_super_index.append(i)
303             y -= 1
304             if y == 0:
305                 progress += float(YIELD_LIMIT)
306                 self.__filter_progress = progress / progress_full
307                 y = YIELD_LIMIT
308                 yield True
309         self.line_offsets = new_line_offsets
310         self.line_levels = new_line_levels
311         self.super_index = new_super_index
312         self.logger.debug("filtering finished")
313
314         self.__filter_progress = 1.
315         self.__handle_filter_process_finished()
316         yield False
317
318     def add_filter(self, filter, dispatcher):
319
320         if self.__active_process is not None:
321             raise ValueError("dispatched a filter process already")
322
323         self.logger.debug("adding filter")
324
325         self.filters.append(filter)
326
327         self.__dispatcher = dispatcher
328         self.__active_process = self.__filter_process(filter)
329         dispatcher(self.__active_process)
330
331     def abort_process(self):
332
333         if self.__active_process is None:
334             raise ValueError("no filter process running")
335
336         self.__dispatcher.cancel()
337         self.__active_process = None
338         self.__dispatcher = None
339
340         del self.filters[-1]
341
342     def get_filter_progress(self):
343
344         if self.__active_process is None:
345             raise ValueError("no filter process running")
346
347         return self.__filter_progress
348
349     def __handle_filter_process_finished(self):
350
351         self.__active_process = None
352         self.handle_process_finished()
353
354     def handle_process_finished(self):
355
356         pass
357
358     def line_index_from_super(self, super_line_index):
359
360         return bisect_left(self.super_index, super_line_index)
361
362     def line_index_to_super(self, line_index):
363
364         return self.super_index[line_index]
365
366     def set_range(self, super_start, super_stop):
367
368         old_super_start = self.line_index_to_super(0)
369         old_super_stop = self.line_index_to_super(
370             len(self.super_index) - 1) + 1
371
372         self.logger.debug("set range (%i, %i), current (%i, %i)",
373                           super_start, super_stop, old_super_start, old_super_stop)
374
375         if len(self.filters) == 0:
376             # Identity.
377             self.super_index = range(super_start, super_stop)
378             self.line_offsets = SubRange(self.super_model.line_offsets,
379                                          super_start, super_stop)
380             self.line_levels = SubRange(self.super_model.line_levels,
381                                         super_start, super_stop)
382             return
383
384         if super_start < old_super_start:
385             # TODO:
386             raise NotImplementedError("Only handling further restriction of the range"
387                                       " (start offset = %i)" % (super_start,))
388
389         if super_stop > old_super_stop:
390             # TODO:
391             raise NotImplementedError("Only handling further restriction of the range"
392                                       " (end offset = %i)" % (super_stop,))
393
394         start = self.line_index_from_super(super_start)
395         stop = self.line_index_from_super(super_stop)
396
397         self.super_index = SubRange(self.super_index, start, stop)
398         self.line_offsets = SubRange(self.line_offsets, start, stop)
399         self.line_levels = SubRange(self.line_levels, start, stop)
400
401
402 class SubRange (object):
403
404     __slots__ = ("size", "start", "stop",)
405
406     def __init__(self, size, start, stop):
407
408         if start > stop:
409             raise ValueError(
410                 "need start <= stop (got %r, %r)" % (start, stop,))
411
412         if isinstance(size, type(self)):
413             # Another SubRange, don't stack:
414             start += size.start
415             stop += size.start
416             size = size.size
417
418         self.size = size
419         self.start = start
420         self.stop = stop
421
422     def __getitem__(self, i):
423
424         if isinstance(i, slice):
425             stop = i.stop
426             if stop >= 0:
427                 stop += self.start
428             else:
429                 stop += self.stop
430
431             return self.size[i.start + self.start:stop]
432         else:
433             return self.size[i + self.start]
434
435     def __len__(self):
436
437         return self.stop - self.start
438
439     def __iter__(self):
440
441         size = self.size
442         for i in range(self.start, self.stop):
443             yield size[i]
444
445
446 class LineViewLogModel (FilteredLogModelBase):
447
448     def __init__(self, super_model):
449
450         FilteredLogModelBase.__init__(self, super_model)
451
452         self.line_offsets = []
453         self.line_levels = []
454
455         self.parent_indices = []
456
457     def reset(self):
458
459         del self.line_offsets[:]
460         del self.line_levels[:]
461
462     def line_index_to_super(self, line_index):
463
464         return self.parent_indices[line_index]
465
466     def insert_line(self, position, super_line_index):
467
468         if position == -1:
469             position = len(self.line_offsets)
470         li = super_line_index
471         self.line_offsets.insert(position, self.super_model.line_offsets[li])
472         self.line_levels.insert(position, self.super_model.line_levels[li])
473         self.parent_indices.insert(position, super_line_index)
474
475         path = (position,)
476         tree_iter = self.get_iter(path)
477         self.row_inserted(path, tree_iter)
478
479     def replace_line(self, line_index, super_line_index):
480
481         li = line_index
482         self.line_offsets[li] = self.super_model.line_offsets[super_line_index]
483         self.line_levels[li] = self.super_model.line_levels[super_line_index]
484         self.parent_indices[li] = super_line_index
485
486         path = (line_index,)
487         tree_iter = self.get_iter(path)
488         self.row_changed(path, tree_iter)
489
490     def remove_line(self, line_index):
491
492         for l in (self.line_offsets,
493                   self.line_levels,
494                   self.parent_indices,):
495             del l[line_index]
496
497         path = (line_index,)
498         self.row_deleted(path)