To receive notifications about scheduled maintenance, please subscribe to the mailing-list gitlab-operations@sympa.ethz.ch. You can subscribe to the mailing-list at https://sympa.ethz.ch

Commit ae407565 authored by Reto Da Forno's avatar Reto Da Forno
Browse files

linktest adjusted

parent b35629c8
......@@ -128,7 +128,6 @@ basedir = /home/flocklab/testmanagementserver
scheduler = /home/flocklab/testmanagementserver/flocklab_scheduler.py
archivedir = /home/flocklab/testarchive
logdir = /home/flocklab/logs ;log directory for web
tempdir = /home/flocklab/tmp
venvwrapper = /home/flocklab/tools/wrapper.sh ;activates python virtual environment (leave blank if no venv)
toolsdir = /home/flocklab/tools
......
......@@ -365,7 +365,8 @@ CREATE TABLE `tbl_serv_link_measurements` (
`platform_fk` int(10) unsigned NOT NULL,
`begin` datetime NOT NULL,
`radio_cfg` enum('', 'fsk_868','lora_868') COLLATE utf8_bin,
`links` longtext COLLATE utf8_bin,
`links` mediumblob,
`links_html` longtext COLLATE utf8_bin,
PRIMARY KEY (`serv_link_measurements_key`),
KEY `date_begin` (`begin`),
KEY `fk_tbl_serv_link_measurements_platforms` (`platform_fk`),
......
......@@ -1080,7 +1080,8 @@ def evaluate_linkmeasurement(testid, cur):
return errors
# Run evaluation script
logger.debug("Evaluating link measurements.")
cmd = [flocklab.config.get('dispatcher', 'linktestevalscript'), _serial_service_file]
tempdir = tempfile.mkdtemp()
cmd = [flocklab.config.get('dispatcher', 'linktestevalscript'), _serial_service_file, tempdir] # arguments are input file and output directory
p = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, universal_newlines=True)
out, err = p.communicate()
rs = p.returncode
......@@ -1089,14 +1090,17 @@ def evaluate_linkmeasurement(testid, cur):
logger.error(msg)
errors.append(msg)
else:
logger.debug(out)
logger.debug("Link measurement evaluations finished.")
# Get platform info
sql = """SELECT `c`.`platforms_fk`, `d`.`name` FROM `tbl_serv_tests` as `a`
LEFT JOIN `tbl_serv_map_test_observer_targetimages` as `b` ON (`a`.serv_tests_key = `b`.test_fk)
LEFT JOIN `tbl_serv_targetimages` AS `c` ON (`b`.`targetimage_fk` = `c`.`serv_targetimages_key`)
LEFT JOIN `tbl_serv_platforms` AS `d` ON (`c`.`platforms_fk` = `d`.`serv_platforms_key`)
WHERE `a`.serv_tests_key = %s LIMIT 1"""
cur.execute(sql % str(testid))
sql = """
SELECT `c`.`platforms_fk`, `d`.`name` FROM `tbl_serv_tests` as `a`
LEFT JOIN `tbl_serv_map_test_observer_targetimages` as `b` ON (`a`.serv_tests_key = `b`.test_fk)
LEFT JOIN `tbl_serv_targetimages` AS `c` ON (`b`.`targetimage_fk` = `c`.`serv_targetimages_key`)
LEFT JOIN `tbl_serv_platforms` AS `d` ON (`c`.`platforms_fk` = `d`.`serv_platforms_key`)
WHERE `a`.serv_tests_key = %s LIMIT 1
""" % str(testid)
cur.execute(sql)
ret = cur.fetchone()
if not ret:
msg = "Could not determine platform for test %d" % testid
......@@ -1106,26 +1110,23 @@ def evaluate_linkmeasurement(testid, cur):
platform_fk = ret[0]
platform_name = ret[1]
# Load the results
resultspath = os.path.realpath("data") # TODO remove hardcoded path
resultsfile = os.path.join(resultspath, "linktest_map.html")
if not os.path.isfile(resultsfile):
msg = "Linktest results file %s not found." % (resultsfile)
resultsfile_html = os.path.join(tempdir, "linktest_map.html")
resultsfile_data = os.path.join(tempdir, "linktest_data.pkl")
if not os.path.isfile(resultsfile_html) or not os.path.isfile(resultsfile_data):
msg = "Linktest results file %s or %s not found." % (resultsfile_html, resultsfile_data)
logger.error(msg)
errors.append(msg)
else:
resultsdata = ""
with open(resultsfile, 'r') as rf:
resultsdata = rf.read()
z = re.findall("<body>(.*)</body>", resultsdata, re.MULTILINE | re.DOTALL)
if z:
resultsdata = z[0]
resultshtml = ""
with open(resultsfile_html, 'r') as f:
resultshtml = f.read()
resultsdata = None
with open(resultsfile_data, 'rb') as f:
resultsdata = f.read()
# Store results in DB
logger.debug("Storing XML file in DB...")
cur.execute("DELETE FROM `tbl_serv_link_measurements` WHERE `test_fk`=%s" % str(testid))
cur.execute("INSERT INTO `tbl_serv_link_measurements` (`test_fk`, `platform_fk`, `begin`, `radio_cfg`, `links`) VALUES (%s, %s, %s, %s, %s)", ((str(testid), platform_fk, teststarttime, '', resultsdata)))
# Remove the temporary files
if os.path.isdir(resultspath):
shutil.rmtree(resultspath)
cur.execute("INSERT INTO `tbl_serv_link_measurements` (`test_fk`, `platform_fk`, `begin`, `radio_cfg`, `links`, `links_html`) VALUES (%s, %s, %s, %s, %s, %s)", (str(testid), platform_fk, teststarttime, '', resultsdata, resultshtml))
return errors
### END evaluate_linkmeasurement()
......
......@@ -1067,10 +1067,12 @@ def schedule_linktest(cur, cn, debug=False):
logger.error("Could not register link test %s (%s)" % (linktestfile, err.strip()))
else:
# flag in db
sql = "INSERT INTO `tbl_serv_link_measurements` (test_fk, begin, platform_fk, links, radio_cfg) \
SELECT %s, NOW(), serv_platforms_key, NULL, '' from tbl_serv_platforms WHERE serv_platforms_key = (SELECT `b`.platforms_fk FROM \
flocklab.tbl_serv_map_test_observer_targetimages as `a` left join \
flocklab.tbl_serv_targetimages as `b` ON (a.targetimage_fk = b.serv_targetimages_key) WHERE `a`.test_fk=%s ORDER BY serv_platforms_key LIMIT 1)" % (testid.group(1), testid.group(1))
sql = """
INSERT INTO `tbl_serv_link_measurements` (test_fk, begin, platform_fk, radio_cfg)
SELECT %s, NOW(), serv_platforms_key, '' from tbl_serv_platforms WHERE serv_platforms_key = (SELECT `b`.platforms_fk FROM
flocklab.tbl_serv_map_test_observer_targetimages as `a` left join
flocklab.tbl_serv_targetimages as `b` ON (a.targetimage_fk = b.serv_targetimages_key) WHERE `a`.test_fk=%s ORDER BY serv_platforms_key LIMIT 1)
""" % (testid.group(1), testid.group(1))
cur.execute(sql)
cn.commit()
# Delete the lockfile:
......
......@@ -39,98 +39,58 @@ import pandas as pd
import json
from collections import OrderedDict
import pickle
import re
from flocklab import Flocklab
from flocklab import *
fl = Flocklab()
assertionOverride = False
outputdir = "./data"
################################################################################
# check arguments (either at least one test ID or the path to test results must be specified)
if len(sys.argv) < 2:
print("no test number or path specified!")
if len(sys.argv) < 3:
print("Not enough arguments provided.\nUsage:\n\t%s [input filename] [output directory]\n" % (__file__))
sys.exit(1)
inputfile = sys.argv[1]
if not "csv" in inputfile or not os.path.isfile(inputfile):
print("invalid input file '%s'" % (inputfile))
sys.exit(1)
outputdir = sys.argv[2]
if not os.path.isdir(outputdir):
print("invalid output directory '%s'" % outputdir)
sys.exit(1)
################################################################################
def getJson(text):
'''Find an convert json in a single line from serial output. Returns None if no valid json could be found.
'''
ret = None
# find index
idx = 0
if not '{' in text:
return ret
for i in range(len(text)):
if text[i] == '{':
idx = i
break
try:
ret = json.loads(text[idx:], strict=False)
except json.JSONDecodeError:
print('WARNING: json could not be parsed: {}'.format(text[idx:]))
return ret
def getRows(roundNo, gDf):
'''Extract rows for requested round from gDf
'''
inRange = False
ret = []
for d in gDf.data.to_list():
if d['type'] == 'StartOfRound':
if d['node'] == roundNo:
inRange = True
elif d['type'] == 'EndOfRound':
if d['node'] == roundNo:
break
elif inRange:
ret.append(d)
return ret
################################################################################
for arg in sys.argv[1:]:
seriallog = ""
testid = None
if os.path.isfile(arg) and "serial" in arg:
seriallog = arg
elif os.path.isdir(arg):
seriallog = os.path.join(arg, "serial.csv")
else:
try:
testid = int(arg)
print('testid: {}'.format(testid))
seriallog = os.getcwd() + "/{}/serial.csv".format(testid)
except:
print("invalid argument %s" % arg)
if testid:
# download test results if directory does not exist
if not os.path.isfile(seriallog):
fl.getResults(testid)
if not os.path.isfile(seriallog):
print("file %s not found" % seriallog)
continue
df = fl.serial2Df(seriallog, error='ignore')
def evalSerialLog():
df = fl.serial2Df(inputfile, error='ignore')
df.sort_values(by=['timestamp', 'observer_id'], inplace=True, ignore_index=True)
# convert output with valid json to dict and remove other rows
keepMask = []
resList = []
for idx, row in df.iterrows():
jsonDict = getJson(row['output'])
resList = []
for k, row in df.iterrows():
# find and convert json in a single line from serial output
jsonDict = None
res = re.search("({.*})", row['output'])
if res:
try:
jsonDict = json.loads(res.group(0), strict=False)
except json.JSONDecodeError:
print('WARNING: json could not be parsed: {}'.format(row['output']))
keepMask.append(1 if jsonDict else 0)
if jsonDict:
resList.append(jsonDict)
dfd = df[np.asarray(keepMask).astype(bool)].copy()
dfd = df[np.asarray(keepMask).astype(bool)].copy()
dfd['data'] = resList
# figure our list of nodes available in the serial trace
......@@ -138,18 +98,18 @@ for arg in sys.argv[1:]:
numNodes = len(nodeList)
# prepare
groups = dfd.groupby('observer_id')
prrMatrix = np.empty( (numNodes, numNodes,) ) * np.nan # packet reception ratio (PRR)
groups = dfd.groupby('observer_id')
prrMatrix = np.empty( (numNodes, numNodes,) ) * np.nan # packet reception ratio (PRR)
crcErrorMatrix = np.empty( (numNodes, numNodes,) ) * np.nan # ratio of packets with CRC error
pathlossMatrix = np.empty( (numNodes, numNodes,) ) * np.nan # path loss
# Get TestConfig and RadioConfig & check for consistency
testConfigDict = OrderedDict()
testConfigDict = OrderedDict()
radioConfigDict = OrderedDict()
for node in nodeList:
testConfigFound = False
testConfigFound = False
radioConfigFound = False
testConfigDict[node] = None
testConfigDict[node] = None
radioConfigDict[node] = None
gDf = groups.get_group(node)
for d in gDf.data.to_list():
......@@ -189,20 +149,29 @@ for arg in sys.argv[1:]:
for roundIdx, roundNo in enumerate(nodeList):
# for roundNo in [nodeList[0]]:
# print('Round: {}'.format(roundNo))
txNode = roundNo
txNode = roundNo
txNodeIdx = roundIdx
numTx = 0
numTx = 0
numRxDict = OrderedDict()
numCrcErrorDict = OrderedDict()
rssiAvgDict = OrderedDict()
rssiAvgDict = OrderedDict()
# iterate over nodes
for nodeIdx, node in enumerate(nodeList):
rows = getRows(roundNo, groups.get_group(node))
# extract rows for requested round from gDf
inRange = False
rows = []
for d in groups.get_group(node).data.to_list():
if d['type'] == 'StartOfRound':
if d['node'] == roundNo:
inRange = True
elif d['type'] == 'EndOfRound':
if d['node'] == roundNo:
break
elif inRange:
rows.append(d)
if node == txNode:
# print(node)
txDoneList = [elem for elem in rows if (elem['type']=='TxDone')]
numTx = len(txDoneList)
# print(numTx, testConfig['numTx'])
assert numTx == testConfig['numTx']
else:
rxDoneList = [elem for elem in rows if (elem['type']=='RxDone' and elem['key']==testConfig['key'] and elem['crc_error']==0)]
......@@ -210,18 +179,15 @@ for arg in sys.argv[1:]:
numRxDict[node] = len(rxDoneList)
numCrcErrorDict[node] = len(crcErrorList)
rssiAvgDict[node] = np.mean([elem['rssi'] for elem in rxDoneList]) if len(rxDoneList) else np.nan
# fill PRR matrix
for rxNode, numRx in numRxDict.items():
rxNodeIdx = nodeList.index(rxNode)
prrMatrix[txNodeIdx][rxNodeIdx] = numRx/numTx
# fill CRC error matrix
for rxNode, numCrcError in numCrcErrorDict.items():
rxNodeIdx = nodeList.index(rxNode)
crcErrorMatrix[txNodeIdx][rxNodeIdx] = numCrcError/numTx
# NOTE: some CRC error cases are ignored while getting the rows (getRows()) because the json parser cannot parse the RxDone output
# fill path loss matrix
for rxNode, rssi in rssiAvgDict.items():
rxNodeIdx = nodeList.index(rxNode)
......@@ -232,10 +198,7 @@ for arg in sys.argv[1:]:
pathlossMatrixDf = pd.DataFrame(data=pathlossMatrix, index=nodeList, columns=nodeList)
# save obtained data to file (including nodeList to resolve idx <-> node ID relations)
if testid:
pklPath = '{}/linktest_data_{}.pkl'.format(outputdir, testid)
else:
pklPath = '{}/linktest_data.pkl'.format(outputdir)
pklPath = '{}/linktest_data.pkl'.format(outputdir)
os.makedirs(os.path.split(pklPath)[0], exist_ok=True)
with open(pklPath, 'wb' ) as f:
d = {
......@@ -250,36 +213,36 @@ for arg in sys.argv[1:]:
# save colored tables to html
html_template = '''
<table>
<tr>
<th><br />Radio Config</th>
</tr>
<tr>
<td>{config}</td>
</tr>
<tr>
<th><br />Pathloss Matrix [dB]</th>
</tr>
<tr>
<td>{pathloss_html}</td>
</tr>
<tr>
<td> </td>
</tr>
<tr>
<th><br />PRR Matrix</th>
</tr>
<tr>
<td>{prr_html}</td>
</tr>
<tr>
<th><br />CRC Error Matrix</th>
</tr>
<tr>
<td>{crc_error_html}</td>
</tr>
</table>
'''
<table>
<tr>
<th><br />Radio Config</th>
</tr>
<tr>
<td>{config}</td>
</tr>
<tr>
<th><br />Pathloss Matrix [dB]</th>
</tr>
<tr>
<td>{pathloss_html}</td>
</tr>
<tr>
<td> </td>
</tr>
<tr>
<th><br />PRR Matrix</th>
</tr>
<tr>
<td>{prr_html}</td>
</tr>
<tr>
<th><br />CRC Error Matrix</th>
</tr>
<tr>
<td>{crc_error_html}</td>
</tr>
</table>
'''
pathlossMatrixDf_styled = (pathlossMatrixDf.style
.background_gradient(cmap='summer', axis=None)
......@@ -300,18 +263,17 @@ for arg in sys.argv[1:]:
)
crc_error_html = crcErrorMatrixDf_styled.render()
if testid:
htmlPath = '{}/linktest_map_{}.html'.format(outputdir, testid)
else:
htmlPath = '{}/linktest_map.html'.format(outputdir)
htmlPath = '{}/linktest_map.html'.format(outputdir)
os.makedirs(os.path.split(htmlPath)[0], exist_ok=True)
with open(htmlPath,"w") as fp:
fp.write(html_template.format(
pathloss_html=pathloss_html,
prr_html=prr_html,
crc_error_html=crc_error_html,
config='Frequency: %.3fMHz, TX power: %ddBm, modulation: %d, datarate: %d, bandwidth: %d, coderate: %d' % (radioConfig['frequency'] / 1000000.0, radioConfig['txPower'], radioConfig['modulation'], radioConfig['datarate'], radioConfig['bandwidth'], radioConfig['coderate'])
config='Frequency: %.3fMHz, TX power: %ddBm, modem: %d, datarate: %d, bandwidth: %d, coderate: %d' % (radioConfig['frequency'] / 1000000.0, radioConfig['txPower'], radioConfig['modulation'], radioConfig['datarate'], radioConfig['bandwidth'], radioConfig['coderate'])
)
)
print("results saved in %s" % os.path.realpath(outputdir))
if __name__ == "__main__":
evalSerialLog()
......@@ -23,11 +23,16 @@ body {
a {
color: #28549f;
text-decoration: none;
}
a img {
border: none;
}
a:hover {
color: #e08e00;
}
div.header img {
vertical-align: middle;
border: 0px none;
......@@ -45,7 +50,7 @@ div.container {
/* background-image: url(../pics/bg_container.png);*/
/* background-repeat: repeat-x; */
border: 0px solid #DDD;
font-family: Verdana, Arial, Helvetica, sans-serif;
font-family: Arial, Helvetica;
font-size: small;
}
......@@ -423,7 +428,7 @@ div.content table.recaptchatable {
}
#content .ui-widget {
font-family: Verdana, Arial, Helvetica, sans-serif;
font-family: Arial, Helvetica;
font-size: small;
}
......
......@@ -38,7 +38,7 @@
// hide ui
if ($(".dlpane").length == 0) {
$("body").first().prepend('<div class="dlpane" style="position:absolute;margin:0;z-index:10000;width:100%;height:100%;background-color:#000;opacity:0.4;filter:alpha(opacity=40);"><\/div>'+
'<div class="dlpane" style="position:absolute;font-family: Verdana, Arial, Helvetica, sans-serif;width:100%;z-index:10001;background-color:gray"><div class="info" style="width:100%"><div style="float:left;"><img height="50" width="50" alt="" src="pics/icons/wait.gif"><\/div>'+
'<div class="dlpane" style="position:absolute;font-family: Arial, Helvetica;width:100%;z-index:10001;background-color:gray"><div class="info" style="width:100%"><div style="float:left;"><img height="50" width="50" alt="" src="pics/icons/wait.gif"><\/div>'+
'<p>Please wait while test results are being fetched (Id '+testid+'). Depending on the amount of data this could take several minutes.. <\/p><\/div><\/div>'
);
}
......@@ -257,7 +257,7 @@ echo '<h1>Manage Tests for '.$_SESSION['firstname'] . ' ' . $_SESSION['lastname'
<th width="100px">Title</th>
<th width="150px">Description</th>
<th width="35px">IMG</th>
<th width="35px" class='qtip_show' title='State'>State</th>
<th width="40px" class='qtip_show' title='State'>State</th>
<th>Start</th>
<th>End</th>
<th width="80px" class='qtip_show' title='Actions'>Actions</th>
......
......@@ -41,6 +41,24 @@
WHERE `a`.links IS NOT NULL";
$rs = mysqli_query($db, $sql) or flocklab_die('Cannot get link test information from database because: ' . mysqli_error($db));
if (isset($_POST['action']) && $_POST['action'] == 'dl') {
if (isset($_POST['test_id']) && is_numeric($_POST['test_id']) && intval($_POST['test_id']) >= 0) {
$sql = "SELECT links FROM `flocklab`.`tbl_serv_link_measurements`
WHERE test_fk = ".sprintf("%d", intval($_POST['test_id']));
$rs = mysqli_query($db, $sql) or flocklab_die('Cannot get link test information from database because: ' . mysqli_error($db));
if ($rs !== false) {
$row = mysqli_fetch_assoc($rs);
header("Content-Type: binary/octet-stream");
header("Content-Disposition: attachment; filename=\"linktest_".$_POST['test_id'].".pkl\"");
echo $row['links'];
} else {
header("HTTP/1.0 400 Bad Request");
}
mysqli_close($db);
}
exit();
}
// get currently used observers
$platforms = Array();
while ($row = mysqli_fetch_array($rs)) {
......@@ -48,17 +66,19 @@
}
$linktestdata = "";
if (isset($_POST['test_id'])) {
if (isset($_POST['test_id']) && is_numeric($_POST['test_id']) && intval($_POST['test_id']) >= 0) {
// fetch the linktest data
$sql = "SELECT links FROM `flocklab`.`tbl_serv_link_measurements`
$sql = "SELECT links_html FROM `flocklab`.`tbl_serv_link_measurements`
WHERE test_fk = ".sprintf("%d", intval($_POST['test_id']));
$rs = mysqli_query($db, $sql) or flocklab_die('Cannot get link test information from database because: ' . mysqli_error($db));
if ($row = mysqli_fetch_array($rs)) {
$linktestdata = $row[0];
}
} else {
$_POST['test_id'] = -1;
}
$linktests = Array();
if (isset($_POST['platform']) && intval($_POST['platform']) >= 0) {
if (isset($_POST['platform']) && is_numeric($_POST['platform']) && intval($_POST['platform']) >= 0) {
// get a list of all linktest for the specified platform
$sql = "SELECT a.test_fk, b.name, a.begin, a.radio_cfg FROM `flocklab`.`tbl_serv_link_measurements` AS `a`
LEFT JOIN tbl_serv_platforms AS `b` ON `a`.platform_fk = `b`.serv_platforms_key
......@@ -68,14 +88,12 @@
while ($row = mysqli_fetch_array($rs)) {
$linktests[] = array('test_id' => $row[0], 'start_time' => $row[2], 'radio_cfg' => $row[3]);
}
} else {
$_POST['platform'] = -1;
}
mysqli_close($db);
?>
<script type="text/javascript" src="scripts/jquery-ui-1.8.21.custom.min.js"></script>
<script type="text/javascript" src="scripts/protovis-d3.3.js"></script>
<script type="text/javascript" src="scripts/flocklab-observer-positions.js"></script>
<script type="text/javascript" src="scripts/jquery.cookie.js"></script>
<h1>Link Tests</h1>
<form>
<table>
......@@ -117,9 +135,20 @@
</tr>
</tbody>
</table>
<table>
<tr>
<td>
<?php
if ($_POST['test_id'] >= 0) {
echo "<a href=# onclick='$(document.selecttest.action).val(\"dl\");document.selecttest.submit()'>Download test results in a machine-readable format (pickle).</a>";
}
?>
</td>
</tr>
</table>
</form>
<?php
echo '<form name="selecttest" method="post" action="'.$_SERVER['PHP_SELF'].'"><input type="hidden" name="platform" value="'.$_POST['platform'].'"><input type="hidden" name="test_id" value="'.$_POST['test_id'].'"></form>';
echo '<form name="selecttest" method="post" action="'.$_SERVER['PHP_SELF'].'"><input type="hidden" name="platform" value="'.$_POST['platform'].'"><input type="hidden" name="test_id" value="'.$_POST['test_id'].'"><input type="hidden" name="action" value=""></form>';
?>
<br />
<br />
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment