Skip to content

Commit c9caafa

Browse files
committed
add/fix documentation
Signed-off-by: Lance-Drane <Lance-Drane@users.noreply.github.com>
1 parent 2ac2811 commit c9caafa

3 files changed

Lines changed: 36 additions & 15 deletions

File tree

doc/user_guides/jupyter.rst

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ This code initializes JupyterHub to work with this run and contacts the web port
5555

5656
---
5757

58-
For updating data files, we generally accommodate for two approaches: one where you want to multiple data files for each timestamp called, and one where you maintain multiple data files for a single timestamp but replace it per timestamp call. Both workflows utilize `self.services.add_analysis_data_file` .
58+
For updating data files, we generally accommodate for two approaches: one where you want to multiple data files for each timestamp called, and one where you maintain multiple data files for a single timestamp but replace it per timestamp call. Both workflows utilize `self.services.add_analysis_data_files` .
5959

6060
For the approach where data files for multiple timestamps are maintained, the below code provides an example of loading it from a file which is regularly updated with the IPS state:
6161

@@ -70,7 +70,7 @@ For the approach where data files for multiple timestamps are maintained, the be
7070
# and that this file is updated per timestamp call
7171
# In this example, we just want to snapshot our IPS state and save it in our JupyterHub workflow
7272
data_file = f'{timestamp}_state.json' # get current data file
73-
self.services.add_analysis_data_file(
73+
self.services.add_analysis_data_files(
7474
current_data_file_path=data_file,
7575
timestamp=timestamp,
7676
)
@@ -88,7 +88,7 @@ Or, if you only want to maintain a single timestamp, set the "replace" flag to T
8888
def step(self, timestamp=0.0):
8989
# assume that we continually update our state
9090
data_file = 'state.json' # get current data file
91-
self.services.add_analysis_data_file(
91+
self.services.add_analysis_data_files(
9292
current_data_file_path=data_file,
9393
replace=True,
9494
)

ipsframework/ips.py

Lines changed: 18 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -561,16 +561,21 @@ def initiate_new_simulation(self, sim_name: str):
561561

562562
def _send_monitor_event(self, sim_name='', eventType='', comment='', ok=True, target=None, operation=None, start_time=None, end_time=None, call_id=0):
563563
"""
564-
Publish a portal monitor event to the *_IPS_MONITOR* event topic.
564+
Publish a monitor event to the *_IPS_MONITOR* event topic.
565565
Event topics that start with an underscore are reserved for use by the
566566
IPS Framework and services.
567567
568-
* *sim_name*: The name of the simulation to which this even belongs.
569-
* *eventType*: The type of the event.
570-
* *comment*: A string containing comment that describes the event.
571-
* *ok*: A string containing the values 'True' or 'False', based on
568+
The IPS *LocalLoggingBridge* component will always be at least one consumer of these messages. If the web portal has been configured,
569+
the IPS *PortalBridge* component will also consume these messages.
570+
When a single message is published, ALL of these components will handle the published message (there is no "shared queue").
571+
572+
:param sim_name: The name of the simulation to which this even belongs.
573+
:param eventType: The type of the event.
574+
:param comment: A string containing comment that describes the event.
575+
:param ok: A string containing the values 'True' or 'False', based on
572576
whether the event indicates normal simulation execution, or an
573577
error condition.
578+
:param target:
574579
"""
575580
event_time = time.time()
576581
if self.verbose_debug:
@@ -584,10 +589,12 @@ def _send_monitor_event(self, sim_name='', eventType='', comment='', ok=True, ta
584589
portal_data['walltime'] = '%.2f' % (event_time - self.config_manager.sim_map[sim_name].start_time)
585590
portal_data['time'] = getTimeString(time.localtime(event_time))
586591

587-
topic_name = '_IPS_MONITOR'
588592
# portal_data['phystimestamp'] = self.timeStamp
589593
get_config = self.config_manager.get_config_parameter
590594
if eventType == 'IPS_START':
595+
# The 'IPS_START' event is always the first event sent from a component, and it should always be submitted internally.
596+
# This event will always mark the first time a component has registered,
597+
# and sending this event is indicative of the first time we make a Web Portal call and the first time we write to the event log files.
591598
user = self.config_manager.get_platform_parameter('USER')
592599
host = self.config_manager.get_platform_parameter('HOST')
593600
d = datetime.datetime.now()
@@ -645,6 +652,7 @@ def _send_monitor_event(self, sim_name='', eventType='', comment='', ok=True, ta
645652
pass
646653

647654
elif eventType == 'IPS_END':
655+
# The IPS_END event is always the last event called by the framework, ONLY sent out to indicate that there are no remaining messages to handle.
648656
portal_data['state'] = 'Completed'
649657
portal_data['stopat'] = getTimeString(time.localtime(event_time))
650658
# Zipkin json format
@@ -656,6 +664,7 @@ def _send_monitor_event(self, sim_name='', eventType='', comment='', ok=True, ta
656664
'tags': {'total_cores': str(self.resource_manager.total_cores)},
657665
}
658666
elif eventType == 'IPS_CALL_END':
667+
# The IPS_CALL_END event is always the last event called by the framework,
659668
trace = {} # Zipkin json format
660669
if start_time is not None and end_time is not None:
661670
trace['timestamp'] = int(start_time * 1e6) # convert to microsecond
@@ -676,17 +685,17 @@ def _send_monitor_event(self, sim_name='', eventType='', comment='', ok=True, ta
676685

677686
if self.verbose_debug:
678687
self.debug('Publishing %s', str(event_body))
679-
self.event_manager.publish(topic_name, 'IPS_SIM', event_body)
688+
# this message will be published to any component subscribed to '_IPS_MONITOR' - this generally includes the local logger bridge and the portal bridge
689+
self.event_manager.publish(topicName='_IPS_MONITOR', eventName='IPS_SIM', eventBody=event_body)
680690

681691
def _send_dynamic_sim_event(self, sim_name='', event_type='', ok=True):
682692
self.debug('_send_dynamic_sim_event(%s:%s)', event_type, sim_name)
683693
event_data = {}
684694
event_data['eventtype'] = event_type
685695
event_data['SIM_NAME'] = sim_name
686696
event_data['ok'] = ok
687-
topic_name = '_IPS_DYNAMIC_SIMULATION'
688697
self.debug('Publishing %s', str(event_data))
689-
self.event_manager.publish(topic_name, 'IPS_DYNAMIC_SIM', event_data)
698+
self.event_manager.publish(topicName='_IPS_DYNAMIC_SIMULATION', eventName='IPS_DYNAMIC_SIM', eventBody=event_data)
690699

691700
# TODO mark status as a "Literal" if we move to Python >= 3.8
692701
def send_terminate_msg(self, sim_name: str, status=Message.SUCCESS):

ipsframework/services.py

Lines changed: 15 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1994,9 +1994,8 @@ def initialize_jupyter_notebook(
19941994
19951995
Does not modify the source notebook.
19961996
1997-
Params:
1998-
- source_notebook_path: location you want to load the source notebook from. This can be either an absolute path, or an IPS-appropriate relative path.
1999-
- dest_notebook_name: (optional, default None) filename of the notebook to use when saving it to the IPS Portal. If not provided, this will defauly to the filename of the source notebook.
1997+
:param source_notebook_path: location you want to load the source notebook from. This can be either an absolute path, or an IPS-appropriate relative path.
1998+
:param dest_notebook_name: (optional, default None) filename of the notebook to use when saving it to the IPS Portal. If not provided, this will defauly to the filename of the source notebook.
20001999
"""
20012000
portal_runid = self._get_jupyter_runid()
20022001
if portal_runid < 0:
@@ -2060,6 +2059,12 @@ def add_analysis_data_files(self, current_data_file_paths: list[str], timestamp:
20602059
def publish(self, topicName: str, eventName: str, eventBody: Any) -> None:
20612060
"""
20622061
Publish event consisting of *eventName* and *eventBody* to topic *topicName* to the IPS event service.
2062+
2063+
Publishing an event multiple components are subscribed to will cause each component to handle the message simultaneously.
2064+
2065+
:param topicName: the name of the topic to publish on, top-level namespace
2066+
:param eventName: event associated with the topic
2067+
:param eventBody: data to send
20632068
"""
20642069
if not topicName.startswith('_IPS'):
20652070
topicName = self.sim_name + '_' + topicName
@@ -2068,6 +2073,11 @@ def publish(self, topicName: str, eventName: str, eventBody: Any) -> None:
20682073
def subscribe(self, topicName: str, callback: Callable) -> None:
20692074
"""
20702075
Subscribe to topic *topicName* on the IPS event service and register *callback* as the method to be invoked when an event is published to that topic.
2076+
2077+
Multiple components can subscribe to the same topic name; if this is the case, each component will handle the message separately when the topic is published to.
2078+
2079+
:param topicName: the name of the topic to subscribe to, top-level namespace
2080+
:param callback: the function which will be called on receiving a message
20712081
"""
20722082
if not topicName.startswith('_IPS'):
20732083
topicName = self.sim_name + '_' + topicName
@@ -2076,6 +2086,8 @@ def subscribe(self, topicName: str, callback: Callable) -> None:
20762086
def unsubscribe(self, topicName: str) -> None:
20772087
"""
20782088
Remove subscription to topic *topicName*.
2089+
2090+
:param topicName: the name of the topic to unsubscribe from
20792091
"""
20802092
if not topicName.startswith('_IPS'):
20812093
topicName = self.sim_name + '_' + topicName

0 commit comments

Comments
 (0)