Examples ======== The Python OTF2 bindings have mainly three use-cases: - Reading an existing trace - Writing a new trace - Rewriting a trace, which means to read a trace and write a trace, which depends on the input trace This page gives a walk through of all these three cases. Read an existing trace ---------------------- In order to read a trace with the Python interface, the first step is to import the necesarry classes. .. code-block:: python import otf2 from otf2.events import * In a :py:obj:`with` statement the trace file is opened with the :py:obj:`otf2.reader.open()` method. .. code-block:: python def read_trace(trace_name="TestTrace/traces.otf2"): with otf2.reader.open(trace_name) as trace: When this call succeeds, the trace is opened and all definitions are read. Definitions are stored in the :py:obj:`definitions` member of the :py:obj:`otf2.reader.Reader`. So it is possible to process specific types of definitions at once, e.g.: .. code-block:: python print("Read {} string definitions".format(len(trace.definitions.strings))) for string in trace.definitions.strings: print("String definition with value '{}' in trace.".format(string)) # Reading events is as easy as reading definitions. The member :py:obj:`events` of the :py:obj:`otf2.reader.Reader` is an iterable over the events of the trace. With an :py:obj:`isinstance` check on an event, specific events can be handled only. All possible events can be looked up in the module :py:mod:`otf2.events`. .. code-block:: python for location, event in trace.events: if isinstance(event, Enter): print("Encountered enter event into '{event.region.name}' on location {location} at {event.time}".format(location, event)) elif isinstance(event, Leave): print("Encountered leave event for '{event.region.name}' on location {location} at {event.time}".format(location, event)) else: print("Encountered event on location {} at {}".format(location, event.time)) # And finally the usual Python magic to create an executable script from the code. .. code-block:: python if __name__ == "__main__": read_trace() Write a new trace ----------------- The second important use-case is the creation of a new trace from scratch. Again, importing the required classes is the first thing. .. code-block:: python import otf2 from otf2.enums import Type In order to write a new trace, some sort of time measurement or timestamp generation, called timer, is needed. This example uses the built-in Python module :py:mod:`time`. .. note:: In OTF2 all timestamps are 64-Bit integers. As :py:func:`time.time()` returns a float, a conversion is needed. .. code-block:: python import time TIMER_GRANULARITY = 1000000 def t(): return int(round(time.time() * TIMER_GRANULARITY)) A new trace is opened with a call to :py:func:`otf2.write.open`. .. note:: The :py:obj:`archive_name` is not the trace anchor file, but the name of the root folder for the whole trace. After the trace is written, this folder will contain the anchor file, the global definition file, and all local event and defintion files. .. note:: The :py:class:`otf2.writer.Writer` needs to know the resolution of the used timer, that is the number of ticks per second. .. code-block:: python def create_trace(archive_name="NewTrace"): with otf2.writer.open(archive_name, timer_resolution=TIMER_GRANULARITY) as trace: New definitions can be created using the member :py:obj:`definitions` of the :py:obj:`otf2.writer.Writer`. The hazzle of the id management is hidden from the user. Instead references to other objects are stored. For details see :ref:`def-handling`. .. code-block:: python root_node = trace.definitions.system_tree_node("root node") system_tree_node = trace.definitions.system_tree_node("myHost", parent=root_node) trace.definitions.system_tree_node_property(system_tree_node, "color", value="black") trace.definitions.system_tree_node_property(system_tree_node, "rack #", value=42) location_group = trace.definitions.location_group("Initial Process", system_tree_parent=system_tree_node) # After having defined a few definitions, getting an event writer for a location is the next step. .. code-block:: python writer = trace.event_writer("Main Thread", group=location_group) # This writer can be used to write events. .. note:: Events need a timestamp as first argument. .. code-block:: python function = trace.definitions.region("My Function") writer.enter(t(), function) writer.leave(t(), function) # Using attributes, adding more information to an event is also possible. .. code-block:: python attr = trace.definitions.attribute(name="StringTest", description="A test attribute", type=Type.STRING) writer.enter(t(), function, attributes={attr: "Hello"}) writer.leave(t(), function, attributes={attr: "World"}) # There is also an short-hand to create simple metrics. .. code-block:: python coffee_metric = trace.definitions.metric("Time since last coffee", unit="min") writer.metric(t(), coffee_metric, 72.0) # And finally the usual Python magic to create an executable script from the code. .. code-block:: python if __name__ == "__main__": create_trace() Rewrite an existing trace ------------------------- Last but not least, the third important use-case is the rewriting of an existing trace. The important point in this case, is the possibility to manipulate a trace to all wishes. Again, importing the required classes is the first thing. .. code-block:: python import otf2 from otf2.enums import Type Then the existing trace is opened with an :py:class:`otf2.reader.Reader` object. .. code-block:: python def rewrite_trace(old_anchor_path="old_trace/traces.otf2", new_archive_path="new_trace"): with otf2.reader.open(old_anchor_path) as trace_reader: And the new trace is opened with an :py:class:`otf2.writer.Writer` instance. .. note:: The important bit here, is that the :py:obj:`definitions` member of the input trace is passed as argument to the :py:func:`otf2.writer.open()` call. .. code-block:: python with otf2.writer.open(new_archive_path, definitions=trace_reader.definitions) as write_trace: # Like in the reading case, the :py:obj:`events` property of the input trace is used, to read all events. .. code-block:: python for location, event in trace_reader.events: # These events can be manipulated by any means, e.g., the following code normalizes all timestamps of the trace, such that the first event occures at time point 0. .. code-block:: python event.time -= trace_reader.definitions.clock_properties.global_offset # Then the manipulated event needs to be written into the new trace. Therefore an event writer is needed. .. code-block:: python writer = write_trace.event_writer_from_location(location) writer(event) # And finally the usual Python magic to create an executable script from the code. .. code-block:: python if __name__ == "__main__": rewrite_trace()