log rendering in real time in audit-viewer

Xeniya Muratova muratova at itsirius.su
Mon Mar 30 13:52:51 UTC 2015


I have fixed bugs in inserting new events, and also added real time rendering option in source dialog, and will be grateful for feedback.

diff -u -X ex or_src/audit-viewer.glade oav/src/audit-viewer.glade
--- or_src/audit-viewer.glade	2012-09-22 04:04:40.000000000 +0400
+++ oav/src/audit-viewer.glade	2015-03-30 12:13:06.810004999 +0300
@@ -2664,6 +2664,16 @@
               </widget>
             </child>
             <child>
+              <widget class="GtkCheckButton" id="source_with_updating">
+                <property name="visible">True</property>
+                <property name="can_focus">True</property>
+                <property name="label" translatable="yes">_Render updates in real time</property>
+                <property name="use_underline">True</property>
+                <property name="response_id">0</property>
+                <property name="draw_indicator">True</property>
+              </widget>
+            </child>
+            <child>
               <widget class="GtkVBox" id="vbox25">
                 <property name="visible">True</property>
                 <child>

diff -u -X ex or_src/event_source.py oav/src/event_source.py
--- or_src/event_source.py	2009-06-09 23:10:41.000000000 +0400
+++ oav/src/event_source.py	2015-03-30 12:41:11.028004422 +0300
@@ -19,7 +19,7 @@
 import datetime
 import os.path
 import re
-
+import os
 import auparse
 
 __all__ = ('ClientEventSource', 'ClientWithRotatedEventSource',
@@ -95,7 +95,7 @@
     '''A source of audit events, reading from an auparse parser.'''
 
     def read_events(self, filters, wanted_fields, want_other_fields,
-                    keep_raw_records):
+                    keep_raw_records, direction = None, tab = None):
         '''Return a sequence of audit events read from parser.
 
         Use filters to select events.  Store wanted_fields in event.fields, the
@@ -265,6 +265,107 @@
     def _create_parser(self):
         return auparse.AuParser(auparse.AUSOURCE_BUFFER, self.str)
 
+class UpdatableEventSourceReader():
+
+    '''A separate reader of audit events, created for each tab of main window.
+    To be used with UpdatableEventSource.
+
+    '''
+
+    def __init__(self, event_source, tab):
+        self.event_source = event_source
+        self.tab = tab
+        self.up_file = open(event_source.base)
+        self.bottom_file = open(event_source.base)
+        self.up_file.seek(0, 2)
+        self.up_pos = self.down_pos = self.up_file.tell()
+        self.bytes = self.event_source.avg_line_length * self.event_source.chunk_size
+
+    def read_down(self):
+        ''' read older lines from file starting from self.down_pos'''       
+        if os.path.exists(self.bottom_file.name): 
+            if os.stat(self.bottom_file.name).st_ino != os.fstat(self.bottom_file.fileno()).st_ino:
+                self.bottom_file.close()
+                self.tab.want_read_down = False
+                return ''
+        if self.down_pos <= self.bytes:
+            self.bottom_file.seek(0)
+            lines = self.bottom_file.read(self.down_pos)
+            try:
+                files = os.listdir(os.path.dirname(self.event_source.base))
+                files = sorted(files, key = lambda x : os.stat(x).st_mtime)
+                filename = self.bottom_file.name
+                self.bottom_file.close()
+                self.bottom_file = open(files[files.index(filename) + 1])
+                self.bottom_file.seek(0, 2)
+                self.down_pos = self.bottom_file.tell()
+            except:
+                self.tab.want_read_down = False
+        else:
+            self.bottom_file.seek(self.down_pos - self.bytes)
+            lines = self.bottom_file.read(self.bytes)
+            lines = lines[lines.find('\n') + 1:]
+            self.down_pos -= len(lines)
+        return lines
+
+    def read_up(self):
+        '''try to read new lines if there are any after the previous reads'''
+        if os.path.exists(self.up_file.name): 
+            if os.stat(self.up_file.name).st_ino != os.fstat(self.up_file.fileno()).st_ino:
+                self.up_file.close()
+                self.up_file = open(self.event_source.base)
+                self.up_pos = 0
+        else:
+            self.up_file.close()
+            self.tab.want_read_up = False
+            return ''
+        self.up_file.seek(0, 2)
+        file_end_pos = self.up_file.tell()
+        if self.up_pos != file_end_pos:
+            self.up_file.seek(self.up_pos)
+            if file_end_pos - self.up_pos > self.bytes*5:
+                lines = self.up_file.read(self.bytes)
+                file_end_pos = self.up_file.tell()
+            else:
+                lines = self.up_file.read()
+            if lines.endswith('\n'):
+                self.up_pos = file_end_pos          
+            else:              
+                lines = lines[:lines.rfind('\n') + 1]
+                self.up_pos += len(lines)                   
+            return lines
+        return ''
+
+
+class UpdatableEventSource(_ParserEventSource):
+
+    def __init__(self, base_file, chunk_size = 50, avg_line_length = 74):
+        self.chunk_size = chunk_size
+        self.avg_line_length = avg_line_length
+        self.readers = {}
+        self.base = base_file
+        self.current_lines = ''
+
+    def add_tab(self, tab):
+        self.readers[tab] = UpdatableEventSourceReader(self, tab)
+
+    def remove_tab(self, tab):
+        del self.readers[tab]
+
+    def read_events(self, filters, wanted_fields, want_other_fields,
+                    keep_raw_records, direction, tab):
+        if direction == 'up':
+            self.current_lines = self.readers[tab].read_up()
+        else:
+            self.current_lines = self.readers[tab].read_down()
+        return _ParserEventSource.read_events(self, filters, wanted_fields, want_other_fields,
+                    keep_raw_records)
+
+
+    def _create_parser(self):
+        return auparse.AuParser(auparse.AUSOURCE_BUFFER, self.current_lines)
+
+
 def check_expression(expr):
     '''Check expr.
 
diff -u -X ex or_src/list_tab.py oav/src/list_tab.py
--- or_src/list_tab.py	2009-12-19 10:00:00.000000000 +0300
+++ oav/src/list_tab.py	2015-03-30 13:26:44.579005427 +0300
@@ -28,6 +28,7 @@
 from search_entry import SearchEntry
 from tab import Tab
 import util
+import event_source
 
 __all__ = ('ListTab')
 
@@ -130,7 +131,7 @@
     date_column_label = '__audit_viewer_date'
 
     __list_number = 1
-    def __init__(self, filters, main_window, will_refresh = False):
+    def __init__(self, filters, main_window, will_refresh = True):
         Tab.__init__(self, filters, main_window, 'list_vbox')
 
         # date_column_label == event date, None == all other columns
@@ -157,6 +158,13 @@
         util.connect_and_run(self.selection, 'changed',
                              self.__selection_changed)
 
+        if isinstance(self.main_window.event_source, event_source.UpdatableEventSource):
+            self.main_window.event_source.add_tab(self)
+            self.want_read_up = True
+            self.want_read_down = True
+            self.__refresh_dont_read_events = False
+            self.refresh(False, 'up')
+            return
         self.__refresh_dont_read_events = will_refresh
         self.refresh()
         self.__refresh_dont_read_events = False
@@ -191,20 +199,25 @@
                                      % (util.filename_to_utf8(filename),
                                         e.strerror))
 
-    def refresh(self):
-        event_sequence = self.__refresh_get_event_sequence()
+    def refresh(self, updating  = False, direction = None):
+        if isinstance(self.main_window.event_source, event_source.UpdatableEventSource) and not updating:
+            self.want_read_down = True
+            self.want_read_up = True
+            self.main_window.event_source.readers[self] = event_source.UpdatableEventSourceReader(self.main_window.event_source, self)
+        event_sequence = self.__refresh_get_event_sequence(direction, self)
         if event_sequence is None:
             return
-
-        if self.filters:
-            t = _(', ').join(f.ui_text() for f in self.filters)
-        else:
-            t = _('None')
-        self.list_filter_label.set_text(t)
-        self.__refresh_update_tree_view()
+        if not updating:
+            if self.filters:
+                t = _(', ').join(f.ui_text() for f in self.filters)
+            else:
+                t = _('None')
+            self.list_filter_label.set_text(t)
+            self.__refresh_update_tree_view()
 
         events = self.__refresh_collect_events(event_sequence)
-        self.__refresh_update_store(events)
+        self.__refresh_update_store(events, updating)
+
 
     def report_on_view(self):
         self.main_window.new_report_tab(self.filters)
@@ -462,7 +475,7 @@
                                        for record in event.records
                                        for (key, value) in record.fields]))
 
-    def __refresh_get_event_sequence(self):
+    def __refresh_get_event_sequence(self, direction = None, tab = None):
         '''Return an event sequence (as if from self.main_window.read_events()).
 
         Return None on error.
@@ -480,7 +493,7 @@
             elif title is not self.date_column_label:
                 wanted_fields.add(title)
         return self.main_window.read_events(self.filters, wanted_fields,
-                                            want_other_fields, True)
+                                            want_other_fields, True, direction, tab)
 
     def __refresh_update_tree_view(self):
         '''Update self.list_tree_view for current configuration.
@@ -560,7 +573,32 @@
         events.sort(key = lambda event: event[0], reverse = self.sort_reverse)
         return events
 
-    def __refresh_update_store(self, events):
+    def __insert_row(self, event):
+        ''' insert new row with event into self.store preserving sort order'''
+        it = None
+        if not isinstance(self.main_window.event_source, event_source.UpdatableEventSource):
+            it = self.store.append(event[1])
+            return it
+        if self.sort_by and self.sort_by not in self.columns:
+            self.sort_by = None
+        if self.sort_by:
+            sort_field = self.__field_columns.index(self.sort_by) + 1
+            for i in range(len(self.store)):
+                if event[0] > self.store[i][sort_field] and self.sort_reverse or \
+                    event[0] <= self.store[i][sort_field] and not self.sort_reverse:
+                    it = self.store.insert(i, event[1])
+                    break
+        else:
+            for i in range(len(self.store)):
+                if event[1][0].id.sec > self.store[i][0].id.sec and self.sort_reverse or \
+                    event[1][0].id.sec <= self.store[i][0].id.sec and not self.sort_reverse:
+                    it = self.store.insert(i, event[1])
+                    break
+        if not it:
+            it = self.store.append(event[1])
+        return it
+
+    def __refresh_update_store(self, events, updating = False):
         '''Update self.store and related data.
 
         events is the result of self.__refresh_collect_events().
@@ -571,11 +609,12 @@
             key = pos.event_key
             l = positions_for_event_key.setdefault(key, [])
             l.append(pos)
-        self.store.clear()
+        if not updating:
+            self.store.clear()
         if (self.text_filter is None and
             len(positions_for_event_key) == 0): # Fast path
             for event in events:
-                self.store.append(event[1])
+                self.__insert_row(event)
         else:
             event_to_it = {}
             text_filter = self.text_filter
@@ -604,7 +643,8 @@
                              or (self.__other_column_event_text(event_tuple[0]).
                                  find(self.text_filter) == -1))):
                             continue
-                it = self.store.append(event_tuple)
+
+                it = self.__insert_row(event)
                 event_id = event_tuple[0].id
                 key = (event_id.serial, event_id.sec, event_id.milli)
                 if key in positions_for_event_key:
diff -u -X ex or_src/main.py oav/src/main.py
--- or_src/main.py	2008-06-26 00:17:59.000000000 +0400
+++ oav/src/main.py	2015-03-25 15:31:29.663932132 +0300
@@ -29,6 +29,7 @@
 from main_window import MainWindow
 import settings
 import util
+import event_source
 
 _ = gettext.gettext
 
@@ -48,12 +49,20 @@
                       help = _('do not attempt to start the privileged backend '
                                'for reading system audit logs'))
     parser.set_defaults(unprivileged = False)
+    parser.add_option('-p', '--updatable', action = 'store_true',
+                      dest = 'updatable',
+                      help = _('read new lines from log '))
+    parser.set_defaults(updatable = False)
+    parser.add_option('-s', '--source', type = 'string',
+                      dest = 'source',
+                      help = _('path to log file '))
     (options, args) = parser.parse_args()
 
     gnome.init(settings.gettext_domain, settings.version)
     gtk.glade.bindtextdomain(settings.gettext_domain, settings.localedir)
     gtk.glade.textdomain(settings.gettext_domain)
 
+    ev_source  = None
     if options.unprivileged:
         cl = None
     else:
@@ -66,7 +75,9 @@
             sys.exit(1)
         except client.ClientNotAvailableError:
             cl = None
+    if options.updatable:
+        ev_source = event_source.UpdatableEventSource(options.source)
 
-    w = MainWindow(cl)
+    w = MainWindow(cl, ev_source)
     if w.setup_initial_window(args):
         gtk.main()
diff -u -X ex or_src/main_window.py oav/src/main_window.py
--- or_src/main_window.py	2008-08-19 14:38:16.000000000 +0400
+++ oav/src/main_window.py	2015-03-30 13:21:25.607005136 +0300
@@ -135,6 +135,8 @@
 
         '''
         try:
+            if isinstance(self.event_source, event_source.UpdatableEventSource):
+                self.updater = gobject.idle_add(self.update_tabs)
             if isinstance(self.event_source, event_source.EmptyEventSource):
                 self.__event_error_report_only_one_push()
                 if self.client is not None:
@@ -246,7 +248,7 @@
         return (filename, extension)
 
     def read_events(self, filters, wanted_fields, want_other_fields,
-                    keep_raw_records):
+                    keep_raw_records, direction  = None, tab = None):
         '''Read audit events.
 
         Return a sequence of events, or None on error (without throwing
@@ -262,7 +264,7 @@
         try:
             return self.event_source.read_events(filters, wanted_fields,
                                                  want_other_fields,
-                                                 keep_raw_records)
+                                                 keep_raw_records, direction, tab)
         except IOError, e:
             if (self.__event_error_report_only_one_depth == 0 or
                 not self.__event_error_reported):
@@ -381,16 +383,30 @@
         '''End a region in which only one error message should be reported.'''
         self.__event_error_report_only_one_depth -= 1
 
-    def __refresh_all_tabs(self):
+    def update_tabs(self):
+        self.__refresh_all_tabs(True)
+        return True
+
+    def __refresh_all_tabs(self, updating = False):
         '''Refresh all tabs, taking care to report errors only once.'''
         self.__event_error_report_only_one_push()
         try:
-            for page_num in xrange(self.main_notebook.get_n_pages()):
-                tab = self.__tab_objects[self.main_notebook
-                                         .get_nth_page(page_num)]
-                tab.refresh()
+            if not updating:
+                for page_num in xrange(self.main_notebook.get_n_pages()):
+                    tab = self.__tab_objects[self.main_notebook
+                                             .get_nth_page(page_num)]
+                    tab.refresh()
+            else:
+                for page_num in xrange(self.main_notebook.get_n_pages()):
+                    tab = self.__tab_objects[self.main_notebook
+                                             .get_nth_page(page_num)]
+                    if tab.want_read_up:
+                        tab.refresh(True, 'up')
+                    if tab.want_read_down:
+                        tab.refresh(True, 'down')
         finally:
             self.__event_error_report_only_one_pop()
+        return True
 
     def __menu_new_list_activate(self, *_):
         self.new_list_tab([])
Only in oav/src/: patch
diff -u -X ex or_src/source_dialog.py oav/src/source_dialog.py
--- or_src/source_dialog.py	2009-06-09 22:35:26.000000000 +0400
+++ oav/src/source_dialog.py	2015-03-30 12:46:15.983005127 +0300
@@ -39,7 +39,8 @@
                            'source_path', 'source_path_browse',
                            'source_path_label',
                            'source_type_file', 'source_type_log',
-                           'source_with_rotated')
+                           'source_with_rotated',
+                           'source_with_updating')
 
     def __init__(self, parent, client):
         DialogBase.__init__(self, 'source_dialog', parent)
@@ -58,6 +59,8 @@
                              self.__source_type_log_toggled)
         util.connect_and_run(self.source_type_file, 'toggled',
                              self.__source_type_file_toggled)
+        util.connect_and_run(self.source_with_updating, 'toggled',
+                             self.__source_with_updating_toggled)
         self._setup_browse_button(self.source_path_browse, self.source_path,
                                   _('Audit Log File'),
                                   gtk.FILE_CHOOSER_ACTION_OPEN)
@@ -102,13 +105,20 @@
             self.source_with_rotated.set_active(False)
             self.source_type_file.set_active(True)
             self.source_path.set_text(source.path)
+        elif isinstance(source, event_source.FileWithRotatedEventSource):
+            self.source_with_rotated.set_active(True)
+            self.source_type_file.set_active(True)
+            self.source_path.set_text(source.base)
         else:
             assert isinstance(source,
-                              event_source.FileWithRotatedEventSource), \
+                              event_source.UpdatableEventSource), \
                 'Unexpected event source'
             self.source_with_rotated.set_active(True)
+            self.source_with_rotated.set_sensitive(False)
+            self.source_type_log.set_sensitive(False)
             self.source_type_file.set_active(True)
             self.source_path.set_text(source.base)
+            self.source_with_updating.set_active(True)
 
     def save(self, main_window):
         '''Modify main_window to reflect dialog state.'''
@@ -124,7 +134,10 @@
                 source = event_source.ClientEventSource(self.client, name)
         else:
             path = self.source_path.get_text()
-            if self.source_with_rotated.get_active():
+            if self.source_with_updating.get_active():
+                source = event_source.UpdatableEventSource(path)
+                main_window.updater = gobject.idle_add(main_window.update_tabs)
+            elif self.source_with_rotated.get_active():
                 source = event_source.FileWithRotatedEventSource(path)
             else:
                 source = event_source.FileEventSource(path)
@@ -175,11 +188,21 @@
                 if it is not None:
                     self.source_log.set_active_iter(it)
 
+    def __source_with_updating_toggled(self, *_):
+        is_set =  self.source_with_updating.get_active()
+        self.source_with_rotated.set_sensitive(not is_set)
+        self.source_type_log.set_sensitive(not is_set)
+        if is_set:
+            self.source_type_file.set_active(True)
+            self.source_type_log.set_active(False)
+            self.source_with_rotated.set_active(True)
+
     def __source_type_file_toggled(self, *_):
         util.set_sensitive_all(self.source_type_file.get_active(),
                                self.source_path_label, self.source_path,
                                self.source_path_browse)
 
+
     def __window_destroy(self, *_):
         self.emit('destroy')
         return False


diff -u -X ex or_src/event_source.py oav/src/event_source.py
--- or_src/event_source.py	2009-06-09 23:10:41.000000000 +0400
+++ oav/src/event_source.py	2015-03-30 12:41:11.028004422 +0300
@@ -19,7 +19,7 @@
 import datetime
 import os.path
 import re
-
+import os
 import auparse
 
 __all__ = ('ClientEventSource', 'ClientWithRotatedEventSource',
@@ -95,7 +95,7 @@
     '''A source of audit events, reading from an auparse parser.'''
 
     def read_events(self, filters, wanted_fields, want_other_fields,
-                    keep_raw_records):
+                    keep_raw_records, direction = None, tab = None):
         '''Return a sequence of audit events read from parser.
 
         Use filters to select events.  Store wanted_fields in event.fields, the
@@ -265,6 +265,107 @@
     def _create_parser(self):
         return auparse.AuParser(auparse.AUSOURCE_BUFFER, self.str)
 
+class UpdatableEventSourceReader():
+
+    '''A separate reader of audit events, created for each tab of main window.
+    To be used with UpdatableEventSource.
+
+    '''
+
+    def __init__(self, event_source, tab):
+        self.event_source = event_source
+        self.tab = tab
+        self.up_file = open(event_source.base)
+        self.bottom_file = open(event_source.base)
+        self.up_file.seek(0, 2)
+        self.up_pos = self.down_pos = self.up_file.tell()
+        self.bytes = self.event_source.avg_line_length * self.event_source.chunk_size
+
+    def read_down(self):
+        ''' read older lines from file starting from self.down_pos'''       
+        if os.path.exists(self.bottom_file.name): 
+            if os.stat(self.bottom_file.name).st_ino != os.fstat(self.bottom_file.fileno()).st_ino:
+                self.bottom_file.close()
+                self.tab.want_read_down = False
+                return ''
+        if self.down_pos <= self.bytes:
+            self.bottom_file.seek(0)
+            lines = self.bottom_file.read(self.down_pos)
+            try:
+                files = os.listdir(os.path.dirname(self.event_source.base))
+                files = sorted(files, key = lambda x : os.stat(x).st_mtime)
+                filename = self.bottom_file.name
+                self.bottom_file.close()
+                self.bottom_file = open(files[files.index(filename) + 1])
+                self.bottom_file.seek(0, 2)
+                self.down_pos = self.bottom_file.tell()
+            except:
+                self.tab.want_read_down = False
+        else:
+            self.bottom_file.seek(self.down_pos - self.bytes)
+            lines = self.bottom_file.read(self.bytes)
+            lines = lines[lines.find('\n') + 1:]
+            self.down_pos -= len(lines)
+        return lines
+
+    def read_up(self):
+        '''try to read new lines if there are any after the previous reads'''
+        if os.path.exists(self.up_file.name): 
+            if os.stat(self.up_file.name).st_ino != os.fstat(self.up_file.fileno()).st_ino:
+                self.up_file.close()
+                self.up_file = open(self.event_source.base)
+                self.up_pos = 0
+        else:
+            self.up_file.close()
+            self.tab.want_read_up = False
+            return ''
+        self.up_file.seek(0, 2)
+        file_end_pos = self.up_file.tell()
+        if self.up_pos != file_end_pos:
+            self.up_file.seek(self.up_pos)
+            if file_end_pos - self.up_pos > self.bytes*5:
+                lines = self.up_file.read(self.bytes)
+                file_end_pos = self.up_file.tell()
+            else:
+                lines = self.up_file.read()
+            if lines.endswith('\n'):
+                self.up_pos = file_end_pos          
+            else:              
+                lines = lines[:lines.rfind('\n') + 1]
+                self.up_pos += len(lines)                   
+            return lines
+        return ''
+
+
+class UpdatableEventSource(_ParserEventSource):
+
+    def __init__(self, base_file, chunk_size = 50, avg_line_length = 74):
+        self.chunk_size = chunk_size
+        self.avg_line_length = avg_line_length
+        self.readers = {}
+        self.base = base_file
+        self.current_lines = ''
+
+    def add_tab(self, tab):
+        self.readers[tab] = UpdatableEventSourceReader(self, tab)
+
+    def remove_tab(self, tab):
+        del self.readers[tab]
+
+    def read_events(self, filters, wanted_fields, want_other_fields,
+                    keep_raw_records, direction, tab):
+        if direction == 'up':
+            self.current_lines = self.readers[tab].read_up()
+        else:
+            self.current_lines = self.readers[tab].read_down()
+        return _ParserEventSource.read_events(self, filters, wanted_fields, want_other_fields,
+                    keep_raw_records)
+
+
+    def _create_parser(self):
+        return auparse.AuParser(auparse.AUSOURCE_BUFFER, self.current_lines)
+
+
 def check_expression(expr):
     '''Check expr.
 
diff -u -X ex or_src/list_tab.py oav/src/list_tab.py
--- or_src/list_tab.py	2009-12-19 10:00:00.000000000 +0300
+++ oav/src/list_tab.py	2015-03-30 13:26:44.579005427 +0300
@@ -28,6 +28,7 @@
 from search_entry import SearchEntry
 from tab import Tab
 import util
+import event_source
 
 __all__ = ('ListTab')
 
@@ -130,7 +131,7 @@
     date_column_label = '__audit_viewer_date'
 
     __list_number = 1
-    def __init__(self, filters, main_window, will_refresh = False):
+    def __init__(self, filters, main_window, will_refresh = True):
         Tab.__init__(self, filters, main_window, 'list_vbox')
 
         # date_column_label == event date, None == all other columns
@@ -157,6 +158,13 @@
         util.connect_and_run(self.selection, 'changed',
                              self.__selection_changed)
 
+        if isinstance(self.main_window.event_source, event_source.UpdatableEventSource):
+            self.main_window.event_source.add_tab(self)
+            self.want_read_up = True
+            self.want_read_down = True
+            self.__refresh_dont_read_events = False
+            self.refresh(False, 'up')
+            return
         self.__refresh_dont_read_events = will_refresh
         self.refresh()
         self.__refresh_dont_read_events = False
@@ -191,20 +199,25 @@
                                      % (util.filename_to_utf8(filename),
                                         e.strerror))
 
-    def refresh(self):
-        event_sequence = self.__refresh_get_event_sequence()
+    def refresh(self, updating  = False, direction = None):
+        if isinstance(self.main_window.event_source, event_source.UpdatableEventSource) and not updating:
+            self.want_read_down = True
+            self.want_read_up = True
+            self.main_window.event_source.readers[self] = event_source.UpdatableEventSourceReader(self.main_window.event_source, self)
+        event_sequence = self.__refresh_get_event_sequence(direction, self)
         if event_sequence is None:
             return
-
-        if self.filters:
-            t = _(', ').join(f.ui_text() for f in self.filters)
-        else:
-            t = _('None')
-        self.list_filter_label.set_text(t)
-        self.__refresh_update_tree_view()
+        if not updating:
+            if self.filters:
+                t = _(', ').join(f.ui_text() for f in self.filters)
+            else:
+                t = _('None')
+            self.list_filter_label.set_text(t)
+            self.__refresh_update_tree_view()
 
         events = self.__refresh_collect_events(event_sequence)
-        self.__refresh_update_store(events)
+        self.__refresh_update_store(events, updating)
+
 
     def report_on_view(self):
         self.main_window.new_report_tab(self.filters)
@@ -462,7 +475,7 @@
                                        for record in event.records
                                        for (key, value) in record.fields]))
 
-    def __refresh_get_event_sequence(self):
+    def __refresh_get_event_sequence(self, direction = None, tab = None):
         '''Return an event sequence (as if from self.main_window.read_events()).
 
         Return None on error.
@@ -480,7 +493,7 @@
             elif title is not self.date_column_label:
                 wanted_fields.add(title)
         return self.main_window.read_events(self.filters, wanted_fields,
-                                            want_other_fields, True)
+                                            want_other_fields, True, direction, tab)
 
     def __refresh_update_tree_view(self):
         '''Update self.list_tree_view for current configuration.
@@ -560,7 +573,32 @@
         events.sort(key = lambda event: event[0], reverse = self.sort_reverse)
         return events
 
-    def __refresh_update_store(self, events):
+    def __insert_row(self, event):
+        ''' insert new row with event into self.store preserving sort order'''
+        it = None
+        if not isinstance(self.main_window.event_source, event_source.UpdatableEventSource):
+            it = self.store.append(event[1])
+            return it
+        if self.sort_by and self.sort_by not in self.columns:
+            self.sort_by = None
+        if self.sort_by:
+            sort_field = self.__field_columns.index(self.sort_by) + 1
+            for i in range(len(self.store)):
+                if event[0] > self.store[i][sort_field] and self.sort_reverse or \
+                    event[0] <= self.store[i][sort_field] and not self.sort_reverse:
+                    it = self.store.insert(i, event[1])
+                    break
+        else:
+            for i in range(len(self.store)):
+                if event[1][0].id.sec > self.store[i][0].id.sec and self.sort_reverse or \
+                    event[1][0].id.sec <= self.store[i][0].id.sec and not self.sort_reverse:
+                    it = self.store.insert(i, event[1])
+                    break
+        if not it:
+            it = self.store.append(event[1])
+        return it
+
+    def __refresh_update_store(self, events, updating = False):
         '''Update self.store and related data.
 
         events is the result of self.__refresh_collect_events().
@@ -571,11 +609,12 @@
             key = pos.event_key
             l = positions_for_event_key.setdefault(key, [])
             l.append(pos)
-        self.store.clear()
+        if not updating:
+            self.store.clear()
         if (self.text_filter is None and
             len(positions_for_event_key) == 0): # Fast path
             for event in events:
-                self.store.append(event[1])
+                self.__insert_row(event)
         else:
             event_to_it = {}
             text_filter = self.text_filter
@@ -604,7 +643,8 @@
                              or (self.__other_column_event_text(event_tuple[0]).
                                  find(self.text_filter) == -1))):
                             continue
-                it = self.store.append(event_tuple)
+
+                it = self.__insert_row(event)
                 event_id = event_tuple[0].id
                 key = (event_id.serial, event_id.sec, event_id.milli)
                 if key in positions_for_event_key:
diff -u -X ex or_src/main.py oav/src/main.py
--- or_src/main.py	2008-06-26 00:17:59.000000000 +0400
+++ oav/src/main.py	2015-03-25 15:31:29.663932132 +0300
@@ -29,6 +29,7 @@
 from main_window import MainWindow
 import settings
 import util
+import event_source
 
 _ = gettext.gettext
 
@@ -48,12 +49,20 @@
                       help = _('do not attempt to start the privileged backend '
                                'for reading system audit logs'))
     parser.set_defaults(unprivileged = False)
+    parser.add_option('-p', '--updatable', action = 'store_true',
+                      dest = 'updatable',
+                      help = _('read new lines from log '))
+    parser.set_defaults(updatable = False)
+    parser.add_option('-s', '--source', type = 'string',
+                      dest = 'source',
+                      help = _('path to log file '))
     (options, args) = parser.parse_args()
 
     gnome.init(settings.gettext_domain, settings.version)
     gtk.glade.bindtextdomain(settings.gettext_domain, settings.localedir)
     gtk.glade.textdomain(settings.gettext_domain)
 
+    ev_source  = None
     if options.unprivileged:
         cl = None
     else:
@@ -66,7 +75,9 @@
             sys.exit(1)
         except client.ClientNotAvailableError:
             cl = None
+    if options.updatable:
+        ev_source = event_source.UpdatableEventSource(options.source)
 
-    w = MainWindow(cl)
+    w = MainWindow(cl, ev_source)
     if w.setup_initial_window(args):
         gtk.main()
diff -u -X ex or_src/main_window.py oav/src/main_window.py
--- or_src/main_window.py	2008-08-19 14:38:16.000000000 +0400
+++ oav/src/main_window.py	2015-03-30 13:21:25.607005136 +0300
@@ -135,6 +135,8 @@
 
         '''
         try:
+            if isinstance(self.event_source, event_source.UpdatableEventSource):
+                self.updater = gobject.idle_add(self.update_tabs)
             if isinstance(self.event_source, event_source.EmptyEventSource):
                 self.__event_error_report_only_one_push()
                 if self.client is not None:
@@ -246,7 +248,7 @@
         return (filename, extension)
 
     def read_events(self, filters, wanted_fields, want_other_fields,
-                    keep_raw_records):
+                    keep_raw_records, direction  = None, tab = None):
         '''Read audit events.
 
         Return a sequence of events, or None on error (without throwing
@@ -262,7 +264,7 @@
         try:
             return self.event_source.read_events(filters, wanted_fields,
                                                  want_other_fields,
-                                                 keep_raw_records)
+                                                 keep_raw_records, direction, tab)
         except IOError, e:
             if (self.__event_error_report_only_one_depth == 0 or
                 not self.__event_error_reported):
@@ -381,16 +383,30 @@
         '''End a region in which only one error message should be reported.'''
         self.__event_error_report_only_one_depth -= 1
 
-    def __refresh_all_tabs(self):
+    def update_tabs(self):
+        self.__refresh_all_tabs(True)
+        return True
+
+    def __refresh_all_tabs(self, updating = False):
         '''Refresh all tabs, taking care to report errors only once.'''
         self.__event_error_report_only_one_push()
         try:
-            for page_num in xrange(self.main_notebook.get_n_pages()):
-                tab = self.__tab_objects[self.main_notebook
-                                         .get_nth_page(page_num)]
-                tab.refresh()
+            if not updating:
+                for page_num in xrange(self.main_notebook.get_n_pages()):
+                    tab = self.__tab_objects[self.main_notebook
+                                             .get_nth_page(page_num)]
+                    tab.refresh()
+            else:
+                for page_num in xrange(self.main_notebook.get_n_pages()):
+                    tab = self.__tab_objects[self.main_notebook
+                                             .get_nth_page(page_num)]
+                    if tab.want_read_up:
+                        tab.refresh(True, 'up')
+                    if tab.want_read_down:
+                        tab.refresh(True, 'down')
         finally:
             self.__event_error_report_only_one_pop()
+        return True
 
     def __menu_new_list_activate(self, *_):
         self.new_list_tab([])



----- Исходное сообщение -----
От: "mitr" <mitr at redhat.com>
Кому: "Xeniya Muratova" <muratova at itsirius.su>
Копия: "linux-audit" <linux-audit at redhat.com>
Отправленные: Среда, 4 Март 2015 г 20:50:53
Тема: Re: log rendering in real time in audit-viewer

Hello,
> Hello Miloslav, and all the guys!
> 
> We use audit-viewer for events monitoring.
> Unfortunately, if some log is rather big it takes to much time for
> audit-viewer to parse and render it.
> Besides, we need to render log updates in real time, i.e. when a new line
> appears in a log, it should appear in a viewer too.
> Can you suggest the better way to extend audit-viewer to meet these
> requirements?

Well, write the code?  Something like inotify could be useful.  There isn’t any hidden switch to enable these features, if that is what you are asking.

As for performance, I may have missed something but I think I have squeezed as much as can be done with Python; improving performance further would very likely require a C extension.

(audit-viewer is a PyGtk2 application, and at I’m afraid I don’t currently have plans to port it to GTK+3/gobject-introspection or do any other non-trivial work on the project, at least in the near term.)
     Mirek






More information about the Linux-audit mailing list