Observers

PyDistSim provides a set of observers that can be used to monitor the simulation. Observers are used to collect data from the simulation, and can be used to plot the simulation results, or to analyze the simulation data.

The observer classes are defined in the module pydistsim.observers. The observer base class is Observer, which defines the interface for all observers.

Inheritance diagram of Observer, AlgorithmObserver, SimulationObserver, NetworkObserver, NodeObserver

Observer inheritance diagram

The observable classes

Standardize the use of observers in the framework, we implemented a mixin class ObserverManagerMixin that can be used to add observer functionality to any class. The mixin class provides methods to add and remove observers, and to notify the observers of changes in the observable object.

Inheritance diagram of ObserverManagerMixin, pydistsim.algorithm.BaseAlgorithm, pydistsim.simulation.Simulation, pydistsim.network.network.DirectedNetwork, pydistsim.network.network.BidirectionalNetwork, pydistsim.network.node.Node

ObserverManagerMixin inheritance diagram

The metric collector

In addition to the observer classes, PyDistSim provides a special observer class called MetricCollector. The metric collector is used to collect metrics from the simulation, and to store the metrics in a data structure.

Inheritance diagram of MetricCollector

MetricCollector inheritance diagram

To enable the metric collection, the simulation object must be configured with a metric collector object like this:

metrics = MetricCollector()
sim = Simulation(net)
sim.add_observers(metrics)

To access the collected metrics, the MetricCollector.create_report() method can be used.

Subclassing MetricCollector

Extend this class and implement the desired event methods to collect custom metrics. For registering events, call the MetricCollector._add_metric() method. Add an instance of your custom collector to the simulation observers for it to work.

Even so, you can use the events attribute to register the new events you want to listen to. This includes new custom events that you would trigger from a custom algorithm.

Example of implementing a custom metric collector:

class ExampleCustomMetricCollector(MetricCollector):
    events = ["example_custom_event"]

    class CustomMetricEventType(StrEnum):
        "Definition if this enum is optional. It helps to avoid typos in the event names."
        EXAMPLE_CUSTOM_EVENT_ZERO = "EXAMPLE_CUSTOM_EVENT_ZERO"
        ...

    def on_example_custom_event(self, a, b, c):
        self._add_metric(
            self.CustomMetricEventType.EXAMPLE_CUSTOM_EVENT_ZERO,
            {"a": a, "b": b, "c": c}
        )