All of lore.kernel.org
 help / color / mirror / Atom feed
* log rendering in real time in audit-viewer
@ 2015-03-04 13:42 Xeniya Muratova
  2015-03-04 17:50 ` Miloslav Trmač
  2015-03-05 12:45 ` Pittigher, Raymond - Exelis
  0 siblings, 2 replies; 7+ messages in thread
From: Xeniya Muratova @ 2015-03-04 13:42 UTC (permalink / raw)
  To: mitr, linux-audit

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?
Thanks in advance.

Kseniya Muratova,

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: log rendering in real time in audit-viewer
  2015-03-04 13:42 log rendering in real time in audit-viewer Xeniya Muratova
@ 2015-03-04 17:50 ` Miloslav Trmač
  2015-03-05 14:36   ` Steve Grubb
                     ` (2 more replies)
  2015-03-05 12:45 ` Pittigher, Raymond - Exelis
  1 sibling, 3 replies; 7+ messages in thread
From: Miloslav Trmač @ 2015-03-04 17:50 UTC (permalink / raw)
  To: Xeniya Muratova; +Cc: linux-audit

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


--
Linux-audit mailing list
Linux-audit@redhat.com
https://www.redhat.com/mailman/listinfo/linux-audit

^ permalink raw reply	[flat|nested] 7+ messages in thread

* RE: log rendering in real time in audit-viewer
  2015-03-04 13:42 log rendering in real time in audit-viewer Xeniya Muratova
  2015-03-04 17:50 ` Miloslav Trmač
@ 2015-03-05 12:45 ` Pittigher, Raymond - Exelis
  1 sibling, 0 replies; 7+ messages in thread
From: Pittigher, Raymond - Exelis @ 2015-03-05 12:45 UTC (permalink / raw)
  To: Xeniya Muratova, mitr, linux-audit

Free or pay for solutions? You have products like splunk and greybar that do a good job. Managengine also has a log viewer that works OK. You will just need to pass audit logs to syslog to use most products.
-
Ray Pittigher
--Exelis Inc, Clifton NJ
--phone 973-284-2275
--email raymond.pittigher@exelisinc.com
________________________________________
From: linux-audit-bounces@redhat.com [linux-audit-bounces@redhat.com] on behalf of Xeniya Muratova [muratova@itsirius.su]
Sent: Wednesday, March 04, 2015 8:42 AM
To: mitr@redhat.com; linux-audit@redhat.com
Subject: log rendering in real time in audit-viewer

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?
Thanks in advance.

Kseniya Muratova,

--
Linux-audit mailing list
Linux-audit@redhat.com
https://www.redhat.com/mailman/listinfo/linux-audit

________________________________

This e-mail and any files transmitted with it may be proprietary and are intended solely for the use of the individual or entity to whom they are addressed. If you have received this e-mail in error please notify the sender. Please note that any views or opinions presented in this e-mail are solely those of the author and do not necessarily represent those of Exelis Inc. The recipient should check this e-mail and any attachments for the presence of viruses. Exelis Inc. accepts no liability for any damage caused by any virus transmitted by this e-mail.

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: log rendering in real time in audit-viewer
  2015-03-04 17:50 ` Miloslav Trmač
@ 2015-03-05 14:36   ` Steve Grubb
       [not found]   ` <1384025748.230407.1427289658707.JavaMail.zimbra@itsirius.su>
       [not found]   ` <192757425.240881.1427723539724.JavaMail.zimbra@itsirius.su>
  2 siblings, 0 replies; 7+ messages in thread
From: Steve Grubb @ 2015-03-05 14:36 UTC (permalink / raw)
  To: linux-audit; +Cc: Miloslav Trmač

On Wednesday, March 04, 2015 12:50:53 PM Miloslav Trmač wrote:
> 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.

And it also uses the auparse library underneath which might could use some 
speeding up. There were some performance improvements over the last year. But 
I don't know if that is enough to be noticeable in a high level application. 
But it would be another obvious place that could be a performance bottleneck.

-Steve
 
> (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


--
Linux-audit mailing list
Linux-audit@redhat.com
https://www.redhat.com/mailman/listinfo/linux-audit

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: [PATCH] log rendering in real time in audit-viewer
       [not found]   ` <1384025748.230407.1427289658707.JavaMail.zimbra@itsirius.su>
@ 2015-03-25 13:21     ` Xeniya Muratova
  0 siblings, 0 replies; 7+ messages in thread
From: Xeniya Muratova @ 2015-03-25 13:21 UTC (permalink / raw)
  To: mitr, linux-audit

This code extends audit-viewer _ParserEventSource to produce UpdatableEventSource (unprivileged).
I have not added this option to SourceDialog, so to see how updating works command line call may be used (python main.py -p -s /path/to/log)

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-25 16:09:59.933965077 +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,106 @@
     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.chunk_size = event_source.chunk_size
+        self.up_file = open(event_source.base_file)
+        self.bottom_file = open(event_source.base_file)
+        self.up_file.seek(0, 2)
+        self.up_pos = self.down_pos = self.up_file.tell()
+
+    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.event_source.avg_line_length * self.chunk_size:
+            self.bottom_file.seek(self.down_pos)
+            lines = self.bottom_file.read(self.down_pos)
+            try:
+                files = os.listdir(os.path.dirname(self.event_source.base_file))
+                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])
+            except:
+                self.tab.want_read_down = False
+                return ''
+        else:
+            self.bottom_file.seek(self.down_pos - self.event_source.avg_line_length * self.chunk_size)
+            lines = self.bottom_file.read(self.event_source.avg_line_length * self.chunk_size)
+            lines = lines[lines.find('\n') + 1:]
+            self.down_pos -= len(lines)
+        return lines
+
+    def read_up(self):
+        '''try to read new lines if there any after the previous read'''
+        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_file)
+                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.event_source.avg_line_length*self.chunk_size*5:
+                lines = self.up_file.read(self.event_source.avg_line_length*self.chunk_size)
+                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_file = 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-25 15:42:07.797941743 +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(True, 'up', True)
+            return
         self.__refresh_dont_read_events = will_refresh
         self.refresh()
         self.__refresh_dont_read_events = False
@@ -191,20 +199,21 @@
                                      % (util.filename_to_utf8(filename),
                                         e.strerror))
 
-    def refresh(self):
-        event_sequence = self.__refresh_get_event_sequence()
+    def refresh(self, updatable  = False, direction = None, init = False):
+        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 updatable or init:
+            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, updatable, direction)
+
 
     def report_on_view(self):
         self.main_window.new_report_tab(self.filters)
@@ -462,7 +471,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 +489,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 +569,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, direction):
+        ''' insert new row with event into self.store preserving sort order'''
+        it = None
+        if not self.sort_by:
+            if self.sort_reverse:
+                if direction == 'up':
+                    it = self.store.insert(0, event[1])
+                else:
+                    it = self.store.append(event[1])
+            else:
+                if direction == 'down':
+                    it = self.store.insert(0, event[1])
+                else:
+                    it = self.store.append(event[1])
+        else:
+            sort_field = self.__field_columns.index(self.sort_by) + 1                                
+            for i in range(len(self.store)):
+                if event[1][sort_field] <= self.store[i][sort_field] and self.sort_reverse or \
+                    event[1][sort_field] > self.store[i][sort_field] 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, updatable = False, direction = None):
         '''Update self.store and related data.
 
         events is the result of self.__refresh_collect_events().
@@ -571,11 +605,15 @@
             key = pos.event_key
             l = positions_for_event_key.setdefault(key, [])
             l.append(pos)
-        self.store.clear()
+        if not updatable:
+            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])
+                if not updatable:
+                    self.store.append(event[1])
+                else:
+                    self.__insert_row(event, direction)
         else:
             event_to_it = {}
             text_filter = self.text_filter
@@ -604,7 +642,10 @@
                              or (self.__other_column_event_text(event_tuple[0]).
                                  find(self.text_filter) == -1))):
                             continue
-                it = self.store.append(event_tuple)
+                if not updatable:
+                    it = self.store.append(event_tuple)
+                else:
+                    it = self.__insert_row(event, direction)
                 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-23 19:13:04.399266459 +0300
@@ -135,6 +135,8 @@
 
         '''
         try:
+            if isinstance(self.event_source, event_source.UpdatableEventSource):
+                self.updater = gobject.idle_add(self.__refresh_all_tabs, True)
             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,24 @@
         '''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 __refresh_all_tabs(self, updatable = 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 updatable:
+                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)]
+                    tab.refresh(True, 'up')
+                    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@redhat.com>
Кому: "Xeniya Muratova" <muratova@itsirius.su>
Копия: "linux-audit" <linux-audit@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

--
Linux-audit mailing list
Linux-audit@redhat.com
https://www.redhat.com/mailman/listinfo/linux-audit

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: log rendering in real time in audit-viewer
       [not found]   ` <192757425.240881.1427723539724.JavaMail.zimbra@itsirius.su>
@ 2015-03-30 13:52     ` Xeniya Muratova
  0 siblings, 0 replies; 7+ messages in thread
From: Xeniya Muratova @ 2015-03-30 13:52 UTC (permalink / raw)
  To: mitr; +Cc: linux-audit

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@redhat.com>
Кому: "Xeniya Muratova" <muratova@itsirius.su>
Копия: "linux-audit" <linux-audit@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



--
Linux-audit mailing list
Linux-audit@redhat.com
https://www.redhat.com/mailman/listinfo/linux-audit

^ permalink raw reply	[flat|nested] 7+ messages in thread

* Re: log rendering in real time in audit-viewer
@ 2015-05-27 15:26 Arthym Krivo
  0 siblings, 0 replies; 7+ messages in thread
From: Arthym Krivo @ 2015-05-27 15:26 UTC (permalink / raw)
  To: mitr; +Cc: linux-audit


[-- Attachment #1.1: Type: text/plain, Size: 182 bytes --]

Hello,
I applied this patch [1] and it works well.
Don't understand why he stayed without attention?

-A.K.

[1] https://www.redhat.com/archives/linux-audit/2015-March/msg00018.html

[-- Attachment #1.2: Type: text/html, Size: 445 bytes --]

[-- Attachment #2: Type: text/plain, Size: 0 bytes --]



^ permalink raw reply	[flat|nested] 7+ messages in thread

end of thread, other threads:[~2015-05-27 15:26 UTC | newest]

Thread overview: 7+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
2015-03-04 13:42 log rendering in real time in audit-viewer Xeniya Muratova
2015-03-04 17:50 ` Miloslav Trmač
2015-03-05 14:36   ` Steve Grubb
     [not found]   ` <1384025748.230407.1427289658707.JavaMail.zimbra@itsirius.su>
2015-03-25 13:21     ` [PATCH] " Xeniya Muratova
     [not found]   ` <192757425.240881.1427723539724.JavaMail.zimbra@itsirius.su>
2015-03-30 13:52     ` Xeniya Muratova
2015-03-05 12:45 ` Pittigher, Raymond - Exelis
2015-05-27 15:26 Arthym Krivo

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.