../queries.py

import helper
import plot
import os

class Query(object):
    result_filename = helper.attr_accessor('__result_filename')
    query = helper.attr_accessor('__query')
    database_filename = helper.attr_accessor('__database_filename')
    query_tmp_filename = helper.attr_accessor('__query_tmp_filename')
    def __init__(self, **kwargs):
        helper.munch_kwargs(self, **kwargs)
        self.query = ''
        self.query_tmp_filename = 'tmp.sql'
    def execute(self):
        os.system(self.execute_s)
    def get_execute_s(self):
        if self.query:
            s = helper.str2bash_redirect(
                'sqlite3 %s << EOF > %s\n%s\nEOF\n' % 
                (self.database_filename, self.result_filename, self.__str__()))
        else:
            s = ''
        print >>open(self.query_tmp_filename, 'w'), s
        return s
    execute_s = property(get_execute_s)
    def isempty(self):
        try: return os.stat(self.result_filename)[6] == 0
        except: return True
    def __str__(self):
        lines = [
            ".separator ' '",
            ".mode list",
            self.query
        ]
        return '\n'.join(lines)

class Analyzer(object):
    # An Analyzer encompasses:
    # Data processing & visualization
    query = helper.attr_accessor('__query')
    plot = helper.attr_accessor('__plot')
    basename = helper.attr_accessor('__basename')
    title = helper.attr_accessor('__title')
    ctxt = helper.attr_accessor('__ctxt')
    queries = helper.attr_accessor('__queries')
    def __init__(self, **kwargs):
        helper.munch_kwargs(self, **kwargs)
        self.queries = []
        self.plot = self.ctxt.new_plot(
            title=self.title,
            output_filename='%s.png' % self.basename)
        self.query = self.new_query()
        self.query.result_filename = self.basename
        for id, time, titles in self.ctxt.markers:
            self.new_datavertical(label=id, x=time)
    def new_query(self, **kwargs):
        query = Query(**kwargs)
        if 'database_filename' not in kwargs:
            query.database_filename = self.ctxt.database_filename
        if 'result_filename' not in kwargs:
            query.result_filename = '%s-%s' % (self.basename, len(self.queries))
        if 'query_tmp_filename' not in kwargs:
            query.query_tmp_filename = '%s-%s.sql' % (
                self.basename, len(self.queries))
        self.queries.append(query)
        return query
    def new_data2D(self, **kwargs):
        data = self.ctxt.new_data2D(**kwargs)
        if 'input_filename' not in kwargs:
            data.input_filename = self.queries[0].result_filename
        self.plot.data.append(data)
        return data
    def new_datavertical(self, **kwargs):
        data = self.ctxt.new_datavertical(**kwargs)
        self.plot.data.append(data)
        return data
    def isempty(self):
        return False not in [x.isempty() for x in self.queries]
    def execute(self):
        # FIXME: this code is redundant with get_execute_s
        os.system(self.execute_query_s)
        if not self.isempty():
            os.system(self.execute_plot_s)
        else:
            os.system('echo -n " [empty]"')
        os.system('echo " %s"' % self.title)
    def get_execute_query_s(self):
        lines = []
        lines.append('echo -n %s' % self.basename)
        lines.append('echo -n " [query]"')
        for q in self.queries:
            lines.append(q.execute_s)
        return '\n'.join(lines)
    execute_query_s = property(get_execute_query_s)
    def get_execute_plot_s(self):
        lines = []
        lines.append('echo -n " [plot]"')
        lines.append(self.plot.execute_s)
        return '\n'.join(lines)
    execute_plot_s = property(get_execute_plot_s)
    def get_execute_s(self):
        # FIXME: deal with empty query results on a shell level...
        lines = []
        #lines.append(helper.unindent
        _ = ('''
            function isempty() {
                size=`wc --chars $* | cut -f1 -d\ `
                if [ "$size" == "0" ]; then
                    return 0
                else 
                    return -1
                fi
            }
            ''')
        lines.append(self.execute_query_s)
        lines.append(self.execute_plot_s)
        #if not self.isempty():
        #    lines.append('echo -n " [plot]"')
        #    lines.append(self.plot.execute_s)
        #else:
        #    lines.append('echo -n " [empty]"')
        lines.append('echo " %s"' % self.title)
        return '\n'.join(lines)
    execute_s = property(get_execute_s)

class SLAProbabilities(Analyzer):
    _query_str = helper.unindent('''
        SELECT time, p_increase, p_decrease
            FROM %(table)s
            WHERE nodeid = %(nodeid)s;
        ''')
    def __init__(self, ctxt, basename, nodeid, table):
        Analyzer.__init__(self,
            ctxt=ctxt,
            basename=basename,
            title='SLA increase/decrease probabilities of node %s' % nodeid)
        #
        self.query.query = self._query_str % {
                'table': table,
                'nodeid': nodeid,
            }
        #
        data1 = self.new_data2D(
            title='p(dec)', x=0, y=2, style='steps')
        data2 = self.new_data2D(
            title='p(dec) + p(inc)', x=0, y='1 + 2', style='steps')
        self.plot.yrange = (0, 1.1)

class SLA1Probabilities(SLAProbabilities): # called "q1" in queries.sh
    def __init__(self, ctxt, basename, nodeid):
        SLAProbabilities.__init__(self,
                ctxt, basename, nodeid, table='ns_sla1_p')

class SLA2Probabilities(SLAProbabilities):
    def __init__(self, ctxt, basename, nodeid):
        SLAProbabilities.__init__(self,
                ctxt, basename, nodeid, table='ns_sla2_p')

class SLA3Probabilities(SLAProbabilities):
    def __init__(self, ctxt, basename, nodeid):
        SLAProbabilities.__init__(self,
                ctxt, basename, nodeid, table='ns_sla3_p')

class SIR(Analyzer): # called "q2" in queries.sh
    _query_str = helper.unindent('''
        SELECT m.time, m.min_SIR
            FROM
                ns_macrx AS m
            INNER JOIN
                ns_phytx AS t
                ON m.pktid = t.pktid
            WHERE
                m.nodeid = t.dest AND
                m.nodeid = %(nodeid)s;
        ''')
    def __init__(self, ctxt, basename, nodeid):
        # FIXME: modify this to discriminate packet types
        Analyzer.__init__(self,
            ctxt=ctxt,
            basename=basename,
            title='SIR of successfully received non-broadcast packets')
        #
        self.query.query = self._query_str % {
                'nodeid': nodeid,
            }
        #
        data1 = self.new_data2D(
            title='packets rcvd at %s' % nodeid, x=0, y=1, style='steps')
        self.plot.logy = True

class DiscriminatingSIR(Analyzer):
    _query_str = helper.unindent('''
        SELECT m.time, m.min_SIR
            FROM
                ns_macrx AS m
            INNER JOIN
                ns_phytx AS t
                ON m.pktid = t.pktid
            WHERE
                m.nodeid = t.dest -- the pkt was intended for us
                %(nodeid_qual)s
                %(type_qual)s;
        ''')
    def __init__(self, ctxt, basename, rxnode=None, txnode=None,
            groups = ['dbrcao']):
        # NOTE: b doesn't matter in the context of reception: we ignore
        # broadcast packets since there is not a clear definition
        # for their "successful" reception (i.e. m.rxnode = t.dest)
        # d & b -> ((type IN ('d') or dest = -1) or type IN (...))
        # d     -> ((type IN ('d') and dest != -1) or type IN (...))
        # b     -> ((type IN ('d') and dest = -1) or type IN (...))
        # else  -> (type IN (...))
        def removechar(l, x): return ''.join([c for c in x if x != l])
        groups = [x for x in [removechar('b', s) for s in groups] if x]
        #
        def x_or_star(x): return x is None and '*' or x
        Analyzer.__init__(self,
            ctxt=ctxt,
            basename=basename,
            title='SIR of successfully received non-broadcast packets %s -> %s'
            % (x_or_star(txnode), x_or_star(rxnode)) )
        #
        nodequal = []
        if rxnode is not None:
            nodequal.append('t.dest = %s' % rxnode)
        if txnode is not None:
            nodequal.append('t.source = %s' % txnode)
        nodeid_qual_str = 'AND (%s)' % ' AND '.join(nodequal)
        #
        queries = {}
        for group in groups:
            qualifiers1 = ["'%s'" % x for x in group if x in 'rcao']
            qstr1 = 't.pkttype IN (%s)' % ', '.join(qualifiers1)
            if 'd' in group and 'b' in group:
                                qstr2 = "(t.pkttype = 'd' OR t.dest = -1)"
            elif 'd' in group:  qstr2 = "(t.pkttype = 'd' AND t.dest != -1)"
            elif 'b' in group:  qstr2 = "(t.pkttype = 'd' AND t.dest = -1)"
            else: qstr2 = ''
            qstr = 'AND (%s)' % ' OR '.join([x for x in [qstr1, qstr2] if x])
            query = self.new_query()
            query.query = self._query_str % {
                    'rxnode': rxnode,
                    'nodeid_qual': nodeid_qual_str,
                    'type_qual': qstr,
                }
            queries[group] = query
        #
        for group in groups:
            self.new_data2D(
                input_filename=queries[group].result_filename,
                title='%s packets' % (group),
                x=0, y=1, style='points')
        self.plot.logy = True

# replacement for SIR:
class SIRx(DiscriminatingSIR):
    def __init__(self, ctxt, basename, nodeid):
        DiscriminatingSIR.__init__(
            self, ctxt, basename, nodeid, groups = ['dbrcao'])

class SIREach(DiscriminatingSIR):
    def __init__(self, ctxt, basename, rxnode=None, txnode=None):
        DiscriminatingSIR.__init__(
            self, ctxt, basename, rxnode, txnode,
            groups = ['d', 'b', 'r', 'c', 'a', 'o'])

class SubjectCollisionHistogram(Analyzer): # called "q3" in queries.sh
    _query_str = helper.unindent('''
        SELECT ROUND(c.time/%(interval)s - 0.5) * %(interval)s AS histtime,
          COUNT(*)
            FROM
                ns_collision AS c
            INNER JOIN
                ns_phytx AS t1
                ON c.pktid1 = t1.pktid
            INNER JOIN
                ns_phytx AS t2
                ON c.pktid2 = t2.pktid
            WHERE
                c.nodeid = %(nodeid)s AND
                c.type = 1
            GROUP BY
                histtime;
        ''')
    def __init__(self, ctxt, basename, nodeid, scale=None):
        Analyzer.__init__(self,
            ctxt=ctxt,
            basename=basename,
            title='Histogram of collisions at node %s' % nodeid)
        #
        self.query.query = self._query_str % {
                'nodeid': nodeid,
                'interval': 1,
            }
        self.query2 = self.new_query()
        self.query2.query = self._query_str % {
                'nodeid': nodeid,
                'interval': 4,
            }
        #
        data1 = self.new_data2D(
            input_filename=self.query2.result_filename,
            title='sum over 4 second intervals',
            x=0, y=1, style='impulses')
        data2 = self.new_data2D(
            title='sum over 1 second intervals',
            x=0, y=1, style='impulses')
        if scale:
            self.plot.yrange = (0, scale)
        else:
            self.plot.yrange = (0, None)

class PowerLevelsFromAToB(Analyzer): # called "q4" in queries.sh
    _query_str = helper.unindent('''
        SELECT t.time, t.txpwr, r.rxpwr, r.rxpwr/t.txpwr
            FROM
                ns_phytx AS t
            INNER JOIN
                ns_phyrx AS r
                ON t.pktid = r.pktid
            WHERE
                t.source = %(src)s AND
                t.dest = %(dest)s AND
                r.nodeid = %(dest)s;
        ''')
    def __init__(self, ctxt, basename, src_nodeid, dest_nodeid):
        # FIXME: generalize this to discriminate packet types
        Analyzer.__init__(self,
            ctxt=ctxt,
            basename=basename,
            title='Power levels of packets from %s to %s' %
                (src_nodeid, dest_nodeid))
        #
        self.query.query = self._query_str % {
                'src': src_nodeid,
                'dest': dest_nodeid,
            }
        #
        data1 = self.new_data2D(
            title='power level txed by %d' % src_nodeid,
            x=0, y=1, style='steps')
        data2 = self.new_data2D(
            title='power level recvd by %d' % dest_nodeid,
            x=0, y=2, style='steps')
        data3 = self.new_data2D(
            title='link gain',
            x=0, y=3, style='steps')
        self.plot.logy = True

class DiscriminatingPowerLevels(Analyzer):
    _query_str = helper.unindent('''
        SELECT t.time, t.txpwr, r.rxpwr, r.rxpwr/t.txpwr
            FROM
                ns_phytx AS t
            INNER JOIN
                ns_phyrx AS r
                ON t.pktid = r.pktid
            WHERE
                %(qualifiers1)s
                %(type_qualifiers)s;
                
        ''')
    def __init__(self, ctxt, basename, rxnode='foo', txnode='foo',
            dest_nodeid='foo', src_nodeid='foo', groups = ['dbrcao']):
        if txnode == 'foo': txnode = src_nodeid
        if src_nodeid == 'foo': src_nodeid = txnode
        if txnode == 'foo' or src_nodeid == 'foo': raise ValueError
        #
        if rxnode == 'foo': rxnode = dest_nodeid
        if dest_nodeid == 'foo': dest_nodeid = rxnode
        if rxnode == 'foo' or dest_nodeid == 'foo': raise ValueError
        # rxnode = The node detecting the packet
        # txnode = The node emitting the packet
        # src_nodeid = The source address of the packet (default: any)
        # dest_nodeid = The destination address of the packet (default: any)
        def x_or_star(x): return x is None and '*' or x
        # FIXME: how to depict src_nodeid in the title?
        Analyzer.__init__(self,
            ctxt=ctxt,
            basename=basename,
            title='Power levels of packets %s -> %s, sensed by %s'
            % (x_or_star(txnode), x_or_star(dest_nodeid), x_or_star(rxnode)) )
        rxqual = txqual = destqual = srcqual = ''
        if rxnode is not None:
            rxqual = 'r.nodeid = %s' % rxnode
        if txnode is not None:
            #txqual = 't.nodeid = %s' % txnode
            #raise NotImplementedError('There is no t.nodeid in the schema')
            # FIXME: There is no t.nodeid in the schema
            pass
        if dest_nodeid is not None:
            destqual = 't.dest = %s' % dest_nodeid
        if src_nodeid is not None:
            srcqual = 't.source = %s' % src_nodeid
        qualifiers = [x for x in [rxqual, txqual, destqual, srcqual] if x]
        qualifiers = ' AND\n        '.join(qualifiers)
        queries = {}
        for group in groups:
            # FIXME: duplicate code...
            qualifiersA = ["'%s'" % x for x in group if x in 'rcao']
            qstrA = 't.pkttype IN (%s)' % ', '.join(qualifiersA)
            if 'd' in group and 'b' in group:
                                qstrB = "(t.pkttype = 'd' OR t.dest = -1)"
            elif 'd' in group:  qstrB = "(t.pkttype = 'd' AND t.dest != -1)"
            elif 'b' in group:  qstrB = "(t.pkttype = 'd' AND t.dest = -1)"
            else: qstrB = ''
            type_qualifiers = 'AND (%s)' % ' OR '.join(
                    [x for x in [qstrA, qstrB] if x])
            query = self.new_query()
            query.query = self._query_str % {
                'qualifiers1': qualifiers,
                'type_qualifiers': type_qualifiers,
            }
            queries[group] = query
        for group in groups:
            self.new_data2D(
                input_filename=queries[group].result_filename,
                title='%s tx packets' % (group),
                x=0, y=1, style='points')
        for group in groups:
            self.new_data2D(
                input_filename=queries[group].result_filename,
                title='%s rx packets' % (group),
                x=0, y=2, style='points')
        self.plot.logy = True

class PowerLevelsEach(DiscriminatingPowerLevels):
    def __init__(self, ctxt, basename, rxnode='foo', txnode='foo',
            dest_nodeid='foo', src_nodeid='foo'):
        DiscriminatingPowerLevels.__init__(
            self, ctxt, basename, rxnode, txnode, dest_nodeid, src_nodeid,
            groups = ['d', 'b', 'r', 'c', 'a', 'o'])

class SystemBandwidth(Analyzer): # called "q5" in queries.sh"
    _recv_query_str = helper.unindent('''
        SELECT
            ROUND(t.time/%(interval)s - 0.5) * %(interval)s AS time_interval,
            SUM(t.bytes)*8/%(interval)s AS bit_rate,
            SUM(t.bytes)*8/%(interval)s/SUM(t.duration) AS basic_rate,
            SUM(t.duration)
            FROM
                ns_phytx AS t
            WHERE
                t.pktid IN (SELECT m.pktid FROM ns_macrx AS m)
            GROUP BY
                time_interval;
        ''')
    _tx_query_str = helper.unindent('''
        SELECT
            ROUND(t.time/%(interval)s - 0.5) * %(interval)s AS time_interval,
            SUM(t.bytes)*8/%(interval)s AS bit_rate,
            SUM(t.bytes)*8/SUM(t.duration) AS basic_rate,
            SUM(t.duration)
            FROM
                ns_phytx AS t
            GROUP BY
                time_interval;
        ''')
    def __init__(self, ctxt, basename, scale=None, interval=1.0):
        # FIXME: generalize this to discriminate packet types
        Analyzer.__init__(self,
            ctxt=ctxt,
            basename=basename,
            title='System bandwidth')
        #
        self.query.query = self._recv_query_str % {
                'interval': interval,
            }
        self.query2 = self.new_query()
        self.query2.query = self._tx_query_str % {
                'interval': interval,
            }
        #
        data1 = self.new_data2D(
            title='receive rate', x=0, y=1, style='steps')
        data2 = self.new_data2D(
            input_filename=self.query2.result_filename,
            title='transmit rate', x=0, y=1, style='steps')
        data3 = self.new_data2D(
            input_filename=self.query2.result_filename,
            title='basic rate', x=0, y=2, style='steps')
        if scale:
            self.plot.yrange = (0, scale)
        else:
            self.plot.yrange = (0, None)

class Bandwidth(Analyzer):
    _recv_query_str = helper.unindent('''
        SELECT
            ROUND(t.time/%(interval)s - 0.5) * %(interval)s AS time_interval,
            SUM(t.bytes)*8/%(interval)s AS bit_rate,
            SUM(t.bytes)*8/%(interval)s/SUM(t.duration) AS basic_rate,
            SUM(t.duration)
            FROM
                ns_phytx AS t
            WHERE
                t.pktid IN (SELECT m.pktid FROM ns_macrx AS m 
                    WHERE %(qualifiers2)s) AND
                %(qualifiers1)s
            GROUP BY
                time_interval;
        ''')
    _tx_query_str = helper.unindent('''
        SELECT
            ROUND(t.time/%(interval)s - 0.5) * %(interval)s AS time_interval,
            SUM(t.bytes)*8/%(interval)s AS bit_rate,
            SUM(t.bytes)*8/SUM(t.duration) AS basic_rate,
            SUM(t.duration)
            FROM
                ns_phytx AS t
            WHERE
                %(qualifiers1)s
            GROUP BY
                time_interval;
        ''')
    def __init__(self, ctxt, basename, src_nodeid=None, dest_nodeid=None, scale=None, interval=1.0):
        def x_or_star(x): return x is None and '*' or x
        Analyzer.__init__(self,
            ctxt=ctxt,
            basename=basename,
            title='Bandwidth of packets %s -> %s' % (x_or_star(src_nodeid),
                x_or_star(dest_nodeid)))
        #
        destqual = srcqual = ''
        if dest_nodeid is not None:
            destqual = 't.dest = %s' % dest_nodeid
        if src_nodeid is not None:
            srcqual = 't.source = %s' % src_nodeid
        qualifiers1 = [x for x in [destqual, srcqual] if x]
        if qualifiers1:
            qualifiers1 = ' AND '.join(qualifiers1)
        else:
            qualifiers1 = '1'
        if dest_nodeid is not None:
            qualifiers2 = 'm.nodeid = %s' % dest_nodeid
        else:
            qualifiers2 = '1'
        #
        self.query.query = self._recv_query_str % {
                'interval': interval,
                'qualifiers1': qualifiers1,
                'qualifiers2': qualifiers2,
            }
        self.query2 = self.new_query()
        self.query2.query = self._tx_query_str % {
                'interval': interval,
                'qualifiers1': qualifiers1,
            }
        #
        data1 = self.new_data2D(
            title='receive rate', x=0, y=1, style='steps')
        data2 = self.new_data2D(
            input_filename=self.query2.result_filename,
            title='transmit rate', x=0, y=1, style='steps')
        data3 = self.new_data2D(
            input_filename=self.query2.result_filename,
            title='basic rate', x=0, y=2, style='steps')
        if scale:
            self.plot.yrange = (0, scale)
        else:
            self.plot.yrange = (0, None)

class SLA3Badness(Analyzer):
    _query_str = helper.unindent('''
        SELECT time, badness_rate, badness_ctr, down_ctr
            FROM
                ns_sla3_badness AS t
            WHERE
                nodeid = %(nodeid)s;
        ''')
    def __init__(self, ctxt, basename, nodeid, scale=None):
        # FIXME: generalize this to discriminate packet types
        Analyzer.__init__(self,
            ctxt=ctxt,
            basename=basename,
            title='SLA3 badness counters for node %s' % nodeid)
        #
        self.query.query = self._query_str % {
                'nodeid': nodeid,
            }
        #
        data1 = self.new_data2D(
            title='down_ctr', x=0, y=3, style='steps')
        data2 = self.new_data2D(
            title='badness_ctr', x=0, y=2, style='steps')
        data3 = self.new_data2D(
            title='badness_rate', x=0, y=1, style='steps')
        if scale:
            self.plot.yrange = (0, scale)
        else:
            self.plot.yrange = (0, None)


class Context(object):
    #
    start_time = helper.attr_accessor('__start_time')
    stop_time = helper.attr_accessor('__stop_time')
    plot_factory = helper.attr_accessor('__plot_factory')
    database_filename = helper.attr_accessor('__database_filename')
    markers = helper.attr_accessor_indirection('markers')
    #
    def __init__(self, markers_filename='log.markers'):
        self.start_time = None
        self.stop_time = None
        self._markers = []
        self._markers_filename = markers_filename
        self.plot_factory = plot.PlotFactoryGNUPLOT()
    def new_plot(self, **kwargs):
        plot = self.plot_factory.new_plot(**kwargs)
        if 'xrange' not in kwargs:
            plot.xrange = (self.start_time, self.stop_time)
        return plot
    def new_data2D(self, **kwargs):
        return self.plot_factory.new_data2D(**kwargs)
    def new_datavertical(self, **kwargs):
        return self.plot_factory.new_datavertical(**kwargs)
    def get_markers(self):
        if not self._markers:
            markers = {}
            for line in open(self._markers_filename):
                line = line.strip()
                x = line.find(' ')
                time, msg = float(line[:x]), line[x+1:]
                if time not in markers:
                    markers[time] = []
                markers[time].append(msg)
            evt_times = markers.keys(); evt_times.sort()
            x = 0
            for time in evt_times:
                id = chr(ord('A') + x)
                self._markers += [(id, time, markers[time])]
                x += 1
        return self._markers

# FIXME: make it easy to implement queries like this:
# SELECT pkttype, count(*) FROM ns_phytx WHERE nodeid != source GROUP BY pkttype;
# SELECT pkttype, count(*) FROM ns_phytx WHERE nodeid == source GROUP BY pkttype;
# SELECT pkttype, count(*) FROM ns_phytx WHERE dest == -1 GROUP BY pkttype;

def test():
    ctxt = Context()
    ctxt.database_filename = 'z.db'
    ctxt.start_time = 0
    ctxt.stop_time = 60
    sla3p = SIR(ctxt, 'q.sla3', 1)
    print sla3p.query.execute_s
    print sla3p.plot.execute_s
    sla3p.execute()

# Catalog of analyzers:
# SLA{1,2,3}Probabilities
# SIR
# SubjectCollisionHistogram

if __name__ == '__main__':
    test()


Generated by GNU enscript 1.6.3.