diff --git a/VERSION b/VERSION index 81340c7..bbdeab6 100644 --- a/VERSION +++ b/VERSION @@ -1 +1 @@ -0.0.4 +0.0.5 diff --git a/char_draw/graph.py b/char_draw/graph.py index 27d9ff5..502fff5 100644 --- a/char_draw/graph.py +++ b/char_draw/graph.py @@ -548,12 +548,16 @@ def get_bbox(self): idx = 0 points = [] for c in data_table.ColumnIterator(column): - y_value = c.get_float_value() - x_value = x_values[idx][0] - scaled_x = (x_value-x_min)*x_scale - scaled_y = (y_value-y_min)*y_scale - points.append((x+scaled_x,y-scaled_y)) - idx += 1 + y_value = c.get_float_value() + if idx < len(x_values): + x_value = x_values[idx][0] + scaled_x = (x_value-x_min)*x_scale + scaled_y = (y_value-y_min)*y_scale + points.append((x+scaled_x,y-scaled_y)) + idx += 1 + else: + break + if self.area: p1 = (points[-1][0],y) if p1 not in points: diff --git a/config b/config index 7ca9952..d75e3c5 100644 --- a/config +++ b/config @@ -23,13 +23,13 @@ ], "log_lookback" : [ 1, 0, 0 ] }, + {"name": "remote_syslog", "type":"RemoteDataTable", "refresh_minutes":1, "ssh_spec":"ssh://james@james-server:22002", + "table_def": {"name": "syslog", "type": "SyslogDataTable", "refresh_minutes": 1}}, {"name": "testdata", "type":"ODBCDataTable", "refresh_minutes":1, "sql_spec":"odbc://james@localhost/myodbc8w/james:5432", "sql_query":"select byhour.hour,sum(case byhour.type when 'error' then 1 else 0 end) as errors,sum(case byhour.type when 'warning' then 1 else 0 end) as warnings, sum(case byhour.type when 'info' then 1 else 0 end) as infos from ( select extract( hour from log_time) as hour, type from testdata ) as byhour group by byhour.hour order by byhour.hour;", "sql_map": [ [ "hour", "Hour"],[ "errors","Number of Errors" ],[ "warnings", "Number of Warnings" ], ["infos","Number of Info Messages"]] }, - {"name": "remote_syslog", "type":"RemoteDataTable", "refresh_minutes":1, "ssh_spec":"ssh://james@james-server:22002", - "table_def": {"name": "syslog", "type": "SyslogDataTable", "refresh_minutes": 1}}, {"name": "syslog", "type": "SyslogDataTable", "refresh_minutes": 1}, {"name": "procdata", "type": "ProcDataTable", "refresh_minutes": 1}, { @@ -333,7 +333,7 @@ "panels": [ { "graphs": [ - { "title" :"Remote Syslog", "type": "LineGraph", "table": "remote_syslog", "xseries": "Time Stamps", "yseries": ["Messages by Time","Errors by Time","Warnings by Time"], "yunit":"Number of Messages"} + { "title" :"Database Summary", "type": "LineGraph", "table": "testdata", "xseries": "Hour", "yseries": ["Number of Errors","Number of Warnings","Number of Info Messages"], "yunit":"Number of Messages"} ] } ] @@ -342,11 +342,11 @@ "panels": [ { "graphs": [ - { "title" :"Database Summary", "type": "LineGraph", "table": "testdata", "xseries": "Hour", "yseries": ["Number of Errors","Number of Warnings","Number of Info Messages"], "yunit":"Number of Messages"} + { "title" :"Remote Syslog", "type": "LineGraph", "table": "remote_syslog", "xseries": "Time Stamps", "yseries": ["Messages by Time","Errors by Time","Warnings by Time"], "yunit":"Number of Messages"} ] } ] - }, + }, { "panels": [ { diff --git a/dashboard/dashboard.py b/dashboard/dashboard.py index f7b8ab9..628f0ff 100644 --- a/dashboard/dashboard.py +++ b/dashboard/dashboard.py @@ -394,7 +394,7 @@ def main( self ): """ main input and redraw loop for the dashboard """ self.window.nodelay(1) self.window.notimeout(0) - self.window.timeout(0) + self.window.timeout(1000) self.window.keypad(1) curses.curs_set(0) curses.raw() diff --git a/dashboard/version.py b/dashboard/version.py index 156d6f9..eead319 100644 --- a/dashboard/version.py +++ b/dashboard/version.py @@ -1 +1 @@ -__version__ = '0.0.4' +__version__ = '0.0.5' diff --git a/data_sources/data_table.py b/data_sources/data_table.py index 5e49d71..06be9df 100644 --- a/data_sources/data_table.py +++ b/data_sources/data_table.py @@ -346,7 +346,7 @@ def perform_refresh( self ): if time.time() - start_time >= self.refresh_minutes*60.0: self.refresh() start_time = time.time() - time.sleep(0) + time.sleep(1) def stop_refresh( self ): """ Stop the background refresh thread """ diff --git a/data_sources/remote_data.py b/data_sources/remote_data.py index 79ef1a2..6e5504c 100644 --- a/data_sources/remote_data.py +++ b/data_sources/remote_data.py @@ -65,7 +65,7 @@ def get_stdout_line( self ): with self.reader_lock: if len(self.stdout_lines): return self.stdout_lines.pop(0) - time.sleep(0) + time.sleep(1) @sync_connection def get_stderr_line( self ): @@ -74,7 +74,7 @@ def get_stderr_line( self ): with self.reader_lock: if len(self.stderr_lines): return self.stderr_lines.pop(0) - time.sleep(0) + time.sleep(1) @sync_connection def open( self, client ): diff --git a/docs/char_draw/graph.m.html b/docs/char_draw/graph.m.html index d63906c..7433dcc 100644 --- a/docs/char_draw/graph.m.html +++ b/docs/char_draw/graph.m.html @@ -2189,12 +2189,16 @@

char_draw.graph module

idx = 0 points = [] for c in data_table.ColumnIterator(column): - y_value = c.get_float_value() - x_value = x_values[idx][0] - scaled_x = (x_value-x_min)*x_scale - scaled_y = (y_value-y_min)*y_scale - points.append((x+scaled_x,y-scaled_y)) - idx += 1 + y_value = c.get_float_value() + if idx < len(x_values): + x_value = x_values[idx][0] + scaled_x = (x_value-x_min)*x_scale + scaled_y = (y_value-y_min)*y_scale + points.append((x+scaled_x,y-scaled_y)) + idx += 1 + else: + break + if self.area: p1 = (points[-1][0],y) if p1 not in points: @@ -7305,12 +7309,16 @@

Instance variables

idx = 0 points = [] for c in data_table.ColumnIterator(column): - y_value = c.get_float_value() - x_value = x_values[idx][0] - scaled_x = (x_value-x_min)*x_scale - scaled_y = (y_value-y_min)*y_scale - points.append((x+scaled_x,y-scaled_y)) - idx += 1 + y_value = c.get_float_value() + if idx < len(x_values): + x_value = x_values[idx][0] + scaled_x = (x_value-x_min)*x_scale + scaled_y = (y_value-y_min)*y_scale + points.append((x+scaled_x,y-scaled_y)) + idx += 1 + else: + break + if self.area: p1 = (points[-1][0],y) if p1 not in points: @@ -7446,12 +7454,16 @@

Static methods

idx = 0 points = [] for c in data_table.ColumnIterator(column): - y_value = c.get_float_value() - x_value = x_values[idx][0] - scaled_x = (x_value-x_min)*x_scale - scaled_y = (y_value-y_min)*y_scale - points.append((x+scaled_x,y-scaled_y)) - idx += 1 + y_value = c.get_float_value() + if idx < len(x_values): + x_value = x_values[idx][0] + scaled_x = (x_value-x_min)*x_scale + scaled_y = (y_value-y_min)*y_scale + points.append((x+scaled_x,y-scaled_y)) + idx += 1 + else: + break + if self.area: p1 = (points[-1][0],y) if p1 not in points: diff --git a/docs/dashboard/dashboard.m.html b/docs/dashboard/dashboard.m.html index c4071ab..35e8170 100644 --- a/docs/dashboard/dashboard.m.html +++ b/docs/dashboard/dashboard.m.html @@ -1527,7 +1527,7 @@

dashboard.dashboard module

""" main input and redraw loop for the dashboard """ self.window.nodelay(1) self.window.notimeout(0) - self.window.timeout(0) + self.window.timeout(1000) self.window.keypad(1) curses.curs_set(0) curses.raw() @@ -1860,7 +1860,7 @@

Classes

""" main input and redraw loop for the dashboard """ self.window.nodelay(1) self.window.notimeout(0) - self.window.timeout(0) + self.window.timeout(1000) self.window.keypad(1) curses.curs_set(0) curses.raw() @@ -2219,7 +2219,7 @@

Static methods

""" main input and redraw loop for the dashboard """ self.window.nodelay(1) self.window.notimeout(0) - self.window.timeout(0) + self.window.timeout(1000) self.window.keypad(1) curses.curs_set(0) curses.raw() diff --git a/docs/dashboard/version.m.html b/docs/dashboard/version.m.html index 41a61f7..2c73465 100644 --- a/docs/dashboard/version.m.html +++ b/docs/dashboard/version.m.html @@ -1059,7 +1059,7 @@

dashboard.version module

-
__version__ = '0.0.4'
+    
__version__ = '0.0.5'
 
diff --git a/docs/data_sources/csv_data.m.html b/docs/data_sources/csv_data.m.html new file mode 100644 index 0000000..82990a5 --- /dev/null +++ b/docs/data_sources/csv_data.m.html @@ -0,0 +1,1889 @@ + + + + + + data_sources.csv_data API documentation + + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

data_sources.csv_data module

+

module that reads a csv file from disk forms the result into a data table based on a column mapping

+ + +
+
# Copyright 2020 James P Goodwin data table package to manage sparse columnar data
+""" module that reads a csv file from disk forms the result into a data table based on a column mapping """
+import locale
+locale.setlocale(locale.LC_ALL,'')
+import sys
+import os
+import glob
+import gzip
+import re
+import keyring
+from datetime import datetime,timedelta
+from data_sources.data_table import DataTable,Column,Cell,blank_type,string_type,float_type,int_type,date_type,format_string,format_float,format_date,format_int,synchronized,from_csv
+
+
+class CSVDataTable( DataTable ):
+    """ class that collects data from a CSV file on disk and extracts columns based on a column map of  the form [[CSV_column_name, DataTable_column_name,DataTable_type (one of _string,_int,_float,_date )],...] """
+    def __init__(self, refresh_minutes=1, csv_spec = None, csv_map= None, csv_name= None ):
+        """ Initialize the CSVDataTable object from the file named in csv_spec and extract the columns in the provided csv_map, name the table based on the name provided or extracted from the CSV """
+        self.csv_spec = csv_spec
+        self.csv_map = csv_map
+        self.csv_name = csv_name
+        DataTable.__init__(self,None,(csv_name if csv_name else "CSVDataTable"),refresh_minutes)
+        self.refresh()
+
+    @synchronized
+    def refresh( self ):
+        """ refresh the table by opening the csv file and loading it into a table """
+        dt = from_csv(open(self.csv_spec,"r"),self.name,self.csv_map)
+        if dt:
+            rows,cols = dt.get_bounds()
+            for idx in range(cols):
+                self.replace_column(idx,dt.get_column(idx))
+
+            if dt.get_name():
+                self.name = dt.get_name()
+
+            self.changed()
+            DataTable.refresh(self)
+
+ +
+ +
+ +
+

Module variables

+
+

var blank_type

+ + +
+
+ +
+
+

var date_type

+ + +
+
+ +
+
+

var float_type

+ + +
+
+ +
+
+

var int_type

+ + +
+
+ +
+
+

var string_type

+ + +
+
+ +
+ + +

Classes

+ +
+

class CSVDataTable

+ + +

class that collects data from a CSV file on disk and extracts columns based on a column map of the form [[CSV_column_name, DataTable_column_name,DataTable_type (one of _string,_int,_float,_date )],...]

+
+ +
+
class CSVDataTable( DataTable ):
+    """ class that collects data from a CSV file on disk and extracts columns based on a column map of  the form [[CSV_column_name, DataTable_column_name,DataTable_type (one of _string,_int,_float,_date )],...] """
+    def __init__(self, refresh_minutes=1, csv_spec = None, csv_map= None, csv_name= None ):
+        """ Initialize the CSVDataTable object from the file named in csv_spec and extract the columns in the provided csv_map, name the table based on the name provided or extracted from the CSV """
+        self.csv_spec = csv_spec
+        self.csv_map = csv_map
+        self.csv_name = csv_name
+        DataTable.__init__(self,None,(csv_name if csv_name else "CSVDataTable"),refresh_minutes)
+        self.refresh()
+
+    @synchronized
+    def refresh( self ):
+        """ refresh the table by opening the csv file and loading it into a table """
+        dt = from_csv(open(self.csv_spec,"r"),self.name,self.csv_map)
+        if dt:
+            rows,cols = dt.get_bounds()
+            for idx in range(cols):
+                self.replace_column(idx,dt.get_column(idx))
+
+            if dt.get_name():
+                self.name = dt.get_name()
+
+            self.changed()
+            DataTable.refresh(self)
+
+ +
+
+ + +
+

Ancestors (in MRO)

+
    +
  • CSVDataTable
  • +
  • data_sources.data_table.DataTable
  • +
  • builtins.object
  • +
+

Static methods

+ +
+
+

def __init__(

self, refresh_minutes=1, csv_spec=None, csv_map=None, csv_name=None)

+
+ + + + +

Initialize the CSVDataTable object from the file named in csv_spec and extract the columns in the provided csv_map, name the table based on the name provided or extracted from the CSV

+
+ +
+
def __init__(self, refresh_minutes=1, csv_spec = None, csv_map= None, csv_name= None ):
+    """ Initialize the CSVDataTable object from the file named in csv_spec and extract the columns in the provided csv_map, name the table based on the name provided or extracted from the CSV """
+    self.csv_spec = csv_spec
+    self.csv_map = csv_map
+    self.csv_name = csv_name
+    DataTable.__init__(self,None,(csv_name if csv_name else "CSVDataTable"),refresh_minutes)
+    self.refresh()
+
+ +
+
+ +
+ + +
+
+

def acquire_refresh_lock(

self)

+
+ + + + +

acquire the refresh lock before reading/writing the table state

+
+ +
+
def acquire_refresh_lock(self):
+    """ acquire the refresh lock before reading/writing the table state """
+    self.refresh_lock.acquire()
+
+ +
+
+ +
+ + +
+
+

def add_column(

self, *args, **kwargs)

+
+ + + + +
+ +
+
@synchronized
+def add_column(self,column):
+    idx = len(self.columns)
+    column.set_idx(idx)
+    if not column.get_name():
+        column.set_name("%s_%d"%(self.name,idx))
+    self.columns.append(column)
+    self.cnames[column.get_name()] = column
+    column.set_table(self)
+
+ +
+
+ +
+ + +
+
+

def changed(

self)

+
+ + + + +

notify listeners that this table has been changed

+
+ +
+
def changed(self):
+    """ notify listeners that this table has been changed """
+    for f in self.listeners:
+        f(self)
+
+ +
+
+ +
+ + +
+
+

def get(

self, *args, **kwargs)

+
+ + + + +
+ +
+
@synchronized
+def get(self, row, reference ):
+    return self.columns[self.map_column(reference)].get(row)
+
+ +
+
+ +
+ + +
+
+

def get_bounds(

self, *args, **kwargs)

+
+ + + + +

return a tuple (rows,cols) where rows is the maximum number of rows and cols is the maximum number of cols

+
+ +
+
@synchronized
+def get_bounds(self):
+    """ return a tuple (rows,cols) where rows is the maximum number of rows and cols is the maximum number of cols """
+    cols = len(self.columns)
+    rows = -1
+    for c in self.columns:
+        size = c.size()
+        if rows < 0 or size > rows:
+            rows = size
+    return (rows,cols)
+
+ +
+
+ +
+ + +
+
+

def get_column(

self, *args, **kwargs)

+
+ + + + +
+ +
+
@synchronized
+def get_column(self, reference):
+    return self.columns[self.map_column(reference)]
+
+ +
+
+ +
+ + +
+
+

def get_columns(

self, *args, **kwargs)

+
+ + + + +

return the list of columns

+
+ +
+
@synchronized
+def get_columns(self):
+    """ return the list of columns """
+    return self.columns
+
+ +
+
+ +
+ + +
+
+

def get_name(

self)

+
+ + + + +

return the name of the table

+
+ +
+
def get_name(self):
+    """ return the name of the table """
+    return self.name
+
+ +
+
+ +
+ + +
+
+

def get_names(

self, *args, **kwargs)

+
+ + + + +

return a list of the names of the columns in order

+
+ +
+
@synchronized
+def get_names(self):
+    """ return a list of the names of the columns in order"""
+    return [c.get_name() for c in self.columns]
+
+ +
+
+ +
+ + +
+
+

def get_refresh_timestamp(

self)

+
+ + + + +

get the time that the table was last refreshed

+
+ +
+
def get_refresh_timestamp( self ):
+    """ get the time that the table was last refreshed """
+    return self.refresh_timestamp
+
+ +
+
+ +
+ + +
+
+

def has_column(

self, *args, **kwargs)

+
+ + + + +
+ +
+
@synchronized
+def has_column(self, reference ):
+    if type(reference) == str or type(reference) == str:
+        return reference in self.cnames
+    elif type(reference) == int:
+        return idx < len(self.columns)
+    else:
+        return False
+
+ +
+
+ +
+ + +
+
+

def insert_column(

self, *args, **kwargs)

+
+ + + + +
+ +
+
@synchronized
+def insert_column(self,idx,column):
+    while idx > len(self.columns):
+        self.add_column(blank_column)
+    if idx == len(self.columns):
+        self.add_column(column)
+    else:
+        if not column.get_name():
+            column.set_name("%s_%d"%(self.name,idx))
+        self.columns.insert(idx,column)
+        self.cnames[column.get_name()] = column
+        column.set_table(self)
+        while idx < len(self.columns):
+            if column.get_name() == "%s_%d"%(self.name,idx-1):
+                column.set_name("%s_%d"%(self.name,idx))
+                self.cnames[column.get_name()] = column
+            self.columns[idx].set_idx(idx)
+            idx += 1
+
+ +
+
+ +
+ + +
+
+

def listen(

self, listen_func)

+
+ + + + +

register for notifications when a change event is raised on this table

+
+ +
+
def listen(self,listen_func):
+    """ register for notifications when a change event is raised on this table """
+    self.listeners.append(listen_func)
+
+ +
+
+ +
+ + +
+
+

def map_column(

self, *args, **kwargs)

+
+ + + + +
+ +
+
@synchronized
+def map_column(self, reference ):
+    if type(reference) == str or type(reference) == str:
+        return self.cnames[reference].get_idx()
+    elif type(reference) == int:
+        return reference
+    else:
+        raise TypeError("wrong type in mapping")
+
+ +
+
+ +
+ + +
+
+

def perform_refresh(

self)

+
+ + + + +

Thread worker that sleeps and refreshes the data on a schedule

+
+ +
+
def perform_refresh( self ):
+    """ Thread worker that sleeps and refreshes the data on a schedule """
+    start_time = time.time()
+    while not self.refresh_thread_stop:
+        if time.time() - start_time >= self.refresh_minutes*60.0:
+            self.refresh()
+            start_time = time.time()
+        time.sleep(1)
+
+ +
+
+ +
+ + +
+
+

def put(

self, *args, **kwargs)

+
+ + + + +
+ +
+
@synchronized
+def put(self, row, reference, value):
+    self.columns[self.map_column(reference)].put(row,value)
+
+ +
+
+ +
+ + +
+
+

def refresh(

self, *args, **kwargs)

+
+ + + + +

refresh the table by opening the csv file and loading it into a table

+
+ +
+
@synchronized
+def refresh( self ):
+    """ refresh the table by opening the csv file and loading it into a table """
+    dt = from_csv(open(self.csv_spec,"r"),self.name,self.csv_map)
+    if dt:
+        rows,cols = dt.get_bounds()
+        for idx in range(cols):
+            self.replace_column(idx,dt.get_column(idx))
+        if dt.get_name():
+            self.name = dt.get_name()
+        self.changed()
+        DataTable.refresh(self)
+
+ +
+
+ +
+ + +
+
+

def release_refresh_lock(

self)

+
+ + + + +

release the refresh lock after reading/writing the table state

+
+ +
+
def release_refresh_lock(self):
+    """ release the refresh lock after reading/writing the table state """
+    self.refresh_lock.release()
+
+ +
+
+ +
+ + +
+
+

def replace_column(

self, *args, **kwargs)

+
+ + + + +
+ +
+
@synchronized
+def replace_column(self,idx,column):
+    column.set_idx(idx)
+    if not column.get_name():
+        column.set_name("%s_%d"%(self.name,idx))
+    if idx == len(self.columns):
+        self.columns.append(column)
+    else:
+        self.columns[idx] = column
+    self.cnames[column.get_name()] = column
+    column.set_table(self)
+
+ +
+
+ +
+ + +
+
+

def start_refresh(

self)

+
+ + + + +

Start the background refresh thread

+
+ +
+
def start_refresh( self ):
+    """ Start the background refresh thread """
+    self.stop_refresh()
+    self.refresh_thread = threading.Thread(target=self.perform_refresh)
+    self.refresh_thread.start()
+
+ +
+
+ +
+ + +
+
+

def stop_refresh(

self)

+
+ + + + +

Stop the background refresh thread

+
+ +
+
def stop_refresh( self ):
+    """ Stop the background refresh thread """
+    self.refresh_thread_stop = True
+    if self.refresh_thread and self.refresh_thread.is_alive():
+        self.refresh_thread.join()
+    self.refresh_thread = None
+    self.refresh_thread_stop = False
+
+ +
+
+ +
+ + +
+
+

def unlisten(

self, listen_func)

+
+ + + + +

unregister for notifications when a change event is raised on this table

+
+ +
+
def unlisten(self,listen_func):
+    """ unregister for notifications when a change event is raised on this table """
+    self.listeners.remove(listen_func)
+
+ +
+
+ +
+ +

Instance variables

+
+

var csv_map

+ + + + +
+
+ +
+
+

var csv_name

+ + + + +
+
+ +
+
+

var csv_spec

+ + + + +
+
+ +
+
+
+ +
+ +
+
+ +
+ + diff --git a/docs/data_sources/data_table.m.html b/docs/data_sources/data_table.m.html index 806ad40..e12b165 100644 --- a/docs/data_sources/data_table.m.html +++ b/docs/data_sources/data_table.m.html @@ -4,7 +4,7 @@ data_sources.data_table API documentation - + @@ -1162,15 +1162,16 @@

Index

data_sources.data_table module

-

module that implement a data table package to manage sparse columnar data window

+

module that implement a data table package to manage sparse columnar data window and refresh them automatically

# Copyright 2017 James P Goodwin data table package to manage sparse columnar data
-""" module that implement a data table package to manage sparse columnar data window """
+""" module that implement a data table package to manage sparse columnar data window and refresh them automatically """
 import sys
 import os
 from datetime import datetime
+from dateutil import parser
 import threading
 import time
 import csv
@@ -1241,7 +1242,7 @@ 

data_sources.data_table module

def set_format(self,format): self.format = format -blank_cell = Cell(blank_type,None,lambda x: "") +blank_cell = Cell(blank_type,"",lambda x: "") class ColumnIterator(object): def __init__(self,column): @@ -1277,7 +1278,7 @@

data_sources.data_table module

del self.values[idx] def ins(self,idx,value): - if idx < len(self.values): + if idx <= len(self.values): self.values.insert(idx,value) else: self.put(idx,value) @@ -1291,9 +1292,17 @@

data_sources.data_table module

def put(self,idx,value): """ put a Cell value at index idx in column """ - while idx >= len(self.values): - self.values.append(blank_cell) - self.values[idx] = value + if idx < len(self.values): + self.values[idx] = value + return + if idx == len(self.values): + self.values.append(value) + return + elif idx > len(self.values): + while idx >= len(self.values): + self.values.append(blank_cell) + self.values[idx] = value + return def get_name(self): return self.name @@ -1430,7 +1439,10 @@

data_sources.data_table module

elif drv and dtt == int_type: cc = Cell(int_type,int(drv),format_float) elif drv and dtt == date_type: - cc = Cell(date_type,datetime.fromtimestamp(float(drv)),format_date) + try: + cc = Cell(date_type,datetime.fromtimestamp(float(drv)),format_date) + except: + cc = Cell(date_type,parser.parse(drv),format_date) elif not drv or dtt == blank_type: cc = blank_cell dtcc.put(dtcc.size(),cc) @@ -1502,7 +1514,7 @@

data_sources.data_table module

if time.time() - start_time >= self.refresh_minutes*60.0: self.refresh() start_time = time.time() - time.sleep(0) + time.sleep(1) def stop_refresh( self ): """ Stop the background refresh thread """ @@ -1843,7 +1855,10 @@

Functions

elif drv and dtt == int_type: cc = Cell(int_type,int(drv),format_float) elif drv and dtt == date_type: - cc = Cell(date_type,datetime.fromtimestamp(float(drv)),format_date) + try: + cc = Cell(date_type,datetime.fromtimestamp(float(drv)),format_date) + except: + cc = Cell(date_type,parser.parse(drv),format_date) elif not drv or dtt == blank_type: cc = blank_cell dtcc.put(dtcc.size(),cc) @@ -2276,7 +2291,7 @@

Instance variables

del self.values[idx] def ins(self,idx,value): - if idx < len(self.values): + if idx <= len(self.values): self.values.insert(idx,value) else: self.put(idx,value) @@ -2290,9 +2305,17 @@

Instance variables

def put(self,idx,value): """ put a Cell value at index idx in column """ - while idx >= len(self.values): - self.values.append(blank_cell) - self.values[idx] = value + if idx < len(self.values): + self.values[idx] = value + return + if idx == len(self.values): + self.values.append(value) + return + elif idx > len(self.values): + while idx >= len(self.values): + self.values.append(blank_cell) + self.values[idx] = value + return def get_name(self): return self.name @@ -2474,7 +2497,7 @@

Static methods

def ins(self,idx,value):
-    if idx < len(self.values):
+    if idx <= len(self.values):
         self.values.insert(idx,value)
     else:
         self.put(idx,value)
@@ -2500,9 +2523,17 @@ 

Static methods

def put(self,idx,value):
     """ put a Cell value at index idx in column """
-    while idx >= len(self.values):
-        self.values.append(blank_cell)
-    self.values[idx] = value
+    if idx < len(self.values):
+        self.values[idx] = value
+        return
+    if idx == len(self.values):
+        self.values.append(value)
+        return
+    elif idx > len(self.values):
+        while idx >= len(self.values):
+            self.values.append(blank_cell)
+        self.values[idx] = value
+        return
 
@@ -2781,7 +2812,7 @@

Instance variables

if time.time() - start_time >= self.refresh_minutes*60.0: self.refresh() start_time = time.time() - time.sleep(0) + time.sleep(1) def stop_refresh( self ): """ Stop the background refresh thread """ @@ -3328,7 +3359,7 @@

Static methods

if time.time() - start_time >= self.refresh_minutes*60.0: self.refresh() start_time = time.time() - time.sleep(0) + time.sleep(1)
diff --git a/docs/data_sources/elastic_data.m.html b/docs/data_sources/elastic_data.m.html index 83d7103..fb3ff83 100644 --- a/docs/data_sources/elastic_data.m.html +++ b/docs/data_sources/elastic_data.m.html @@ -4,7 +4,7 @@ data_sources.elastic_data API documentation - + @@ -1103,12 +1103,12 @@

Index

data_sources.elastic_data module

-

module that aggregates data from the syslog and provides a set of data tables

+

module that does an Elasticsearch query and creates a table of data based on the response

# Copyright 2020 James P Goodwin data table package to manage sparse columnar data
-""" module that aggregates data from the syslog and provides a set of data tables """
+""" module that does an Elasticsearch query and creates a table of data based on the response """
 import locale
 locale.setlocale(locale.LC_ALL,'')
 import sys
@@ -1781,7 +1781,7 @@ 

Static methods

if time.time() - start_time >= self.refresh_minutes*60.0: self.refresh() start_time = time.time() - time.sleep(0) + time.sleep(1)
diff --git a/docs/data_sources/json_data.m.html b/docs/data_sources/json_data.m.html new file mode 100644 index 0000000..2a77e3a --- /dev/null +++ b/docs/data_sources/json_data.m.html @@ -0,0 +1,1867 @@ + + + + + + data_sources.json_data API documentation + + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

data_sources.json_data module

+

module that reads a JSON file from disk forms the result into a data table

+ + +
+
# Copyright 2020 James P Goodwin data table package to manage sparse columnar data
+""" module that reads a JSON file from disk forms the result into a data table """
+import locale
+locale.setlocale(locale.LC_ALL,'')
+import sys
+import os
+import glob
+import gzip
+import re
+import keyring
+from datetime import datetime,timedelta
+from data_sources.data_table import DataTable,Column,Cell,blank_type,string_type,float_type,int_type,date_type,format_string,format_float,format_date,format_int,synchronized,from_json
+
+class JSONDataTable( DataTable ):
+    """ class that collects data from a JSON file on disk of the form written by data_sources.data_table.to_json() and updates this table with it """
+    def __init__(self, json_spec = None ):
+        """ Initialize the JSONDataTable object from the file named in json_spec, refresh minutes will come from the loaded json """
+        self.json_spec = json_spec
+        DataTable.__init__(self,None,"JSONDataTable",120)
+        self.refresh()
+
+    @synchronized
+    def refresh( self ):
+        """ refresh the table by opening the JSON file and loading it into a table """
+        dt = from_json(open(self.json_spec,"r"))
+        if dt:
+            rows,cols = dt.get_bounds()
+            for idx in range(cols):
+                self.replace_column(idx,dt.get_column(idx))
+
+            if dt.get_name():
+                self.name = dt.get_name()
+
+            self.refresh_minutes = dt.refresh_minutes
+
+            self.changed()
+            DataTable.refresh(self)
+
+ +
+ +
+ +
+

Module variables

+
+

var blank_type

+ + +
+
+ +
+
+

var date_type

+ + +
+
+ +
+
+

var float_type

+ + +
+
+ +
+
+

var int_type

+ + +
+
+ +
+
+

var string_type

+ + +
+
+ +
+ + +

Classes

+ +
+

class JSONDataTable

+ + +

class that collects data from a JSON file on disk of the form written by data_sources.data_table.to_json() and updates this table with it

+
+ +
+
class JSONDataTable( DataTable ):
+    """ class that collects data from a JSON file on disk of the form written by data_sources.data_table.to_json() and updates this table with it """
+    def __init__(self, json_spec = None ):
+        """ Initialize the JSONDataTable object from the file named in json_spec, refresh minutes will come from the loaded json """
+        self.json_spec = json_spec
+        DataTable.__init__(self,None,"JSONDataTable",120)
+        self.refresh()
+
+    @synchronized
+    def refresh( self ):
+        """ refresh the table by opening the JSON file and loading it into a table """
+        dt = from_json(open(self.json_spec,"r"))
+        if dt:
+            rows,cols = dt.get_bounds()
+            for idx in range(cols):
+                self.replace_column(idx,dt.get_column(idx))
+
+            if dt.get_name():
+                self.name = dt.get_name()
+
+            self.refresh_minutes = dt.refresh_minutes
+
+            self.changed()
+            DataTable.refresh(self)
+
+ +
+
+ + +
+

Ancestors (in MRO)

+
    +
  • JSONDataTable
  • +
  • data_sources.data_table.DataTable
  • +
  • builtins.object
  • +
+

Static methods

+ +
+
+

def __init__(

self, json_spec=None)

+
+ + + + +

Initialize the JSONDataTable object from the file named in json_spec, refresh minutes will come from the loaded json

+
+ +
+
def __init__(self, json_spec = None ):
+    """ Initialize the JSONDataTable object from the file named in json_spec, refresh minutes will come from the loaded json """
+    self.json_spec = json_spec
+    DataTable.__init__(self,None,"JSONDataTable",120)
+    self.refresh()
+
+ +
+
+ +
+ + +
+
+

def acquire_refresh_lock(

self)

+
+ + + + +

acquire the refresh lock before reading/writing the table state

+
+ +
+
def acquire_refresh_lock(self):
+    """ acquire the refresh lock before reading/writing the table state """
+    self.refresh_lock.acquire()
+
+ +
+
+ +
+ + +
+
+

def add_column(

self, *args, **kwargs)

+
+ + + + +
+ +
+
@synchronized
+def add_column(self,column):
+    idx = len(self.columns)
+    column.set_idx(idx)
+    if not column.get_name():
+        column.set_name("%s_%d"%(self.name,idx))
+    self.columns.append(column)
+    self.cnames[column.get_name()] = column
+    column.set_table(self)
+
+ +
+
+ +
+ + +
+
+

def changed(

self)

+
+ + + + +

notify listeners that this table has been changed

+
+ +
+
def changed(self):
+    """ notify listeners that this table has been changed """
+    for f in self.listeners:
+        f(self)
+
+ +
+
+ +
+ + +
+
+

def get(

self, *args, **kwargs)

+
+ + + + +
+ +
+
@synchronized
+def get(self, row, reference ):
+    return self.columns[self.map_column(reference)].get(row)
+
+ +
+
+ +
+ + +
+
+

def get_bounds(

self, *args, **kwargs)

+
+ + + + +

return a tuple (rows,cols) where rows is the maximum number of rows and cols is the maximum number of cols

+
+ +
+
@synchronized
+def get_bounds(self):
+    """ return a tuple (rows,cols) where rows is the maximum number of rows and cols is the maximum number of cols """
+    cols = len(self.columns)
+    rows = -1
+    for c in self.columns:
+        size = c.size()
+        if rows < 0 or size > rows:
+            rows = size
+    return (rows,cols)
+
+ +
+
+ +
+ + +
+
+

def get_column(

self, *args, **kwargs)

+
+ + + + +
+ +
+
@synchronized
+def get_column(self, reference):
+    return self.columns[self.map_column(reference)]
+
+ +
+
+ +
+ + +
+
+

def get_columns(

self, *args, **kwargs)

+
+ + + + +

return the list of columns

+
+ +
+
@synchronized
+def get_columns(self):
+    """ return the list of columns """
+    return self.columns
+
+ +
+
+ +
+ + +
+
+

def get_name(

self)

+
+ + + + +

return the name of the table

+
+ +
+
def get_name(self):
+    """ return the name of the table """
+    return self.name
+
+ +
+
+ +
+ + +
+
+

def get_names(

self, *args, **kwargs)

+
+ + + + +

return a list of the names of the columns in order

+
+ +
+
@synchronized
+def get_names(self):
+    """ return a list of the names of the columns in order"""
+    return [c.get_name() for c in self.columns]
+
+ +
+
+ +
+ + +
+
+

def get_refresh_timestamp(

self)

+
+ + + + +

get the time that the table was last refreshed

+
+ +
+
def get_refresh_timestamp( self ):
+    """ get the time that the table was last refreshed """
+    return self.refresh_timestamp
+
+ +
+
+ +
+ + +
+
+

def has_column(

self, *args, **kwargs)

+
+ + + + +
+ +
+
@synchronized
+def has_column(self, reference ):
+    if type(reference) == str or type(reference) == str:
+        return reference in self.cnames
+    elif type(reference) == int:
+        return idx < len(self.columns)
+    else:
+        return False
+
+ +
+
+ +
+ + +
+
+

def insert_column(

self, *args, **kwargs)

+
+ + + + +
+ +
+
@synchronized
+def insert_column(self,idx,column):
+    while idx > len(self.columns):
+        self.add_column(blank_column)
+    if idx == len(self.columns):
+        self.add_column(column)
+    else:
+        if not column.get_name():
+            column.set_name("%s_%d"%(self.name,idx))
+        self.columns.insert(idx,column)
+        self.cnames[column.get_name()] = column
+        column.set_table(self)
+        while idx < len(self.columns):
+            if column.get_name() == "%s_%d"%(self.name,idx-1):
+                column.set_name("%s_%d"%(self.name,idx))
+                self.cnames[column.get_name()] = column
+            self.columns[idx].set_idx(idx)
+            idx += 1
+
+ +
+
+ +
+ + +
+
+

def listen(

self, listen_func)

+
+ + + + +

register for notifications when a change event is raised on this table

+
+ +
+
def listen(self,listen_func):
+    """ register for notifications when a change event is raised on this table """
+    self.listeners.append(listen_func)
+
+ +
+
+ +
+ + +
+
+

def map_column(

self, *args, **kwargs)

+
+ + + + +
+ +
+
@synchronized
+def map_column(self, reference ):
+    if type(reference) == str or type(reference) == str:
+        return self.cnames[reference].get_idx()
+    elif type(reference) == int:
+        return reference
+    else:
+        raise TypeError("wrong type in mapping")
+
+ +
+
+ +
+ + +
+
+

def perform_refresh(

self)

+
+ + + + +

Thread worker that sleeps and refreshes the data on a schedule

+
+ +
+
def perform_refresh( self ):
+    """ Thread worker that sleeps and refreshes the data on a schedule """
+    start_time = time.time()
+    while not self.refresh_thread_stop:
+        if time.time() - start_time >= self.refresh_minutes*60.0:
+            self.refresh()
+            start_time = time.time()
+        time.sleep(1)
+
+ +
+
+ +
+ + +
+
+

def put(

self, *args, **kwargs)

+
+ + + + +
+ +
+
@synchronized
+def put(self, row, reference, value):
+    self.columns[self.map_column(reference)].put(row,value)
+
+ +
+
+ +
+ + +
+
+

def refresh(

self, *args, **kwargs)

+
+ + + + +

refresh the table by opening the JSON file and loading it into a table

+
+ +
+
@synchronized
+def refresh( self ):
+    """ refresh the table by opening the JSON file and loading it into a table """
+    dt = from_json(open(self.json_spec,"r"))
+    if dt:
+        rows,cols = dt.get_bounds()
+        for idx in range(cols):
+            self.replace_column(idx,dt.get_column(idx))
+        if dt.get_name():
+            self.name = dt.get_name()
+        self.refresh_minutes = dt.refresh_minutes
+        self.changed()
+        DataTable.refresh(self)
+
+ +
+
+ +
+ + +
+
+

def release_refresh_lock(

self)

+
+ + + + +

release the refresh lock after reading/writing the table state

+
+ +
+
def release_refresh_lock(self):
+    """ release the refresh lock after reading/writing the table state """
+    self.refresh_lock.release()
+
+ +
+
+ +
+ + +
+
+

def replace_column(

self, *args, **kwargs)

+
+ + + + +
+ +
+
@synchronized
+def replace_column(self,idx,column):
+    column.set_idx(idx)
+    if not column.get_name():
+        column.set_name("%s_%d"%(self.name,idx))
+    if idx == len(self.columns):
+        self.columns.append(column)
+    else:
+        self.columns[idx] = column
+    self.cnames[column.get_name()] = column
+    column.set_table(self)
+
+ +
+
+ +
+ + +
+
+

def start_refresh(

self)

+
+ + + + +

Start the background refresh thread

+
+ +
+
def start_refresh( self ):
+    """ Start the background refresh thread """
+    self.stop_refresh()
+    self.refresh_thread = threading.Thread(target=self.perform_refresh)
+    self.refresh_thread.start()
+
+ +
+
+ +
+ + +
+
+

def stop_refresh(

self)

+
+ + + + +

Stop the background refresh thread

+
+ +
+
def stop_refresh( self ):
+    """ Stop the background refresh thread """
+    self.refresh_thread_stop = True
+    if self.refresh_thread and self.refresh_thread.is_alive():
+        self.refresh_thread.join()
+    self.refresh_thread = None
+    self.refresh_thread_stop = False
+
+ +
+
+ +
+ + +
+
+

def unlisten(

self, listen_func)

+
+ + + + +

unregister for notifications when a change event is raised on this table

+
+ +
+
def unlisten(self,listen_func):
+    """ unregister for notifications when a change event is raised on this table """
+    self.listeners.remove(listen_func)
+
+ +
+
+ +
+ +

Instance variables

+
+

var json_spec

+ + + + +
+
+ +
+
+
+ +
+ +
+
+ +
+ + diff --git a/docs/data_sources/logs_data.m.html b/docs/data_sources/logs_data.m.html new file mode 100644 index 0000000..903c9d4 --- /dev/null +++ b/docs/data_sources/logs_data.m.html @@ -0,0 +1,3037 @@ + + + + + + data_sources.logs_data API documentation + + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

data_sources.logs_data module

+

module that aggregates data from a log file and provides a set of data tables

+ + +
+
# Copyright 2020 James P Goodwin data table package to manage sparse columnar data
+""" module that aggregates data from a log file and provides a set of data tables """
+import locale
+locale.setlocale(locale.LC_ALL,'')
+import sys
+import os
+import glob
+import gzip
+import re
+import statistics
+from dateutil import parser
+from datetime import datetime,timedelta
+from data_sources.data_table import DataTable,Column,Cell,blank_type,string_type,float_type,int_type,date_type,format_string,format_float,format_date,format_int,synchronized
+
+format_map = { date_type : format_date, int_type : format_int, float_type : format_float, string_type : format_string }
+
+class ActionCell(Cell):
+    def __init__(self,type,value,format,action):
+        Cell.__init__(self,type,None,format)
+        self.action = action
+        self.values = []
+        self.put_value(value)
+
+    def default_value(self):
+        if self.type == date_type:
+            return datetime.min
+        elif self.type == int_type:
+            return 0
+        elif self.type == float_type:
+            return 0.0
+        elif self.type == string_type:
+            return ""
+
+    def put_value(self,value):
+        if value == None:
+            self.value = self.default_value()
+        else:
+            self.values.append(value)
+            try:
+                if self.action == "key":
+                    self.value = value
+                elif self.action == "avg":
+                    self.value = statistics.mean(self.values)
+                elif self.action == "mode":
+                    self.value = statistics.mode(self.values)
+                elif self.action == "median":
+                    self.value = statistics.median(self.values)
+                elif self.action == "min":
+                    self.value = min(self.values)
+                elif self.action == "max":
+                    self.value = max(self.values)
+                elif self.action == "sum":
+                    self.value = sum(self.values)
+                elif self.action.startswith("count("):
+                    regex = self.action.split("(")[1].split(")")[0]
+                    if self.value == None:
+                        self.value = self.default_value()
+                    if re.match(regex,str(value)):
+                        self.value += 1
+            except:
+                self.value = self.default_value()
+
+class Value():
+    """ structure for mapped values """
+    def __init__(self,column_name,type,action,value):
+        """ initialize the value structure with mapping information and value from log """
+        self.column_name = column_name
+        self.type = type
+        self.action = action
+        self.value = value
+
+    def get_value(self):
+        """ based on type return the value from the log """
+        if self.type == date_type:
+            try:
+                return datetime.fromtimestamp(float(self.value))
+            except:
+                return parser.parse(self.value)
+        elif self.type == int_type:
+            if self.action.startswith("count("):
+                return self.value
+            else:
+                return int(self.value)
+        elif self.type == float_type:
+            return float(self.value)
+        elif self.type == string_type:
+            return self.value
+        else:
+            return str(self.value)
+
+    def to_cell(self):
+        """ construct and return a cell based on type, action and value """
+        return ActionCell( self.type, self.get_value(), format_map[self.type], self.action )
+
+class LogDataTable( DataTable ):
+    """ class that collects a time based aggregation of data from the syslog into a data_table """
+    def __init__(self,log_glob=None,log_map=None,log_lookback=None,refresh_minutes=10):
+        """ Initialize the LogDataTable with a file glob pattern to collect the
+        matching logs on this machine, a timespan to aggregate for, aggregation
+        bucket in hours, a refresh interval for updating in minutes and a
+        log_map of the structure [{
+            "line_regex" : "python regex with groups to match columns",
+            "num_buckets" : "number of buckets for this key",
+            "bucket_size" : "size of a bucket",
+            "bucket_type" : "type of buckets",
+            "column_map" : [
+                [group_number 1..n,
+                "Column Name",
+                "type one of _int,_float,_string,_date",
+                "action one of key,avg,min,max,count(value),mode,median"],
+                ...]},...]
+        the key action is special and indicates that this is the bucket key for this type of line
+        log_lookback is of the form [ days, hours, minutes ] all must be specified """
+        self.log_glob = log_glob
+        self.log_map = log_map
+        self.log_lookback = log_lookback
+        self.file_map = {}
+
+        DataTable.__init__(self,None,
+            "LogDataTable: %s, %d minutes refresh"%(
+                self.log_glob,
+                refresh_minutes),
+                refresh_minutes)
+        self.refresh()
+
+    @synchronized
+    def refresh( self ):
+        """ refresh or rebuild tables """
+
+        def get_bucket( line_spec,value ):
+            if not self.has_column(value.column_name):
+                self.add_column(Column(name=value.column_name))
+            bc = self.get_column(value.column_name)
+            for idx in range(bc.size()):
+                if bc.get(idx).get_value() >= value.get_value():
+                    break
+            else:
+                idx = bc.size()
+            if idx < bc.size():
+                if line_spec["bucket_type"] == string_type:
+                    if bc.get(idx).get_value() != value.get_value():
+                        bc.ins(idx,Cell(line_spec["bucket_type"],value.get_value(),format_map[line_spec["bucket_type"]]))
+                return idx
+            elif idx == 0 and bc.size() > 0:
+                diff = bc.get(idx).get_value() - value.get_value()
+                if line_spec["bucket_type"] == date_type:
+                    while diff > timedelta(minutes=line_spec["bucket_size"]):
+                        new_bucket = bc.get(idx).get_value() - timedelta(minutes=line_spec["bucket_size"])
+                        bc.ins(idx,Cell(line_spec["bucket_type"],new_bucket,format_map[line_spec["bucket_type"]]))
+                        diff = bc.get(idx).get_value() - value.get_value()
+                    return idx
+                elif line_spec["bucket_type"] == string_type:
+                    bc.ins(idx,Cell(line_spec["bucket_type"],value.get_value(),format_map[line_spec["bucket_type"]]))
+                    return idx
+                else:
+                    while diff > line_spec["bucket_size"]:
+                        new_bucket = bc.get(idx).get_value() - line_spec["bucket_size"]
+                        bc.ins(idx,Cell(line_spec["bucket_type"],new_bucket,format_map[line_spec["bucket_type"]]))
+                        diff = bc.get(idx).get_value() - value.get_value()
+                    return idx
+            elif idx == bc.size():
+                if line_spec["bucket_type"] == string_type:
+                    bc.put(idx,Cell(line_spec["bucket_type"],value.get_value(),format_map[line_spec["bucket_type"]]))
+                    return idx
+                else:
+                    while True:
+                        if idx > 0:
+                            prev_bucket = bc.get(idx-1).get_value()
+                        else:
+                            prev_bucket = value.get_value()
+
+                        if line_spec["bucket_type"] == date_type:
+                            new_bucket = prev_bucket + timedelta(minutes=line_spec["bucket_size"])
+                        else:
+                            new_bucket = prev_bucket + line_spec["bucket_size"]
+
+                        bc.put(idx,Cell(line_spec["bucket_type"],new_bucket,format_map[line_spec["bucket_type"]]))
+                        if value.get_value() < new_bucket:
+                            return idx
+                        idx = bc.size()
+
+        def put_value( value, bidx ):
+            if not self.has_column(value.column_name):
+                self.add_column(Column(name=value.column_name))
+            cc = self.get_column(value.column_name)
+            if bidx < cc.size():
+                c = cc.get(bidx)
+                if c.type == blank_type:
+                    cc.put(bidx,value.to_cell())
+                else:
+                    cc.get(bidx).put_value(value.get_value())
+            else:
+                cc.put(bidx,value.to_cell())
+
+        def prune_buckets( line_spec ):
+            for group,column_name,type,action in line_spec["column_map"]:
+                if self.has_column(column_name):
+                    cc = self.get_column(column_name)
+                    while cc.size() > line_spec["num_buckets"]:
+                        cc.delete(0)
+
+        def top_buckets( line_spec ):
+            columns = []
+            key_idx = None
+            idx = 0
+            for group,column_name,type,action in line_spec["column_map"]:
+                columns.append(self.get_column(column_name))
+                if action == "key":
+                    key_idx = idx
+                idx += 1
+
+            sort_rows = []
+            for idx in range(columns[key_idx].size()):
+                values = []
+                for cidx in range(len(columns)):
+                    if cidx != key_idx:
+                        values.append(columns[cidx].get(idx).get_value())
+                values.append(idx)
+                sort_rows.append(values)
+
+            sort_rows.sort(reverse=True)
+            new_columns = []
+            for group,column_name,type,action in line_spec["column_map"]:
+                new_columns.append(Column(name=column_name))
+
+            for ridx in range(min(len(sort_rows),line_spec["num_buckets"])):
+                for cidx in range(len(columns)):
+                    new_columns[cidx].put(sort_rows[ridx][-1],columns[cidx].get(sort_rows[ridx][-1]))
+
+            for c in new_columns:
+                self.replace_column(self.map_column(c.get_name()),c)
+
+        lb_days,lb_hours,lb_minutes = self.log_lookback
+        start_time = datetime.now() - timedelta(days=lb_days,hours=lb_hours,minutes=lb_minutes)
+
+        log_files = glob.glob(self.log_glob)
+
+        for lf in log_files:
+            lfp = 0
+            stat = os.stat(lf)
+            if stat.st_mtime < start_time.timestamp():
+                continue
+
+            if lf in self.file_map:
+                lft,lfp = self.file_map[lf]
+                if stat.st_mtime <= lft:
+                    continue
+
+            if lf.endswith(".gz"):
+                lf_f = gzip.open(lf,"rt",encoding="utf-8")
+            else:
+                lf_f = open(lf,"r",encoding="utf-8")
+
+            lf_f.seek(lfp,0)
+
+            for line in lf_f:
+                line = line.strip()
+                for line_spec in self.log_map:
+                    m = re.match(line_spec["line_regex"],line)
+                    if m:
+                        values = []
+                        key_idx = None
+                        for group,column_name,type,action in line_spec["column_map"]:
+                            values.append(Value( column_name, type, action, m.group(group) ))
+                            if action == "key":
+                                key_idx = len(values)-1
+                        bidx = get_bucket(line_spec,values[key_idx])
+                        for v in values:
+                            if v.action != "key":
+                                put_value( v, bidx )
+                        if values[key_idx].type != string_type:
+                            prune_buckets(line_spec)
+
+            self.file_map[lf] = (stat.st_mtime,lf_f.tell())
+
+        for line_spec in self.log_map:
+            key_idx = None
+            idx = 0
+            for group,column_name,type,action in line_spec["column_map"]:
+                if action == "key":
+                    key_idx = idx
+                    break
+                idx += 1
+
+            kg,kn,kt,ka = line_spec["column_map"][key_idx]
+            kc = self.get_column(kn)
+            for idx in range(kc.size()):
+                for fg,fn,ft,fa in line_spec["column_map"]:
+                    if fn != kn:
+                        fc = self.get_column(fn)
+                        cc = fc.get(idx)
+                        if cc.type == blank_type:
+                            fc.put(idx,ActionCell(ft,None,format_map[ft],fa))
+
+            if kt == string_type:
+                top_buckets( line_spec )
+
+        self.changed()
+
+        DataTable.refresh(self)
+
+ +
+ +
+ +
+

Module variables

+
+

var blank_type

+ + +
+
+ +
+
+

var date_type

+ + +
+
+ +
+
+

var float_type

+ + +
+
+ +
+
+

var format_map

+ + +
+
+ +
+
+

var int_type

+ + +
+
+ +
+
+

var string_type

+ + +
+
+ +
+ + +

Classes

+ +
+

class ActionCell

+ + +
+ +
+
class ActionCell(Cell):
+    def __init__(self,type,value,format,action):
+        Cell.__init__(self,type,None,format)
+        self.action = action
+        self.values = []
+        self.put_value(value)
+
+    def default_value(self):
+        if self.type == date_type:
+            return datetime.min
+        elif self.type == int_type:
+            return 0
+        elif self.type == float_type:
+            return 0.0
+        elif self.type == string_type:
+            return ""
+
+    def put_value(self,value):
+        if value == None:
+            self.value = self.default_value()
+        else:
+            self.values.append(value)
+            try:
+                if self.action == "key":
+                    self.value = value
+                elif self.action == "avg":
+                    self.value = statistics.mean(self.values)
+                elif self.action == "mode":
+                    self.value = statistics.mode(self.values)
+                elif self.action == "median":
+                    self.value = statistics.median(self.values)
+                elif self.action == "min":
+                    self.value = min(self.values)
+                elif self.action == "max":
+                    self.value = max(self.values)
+                elif self.action == "sum":
+                    self.value = sum(self.values)
+                elif self.action.startswith("count("):
+                    regex = self.action.split("(")[1].split(")")[0]
+                    if self.value == None:
+                        self.value = self.default_value()
+                    if re.match(regex,str(value)):
+                        self.value += 1
+            except:
+                self.value = self.default_value()
+
+ +
+
+ + +
+

Ancestors (in MRO)

+
    +
  • ActionCell
  • +
  • data_sources.data_table.Cell
  • +
  • builtins.object
  • +
+

Static methods

+ +
+
+

def __init__(

self, type, value, format, action)

+
+ + + + +

Initialize self. See help(type(self)) for accurate signature.

+
+ +
+
def __init__(self,type,value,format,action):
+    Cell.__init__(self,type,None,format)
+    self.action = action
+    self.values = []
+    self.put_value(value)
+
+ +
+
+ +
+ + +
+
+

def default_value(

self)

+
+ + + + +
+ +
+
def default_value(self):
+    if self.type == date_type:
+        return datetime.min
+    elif self.type == int_type:
+        return 0
+    elif self.type == float_type:
+        return 0.0
+    elif self.type == string_type:
+        return ""
+
+ +
+
+ +
+ + +
+
+

def get_float_value(

self)

+
+ + + + +
+ +
+
def get_float_value(self):
+    if self.type in [float_type,int_type]:
+        return float(self.value)
+    elif self.type == date_type:
+        return self.value.timestamp()
+    else:
+        return 0.0
+
+ +
+
+ +
+ + +
+
+

def get_format(

self)

+
+ + + + +
+ +
+
def get_format(self):
+    return self.format
+
+ +
+
+ +
+ + +
+
+

def get_type(

self)

+
+ + + + +
+ +
+
def get_type(self):
+    return self.type
+
+ +
+
+ +
+ + +
+
+

def get_value(

self)

+
+ + + + +
+ +
+
def get_value(self):
+    return self.value
+
+ +
+
+ +
+ + +
+
+

def put_value(

self, value)

+
+ + + + +
+ +
+
def put_value(self,value):
+    if value == None:
+        self.value = self.default_value()
+    else:
+        self.values.append(value)
+        try:
+            if self.action == "key":
+                self.value = value
+            elif self.action == "avg":
+                self.value = statistics.mean(self.values)
+            elif self.action == "mode":
+                self.value = statistics.mode(self.values)
+            elif self.action == "median":
+                self.value = statistics.median(self.values)
+            elif self.action == "min":
+                self.value = min(self.values)
+            elif self.action == "max":
+                self.value = max(self.values)
+            elif self.action == "sum":
+                self.value = sum(self.values)
+            elif self.action.startswith("count("):
+                regex = self.action.split("(")[1].split(")")[0]
+                if self.value == None:
+                    self.value = self.default_value()
+                if re.match(regex,str(value)):
+                    self.value += 1
+        except:
+            self.value = self.default_value()
+
+ +
+
+ +
+ + +
+
+

def set_format(

self, format)

+
+ + + + +
+ +
+
def set_format(self,format):
+    self.format = format
+
+ +
+
+ +
+ +

Instance variables

+
+

var action

+ + + + +
+
+ +
+
+

var values

+ + + + +
+
+ +
+
+
+ +
+

class LogDataTable

+ + +

class that collects a time based aggregation of data from the syslog into a data_table

+
+ +
+
class LogDataTable( DataTable ):
+    """ class that collects a time based aggregation of data from the syslog into a data_table """
+    def __init__(self,log_glob=None,log_map=None,log_lookback=None,refresh_minutes=10):
+        """ Initialize the LogDataTable with a file glob pattern to collect the
+        matching logs on this machine, a timespan to aggregate for, aggregation
+        bucket in hours, a refresh interval for updating in minutes and a
+        log_map of the structure [{
+            "line_regex" : "python regex with groups to match columns",
+            "num_buckets" : "number of buckets for this key",
+            "bucket_size" : "size of a bucket",
+            "bucket_type" : "type of buckets",
+            "column_map" : [
+                [group_number 1..n,
+                "Column Name",
+                "type one of _int,_float,_string,_date",
+                "action one of key,avg,min,max,count(value),mode,median"],
+                ...]},...]
+        the key action is special and indicates that this is the bucket key for this type of line
+        log_lookback is of the form [ days, hours, minutes ] all must be specified """
+        self.log_glob = log_glob
+        self.log_map = log_map
+        self.log_lookback = log_lookback
+        self.file_map = {}
+
+        DataTable.__init__(self,None,
+            "LogDataTable: %s, %d minutes refresh"%(
+                self.log_glob,
+                refresh_minutes),
+                refresh_minutes)
+        self.refresh()
+
+    @synchronized
+    def refresh( self ):
+        """ refresh or rebuild tables """
+
+        def get_bucket( line_spec,value ):
+            if not self.has_column(value.column_name):
+                self.add_column(Column(name=value.column_name))
+            bc = self.get_column(value.column_name)
+            for idx in range(bc.size()):
+                if bc.get(idx).get_value() >= value.get_value():
+                    break
+            else:
+                idx = bc.size()
+            if idx < bc.size():
+                if line_spec["bucket_type"] == string_type:
+                    if bc.get(idx).get_value() != value.get_value():
+                        bc.ins(idx,Cell(line_spec["bucket_type"],value.get_value(),format_map[line_spec["bucket_type"]]))
+                return idx
+            elif idx == 0 and bc.size() > 0:
+                diff = bc.get(idx).get_value() - value.get_value()
+                if line_spec["bucket_type"] == date_type:
+                    while diff > timedelta(minutes=line_spec["bucket_size"]):
+                        new_bucket = bc.get(idx).get_value() - timedelta(minutes=line_spec["bucket_size"])
+                        bc.ins(idx,Cell(line_spec["bucket_type"],new_bucket,format_map[line_spec["bucket_type"]]))
+                        diff = bc.get(idx).get_value() - value.get_value()
+                    return idx
+                elif line_spec["bucket_type"] == string_type:
+                    bc.ins(idx,Cell(line_spec["bucket_type"],value.get_value(),format_map[line_spec["bucket_type"]]))
+                    return idx
+                else:
+                    while diff > line_spec["bucket_size"]:
+                        new_bucket = bc.get(idx).get_value() - line_spec["bucket_size"]
+                        bc.ins(idx,Cell(line_spec["bucket_type"],new_bucket,format_map[line_spec["bucket_type"]]))
+                        diff = bc.get(idx).get_value() - value.get_value()
+                    return idx
+            elif idx == bc.size():
+                if line_spec["bucket_type"] == string_type:
+                    bc.put(idx,Cell(line_spec["bucket_type"],value.get_value(),format_map[line_spec["bucket_type"]]))
+                    return idx
+                else:
+                    while True:
+                        if idx > 0:
+                            prev_bucket = bc.get(idx-1).get_value()
+                        else:
+                            prev_bucket = value.get_value()
+
+                        if line_spec["bucket_type"] == date_type:
+                            new_bucket = prev_bucket + timedelta(minutes=line_spec["bucket_size"])
+                        else:
+                            new_bucket = prev_bucket + line_spec["bucket_size"]
+
+                        bc.put(idx,Cell(line_spec["bucket_type"],new_bucket,format_map[line_spec["bucket_type"]]))
+                        if value.get_value() < new_bucket:
+                            return idx
+                        idx = bc.size()
+
+        def put_value( value, bidx ):
+            if not self.has_column(value.column_name):
+                self.add_column(Column(name=value.column_name))
+            cc = self.get_column(value.column_name)
+            if bidx < cc.size():
+                c = cc.get(bidx)
+                if c.type == blank_type:
+                    cc.put(bidx,value.to_cell())
+                else:
+                    cc.get(bidx).put_value(value.get_value())
+            else:
+                cc.put(bidx,value.to_cell())
+
+        def prune_buckets( line_spec ):
+            for group,column_name,type,action in line_spec["column_map"]:
+                if self.has_column(column_name):
+                    cc = self.get_column(column_name)
+                    while cc.size() > line_spec["num_buckets"]:
+                        cc.delete(0)
+
+        def top_buckets( line_spec ):
+            columns = []
+            key_idx = None
+            idx = 0
+            for group,column_name,type,action in line_spec["column_map"]:
+                columns.append(self.get_column(column_name))
+                if action == "key":
+                    key_idx = idx
+                idx += 1
+
+            sort_rows = []
+            for idx in range(columns[key_idx].size()):
+                values = []
+                for cidx in range(len(columns)):
+                    if cidx != key_idx:
+                        values.append(columns[cidx].get(idx).get_value())
+                values.append(idx)
+                sort_rows.append(values)
+
+            sort_rows.sort(reverse=True)
+            new_columns = []
+            for group,column_name,type,action in line_spec["column_map"]:
+                new_columns.append(Column(name=column_name))
+
+            for ridx in range(min(len(sort_rows),line_spec["num_buckets"])):
+                for cidx in range(len(columns)):
+                    new_columns[cidx].put(sort_rows[ridx][-1],columns[cidx].get(sort_rows[ridx][-1]))
+
+            for c in new_columns:
+                self.replace_column(self.map_column(c.get_name()),c)
+
+        lb_days,lb_hours,lb_minutes = self.log_lookback
+        start_time = datetime.now() - timedelta(days=lb_days,hours=lb_hours,minutes=lb_minutes)
+
+        log_files = glob.glob(self.log_glob)
+
+        for lf in log_files:
+            lfp = 0
+            stat = os.stat(lf)
+            if stat.st_mtime < start_time.timestamp():
+                continue
+
+            if lf in self.file_map:
+                lft,lfp = self.file_map[lf]
+                if stat.st_mtime <= lft:
+                    continue
+
+            if lf.endswith(".gz"):
+                lf_f = gzip.open(lf,"rt",encoding="utf-8")
+            else:
+                lf_f = open(lf,"r",encoding="utf-8")
+
+            lf_f.seek(lfp,0)
+
+            for line in lf_f:
+                line = line.strip()
+                for line_spec in self.log_map:
+                    m = re.match(line_spec["line_regex"],line)
+                    if m:
+                        values = []
+                        key_idx = None
+                        for group,column_name,type,action in line_spec["column_map"]:
+                            values.append(Value( column_name, type, action, m.group(group) ))
+                            if action == "key":
+                                key_idx = len(values)-1
+                        bidx = get_bucket(line_spec,values[key_idx])
+                        for v in values:
+                            if v.action != "key":
+                                put_value( v, bidx )
+                        if values[key_idx].type != string_type:
+                            prune_buckets(line_spec)
+
+            self.file_map[lf] = (stat.st_mtime,lf_f.tell())
+
+        for line_spec in self.log_map:
+            key_idx = None
+            idx = 0
+            for group,column_name,type,action in line_spec["column_map"]:
+                if action == "key":
+                    key_idx = idx
+                    break
+                idx += 1
+
+            kg,kn,kt,ka = line_spec["column_map"][key_idx]
+            kc = self.get_column(kn)
+            for idx in range(kc.size()):
+                for fg,fn,ft,fa in line_spec["column_map"]:
+                    if fn != kn:
+                        fc = self.get_column(fn)
+                        cc = fc.get(idx)
+                        if cc.type == blank_type:
+                            fc.put(idx,ActionCell(ft,None,format_map[ft],fa))
+
+            if kt == string_type:
+                top_buckets( line_spec )
+
+        self.changed()
+
+        DataTable.refresh(self)
+
+ +
+
+ + +
+

Ancestors (in MRO)

+
    +
  • LogDataTable
  • +
  • data_sources.data_table.DataTable
  • +
  • builtins.object
  • +
+

Static methods

+ +
+
+

def __init__(

self, log_glob=None, log_map=None, log_lookback=None, refresh_minutes=10)

+
+ + + + +

Initialize the LogDataTable with a file glob pattern to collect the +matching logs on this machine, a timespan to aggregate for, aggregation +bucket in hours, a refresh interval for updating in minutes and a +log_map of the structure [{ + "line_regex" : "python regex with groups to match columns", + "num_buckets" : "number of buckets for this key", + "bucket_size" : "size of a bucket", + "bucket_type" : "type of buckets", + "column_map" : [ + [group_number 1..n, + "Column Name", + "type one of _int,_float,_string,_date", + "action one of key,avg,min,max,count(value),mode,median"], + ...]},...] +the key action is special and indicates that this is the bucket key for this type of line +log_lookback is of the form [ days, hours, minutes ] all must be specified

+
+ +
+
def __init__(self,log_glob=None,log_map=None,log_lookback=None,refresh_minutes=10):
+    """ Initialize the LogDataTable with a file glob pattern to collect the
+    matching logs on this machine, a timespan to aggregate for, aggregation
+    bucket in hours, a refresh interval for updating in minutes and a
+    log_map of the structure [{
+        "line_regex" : "python regex with groups to match columns",
+        "num_buckets" : "number of buckets for this key",
+        "bucket_size" : "size of a bucket",
+        "bucket_type" : "type of buckets",
+        "column_map" : [
+            [group_number 1..n,
+            "Column Name",
+            "type one of _int,_float,_string,_date",
+            "action one of key,avg,min,max,count(value),mode,median"],
+            ...]},...]
+    the key action is special and indicates that this is the bucket key for this type of line
+    log_lookback is of the form [ days, hours, minutes ] all must be specified """
+    self.log_glob = log_glob
+    self.log_map = log_map
+    self.log_lookback = log_lookback
+    self.file_map = {}
+    DataTable.__init__(self,None,
+        "LogDataTable: %s, %d minutes refresh"%(
+            self.log_glob,
+            refresh_minutes),
+            refresh_minutes)
+    self.refresh()
+
+ +
+
+ +
+ + +
+
+

def acquire_refresh_lock(

self)

+
+ + + + +

acquire the refresh lock before reading/writing the table state

+
+ +
+
def acquire_refresh_lock(self):
+    """ acquire the refresh lock before reading/writing the table state """
+    self.refresh_lock.acquire()
+
+ +
+
+ +
+ + +
+
+

def add_column(

self, *args, **kwargs)

+
+ + + + +
+ +
+
@synchronized
+def add_column(self,column):
+    idx = len(self.columns)
+    column.set_idx(idx)
+    if not column.get_name():
+        column.set_name("%s_%d"%(self.name,idx))
+    self.columns.append(column)
+    self.cnames[column.get_name()] = column
+    column.set_table(self)
+
+ +
+
+ +
+ + +
+
+

def changed(

self)

+
+ + + + +

notify listeners that this table has been changed

+
+ +
+
def changed(self):
+    """ notify listeners that this table has been changed """
+    for f in self.listeners:
+        f(self)
+
+ +
+
+ +
+ + +
+
+

def get(

self, *args, **kwargs)

+
+ + + + +
+ +
+
@synchronized
+def get(self, row, reference ):
+    return self.columns[self.map_column(reference)].get(row)
+
+ +
+
+ +
+ + +
+
+

def get_bounds(

self, *args, **kwargs)

+
+ + + + +

return a tuple (rows,cols) where rows is the maximum number of rows and cols is the maximum number of cols

+
+ +
+
@synchronized
+def get_bounds(self):
+    """ return a tuple (rows,cols) where rows is the maximum number of rows and cols is the maximum number of cols """
+    cols = len(self.columns)
+    rows = -1
+    for c in self.columns:
+        size = c.size()
+        if rows < 0 or size > rows:
+            rows = size
+    return (rows,cols)
+
+ +
+
+ +
+ + +
+
+

def get_column(

self, *args, **kwargs)

+
+ + + + +
+ +
+
@synchronized
+def get_column(self, reference):
+    return self.columns[self.map_column(reference)]
+
+ +
+
+ +
+ + +
+
+

def get_columns(

self, *args, **kwargs)

+
+ + + + +

return the list of columns

+
+ +
+
@synchronized
+def get_columns(self):
+    """ return the list of columns """
+    return self.columns
+
+ +
+
+ +
+ + +
+
+

def get_name(

self)

+
+ + + + +

return the name of the table

+
+ +
+
def get_name(self):
+    """ return the name of the table """
+    return self.name
+
+ +
+
+ +
+ + +
+
+

def get_names(

self, *args, **kwargs)

+
+ + + + +

return a list of the names of the columns in order

+
+ +
+
@synchronized
+def get_names(self):
+    """ return a list of the names of the columns in order"""
+    return [c.get_name() for c in self.columns]
+
+ +
+
+ +
+ + +
+
+

def get_refresh_timestamp(

self)

+
+ + + + +

get the time that the table was last refreshed

+
+ +
+
def get_refresh_timestamp( self ):
+    """ get the time that the table was last refreshed """
+    return self.refresh_timestamp
+
+ +
+
+ +
+ + +
+
+

def has_column(

self, *args, **kwargs)

+
+ + + + +
+ +
+
@synchronized
+def has_column(self, reference ):
+    if type(reference) == str or type(reference) == str:
+        return reference in self.cnames
+    elif type(reference) == int:
+        return idx < len(self.columns)
+    else:
+        return False
+
+ +
+
+ +
+ + +
+
+

def insert_column(

self, *args, **kwargs)

+
+ + + + +
+ +
+
@synchronized
+def insert_column(self,idx,column):
+    while idx > len(self.columns):
+        self.add_column(blank_column)
+    if idx == len(self.columns):
+        self.add_column(column)
+    else:
+        if not column.get_name():
+            column.set_name("%s_%d"%(self.name,idx))
+        self.columns.insert(idx,column)
+        self.cnames[column.get_name()] = column
+        column.set_table(self)
+        while idx < len(self.columns):
+            if column.get_name() == "%s_%d"%(self.name,idx-1):
+                column.set_name("%s_%d"%(self.name,idx))
+                self.cnames[column.get_name()] = column
+            self.columns[idx].set_idx(idx)
+            idx += 1
+
+ +
+
+ +
+ + +
+
+

def listen(

self, listen_func)

+
+ + + + +

register for notifications when a change event is raised on this table

+
+ +
+
def listen(self,listen_func):
+    """ register for notifications when a change event is raised on this table """
+    self.listeners.append(listen_func)
+
+ +
+
+ +
+ + +
+
+

def map_column(

self, *args, **kwargs)

+
+ + + + +
+ +
+
@synchronized
+def map_column(self, reference ):
+    if type(reference) == str or type(reference) == str:
+        return self.cnames[reference].get_idx()
+    elif type(reference) == int:
+        return reference
+    else:
+        raise TypeError("wrong type in mapping")
+
+ +
+
+ +
+ + +
+
+

def perform_refresh(

self)

+
+ + + + +

Thread worker that sleeps and refreshes the data on a schedule

+
+ +
+
def perform_refresh( self ):
+    """ Thread worker that sleeps and refreshes the data on a schedule """
+    start_time = time.time()
+    while not self.refresh_thread_stop:
+        if time.time() - start_time >= self.refresh_minutes*60.0:
+            self.refresh()
+            start_time = time.time()
+        time.sleep(1)
+
+ +
+
+ +
+ + +
+
+

def put(

self, *args, **kwargs)

+
+ + + + +
+ +
+
@synchronized
+def put(self, row, reference, value):
+    self.columns[self.map_column(reference)].put(row,value)
+
+ +
+
+ +
+ + +
+
+

def refresh(

self, *args, **kwargs)

+
+ + + + +

refresh or rebuild tables

+
+ +
+
@synchronized
+def refresh( self ):
+    """ refresh or rebuild tables """
+    def get_bucket( line_spec,value ):
+        if not self.has_column(value.column_name):
+            self.add_column(Column(name=value.column_name))
+        bc = self.get_column(value.column_name)
+        for idx in range(bc.size()):
+            if bc.get(idx).get_value() >= value.get_value():
+                break
+        else:
+            idx = bc.size()
+        if idx < bc.size():
+            if line_spec["bucket_type"] == string_type:
+                if bc.get(idx).get_value() != value.get_value():
+                    bc.ins(idx,Cell(line_spec["bucket_type"],value.get_value(),format_map[line_spec["bucket_type"]]))
+            return idx
+        elif idx == 0 and bc.size() > 0:
+            diff = bc.get(idx).get_value() - value.get_value()
+            if line_spec["bucket_type"] == date_type:
+                while diff > timedelta(minutes=line_spec["bucket_size"]):
+                    new_bucket = bc.get(idx).get_value() - timedelta(minutes=line_spec["bucket_size"])
+                    bc.ins(idx,Cell(line_spec["bucket_type"],new_bucket,format_map[line_spec["bucket_type"]]))
+                    diff = bc.get(idx).get_value() - value.get_value()
+                return idx
+            elif line_spec["bucket_type"] == string_type:
+                bc.ins(idx,Cell(line_spec["bucket_type"],value.get_value(),format_map[line_spec["bucket_type"]]))
+                return idx
+            else:
+                while diff > line_spec["bucket_size"]:
+                    new_bucket = bc.get(idx).get_value() - line_spec["bucket_size"]
+                    bc.ins(idx,Cell(line_spec["bucket_type"],new_bucket,format_map[line_spec["bucket_type"]]))
+                    diff = bc.get(idx).get_value() - value.get_value()
+                return idx
+        elif idx == bc.size():
+            if line_spec["bucket_type"] == string_type:
+                bc.put(idx,Cell(line_spec["bucket_type"],value.get_value(),format_map[line_spec["bucket_type"]]))
+                return idx
+            else:
+                while True:
+                    if idx > 0:
+                        prev_bucket = bc.get(idx-1).get_value()
+                    else:
+                        prev_bucket = value.get_value()
+                    if line_spec["bucket_type"] == date_type:
+                        new_bucket = prev_bucket + timedelta(minutes=line_spec["bucket_size"])
+                    else:
+                        new_bucket = prev_bucket + line_spec["bucket_size"]
+                    bc.put(idx,Cell(line_spec["bucket_type"],new_bucket,format_map[line_spec["bucket_type"]]))
+                    if value.get_value() < new_bucket:
+                        return idx
+                    idx = bc.size()
+    def put_value( value, bidx ):
+        if not self.has_column(value.column_name):
+            self.add_column(Column(name=value.column_name))
+        cc = self.get_column(value.column_name)
+        if bidx < cc.size():
+            c = cc.get(bidx)
+            if c.type == blank_type:
+                cc.put(bidx,value.to_cell())
+            else:
+                cc.get(bidx).put_value(value.get_value())
+        else:
+            cc.put(bidx,value.to_cell())
+    def prune_buckets( line_spec ):
+        for group,column_name,type,action in line_spec["column_map"]:
+            if self.has_column(column_name):
+                cc = self.get_column(column_name)
+                while cc.size() > line_spec["num_buckets"]:
+                    cc.delete(0)
+    def top_buckets( line_spec ):
+        columns = []
+        key_idx = None
+        idx = 0
+        for group,column_name,type,action in line_spec["column_map"]:
+            columns.append(self.get_column(column_name))
+            if action == "key":
+                key_idx = idx
+            idx += 1
+        sort_rows = []
+        for idx in range(columns[key_idx].size()):
+            values = []
+            for cidx in range(len(columns)):
+                if cidx != key_idx:
+                    values.append(columns[cidx].get(idx).get_value())
+            values.append(idx)
+            sort_rows.append(values)
+        sort_rows.sort(reverse=True)
+        new_columns = []
+        for group,column_name,type,action in line_spec["column_map"]:
+            new_columns.append(Column(name=column_name))
+        for ridx in range(min(len(sort_rows),line_spec["num_buckets"])):
+            for cidx in range(len(columns)):
+                new_columns[cidx].put(sort_rows[ridx][-1],columns[cidx].get(sort_rows[ridx][-1]))
+        for c in new_columns:
+            self.replace_column(self.map_column(c.get_name()),c)
+    lb_days,lb_hours,lb_minutes = self.log_lookback
+    start_time = datetime.now() - timedelta(days=lb_days,hours=lb_hours,minutes=lb_minutes)
+    log_files = glob.glob(self.log_glob)
+    for lf in log_files:
+        lfp = 0
+        stat = os.stat(lf)
+        if stat.st_mtime < start_time.timestamp():
+            continue
+        if lf in self.file_map:
+            lft,lfp = self.file_map[lf]
+            if stat.st_mtime <= lft:
+                continue
+        if lf.endswith(".gz"):
+            lf_f = gzip.open(lf,"rt",encoding="utf-8")
+        else:
+            lf_f = open(lf,"r",encoding="utf-8")
+        lf_f.seek(lfp,0)
+        for line in lf_f:
+            line = line.strip()
+            for line_spec in self.log_map:
+                m = re.match(line_spec["line_regex"],line)
+                if m:
+                    values = []
+                    key_idx = None
+                    for group,column_name,type,action in line_spec["column_map"]:
+                        values.append(Value( column_name, type, action, m.group(group) ))
+                        if action == "key":
+                            key_idx = len(values)-1
+                    bidx = get_bucket(line_spec,values[key_idx])
+                    for v in values:
+                        if v.action != "key":
+                            put_value( v, bidx )
+                    if values[key_idx].type != string_type:
+                        prune_buckets(line_spec)
+        self.file_map[lf] = (stat.st_mtime,lf_f.tell())
+    for line_spec in self.log_map:
+        key_idx = None
+        idx = 0
+        for group,column_name,type,action in line_spec["column_map"]:
+            if action == "key":
+                key_idx = idx
+                break
+            idx += 1
+        kg,kn,kt,ka = line_spec["column_map"][key_idx]
+        kc = self.get_column(kn)
+        for idx in range(kc.size()):
+            for fg,fn,ft,fa in line_spec["column_map"]:
+                if fn != kn:
+                    fc = self.get_column(fn)
+                    cc = fc.get(idx)
+                    if cc.type == blank_type:
+                        fc.put(idx,ActionCell(ft,None,format_map[ft],fa))
+        if kt == string_type:
+            top_buckets( line_spec )
+    self.changed()
+    DataTable.refresh(self)
+
+ +
+
+ +
+ + +
+
+

def release_refresh_lock(

self)

+
+ + + + +

release the refresh lock after reading/writing the table state

+
+ +
+
def release_refresh_lock(self):
+    """ release the refresh lock after reading/writing the table state """
+    self.refresh_lock.release()
+
+ +
+
+ +
+ + +
+
+

def replace_column(

self, *args, **kwargs)

+
+ + + + +
+ +
+
@synchronized
+def replace_column(self,idx,column):
+    column.set_idx(idx)
+    if not column.get_name():
+        column.set_name("%s_%d"%(self.name,idx))
+    if idx == len(self.columns):
+        self.columns.append(column)
+    else:
+        self.columns[idx] = column
+    self.cnames[column.get_name()] = column
+    column.set_table(self)
+
+ +
+
+ +
+ + +
+
+

def start_refresh(

self)

+
+ + + + +

Start the background refresh thread

+
+ +
+
def start_refresh( self ):
+    """ Start the background refresh thread """
+    self.stop_refresh()
+    self.refresh_thread = threading.Thread(target=self.perform_refresh)
+    self.refresh_thread.start()
+
+ +
+
+ +
+ + +
+
+

def stop_refresh(

self)

+
+ + + + +

Stop the background refresh thread

+
+ +
+
def stop_refresh( self ):
+    """ Stop the background refresh thread """
+    self.refresh_thread_stop = True
+    if self.refresh_thread and self.refresh_thread.is_alive():
+        self.refresh_thread.join()
+    self.refresh_thread = None
+    self.refresh_thread_stop = False
+
+ +
+
+ +
+ + +
+
+

def unlisten(

self, listen_func)

+
+ + + + +

unregister for notifications when a change event is raised on this table

+
+ +
+
def unlisten(self,listen_func):
+    """ unregister for notifications when a change event is raised on this table """
+    self.listeners.remove(listen_func)
+
+ +
+
+ +
+ +

Instance variables

+
+

var file_map

+ + + + +
+
+ +
+
+

var log_glob

+ + + + +
+
+ +
+
+

var log_lookback

+ + + + +
+
+ +
+
+

var log_map

+ + + + +
+
+ +
+
+
+ +
+

class Value

+ + +

structure for mapped values

+
+ +
+
class Value():
+    """ structure for mapped values """
+    def __init__(self,column_name,type,action,value):
+        """ initialize the value structure with mapping information and value from log """
+        self.column_name = column_name
+        self.type = type
+        self.action = action
+        self.value = value
+
+    def get_value(self):
+        """ based on type return the value from the log """
+        if self.type == date_type:
+            try:
+                return datetime.fromtimestamp(float(self.value))
+            except:
+                return parser.parse(self.value)
+        elif self.type == int_type:
+            if self.action.startswith("count("):
+                return self.value
+            else:
+                return int(self.value)
+        elif self.type == float_type:
+            return float(self.value)
+        elif self.type == string_type:
+            return self.value
+        else:
+            return str(self.value)
+
+    def to_cell(self):
+        """ construct and return a cell based on type, action and value """
+        return ActionCell( self.type, self.get_value(), format_map[self.type], self.action )
+
+ +
+
+ + +
+

Ancestors (in MRO)

+
    +
  • Value
  • +
  • builtins.object
  • +
+

Static methods

+ +
+
+

def __init__(

self, column_name, type, action, value)

+
+ + + + +

initialize the value structure with mapping information and value from log

+
+ +
+
def __init__(self,column_name,type,action,value):
+    """ initialize the value structure with mapping information and value from log """
+    self.column_name = column_name
+    self.type = type
+    self.action = action
+    self.value = value
+
+ +
+
+ +
+ + +
+
+

def get_value(

self)

+
+ + + + +

based on type return the value from the log

+
+ +
+
def get_value(self):
+    """ based on type return the value from the log """
+    if self.type == date_type:
+        try:
+            return datetime.fromtimestamp(float(self.value))
+        except:
+            return parser.parse(self.value)
+    elif self.type == int_type:
+        if self.action.startswith("count("):
+            return self.value
+        else:
+            return int(self.value)
+    elif self.type == float_type:
+        return float(self.value)
+    elif self.type == string_type:
+        return self.value
+    else:
+        return str(self.value)
+
+ +
+
+ +
+ + +
+
+

def to_cell(

self)

+
+ + + + +

construct and return a cell based on type, action and value

+
+ +
+
def to_cell(self):
+    """ construct and return a cell based on type, action and value """
+    return ActionCell( self.type, self.get_value(), format_map[self.type], self.action )
+
+ +
+
+ +
+ +

Instance variables

+
+

var action

+ + + + +
+
+ +
+
+

var column_name

+ + + + +
+
+ +
+
+

var type

+ + + + +
+
+ +
+
+

var value

+ + + + +
+
+ +
+
+
+ +
+ +
+
+ +
+ + diff --git a/docs/data_sources/odbc_data.m.html b/docs/data_sources/odbc_data.m.html new file mode 100644 index 0000000..09eea7c --- /dev/null +++ b/docs/data_sources/odbc_data.m.html @@ -0,0 +1,1961 @@ + + + + + + data_sources.odbc_data API documentation + + + + + + + + + + + + + + + +Top + +
+ + + + +
+ + + + + + +
+

data_sources.odbc_data module

+

module that performs a sql query on an odbc database and forms the result into a data table

+ + +
+
# Copyright 2020 James P Goodwin data table package to manage sparse columnar data
+""" module that performs a sql query on an odbc database and forms the result into a data table """
+import locale
+locale.setlocale(locale.LC_ALL,'')
+import sys
+import os
+import glob
+import gzip
+import re
+import pyodbc
+import keyring
+from datetime import datetime,timedelta
+from data_sources.data_table import DataTable,Column,Cell,blank_type,string_type,float_type,int_type,date_type,format_string,format_float,format_date,format_int,synchronized
+
+
+class ODBCDataTable( DataTable ):
+    """ class that collects data from the response to a specific sql query on an odbc connected database and populates tables based on a field map """
+    def __init__(self,refresh_minutes=1,sql_spec=None,sql_query=None,sql_map=None):
+        """ Initalize the ODBCDataTable object pass in a sql_spec to connect to the database of the form odbc://user@server/driver/database:port, a sql_query to be executed, and a field map of the form [[sql_column_name, data_table_column_name],..] indicating the columns to collect from the result """
+        self.sql_spec = sql_spec
+        self.sql_query = sql_query
+        self.sql_map = sql_map
+        DataTable.__init__(self,None,
+            "ODBCDataTable query:%s,database:%s,fieldmap:%s,refreshed every %d minutes"%(
+            sql_query,sql_spec,sql_map,refresh_minutes),
+            refresh_minutes)
+
+        self.refresh()
+
+    @synchronized
+    def refresh( self ):
+        """ refresh the table from the query """
+        username,server,driver,database,port = re.match(r"odbc://([a-z_][a-z0-9_-]*\${0,1})@([^/]*)/([^/]*)/([^:]*):{0,1}(\d*){0,1}",self.sql_spec).groups()
+
+        password = keyring.get_password(self.sql_spec, username)
+        if not password:
+            return
+
+        conn = pyodbc.connect("DRIVER={%s};DATABASE=%s;UID=%s;PWD=%s;SERVER=%s;PORT=%s;"%(driver,database,username,password,server,port))
+        if not conn:
+            return
+
+        result = conn.execute(self.sql_query)
+
+        for row in result:
+            for sql_column,data_column in self.sql_map:
+                value = getattr(row,sql_column)
+                if not self.has_column(data_column):
+                    self.add_column(Column(name=data_column))
+                c = self.get_column(data_column)
+                if isinstance(value,datetime):
+                    cc = Cell(date_type,value,format_date)
+                elif isinstance(value,int):
+                    cc = Cell(int_type,value,format_int)
+                elif isinstance(value,float):
+                    cc = Cell(float_type,value,format_float)
+                elif isinstance(value,str):
+                    cc = Cell(string_type,value,format_string)
+                else:
+                    cc = Cell(string_type,str(value),format_string)
+                c.put(c.size(),cc)
+
+        self.changed()
+        DataTable.refresh(self)
+
+ +
+ +
+ +
+

Module variables

+
+

var blank_type

+ + +
+
+ +
+
+

var date_type

+ + +
+
+ +
+
+

var float_type

+ + +
+
+ +
+
+

var int_type

+ + +
+
+ +
+
+

var string_type

+ + +
+
+ +
+ + +

Classes

+ +
+

class ODBCDataTable

+ + +

class that collects data from the response to a specific sql query on an odbc connected database and populates tables based on a field map

+
+ +
+
class ODBCDataTable( DataTable ):
+    """ class that collects data from the response to a specific sql query on an odbc connected database and populates tables based on a field map """
+    def __init__(self,refresh_minutes=1,sql_spec=None,sql_query=None,sql_map=None):
+        """ Initalize the ODBCDataTable object pass in a sql_spec to connect to the database of the form odbc://user@server/driver/database:port, a sql_query to be executed, and a field map of the form [[sql_column_name, data_table_column_name],..] indicating the columns to collect from the result """
+        self.sql_spec = sql_spec
+        self.sql_query = sql_query
+        self.sql_map = sql_map
+        DataTable.__init__(self,None,
+            "ODBCDataTable query:%s,database:%s,fieldmap:%s,refreshed every %d minutes"%(
+            sql_query,sql_spec,sql_map,refresh_minutes),
+            refresh_minutes)
+
+        self.refresh()
+
+    @synchronized
+    def refresh( self ):
+        """ refresh the table from the query """
+        username,server,driver,database,port = re.match(r"odbc://([a-z_][a-z0-9_-]*\${0,1})@([^/]*)/([^/]*)/([^:]*):{0,1}(\d*){0,1}",self.sql_spec).groups()
+
+        password = keyring.get_password(self.sql_spec, username)
+        if not password:
+            return
+
+        conn = pyodbc.connect("DRIVER={%s};DATABASE=%s;UID=%s;PWD=%s;SERVER=%s;PORT=%s;"%(driver,database,username,password,server,port))
+        if not conn:
+            return
+
+        result = conn.execute(self.sql_query)
+
+        for row in result:
+            for sql_column,data_column in self.sql_map:
+                value = getattr(row,sql_column)
+                if not self.has_column(data_column):
+                    self.add_column(Column(name=data_column))
+                c = self.get_column(data_column)
+                if isinstance(value,datetime):
+                    cc = Cell(date_type,value,format_date)
+                elif isinstance(value,int):
+                    cc = Cell(int_type,value,format_int)
+                elif isinstance(value,float):
+                    cc = Cell(float_type,value,format_float)
+                elif isinstance(value,str):
+                    cc = Cell(string_type,value,format_string)
+                else:
+                    cc = Cell(string_type,str(value),format_string)
+                c.put(c.size(),cc)
+
+        self.changed()
+        DataTable.refresh(self)
+
+ +
+
+ + +
+

Ancestors (in MRO)

+
    +
  • ODBCDataTable
  • +
  • data_sources.data_table.DataTable
  • +
  • builtins.object
  • +
+

Static methods

+ +
+
+

def __init__(

self, refresh_minutes=1, sql_spec=None, sql_query=None, sql_map=None)

+
+ + + + +

Initalize the ODBCDataTable object pass in a sql_spec to connect to the database of the form odbc://user@server/driver/database:port, a sql_query to be executed, and a field map of the form [[sql_column_name, data_table_column_name],..] indicating the columns to collect from the result

+
+ +
+
def __init__(self,refresh_minutes=1,sql_spec=None,sql_query=None,sql_map=None):
+    """ Initalize the ODBCDataTable object pass in a sql_spec to connect to the database of the form odbc://user@server/driver/database:port, a sql_query to be executed, and a field map of the form [[sql_column_name, data_table_column_name],..] indicating the columns to collect from the result """
+    self.sql_spec = sql_spec
+    self.sql_query = sql_query
+    self.sql_map = sql_map
+    DataTable.__init__(self,None,
+        "ODBCDataTable query:%s,database:%s,fieldmap:%s,refreshed every %d minutes"%(
+        sql_query,sql_spec,sql_map,refresh_minutes),
+        refresh_minutes)
+    self.refresh()
+
+ +
+
+ +
+ + +
+
+

def acquire_refresh_lock(

self)

+
+ + + + +

acquire the refresh lock before reading/writing the table state

+
+ +
+
def acquire_refresh_lock(self):
+    """ acquire the refresh lock before reading/writing the table state """
+    self.refresh_lock.acquire()
+
+ +
+
+ +
+ + +
+
+

def add_column(

self, *args, **kwargs)

+
+ + + + +
+ +
+
@synchronized
+def add_column(self,column):
+    idx = len(self.columns)
+    column.set_idx(idx)
+    if not column.get_name():
+        column.set_name("%s_%d"%(self.name,idx))
+    self.columns.append(column)
+    self.cnames[column.get_name()] = column
+    column.set_table(self)
+
+ +
+
+ +
+ + +
+
+

def changed(

self)

+
+ + + + +

notify listeners that this table has been changed

+
+ +
+
def changed(self):
+    """ notify listeners that this table has been changed """
+    for f in self.listeners:
+        f(self)
+
+ +
+
+ +
+ + +
+
+

def get(

self, *args, **kwargs)

+
+ + + + +
+ +
+
@synchronized
+def get(self, row, reference ):
+    return self.columns[self.map_column(reference)].get(row)
+
+ +
+
+ +
+ + +
+
+

def get_bounds(

self, *args, **kwargs)

+
+ + + + +

return a tuple (rows,cols) where rows is the maximum number of rows and cols is the maximum number of cols

+
+ +
+
@synchronized
+def get_bounds(self):
+    """ return a tuple (rows,cols) where rows is the maximum number of rows and cols is the maximum number of cols """
+    cols = len(self.columns)
+    rows = -1
+    for c in self.columns:
+        size = c.size()
+        if rows < 0 or size > rows:
+            rows = size
+    return (rows,cols)
+
+ +
+
+ +
+ + +
+
+

def get_column(

self, *args, **kwargs)

+
+ + + + +
+ +
+
@synchronized
+def get_column(self, reference):
+    return self.columns[self.map_column(reference)]
+
+ +
+
+ +
+ + +
+
+

def get_columns(

self, *args, **kwargs)

+
+ + + + +

return the list of columns

+
+ +
+
@synchronized
+def get_columns(self):
+    """ return the list of columns """
+    return self.columns
+
+ +
+
+ +
+ + +
+
+

def get_name(

self)

+
+ + + + +

return the name of the table

+
+ +
+
def get_name(self):
+    """ return the name of the table """
+    return self.name
+
+ +
+
+ +
+ + +
+
+

def get_names(

self, *args, **kwargs)

+
+ + + + +

return a list of the names of the columns in order

+
+ +
+
@synchronized
+def get_names(self):
+    """ return a list of the names of the columns in order"""
+    return [c.get_name() for c in self.columns]
+
+ +
+
+ +
+ + +
+
+

def get_refresh_timestamp(

self)

+
+ + + + +

get the time that the table was last refreshed

+
+ +
+
def get_refresh_timestamp( self ):
+    """ get the time that the table was last refreshed """
+    return self.refresh_timestamp
+
+ +
+
+ +
+ + +
+
+

def has_column(

self, *args, **kwargs)

+
+ + + + +
+ +
+
@synchronized
+def has_column(self, reference ):
+    if type(reference) == str or type(reference) == str:
+        return reference in self.cnames
+    elif type(reference) == int:
+        return idx < len(self.columns)
+    else:
+        return False
+
+ +
+
+ +
+ + +
+
+

def insert_column(

self, *args, **kwargs)

+
+ + + + +
+ +
+
@synchronized
+def insert_column(self,idx,column):
+    while idx > len(self.columns):
+        self.add_column(blank_column)
+    if idx == len(self.columns):
+        self.add_column(column)
+    else:
+        if not column.get_name():
+            column.set_name("%s_%d"%(self.name,idx))
+        self.columns.insert(idx,column)
+        self.cnames[column.get_name()] = column
+        column.set_table(self)
+        while idx < len(self.columns):
+            if column.get_name() == "%s_%d"%(self.name,idx-1):
+                column.set_name("%s_%d"%(self.name,idx))
+                self.cnames[column.get_name()] = column
+            self.columns[idx].set_idx(idx)
+            idx += 1
+
+ +
+
+ +
+ + +
+
+

def listen(

self, listen_func)

+
+ + + + +

register for notifications when a change event is raised on this table

+
+ +
+
def listen(self,listen_func):
+    """ register for notifications when a change event is raised on this table """
+    self.listeners.append(listen_func)
+
+ +
+
+ +
+ + +
+
+

def map_column(

self, *args, **kwargs)

+
+ + + + +
+ +
+
@synchronized
+def map_column(self, reference ):
+    if type(reference) == str or type(reference) == str:
+        return self.cnames[reference].get_idx()
+    elif type(reference) == int:
+        return reference
+    else:
+        raise TypeError("wrong type in mapping")
+
+ +
+
+ +
+ + +
+
+

def perform_refresh(

self)

+
+ + + + +

Thread worker that sleeps and refreshes the data on a schedule

+
+ +
+
def perform_refresh( self ):
+    """ Thread worker that sleeps and refreshes the data on a schedule """
+    start_time = time.time()
+    while not self.refresh_thread_stop:
+        if time.time() - start_time >= self.refresh_minutes*60.0:
+            self.refresh()
+            start_time = time.time()
+        time.sleep(1)
+
+ +
+
+ +
+ + +
+
+

def put(

self, *args, **kwargs)

+
+ + + + +
+ +
+
@synchronized
+def put(self, row, reference, value):
+    self.columns[self.map_column(reference)].put(row,value)
+
+ +
+
+ +
+ + +
+
+

def refresh(

self, *args, **kwargs)

+
+ + + + +

refresh the table from the query

+
+ +
+
@synchronized
+def refresh( self ):
+    """ refresh the table from the query """
+    username,server,driver,database,port = re.match(r"odbc://([a-z_][a-z0-9_-]*\${0,1})@([^/]*)/([^/]*)/([^:]*):{0,1}(\d*){0,1}",self.sql_spec).groups()
+    password = keyring.get_password(self.sql_spec, username)
+    if not password:
+        return
+    conn = pyodbc.connect("DRIVER={%s};DATABASE=%s;UID=%s;PWD=%s;SERVER=%s;PORT=%s;"%(driver,database,username,password,server,port))
+    if not conn:
+        return
+    result = conn.execute(self.sql_query)
+    for row in result:
+        for sql_column,data_column in self.sql_map:
+            value = getattr(row,sql_column)
+            if not self.has_column(data_column):
+                self.add_column(Column(name=data_column))
+            c = self.get_column(data_column)
+            if isinstance(value,datetime):
+                cc = Cell(date_type,value,format_date)
+            elif isinstance(value,int):
+                cc = Cell(int_type,value,format_int)
+            elif isinstance(value,float):
+                cc = Cell(float_type,value,format_float)
+            elif isinstance(value,str):
+                cc = Cell(string_type,value,format_string)
+            else:
+                cc = Cell(string_type,str(value),format_string)
+            c.put(c.size(),cc)
+    self.changed()
+    DataTable.refresh(self)
+
+ +
+
+ +
+ + +
+
+

def release_refresh_lock(

self)

+
+ + + + +

release the refresh lock after reading/writing the table state

+
+ +
+
def release_refresh_lock(self):
+    """ release the refresh lock after reading/writing the table state """
+    self.refresh_lock.release()
+
+ +
+
+ +
+ + +
+
+

def replace_column(

self, *args, **kwargs)

+
+ + + + +
+ +
+
@synchronized
+def replace_column(self,idx,column):
+    column.set_idx(idx)
+    if not column.get_name():
+        column.set_name("%s_%d"%(self.name,idx))
+    if idx == len(self.columns):
+        self.columns.append(column)
+    else:
+        self.columns[idx] = column
+    self.cnames[column.get_name()] = column
+    column.set_table(self)
+
+ +
+
+ +
+ + +
+
+

def start_refresh(

self)

+
+ + + + +

Start the background refresh thread

+
+ +
+
def start_refresh( self ):
+    """ Start the background refresh thread """
+    self.stop_refresh()
+    self.refresh_thread = threading.Thread(target=self.perform_refresh)
+    self.refresh_thread.start()
+
+ +
+
+ +
+ + +
+
+

def stop_refresh(

self)

+
+ + + + +

Stop the background refresh thread

+
+ +
+
def stop_refresh( self ):
+    """ Stop the background refresh thread """
+    self.refresh_thread_stop = True
+    if self.refresh_thread and self.refresh_thread.is_alive():
+        self.refresh_thread.join()
+    self.refresh_thread = None
+    self.refresh_thread_stop = False
+
+ +
+
+ +
+ + +
+
+

def unlisten(

self, listen_func)

+
+ + + + +

unregister for notifications when a change event is raised on this table

+
+ +
+
def unlisten(self,listen_func):
+    """ unregister for notifications when a change event is raised on this table """
+    self.listeners.remove(listen_func)
+
+ +
+
+ +
+ +

Instance variables

+
+

var sql_map

+ + + + +
+
+ +
+
+

var sql_query

+ + + + +
+
+ +
+
+

var sql_spec

+ + + + +
+
+ +
+
+
+ +
+ +
+
+ +
+ + diff --git a/docs/data_sources/proc_data.m.html b/docs/data_sources/proc_data.m.html index 5f329b5..9a8a0b4 100644 --- a/docs/data_sources/proc_data.m.html +++ b/docs/data_sources/proc_data.m.html @@ -4,7 +4,7 @@ data_sources.proc_data API documentation - + @@ -1116,12 +1116,12 @@

Index

data_sources.proc_data module

-

module that aggregates data from the syslog and provides a set of data tables

+

module that aggregates data from system information using psutil and provides a set of data tables

# Copyright 2020 James P Goodwin data table package to manage sparse columnar data
-""" module that aggregates data from the syslog and provides a set of data tables """
+""" module that aggregates data from system information using psutil and provides a set of data tables """
 import locale
 locale.setlocale(locale.LC_ALL,'')
 import sys
@@ -2009,7 +2009,7 @@ 

Static methods

if time.time() - start_time >= self.refresh_minutes*60.0: self.refresh() start_time = time.time() - time.sleep(0) + time.sleep(1)
diff --git a/docs/data_sources/remote_data.m.html b/docs/data_sources/remote_data.m.html index fe65f41..e9a8fe5 100644 --- a/docs/data_sources/remote_data.m.html +++ b/docs/data_sources/remote_data.m.html @@ -4,7 +4,7 @@ data_sources.remote_data API documentation - + @@ -1132,12 +1132,12 @@

Index

data_sources.remote_data module

-

module that implement a data table package to manage sparse columnar data window

+

module that implements a remote data table proxy over ssh connection to the dashboard running as a table server

# Copyright 2017 James P Goodwin data table package to manage sparse columnar data
-""" module that implement a data table package to manage sparse columnar data window """
+""" module that implements a remote data table proxy over ssh connection to the dashboard running as a table server  """
 import sys
 import os
 import re
@@ -1203,7 +1203,7 @@ 

data_sources.remote_data module

with self.reader_lock: if len(self.stdout_lines): return self.stdout_lines.pop(0) - time.sleep(0) + time.sleep(1) @sync_connection def get_stderr_line( self ): @@ -1212,7 +1212,7 @@

data_sources.remote_data module

with self.reader_lock: if len(self.stderr_lines): return self.stderr_lines.pop(0) - time.sleep(0) + time.sleep(1) @sync_connection def open( self, client ): @@ -1542,7 +1542,7 @@

Classes

with self.reader_lock: if len(self.stdout_lines): return self.stdout_lines.pop(0) - time.sleep(0) + time.sleep(1) @sync_connection def get_stderr_line( self ): @@ -1551,7 +1551,7 @@

Classes

with self.reader_lock: if len(self.stderr_lines): return self.stderr_lines.pop(0) - time.sleep(0) + time.sleep(1) @sync_connection def open( self, client ): @@ -1733,7 +1733,7 @@

Static methods

with self.reader_lock: if len(self.stderr_lines): return self.stderr_lines.pop(0) - time.sleep(0) + time.sleep(1)
@@ -1761,7 +1761,7 @@

Static methods

with self.reader_lock: if len(self.stdout_lines): return self.stdout_lines.pop(0) - time.sleep(0) + time.sleep(1)
@@ -2780,7 +2780,7 @@

Static methods

if time.time() - start_time >= self.refresh_minutes*60.0: self.refresh() start_time = time.time() - time.sleep(0) + time.sleep(1)
diff --git a/docs/data_sources/syslog_data.m.html b/docs/data_sources/syslog_data.m.html index f0b470c..c9e9ee6 100644 --- a/docs/data_sources/syslog_data.m.html +++ b/docs/data_sources/syslog_data.m.html @@ -1826,7 +1826,7 @@

Static methods

if time.time() - start_time >= self.refresh_minutes*60.0: self.refresh() start_time = time.time() - time.sleep(0) + time.sleep(1) diff --git a/docs/index.html b/docs/index.html index eaf490c..d32741c 100644 --- a/docs/index.html +++ b/docs/index.html @@ -1210,8 +1210,12 @@

Packages/Modules

  • data_sources