From 6d4ecaef02055cef37d7f4ccbb02882936fa3e89 Mon Sep 17 00:00:00 2001 From: Ford McDonald Date: Tue, 3 Jun 2025 13:46:35 -0400 Subject: [PATCH 1/4] concurrent exports to test speed performance --- xnat_admin_tools/test_xnat_api.py | 56 +++++++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) diff --git a/xnat_admin_tools/test_xnat_api.py b/xnat_admin_tools/test_xnat_api.py index 7ad6d34..7fd891b 100644 --- a/xnat_admin_tools/test_xnat_api.py +++ b/xnat_admin_tools/test_xnat_api.py @@ -6,9 +6,11 @@ import sys import requests +import time from dotenv import load_dotenv from requests.auth import HTTPBasicAuth from xnat_tools.dicom_export import dicom_export +from concurrent.futures import ThreadPoolExecutor, TimeoutError load_dotenv() @@ -19,6 +21,10 @@ # Only Export Scans < 100MB MAX_TEST_EXPORT_SIZE = 100000000 +if BASE_URL == "https://qa-xnat.bnc.brown.edu": + session_ids = ["XNAT_DEV_E00016", "XNAT_DEV_E00017"] +else: + session_ids = ["XNAT_E00114", "XNAT_E00152"] # Helper function for making requests def make_request(method, endpoint, data=None, params=None): @@ -54,6 +60,35 @@ def extract_valid_sequence(session_report): return rand_scan["data_fields"]["ID"] +def run_export_for_session(sess_id): + print(f"Starting export for session {sess_id}...") + out_dir = f"./tests/xnat2bids_{sess_id}" + + if os.path.exists(out_dir): + shutil.rmtree(out_dir, ignore_errors=True) + os.makedirs(out_dir, exist_ok=True) + + try: + dicom_export( + session=sess_id, + bids_root_dir=out_dir, + user=USERNAME, + password=PASSWORD, + host=BASE_URL, + session_suffix="-1", + bidsmap_file="", + includeseq=[], + skipseq=[], + log_id=f"pytest-{sess_id}", + verbose=0, + overwrite=True, + validate_frames=False, + correct_dicoms_config="", + ) + print(f"Export for session {sess_id} completed.") + except Exception as e: + print(f"Export for session {sess_id} failed: {e}") + # Test Suite def test_xnat_api(): print("Starting XNAT API tests...") @@ -184,6 +219,27 @@ def test_xnat_api(): assert len(dicom_files) > 0, "DICOM export failed: No files found" print(f"DICOM export successful. Files exported: {len(dicom_files)}") + # Concurrent export speed test + start_time = time.time() + TIMEOUT = 900 + + with ThreadPoolExecutor(max_workers=2) as executor: + futures = [ + executor.submit(run_export_for_session, session_ids[0]), + executor.submit(run_export_for_session, session_ids[1]), + ] + + try: + for future in futures: + future.result(timeout=TIMEOUT) + except TimeoutError: + raise AssertionError(f"Test failed: DICOM export took longer than {TIMEOUT} seconds.") + + # End timing + end_time = time.time() + duration = end_time - start_time + + print(f"Concurrent exports completed in {duration:.2f} seconds.") # Run the tests def main(): From ee4056acd06f965b24e32b938f81a4996edbe621 Mon Sep 17 00:00:00 2001 From: Ford McDonald Date: Tue, 3 Jun 2025 13:57:07 -0400 Subject: [PATCH 2/4] pre-commit linting changes --- xnat_admin_tools/test_xnat_api.py | 22 ++++++++++++---------- 1 file changed, 12 insertions(+), 10 deletions(-) diff --git a/xnat_admin_tools/test_xnat_api.py b/xnat_admin_tools/test_xnat_api.py index 7fd891b..d82547c 100644 --- a/xnat_admin_tools/test_xnat_api.py +++ b/xnat_admin_tools/test_xnat_api.py @@ -4,13 +4,13 @@ import random import shutil import sys +import time +from concurrent.futures import ThreadPoolExecutor, TimeoutError import requests -import time from dotenv import load_dotenv from requests.auth import HTTPBasicAuth from xnat_tools.dicom_export import dicom_export -from concurrent.futures import ThreadPoolExecutor, TimeoutError load_dotenv() @@ -26,7 +26,7 @@ else: session_ids = ["XNAT_E00114", "XNAT_E00152"] -# Helper function for making requests + def make_request(method, endpoint, data=None, params=None): url = f"{BASE_URL}{endpoint}" try: @@ -89,6 +89,7 @@ def run_export_for_session(sess_id): except Exception as e: print(f"Export for session {sess_id} failed: {e}") + # Test Suite def test_xnat_api(): print("Starting XNAT API tests...") @@ -219,28 +220,29 @@ def test_xnat_api(): assert len(dicom_files) > 0, "DICOM export failed: No files found" print(f"DICOM export successful. Files exported: {len(dicom_files)}") - # Concurrent export speed test + # Concurrent export speed test start_time = time.time() - TIMEOUT = 900 + TIMEOUT = 900 with ThreadPoolExecutor(max_workers=2) as executor: futures = [ executor.submit(run_export_for_session, session_ids[0]), executor.submit(run_export_for_session, session_ids[1]), ] - + try: for future in futures: future.result(timeout=TIMEOUT) except TimeoutError: - raise AssertionError(f"Test failed: DICOM export took longer than {TIMEOUT} seconds.") - - # End timing + raise AssertionError( + f"Test failed: DICOM export took longer than {TIMEOUT} seconds." + ) + end_time = time.time() duration = end_time - start_time print(f"Concurrent exports completed in {duration:.2f} seconds.") -# Run the tests + def main(): test_xnat_api() From 1c2e991acab4d1e35ef7ff7a267501ae9bf61899 Mon Sep 17 00:00:00 2001 From: Ford McDonald Date: Tue, 3 Jun 2025 14:45:30 -0400 Subject: [PATCH 3/4] ensure threads execute in parallel --- xnat_admin_tools/test_xnat_api.py | 25 +++++++++++++------------ 1 file changed, 13 insertions(+), 12 deletions(-) diff --git a/xnat_admin_tools/test_xnat_api.py b/xnat_admin_tools/test_xnat_api.py index d82547c..5b9c3f0 100644 --- a/xnat_admin_tools/test_xnat_api.py +++ b/xnat_admin_tools/test_xnat_api.py @@ -5,7 +5,7 @@ import shutil import sys import time -from concurrent.futures import ThreadPoolExecutor, TimeoutError +from concurrent.futures import ThreadPoolExecutor, wait import requests from dotenv import load_dotenv @@ -225,18 +225,19 @@ def test_xnat_api(): TIMEOUT = 900 with ThreadPoolExecutor(max_workers=2) as executor: - futures = [ - executor.submit(run_export_for_session, session_ids[0]), - executor.submit(run_export_for_session, session_ids[1]), - ] + futures = [executor.submit(run_export_for_session, sid) for sid in session_ids] + + done, not_done = wait(futures, timeout=TIMEOUT) + + if not_done: + raise AssertionError( + f"Test failed: one or more exports exceeded {TIMEOUT} seconds." + ) - try: - for future in futures: - future.result(timeout=TIMEOUT) - except TimeoutError: - raise AssertionError( - f"Test failed: DICOM export took longer than {TIMEOUT} seconds." - ) + for future in done: + exception = future.exception() + if exception: + raise exception end_time = time.time() duration = end_time - start_time From c227b94d931bb2265c3ed23bc9d5c1dcc268084f Mon Sep 17 00:00:00 2001 From: Ford McDonald Date: Tue, 3 Jun 2025 15:49:12 -0400 Subject: [PATCH 4/4] export only one session --- xnat_admin_tools/test_xnat_api.py | 23 +++++------------------ 1 file changed, 5 insertions(+), 18 deletions(-) diff --git a/xnat_admin_tools/test_xnat_api.py b/xnat_admin_tools/test_xnat_api.py index 5b9c3f0..15cc80d 100644 --- a/xnat_admin_tools/test_xnat_api.py +++ b/xnat_admin_tools/test_xnat_api.py @@ -5,7 +5,6 @@ import shutil import sys import time -from concurrent.futures import ThreadPoolExecutor, wait import requests from dotenv import load_dotenv @@ -22,9 +21,9 @@ MAX_TEST_EXPORT_SIZE = 100000000 if BASE_URL == "https://qa-xnat.bnc.brown.edu": - session_ids = ["XNAT_DEV_E00016", "XNAT_DEV_E00017"] + session_ids = "XNAT_DEV_E00017" else: - session_ids = ["XNAT_E00114", "XNAT_E00152"] + session_ids = "XNAT_E00114" def make_request(method, endpoint, data=None, params=None): @@ -222,26 +221,14 @@ def test_xnat_api(): # Concurrent export speed test start_time = time.time() - TIMEOUT = 900 + TIMEOUT = 1200 - with ThreadPoolExecutor(max_workers=2) as executor: - futures = [executor.submit(run_export_for_session, sid) for sid in session_ids] - - done, not_done = wait(futures, timeout=TIMEOUT) - - if not_done: - raise AssertionError( - f"Test failed: one or more exports exceeded {TIMEOUT} seconds." - ) - - for future in done: - exception = future.exception() - if exception: - raise exception + run_export_for_session(session_id) end_time = time.time() duration = end_time - start_time + assert duration < TIMEOUT print(f"Concurrent exports completed in {duration:.2f} seconds.")