1+ import os
2+ import shutil
3+ from argparse import ArgumentParser
4+
5+ import requests
6+ import psutil
7+ import panel as pn
8+ from tornado .web import RequestHandler
9+
10+ # 1. Run setup (replaces --setup flag)
11+ from aind_ephys_portal .setup import * # noqa: F401,F403
12+ from aind_ephys_portal .panel .logging import list_gui_sessions , get_max_number_of_gui_sessions , LOG_DIR # noqa: F401
13+
14+
15+ if LOG_DIR .is_dir ():
16+ print (f"Cleaning up old log files in { LOG_DIR } ..." )
17+ shutil .rmtree (LOG_DIR )
18+
19+
20+ def get_ecs_task_id ():
21+ metadata_uri = os .environ .get ("ECS_CONTAINER_METADATA_URI_V4" )
22+ if metadata_uri :
23+ # Request the metadata from the local ECS agent
24+ response = requests .get (f"{ metadata_uri } /task" )
25+ task_arn = response .json ().get ("TaskARN" )
26+ # The Task ID is the last part of the ARN string
27+ return task_arn .split ('/' )[- 1 ]
28+ return "local-dev"
29+
30+
31+ MAX_GUI_SESSIONS_PER_TASK = get_max_number_of_gui_sessions ()
32+ print (f"Max GUI sessions per task: { MAX_GUI_SESSIONS_PER_TASK } " )
33+
34+ # 2. Health Check & Index Redirect
35+ class HealthHandler (RequestHandler ):
36+ def get (self ):
37+ mem = psutil .virtual_memory ()
38+ # count number of GUI app sessions
39+ gui_sessions = list_gui_sessions ()
40+ task_id = get_ecs_task_id ()
41+ if mem .percent > 70 or len (gui_sessions ) > MAX_GUI_SESSIONS_PER_TASK :
42+ self .set_status (503 )
43+ self .write (
44+ f"Busy:\n Memory at { mem .percent } % - Num sessions: { len (gui_sessions )} "
45+ f"(max { MAX_GUI_SESSIONS_PER_TASK } ) Task ID: { task_id } "
46+ )
47+ else :
48+ self .set_status (200 )
49+ self .write (
50+ f"Healthy:\n Memory at { mem .percent } % - Num sessions: { len (gui_sessions )} "
51+ f"(max { MAX_GUI_SESSIONS_PER_TASK } ) Task ID: { task_id } "
52+ )
53+
54+ class IndexRedirectHandler (RequestHandler ):
55+ def get (self ):
56+ self .redirect ("/ephys_portal_app" )
57+
58+ # 3. App file paths — Panel will exec these per-session with a proper context
59+ APP_DIR = "src/aind_ephys_portal"
60+ apps = {
61+ "ephys_portal_app" : os .path .join (APP_DIR , "ephys_portal_app.py" ),
62+ "ephys_gui_app" : os .path .join (APP_DIR , "ephys_gui_app.py" ),
63+ "ephys_launcher_app" : os .path .join (APP_DIR , "ephys_launcher_app.py" ),
64+ "ephys_monitor_app" : os .path .join (APP_DIR , "ephys_monitor_app.py" ),
65+ }
66+
67+ # 4. Parse ALLOW_WEBSOCKET_ORIGIN from env
68+ allow_ws = os .environ .get ("ALLOW_WEBSOCKET_ORIGIN" , "*" ).split ("," )
69+
70+ parser = ArgumentParser (description = "Ephys Portal Server" )
71+ parser .add_argument ("--port" , type = int , default = 8000 , help = "Port to run the server on" )
72+ parser .add_argument ("--address" , type = str , default = "localhost" , help = "Address to run the server on" )
73+ parser .add_argument ("--test" , action = "store_true" , help = "Run in test mode (connects to test API gateway)" )
74+
75+ if __name__ == "__main__" :
76+ args = parser .parse_args ()
77+ port = args .port
78+ address = args .address
79+ test_mode = args .test
80+
81+ # Set number of threads for Panel's thread pool to handle multiple sessions in parallel
82+ pn .config .nthreads = 8
83+
84+ if test_mode :
85+ print ("Running in TEST MODE: connecting to test API gateway" )
86+ os .environ ["TEST_ENV" ] = "1"
87+
88+ print (f"Ephys Portal is running on http://{ address } :{ port } " )
89+ for app in apps :
90+ print (f" - { app } : http://{ address } :{ port } /{ app } " )
91+ print (f" - Health check: http://{ address } :{ port } /health" )
92+ pn .serve (
93+ apps ,
94+ address = address ,
95+ port = port ,
96+ allow_websocket_origin = allow_ws ,
97+ static_dirs = {"images" : os .path .join (APP_DIR , "images" )},
98+ extra_patterns = [(r"/health" , HealthHandler ), (r"/" , IndexRedirectHandler )],
99+ check_unused_sessions = 2000 ,
100+ unused_session_lifetime = 5000 ,
101+ show = False ,
102+ )
0 commit comments