../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.