1 # -*- coding: utf-8; mode: python; -*-
3 # GStreamer Debug Viewer - View and analyze GStreamer debug log files
5 # Copyright (C) 2007 René Stadler <mail@renestadler.de>
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)
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
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/>.
20 """GStreamer Debug Viewer GUI module."""
22 from array import array
23 from bisect import bisect_left
26 from gi.repository import GObject
27 from gi.repository import Gtk
29 from GstDebugViewer import Common, Data
32 class LogModelBase (Common.GUI.GenericTreeModel, metaclass=Common.GUI.MetaModel):
34 columns = ("COL_TIME", GObject.TYPE_UINT64,
36 "COL_THREAD", GObject.TYPE_UINT64,
40 "COL_LINE_NUMBER", int,
47 Common.GUI.GenericTreeModel.__init__(self)
49 # self.props.leak_references = False
51 self.line_offsets = array("I")
52 self.line_levels = [] # FIXME: Not so nice!
55 def ensure_cached(self, line_offset):
57 raise NotImplementedError("derived classes must override this method")
59 def access_offset(self, offset):
61 raise NotImplementedError("derived classes must override this method")
63 def iter_rows_offset(self):
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
72 for i, offset in enumerate(self.line_offsets):
74 row = line_cache[offset]
76 row[COL_LEVEL] = line_levels[i]
77 msg_offset = row[COL_MESSAGE]
78 row[COL_MESSAGE] = access_offset(offset + msg_offset)
80 row[COL_MESSAGE] = msg_offset
82 def on_get_flags(self):
84 flags = Gtk.TreeModelFlags.LIST_ONLY | Gtk.TreeModelFlags.ITERS_PERSIST
88 def on_get_n_columns(self):
90 return len(self.column_types)
92 def on_get_column_type(self, col_id):
94 return self.column_types[col_id]
96 def on_get_iter(self, path):
107 if line_index > len(self.line_offsets) - 1:
112 def on_get_path(self, rowref):
118 def on_get_value(self, line_index, col_id):
120 last_index = len(self.line_offsets) - 1
122 if line_index > last_index:
125 if col_id == self.COL_LEVEL:
126 return self.line_levels[line_index]
128 line_offset = self.line_offsets[line_index]
129 self.ensure_cached(line_offset)
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)
140 def get_value_range(self, col_id, start, stop):
142 if col_id != self.COL_LEVEL:
143 raise NotImplementedError("XXX FIXME")
145 return self.line_levels[start:stop]
147 def on_iter_next(self, line_index):
149 last_index = len(self.line_offsets) - 1
151 if line_index >= last_index:
154 return line_index + 1
156 def on_iter_children(self, parent):
158 return self.on_iter_nth_child(parent, 0)
160 def on_iter_has_child(self, rowref):
164 def on_iter_n_children(self, rowref):
166 if rowref is not None:
169 return len(self.line_offsets)
171 def on_iter_nth_child(self, parent, n):
173 last_index = len(self.line_offsets) - 1
175 if parent or n > last_index:
180 def on_iter_parent(self, child):
184 # def on_ref_node (self, rowref):
188 # def on_unref_node (self, rowref):
193 class LazyLogModel (LogModelBase):
195 def __init__(self, log_obj=None):
197 LogModelBase.__init__(self)
199 self.__log_obj = log_obj
202 self.set_log(log_obj)
204 def set_log(self, log_obj):
206 self.__fileobj = log_obj.fileobj
208 self.line_cache.clear()
209 self.line_offsets = log_obj.line_cache.offsets
210 self.line_levels = log_obj.line_cache.levels
212 def access_offset(self, offset):
214 # TODO: Implement using one slice access instead of seek+readline.
215 self.__fileobj.seek(offset)
216 return self.__fileobj.readline()
218 def ensure_cached(self, line_offset):
220 if line_offset in self.line_cache:
223 if len(self.line_cache) > 10000:
224 self.line_cache.clear()
226 self.__fileobj.seek(line_offset)
227 line = self.__fileobj.readline()
229 self.line_cache[line_offset] = Data.LogLine.parse_full(line)
232 class FilteredLogModelBase (LogModelBase):
234 def __init__(self, super_model):
236 LogModelBase.__init__(self)
238 self.logger = logging.getLogger("filter-model-base")
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
245 def line_index_to_super(self, line_index):
247 raise NotImplementedError("index conversion not supported")
249 def line_index_from_super(self, super_line_index):
251 raise NotImplementedError("index conversion not supported")
254 class FilteredLogModel (FilteredLogModelBase):
256 def __init__(self, super_model):
258 FilteredLogModelBase.__init__(self, super_model)
260 self.logger = logging.getLogger("filtered-log-model")
264 self.__active_process = None
265 self.__filter_progress = 0.
269 self.logger.debug("reset filter")
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))
277 def __filter_process(self, filter):
281 self.logger.debug("preparing new filter")
282 new_line_offsets = array("I")
284 new_super_index = array("I")
285 level_id = self.COL_LEVEL
286 func = filter.filter_func
290 for row, offset in self.iter_rows_offset():
291 line_index = self.super_index[i]
292 yield (line_index, row, offset,)
294 self.logger.debug("running filter")
296 progress_full = float(len(self))
298 for i, row, offset in enum():
300 new_line_offsets.append(offset)
301 new_line_levels.append(row[level_id])
302 new_super_index.append(i)
305 progress += float(YIELD_LIMIT)
306 self.__filter_progress = progress / progress_full
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")
314 self.__filter_progress = 1.
315 self.__handle_filter_process_finished()
318 def add_filter(self, filter, dispatcher):
320 if self.__active_process is not None:
321 raise ValueError("dispatched a filter process already")
323 self.logger.debug("adding filter")
325 self.filters.append(filter)
327 self.__dispatcher = dispatcher
328 self.__active_process = self.__filter_process(filter)
329 dispatcher(self.__active_process)
331 def abort_process(self):
333 if self.__active_process is None:
334 raise ValueError("no filter process running")
336 self.__dispatcher.cancel()
337 self.__active_process = None
338 self.__dispatcher = None
342 def get_filter_progress(self):
344 if self.__active_process is None:
345 raise ValueError("no filter process running")
347 return self.__filter_progress
349 def __handle_filter_process_finished(self):
351 self.__active_process = None
352 self.handle_process_finished()
354 def handle_process_finished(self):
358 def line_index_from_super(self, super_line_index):
360 return bisect_left(self.super_index, super_line_index)
362 def line_index_to_super(self, line_index):
364 return self.super_index[line_index]
366 def set_range(self, super_start, super_stop):
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
372 self.logger.debug("set range (%i, %i), current (%i, %i)",
373 super_start, super_stop, old_super_start, old_super_stop)
375 if len(self.filters) == 0:
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)
384 if super_start < old_super_start:
386 raise NotImplementedError("Only handling further restriction of the range"
387 " (start offset = %i)" % (super_start,))
389 if super_stop > old_super_stop:
391 raise NotImplementedError("Only handling further restriction of the range"
392 " (end offset = %i)" % (super_stop,))
394 start = self.line_index_from_super(super_start)
395 stop = self.line_index_from_super(super_stop)
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)
402 class SubRange (object):
404 __slots__ = ("size", "start", "stop",)
406 def __init__(self, size, start, stop):
410 "need start <= stop (got %r, %r)" % (start, stop,))
412 if isinstance(size, type(self)):
413 # Another SubRange, don't stack:
422 def __getitem__(self, i):
424 if isinstance(i, slice):
431 return self.size[i.start + self.start:stop]
433 return self.size[i + self.start]
437 return self.stop - self.start
442 for i in range(self.start, self.stop):
446 class LineViewLogModel (FilteredLogModelBase):
448 def __init__(self, super_model):
450 FilteredLogModelBase.__init__(self, super_model)
452 self.line_offsets = []
453 self.line_levels = []
455 self.parent_indices = []
459 del self.line_offsets[:]
460 del self.line_levels[:]
462 def line_index_to_super(self, line_index):
464 return self.parent_indices[line_index]
466 def insert_line(self, position, super_line_index):
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)
476 tree_iter = self.get_iter(path)
477 self.row_inserted(path, tree_iter)
479 def replace_line(self, line_index, super_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
487 tree_iter = self.get_iter(path)
488 self.row_changed(path, tree_iter)
490 def remove_line(self, line_index):
492 for l in (self.line_offsets,
494 self.parent_indices,):
498 self.row_deleted(path)