diff --git a/dashboard/src/components/TestTable.jsx b/dashboard/src/components/TestTable.jsx
index f9df034..d26c879 100644
--- a/dashboard/src/components/TestTable.jsx
+++ b/dashboard/src/components/TestTable.jsx
@@ -233,8 +233,8 @@ export default function TestTable({ tests = [], allTests = [], isLoading }) {
| STA{r.station} |
{r.tput} Mbps |
- {r.dlRssi} dBm |
- {r.ulRssi} dBm |
+ {r.dlRssi ?? '-'} dBm |
+ {r.ulRssi ?? '-'} dBm |
))}
diff --git a/server/db_py.py b/server/db_py.py
index 24ca79e..475e683 100644
--- a/server/db_py.py
+++ b/server/db_py.py
@@ -1,5 +1,6 @@
import json
import os
+import re
import sqlite3
import threading
from contextlib import contextmanager
@@ -150,6 +151,148 @@ def mark_completed(test_id, device, completed_at, duration_seconds, tput_results
)
+def extract_measurement_metrics(db_file_path):
+ conn = sqlite3.connect(db_file_path)
+ conn.row_factory = sqlite3.Row
+ try:
+ tput_rows = conn.execute(
+ """
+ SELECT CAST(ROUND(AVG(IXC_avg_tput),0)AS INT) AS avg_tput, COM_Dst_endpoint
+ FROM MEASUREMENT
+ WHERE IXC_avg_tput IS NOT NULL
+ GROUP BY COM_Dst_endpoint
+ """
+ ).fetchall()
+
+ rssi_rows = conn.execute(
+ """
+ SELECT
+ COM_Dst_endpoint,
+
+ CAST(
+ AVG(
+ CASE
+ WHEN COM_Dst_endpoint = 'STA4' THEN
+ (
+ (CASE WHEN STA_rssi IS NOT NULL AND STA_rssi != 0 AND STA_rssi >= -199 THEN STA_rssi END) +
+ (CASE WHEN STA_all_rssi_0 IS NOT NULL AND STA_all_rssi_0 != 0 AND STA_all_rssi_0 >= -199 THEN STA_all_rssi_0 END) +
+ (CASE WHEN STA_all_rssi_1 IS NOT NULL AND STA_all_rssi_1 != 0 AND STA_all_rssi_1 >= -199 THEN STA_all_rssi_1 END) +
+ (CASE WHEN STA_all_rssi_2 IS NOT NULL AND STA_all_rssi_2 != 0 AND STA_all_rssi_2 >= -199 THEN STA_all_rssi_2 END)
+ ) * 1.0 /
+ NULLIF(
+ (STA_rssi IS NOT NULL AND STA_rssi != 0 AND STA_rssi >= -199) +
+ (STA_all_rssi_0 IS NOT NULL AND STA_all_rssi_0 != 0 AND STA_all_rssi_0 >= -199) +
+ (STA_all_rssi_1 IS NOT NULL AND STA_all_rssi_1 != 0 AND STA_all_rssi_1 >= -199) +
+ (STA_all_rssi_2 IS NOT NULL AND STA_all_rssi_2 != 0 AND STA_all_rssi_2 >= -199),
+ 0
+ )
+ WHEN COM_Dst_endpoint IN ('STA56', 'STA63') THEN
+ CASE
+ WHEN STA_rssi IS NOT NULL AND STA_rssi != 0 AND STA_rssi >= -199
+ THEN STA_rssi
+ END
+ END
+ ) AS INTEGER
+ ) AS dl_rssi,
+
+ CAST(
+ AVG(
+ (
+ (CASE WHEN AP_sta_rssi_0 IS NOT NULL AND AP_sta_rssi_0 != 0 AND AP_sta_rssi_0 >= -199 THEN AP_sta_rssi_0 END) +
+ (CASE WHEN AP_sta_rssi_1 IS NOT NULL AND AP_sta_rssi_1 != 0 AND AP_sta_rssi_1 >= -199 THEN AP_sta_rssi_1 END) +
+ (CASE WHEN AP_sta_rssi_2 IS NOT NULL AND AP_sta_rssi_2 != 0 AND AP_sta_rssi_2 >= -199 THEN AP_sta_rssi_2 END) +
+ (CASE WHEN AP_sta_rssi_3 IS NOT NULL AND AP_sta_rssi_3 != 0 AND AP_sta_rssi_3 >= -199 THEN AP_sta_rssi_3 END)
+ ) * 1.0 /
+ NULLIF(
+ (AP_sta_rssi_0 IS NOT NULL AND AP_sta_rssi_0 != 0 AND AP_sta_rssi_0 >= -199) +
+ (AP_sta_rssi_1 IS NOT NULL AND AP_sta_rssi_1 != 0 AND AP_sta_rssi_1 >= -199) +
+ (AP_sta_rssi_2 IS NOT NULL AND AP_sta_rssi_2 != 0 AND AP_sta_rssi_2 >= -199) +
+ (AP_sta_rssi_3 IS NOT NULL AND AP_sta_rssi_3 != 0 AND AP_sta_rssi_3 >= -199),
+ 0
+ )
+ ) AS INTEGER
+ ) AS ul_rssi
+
+ FROM MEASUREMENT
+ GROUP BY COM_Dst_endpoint
+ """
+ ).fetchall()
+
+ elapsed_row = conn.execute(
+ """
+ WITH first_last AS (
+ SELECT
+ (SELECT COM_time FROM MEASUREMENT ORDER BY rowid ASC LIMIT 1) AS start_time,
+ (SELECT COM_time FROM MEASUREMENT ORDER BY rowid DESC LIMIT 1) AS end_time
+ )
+ SELECT
+ ROUND(
+ CASE
+ WHEN end_sec >= start_sec THEN
+ end_sec - start_sec
+ ELSE
+ (end_sec - start_sec) + 86400
+ END
+ ) AS elapsed_seconds
+ FROM (
+ SELECT
+ (strftime('%H', start_time) * 3600 +
+ strftime('%M', start_time) * 60 +
+ strftime('%S', start_time)) AS start_sec,
+
+ (strftime('%H', end_time) * 3600 +
+ strftime('%M', end_time) * 60 +
+ strftime('%S', end_time)) AS end_sec
+ FROM first_last
+ );
+ """
+ ).fetchone()
+ finally:
+ conn.close()
+
+ result = {"duration_seconds": None, "tputResults": []}
+
+ if elapsed_row and elapsed_row["elapsed_seconds"] is not None:
+ result["duration_seconds"] = int(elapsed_row["elapsed_seconds"])
+
+ rssi_by_endpoint = {
+ row["COM_Dst_endpoint"]: {
+ "dl_rssi": row["dl_rssi"],
+ "ul_rssi": row["ul_rssi"],
+ }
+ for row in rssi_rows
+ if row["COM_Dst_endpoint"] not in (None, "")
+ }
+
+ tput_results = []
+ for row in tput_rows:
+ dst_endpoint = row["COM_Dst_endpoint"]
+ if dst_endpoint in (None, ""):
+ continue
+
+ match = re.search(r"STA\s*0*(\d+)", str(dst_endpoint), re.IGNORECASE)
+ if not match:
+ continue
+
+ avg_tput = row["avg_tput"]
+ if avg_tput is None:
+ continue
+
+ rssi_values = rssi_by_endpoint.get(dst_endpoint, {})
+
+ tput_results.append(
+ {
+ "station": int(match.group(1)),
+ "tput": int(avg_tput),
+ "dlRssi": rssi_values.get("dl_rssi"),
+ "ulRssi": rssi_values.get("ul_rssi"),
+ }
+ )
+
+ result["tputResults"] = sorted(tput_results, key=lambda item: item["station"])
+ return result
+
+
def set_coe_pair(test_row_id, coe_pair):
json_value = json.dumps(coe_pair or [])
with _tx():
diff --git a/server/parser.py b/server/parser.py
index 66acf22..8302dee 100644
--- a/server/parser.py
+++ b/server/parser.py
@@ -68,27 +68,3 @@ def parse_timestamp(filename):
return f"{year}-{month}-{day}T{hour}:{minute}:{second}"
-def parse_elapsed_time(time_str):
- match = re.match(r"^(\d+):(\d{2}):(\d{2})\.(\d+)$", time_str)
- if not match:
- return None
-
- hours, minutes, seconds, fraction = match.groups()
- return int(hours) * 3600 + int(minutes) * 60 + int(seconds) + float(f"0.{fraction}")
-
-
-def parse_tput_rssi(line):
- match = re.search(
- r"STA(\d+) over angles >> AVG IxChariot TPUT: (\d+) Mbps, AVG DL RSSI: (-?\d+) dBm, AVG UL RSSI: (-?\d+) dBm",
- line,
- )
- if not match:
- return None
-
- station, tput, dl_rssi, ul_rssi = match.groups()
- return {
- "station": int(station),
- "tput": int(tput),
- "dlRssi": int(dl_rssi),
- "ulRssi": int(ul_rssi),
- }
diff --git a/server/scanner.py b/server/scanner.py
index 84cbf36..d7fc7af 100644
--- a/server/scanner.py
+++ b/server/scanner.py
@@ -1,10 +1,12 @@
import os
import re
import threading
+import tempfile
import smbclient
from db_py import (
clear_tests,
+ extract_measurement_metrics,
get_config,
mark_completed,
reset_all_results_state,
@@ -13,11 +15,9 @@ from db_py import (
upsert_test,
)
from parser import (
- parse_elapsed_time,
parse_result_filename,
parse_target_filename,
parse_timestamp,
- parse_tput_rssi,
)
@@ -431,26 +431,20 @@ def process_result_dir(results_dir, result_dir_name):
result_dir_path = _join_path(results_dir, result_dir_name)
try:
- log_files = [
- entry.name
- for entry in _iter_dir_entries(result_dir_path)
- if entry.is_file()
- and entry.name.endswith(".txt")
- and test_id in entry.name
- ]
+ files = [entry.name for entry in _iter_dir_entries(result_dir_path) if entry.is_file()]
except OSError as exc:
print(f"[scanner] Cannot read result dir {result_dir_name}: {exc}")
return
- if not log_files:
- print(f"[scanner] completed (no log yet): {test_id}")
+ measurement_db = next((name for name in files if name.lower() == "measurement.db"), None)
+ if not measurement_db:
+ print(f"[scanner] completed (no measurement.db yet): {test_id}")
mark_completed(test_id, device, None, None)
return
- latest_log = sorted(log_files)[-1]
- log_path = _join_path(result_dir_path, latest_log)
- completed_at = parse_timestamp(latest_log)
- data = extract_log_data(log_path)
+ db_path = _join_path(result_dir_path, measurement_db)
+ completed_at = parse_timestamp(result_dir_name)
+ data = extract_measurement_data(db_path)
'''print(
f"[scanner] completed: {test_id} device={device} "
@@ -472,25 +466,29 @@ def parse_deleted_result_dir_name(dir_name):
return test_id, device
-def extract_log_data(log_file_path):
- elapsed_regex = re.compile(r"\[.*?INFO\]\s+Elapsed time\s*:\s*([\d]+:[\d]{2}:[\d]{2}\.[\d]+)")
- tput_regex = re.compile(
- r"\[.*?INFO\]\s+STA(\d+) over angles >> AVG IxChariot TPUT: (\d+) Mbps, AVG DL RSSI: (-?\d+) dBm, AVG UL RSSI: (-?\d+) dBm"
- )
-
+def extract_measurement_data(db_file_path):
result = {"duration_seconds": None, "tputResults": []}
- try:
- for line in _read_text_lines(log_file_path):
- time_match = elapsed_regex.search(line)
- if time_match:
- result["duration_seconds"] = parse_elapsed_time(time_match.group(1))
- continue
+ local_db_path = None
- if tput_regex.search(line):
- parsed = parse_tput_rssi(line)
- if parsed:
- result["tputResults"].append(parsed)
- except OSError as exc:
- print(f"[scanner] Cannot read log file {log_file_path}: {exc}")
+ try:
+ source_db_path = _normalize_input_path(db_file_path)
+ if _is_unc_path(source_db_path):
+ _register_smb_session_if_needed(source_db_path)
+ with smbclient.open_file(source_db_path, mode="rb") as src_fh:
+ with tempfile.NamedTemporaryFile(delete=False, suffix=".db") as tmp_fh:
+ tmp_fh.write(src_fh.read())
+ local_db_path = tmp_fh.name
+ source_db_path = local_db_path
+
+ result = extract_measurement_metrics(source_db_path)
+
+ except (OSError, ValueError, Exception) as exc:
+ print(f"[scanner] Cannot read measurement db {db_file_path}: {exc}")
+ finally:
+ if local_db_path:
+ try:
+ os.remove(local_db_path)
+ except OSError:
+ pass
return result