diff --git a/server/db_py.py b/server/db_py.py index d5bca76..0799304 100644 --- a/server/db_py.py +++ b/server/db_py.py @@ -246,43 +246,10 @@ def extract_measurement_metrics(db_file_path): 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"]) + result = {"tputResults": []} rssi_by_endpoint = { row["COM_Dst_endpoint"]: { diff --git a/server/scanner.py b/server/scanner.py index 0fcebd2..1fb8241 100644 --- a/server/scanner.py +++ b/server/scanner.py @@ -26,6 +26,7 @@ _WIN_DRIVE_PATH_RE = re.compile(r"^[A-Za-z]:[\\/]") _SCAN_STATE_LOCK = threading.Lock() _ACTIVE_SCAN_COUNT = 0 _DEFAULT_SCAN_EXCLUSIONS = "" +_ELAPSED_TIME_LINE_RE = re.compile(r"Elapsed\s+time\s*:\s*(\d+):(\d{1,2}):(\d{1,2}(?:\.\d+)?)", re.IGNORECASE) def _parse_scan_exclusions(value): raw = value if value not in (None, "") else _DEFAULT_SCAN_EXCLUSIONS @@ -277,6 +278,45 @@ def _read_text_lines(path): yield line +def _is_candidate_log_file(filename): + name = (filename or "").strip().lower() + if not name or name == "measurement.db": + return False + + if name.endswith(".log") or name.endswith(".txt"): + return True + + return "log" in name + + +def _parse_elapsed_time_to_seconds(line): + match = _ELAPSED_TIME_LINE_RE.search(line or "") + if not match: + return None + + hours = int(match.group(1)) + minutes = int(match.group(2)) + seconds = float(match.group(3)) + return int(hours * 3600 + minutes * 60 + seconds) + + +def extract_elapsed_seconds_from_logs(result_dir_path, files): + elapsed_seconds = None + log_files = [name for name in files if _is_candidate_log_file(name)] + + for log_name in log_files: + log_path = _join_path(result_dir_path, log_name) + try: + for line in _read_text_lines(log_path): + parsed = _parse_elapsed_time_to_seconds(line) + if parsed is not None: + elapsed_seconds = parsed + except OSError as exc: + print(f"[scanner] Cannot read log file {log_path}: {exc}") + + return elapsed_seconds + + def scan_results_only(results_dir, results_dir_ref): """Re-scan only the results directories without clearing or re-scanning targets.""" results_dir = _normalize_input_path(results_dir) @@ -444,6 +484,8 @@ def process_result_dir(results_dir, result_dir_name): print(f"[scanner] Cannot read result dir {result_dir_name}: {exc}") return None + elapsed_seconds = extract_elapsed_seconds_from_logs(result_dir_path, files) + 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}") @@ -451,7 +493,7 @@ def process_result_dir(results_dir, result_dir_name): "test_id": test_id, "device": device, "completed_at": None, - "duration_seconds": None, + "duration_seconds": elapsed_seconds, "tput_results": None, } @@ -467,7 +509,7 @@ def process_result_dir(results_dir, result_dir_name): "test_id": test_id, "device": device, "completed_at": completed_at, - "duration_seconds": data["duration_seconds"], + "duration_seconds": elapsed_seconds, "tput_results": data["tputResults"], } diff --git a/server/watcher.py b/server/watcher.py index ce9cbd7..2f2d0a8 100644 --- a/server/watcher.py +++ b/server/watcher.py @@ -44,8 +44,10 @@ class ResultsDirEventHandler(FileSystemEventHandler): ) def _mark_completed_from_file(self, file_path): - # measurement.db arriving after directory creation should update that single test. - if os.path.basename(file_path).lower() != "measurement.db": + # New measurement/log files arriving after directory creation should update that single test. + filename = os.path.basename(file_path).lower() + is_duration_log = filename.endswith(".log") or filename.endswith(".txt") or "log" in filename + if filename != "measurement.db" and not is_duration_log: return parent_dir = os.path.dirname(file_path.rstrip("\\/"))