From 45e600612ec6444e25038f6eff2ad783abf5afdb Mon Sep 17 00:00:00 2001 From: Mia Wu Date: Wed, 3 Jun 2026 14:30:43 -0400 Subject: [PATCH] update to read from db --- dashboard/src/components/TestTable.jsx | 4 +- server/db_py.py | 143 +++++++++++++++++++++++++ server/parser.py | 24 ----- server/scanner.py | 64 ++++++----- 4 files changed, 176 insertions(+), 59 deletions(-) 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