How to trace simulator calls¶
Simulator call traces help explain the interactions in a scenario.
They show calls in both directions, including arguments, return values,
and exceptions. Traces bind the simulator name to the simulator field:
sim.local.<sim_id>for in-process simulatorssim.remote.<sim_id>for networked simulators
The traces are emitted at loguru’s TRACE level. Enable mosaik’s
logging, then select the desired namespace using a sink filter:
import sys
from loguru import logger
logger.enable("mosaik")
simulator_filter = "sim.local.Input"
logger.add(
sys.stderr,
level="TRACE",
filter=lambda record: record["extra"]
.get("simulator", "")
.startswith(simulator_filter),
format="{level: <8} | {extra[simulator]} | {message}",
)
The filter can be sim for all simulators, sim.local or
sim.remote for one transport type, or the full name of a specific
simulator.
Example scenario¶
This scenario connects a constant input to an output collector and
only displays traces for the simulator with the ID Input:
"""Small scenario demonstrating simulator call tracing.
Run this file from the repository root with::
python docs/how-tos/code/simulator_tracing.py
"""
from __future__ import annotations
import sys
from loguru import logger
import mosaik
from mosaik.scenario import SimConfig
SIM_CONFIG: SimConfig = {
"Input": {"python": "mosaik.basic_simulators:InputSimulator"},
"Output": {"python": "mosaik.basic_simulators:OutputSimulator"},
}
def main() -> None:
# Remove loguru's default sink so this example only prints the
# selected simulator traces. Applications with their own logging
# setup can keep their existing sinks.
logger.remove()
logger.enable("mosaik")
# Shorter prefixes select broader groups, for example "sim" for all
# simulators or "sim.local" for all in-process simulators.
simulator_filter = "sim.local.Input"
trace_handler = logger.add(
sys.stderr,
level="TRACE",
filter=lambda record: (
record["extra"].get("simulator", "").startswith(simulator_filter)
),
format="{level: <8} | {extra[simulator]} | {message}",
)
try:
with mosaik.World(
SIM_CONFIG, configure_logging=False, skip_greetings=True
) as world:
input_sim = world.start("Input", sim_id="Input", step_size=1)
output_sim = world.start("Output", sim_id="Output")
source = input_sim.Constant(constant=42)
collector = output_sim.Dict()
world.connect(source, collector, "value")
world.run(until=2, print_progress=False)
print("Collected data:", output_sim.get_dict(collector.eid))
finally:
logger.remove(trace_handler)
if __name__ == "__main__":
main()
Representative output¶
Some large return values are abbreviated here. The scenario itself prints them in full:
TRACE | sim.local.Input | mosaik -> simulator: init('Input', time_resolution=1.0, step_size=1)
TRACE | sim.local.Input | simulator -> mosaik: init returned {...}
TRACE | sim.local.Input | mosaik -> simulator: create(1, 'Constant', constant=42)
TRACE | sim.local.Input | simulator -> mosaik: create returned [{'eid': 'Constant-0', 'type': 'Constant'}]
TRACE | sim.local.Input | mosaik -> simulator: step(0, {}, 2)
TRACE | sim.local.Input | simulator -> mosaik: step returned 1
The arrows indicate whether the call or response is travelling from mosaik to the simulator or from the simulator back to mosaik.