mirror of
https://gitlab.freedesktop.org/gstreamer/gstreamer.git
synced 2024-12-04 07:26:33 +00:00
9864042ff8
RangeFilteredLogModel is gone. The functionality is trivially implemented in FilteredLogModel now. Changing the range is now O(log n) at worst (was O(n) at best, for rewriting the arrays). Stacking filtered models is not supported anymore, which simplifies the code.
464 lines
13 KiB
Python
464 lines
13 KiB
Python
# -*- coding: utf-8; mode: python; -*-
|
|
#
|
|
# GStreamer Debug Viewer - View and analyze GStreamer debug log files
|
|
#
|
|
# Copyright (C) 2007 René Stadler <mail@renestadler.de>
|
|
#
|
|
# This program is free software; you can redistribute it and/or modify it
|
|
# under the terms of the GNU General Public License as published by the Free
|
|
# Software Foundation; either version 3 of the License, or (at your option)
|
|
# any later version.
|
|
#
|
|
# This program is distributed in the hope that it will be useful, but WITHOUT
|
|
# ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
|
|
# FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for
|
|
# more details.
|
|
#
|
|
# You should have received a copy of the GNU General Public License along with
|
|
# this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
"""GStreamer Debug Viewer GUI module."""
|
|
|
|
from bisect import bisect_left
|
|
import logging
|
|
|
|
import gobject
|
|
import gtk
|
|
|
|
from GstDebugViewer import Common, Data
|
|
|
|
class LogModelBase (gtk.GenericTreeModel):
|
|
|
|
__metaclass__ = Common.GUI.MetaModel
|
|
|
|
columns = ("COL_TIME", gobject.TYPE_UINT64,
|
|
"COL_PID", int,
|
|
"COL_THREAD", gobject.TYPE_UINT64,
|
|
"COL_LEVEL", object,
|
|
"COL_CATEGORY", str,
|
|
"COL_FILENAME", str,
|
|
"COL_LINE_NUMBER", int,
|
|
"COL_FUNCTION", str,
|
|
"COL_OBJECT", str,
|
|
"COL_MESSAGE", str,)
|
|
|
|
def __init__ (self):
|
|
|
|
gtk.GenericTreeModel.__init__ (self)
|
|
|
|
##self.props.leak_references = False
|
|
|
|
self.line_offsets = []
|
|
self.line_levels = [] # FIXME: Not so nice!
|
|
self.line_cache = {}
|
|
|
|
def ensure_cached (self, line_offset):
|
|
|
|
raise NotImplementedError ("derived classes must override this method")
|
|
|
|
def access_offset (self, offset):
|
|
|
|
raise NotImplementedError ("derived classes must override this method")
|
|
|
|
def iter_rows_offset (self):
|
|
|
|
ensure_cached = self.ensure_cached
|
|
line_cache = self.line_cache
|
|
line_levels = self.line_levels
|
|
COL_LEVEL = self.COL_LEVEL
|
|
|
|
for i, offset in enumerate (self.line_offsets):
|
|
ensure_cached (offset)
|
|
row = line_cache[offset]
|
|
row[COL_LEVEL] = line_levels[i] # FIXME
|
|
yield (row, offset,)
|
|
|
|
def on_get_flags (self):
|
|
|
|
flags = gtk.TREE_MODEL_LIST_ONLY | gtk.TREE_MODEL_ITERS_PERSIST
|
|
|
|
return flags
|
|
|
|
def on_get_n_columns (self):
|
|
|
|
return len (self.column_types)
|
|
|
|
def on_get_column_type (self, col_id):
|
|
|
|
return self.column_types[col_id]
|
|
|
|
def on_get_iter (self, path):
|
|
|
|
if not path:
|
|
return
|
|
|
|
if len (path) > 1:
|
|
# Flat model.
|
|
return None
|
|
|
|
line_index = path[0]
|
|
|
|
if line_index > len (self.line_offsets) - 1:
|
|
return None
|
|
|
|
return line_index
|
|
|
|
def on_get_path (self, rowref):
|
|
|
|
line_index = rowref
|
|
|
|
return (line_index,)
|
|
|
|
def on_get_value (self, line_index, col_id):
|
|
|
|
last_index = len (self.line_offsets) - 1
|
|
|
|
if line_index > last_index:
|
|
return None
|
|
|
|
if col_id == self.COL_LEVEL:
|
|
return self.line_levels[line_index]
|
|
|
|
line_offset = self.line_offsets[line_index]
|
|
self.ensure_cached (line_offset)
|
|
|
|
value = self.line_cache[line_offset][col_id]
|
|
if col_id == self.COL_MESSAGE:
|
|
message_offset = value
|
|
value = self.access_offset (line_offset + message_offset).strip ()
|
|
|
|
return value
|
|
|
|
def on_iter_next (self, line_index):
|
|
|
|
last_index = len (self.line_offsets) - 1
|
|
|
|
if line_index >= last_index:
|
|
return None
|
|
else:
|
|
return line_index + 1
|
|
|
|
def on_iter_children (self, parent):
|
|
|
|
return self.on_iter_nth_child (parent, 0)
|
|
|
|
def on_iter_has_child (self, rowref):
|
|
|
|
return False
|
|
|
|
def on_iter_n_children (self, rowref):
|
|
|
|
if rowref is not None:
|
|
return 0
|
|
|
|
return len (self.line_offsets)
|
|
|
|
def on_iter_nth_child (self, parent, n):
|
|
|
|
last_index = len (self.line_offsets) - 1
|
|
|
|
if parent or n > last_index:
|
|
return None
|
|
|
|
return n
|
|
|
|
def on_iter_parent (self, child):
|
|
|
|
return None
|
|
|
|
## def on_ref_node (self, rowref):
|
|
|
|
## pass
|
|
|
|
## def on_unref_node (self, rowref):
|
|
|
|
## pass
|
|
|
|
class LazyLogModel (LogModelBase):
|
|
|
|
def __init__ (self, log_obj = None):
|
|
|
|
LogModelBase.__init__ (self)
|
|
|
|
self.__log_obj = log_obj
|
|
|
|
if log_obj:
|
|
self.set_log (log_obj)
|
|
|
|
def set_log (self, log_obj):
|
|
|
|
self.__fileobj = log_obj.fileobj
|
|
|
|
self.line_cache.clear ()
|
|
self.line_offsets = log_obj.line_cache.offsets
|
|
self.line_levels = log_obj.line_cache.levels
|
|
|
|
def access_offset (self, offset):
|
|
|
|
# TODO: Implement using one slice access instead of seek+readline.
|
|
self.__fileobj.seek (offset)
|
|
return self.__fileobj.readline ()
|
|
|
|
def ensure_cached (self, line_offset):
|
|
|
|
if line_offset in self.line_cache:
|
|
return
|
|
|
|
if len (self.line_cache) > 10000:
|
|
self.line_cache.clear ()
|
|
|
|
self.__fileobj.seek (line_offset)
|
|
line = self.__fileobj.readline ()
|
|
|
|
self.line_cache[line_offset] = Data.LogLine.parse_full (line)
|
|
|
|
class FilteredLogModelBase (LogModelBase):
|
|
|
|
def __init__ (self, super_model):
|
|
|
|
LogModelBase.__init__ (self)
|
|
|
|
self.logger = logging.getLogger ("filter-model-base")
|
|
|
|
self.super_model = super_model
|
|
self.access_offset = super_model.access_offset
|
|
self.ensure_cached = super_model.ensure_cached
|
|
self.line_cache = super_model.line_cache
|
|
|
|
def line_index_to_super (self, line_index):
|
|
|
|
raise NotImplementedError ("index conversion not supported")
|
|
|
|
def line_index_from_super (self, super_line_index):
|
|
|
|
raise NotImplementedError ("index conversion not supported")
|
|
|
|
class FilteredLogModel (FilteredLogModelBase):
|
|
|
|
def __init__ (self, super_model):
|
|
|
|
FilteredLogModelBase.__init__ (self, super_model)
|
|
|
|
self.logger = logging.getLogger ("filtered-log-model")
|
|
|
|
self.filters = []
|
|
self.reset ()
|
|
self.__active_process = None
|
|
self.__filter_progress = 0.
|
|
|
|
def reset (self):
|
|
|
|
self.line_offsets = self.super_model.line_offsets
|
|
self.line_levels = self.super_model.line_levels
|
|
self.super_index = xrange (len (self.line_offsets))
|
|
|
|
del self.filters[:]
|
|
|
|
def __filter_process (self, filter):
|
|
|
|
YIELD_LIMIT = 10000
|
|
|
|
self.logger.debug ("preparing new filter")
|
|
## del self.line_offsets[:]
|
|
## del self.line_levels[:]
|
|
new_line_offsets = []
|
|
new_line_levels = []
|
|
new_super_index = []
|
|
level_id = self.COL_LEVEL
|
|
func = filter.filter_func
|
|
def enum ():
|
|
i = 0
|
|
for row, offset in self.iter_rows_offset ():
|
|
line_index = self.super_index[i]
|
|
yield (line_index, row, offset,)
|
|
i += 1
|
|
self.logger.debug ("running filter")
|
|
progress = 0.
|
|
progress_full = float (len (self))
|
|
y = YIELD_LIMIT
|
|
for i, row, offset in enum ():
|
|
if func (row):
|
|
new_line_offsets.append (offset)
|
|
new_line_levels.append (row[level_id])
|
|
new_super_index.append (i)
|
|
y -= 1
|
|
if y == 0:
|
|
progress += float (YIELD_LIMIT)
|
|
self.__filter_progress = progress / progress_full
|
|
y = YIELD_LIMIT
|
|
yield True
|
|
self.line_offsets = new_line_offsets
|
|
self.line_levels = new_line_levels
|
|
self.super_index = new_super_index
|
|
self.logger.debug ("filtering finished")
|
|
|
|
self.__filter_progress = 1.
|
|
self.__handle_filter_process_finished ()
|
|
yield False
|
|
|
|
def add_filter (self, filter, dispatcher):
|
|
|
|
if self.__active_process is not None:
|
|
raise ValueError ("dispatched a filter process already")
|
|
|
|
self.filters.append (filter)
|
|
|
|
self.__dispatcher = dispatcher
|
|
self.__active_process = self.__filter_process (filter)
|
|
dispatcher (self.__active_process)
|
|
|
|
def abort_process (self):
|
|
|
|
if self.__active_process is None:
|
|
raise ValueError ("no filter process running")
|
|
|
|
self.__dispatcher.cancel ()
|
|
self.__active_process = None
|
|
self.__dispatcher = None
|
|
|
|
del self.filters[-1]
|
|
|
|
def get_filter_progress (self):
|
|
|
|
if self.__active_process is None:
|
|
raise ValueError ("no filter process running")
|
|
|
|
return self.__filter_progress
|
|
|
|
def __handle_filter_process_finished (self):
|
|
|
|
self.__active_process = None
|
|
self.handle_process_finished ()
|
|
|
|
def handle_process_finished (self):
|
|
|
|
pass
|
|
|
|
def line_index_from_super (self, super_line_index):
|
|
|
|
return bisect_left (self.super_index, super_line_index)
|
|
|
|
def line_index_to_super (self, line_index):
|
|
|
|
return self.super_index[line_index]
|
|
|
|
def set_range (self, super_start, super_stop):
|
|
|
|
old_super_start = self.line_index_to_super (0)
|
|
old_super_stop = self.line_index_to_super (len (self.super_index) - 1) + 1
|
|
|
|
self.logger.debug ("set range (%i, %i), current (%i, %i)",
|
|
super_start, super_stop, old_super_start, old_super_stop)
|
|
|
|
if len (self.filters) == 0:
|
|
# Identity.
|
|
self.super_index = xrange (super_start, super_stop)
|
|
self.line_offsets = SubRange (self.super_model.line_offsets,
|
|
super_start, super_stop)
|
|
self.line_levels = SubRange (self.super_model.line_levels,
|
|
super_start, super_stop)
|
|
return
|
|
|
|
if super_start < old_super_start:
|
|
# TODO:
|
|
raise NotImplementedError ("Only handling further restriction of the range"
|
|
" (start offset = %i)" % (start_offset,))
|
|
|
|
if super_stop > old_super_stop:
|
|
# TODO:
|
|
raise NotImplementedError ("Only handling further restriction of the range"
|
|
" (end offset = %i)" % (stop_offset,))
|
|
|
|
start = self.line_index_from_super (super_start)
|
|
stop = self.line_index_from_super (super_stop)
|
|
|
|
self.super_index = SubRange (self.super_index, start, stop)
|
|
self.line_offsets = SubRange (self.line_offsets, start, stop)
|
|
self.line_levels = SubRange (self.line_levels, start, stop)
|
|
|
|
class SubRange (object):
|
|
|
|
__slots__ = ("l", "start", "stop",)
|
|
|
|
def __init__ (self, l, start, stop):
|
|
|
|
if start > stop:
|
|
raise ValueError ("need start <= stop (got %r, %r)" % (start, stop,))
|
|
|
|
if type (l) == type (self):
|
|
# Another SubRange, don't stack:
|
|
start += l.start
|
|
stop += l.start
|
|
l = l.l
|
|
|
|
self.l = l
|
|
self.start = start
|
|
self.stop = stop
|
|
|
|
def __getitem__ (self, i):
|
|
|
|
return self.l[i + self.start]
|
|
|
|
def __len__ (self):
|
|
|
|
return self.stop - self.start
|
|
|
|
def __iter__ (self):
|
|
|
|
l = self.l
|
|
for i in xrange (self.start, self.stop):
|
|
yield l[i]
|
|
|
|
class LineViewLogModel (FilteredLogModelBase):
|
|
|
|
def __init__ (self, super_model):
|
|
|
|
FilteredLogModelBase.__init__ (self, super_model)
|
|
|
|
self.line_offsets = []
|
|
self.line_levels = []
|
|
|
|
self.parent_indices = []
|
|
|
|
def reset (self):
|
|
|
|
del self.line_offsets[:]
|
|
del self.line_levels[:]
|
|
|
|
def line_index_to_super (self, line_index):
|
|
|
|
return self.parent_indices[line_index]
|
|
|
|
def insert_line (self, position, super_line_index):
|
|
|
|
if position == -1:
|
|
position = len (self.line_offsets)
|
|
li = super_line_index
|
|
self.line_offsets.insert (position, self.super_model.line_offsets[li])
|
|
self.line_levels.insert (position, self.super_model.line_levels[li])
|
|
self.parent_indices.insert (position, super_line_index)
|
|
|
|
path = (position,)
|
|
tree_iter = self.get_iter (path)
|
|
self.row_inserted (path, tree_iter)
|
|
|
|
def replace_line (self, line_index, super_line_index):
|
|
|
|
li = line_index
|
|
self.line_offsets[li] = self.super_model.line_offsets[super_line_index]
|
|
self.line_levels[li] = self.super_model.line_levels[super_line_index]
|
|
self.parent_indices[li] = super_line_index
|
|
|
|
path = (line_index,)
|
|
tree_iter = self.get_iter (path)
|
|
self.row_changed (path, tree_iter)
|
|
|
|
def remove_line (self, line_index):
|
|
|
|
for l in (self.line_offsets,
|
|
self.line_levels,
|
|
self.parent_indices,):
|
|
del l[line_index]
|
|
|
|
path = (line_index,)
|
|
self.row_deleted (path)
|