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.

import otf2
from otf2.events import *

In a with statement the trace file is opened with the otf2.reader.open() method.

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 definitions member of the otf2.reader.Reader. So it is possible to process specific types of definitions at once, e.g.:

        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 events of the otf2.reader.Reader is an iterable over the events of the trace. With an isinstance check on an event, specific events can be handled only. All possible events can be looked up in the module otf2.events.

        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))
                print("Encountered event on location {} at {}".format(location, event.time))

And finally the usual Python magic to create an executable script from the code.

if __name__ == "__main__":

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.

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 time.


In OTF2 all timestamps are 64-Bit integers. As time.time() returns a float, a conversion is needed.

import time


def t():
    return int(round(time.time() * TIMER_GRANULARITY))

A new trace is opened with a call to otf2.write.open().


The 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.


The otf2.writer.Writer needs to know the resolution of the used timer, that is the number of ticks per second.

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 definitions of the otf2.writer.Writer. The hazzle of the id management is hidden from the user. Instead references to other objects are stored. For details see Creating definitions.

        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.

        writer = trace.event_writer("Main Thread", group=location_group)

This writer can be used to write events.


Events need a timestamp as first argument.

        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.

        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.

        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.

if __name__ == "__main__":

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.

import otf2
from otf2.enums import Type

Then the existing trace is opened with an otf2.reader.Reader object.

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 otf2.writer.Writer instance.


The important bit here, is that the definitions member of the input trace is passed as argument to the otf2.writer.open() call.

        with otf2.writer.open(new_archive_path,
                              definitions=trace_reader.definitions) as write_trace:

Like in the reading case, the events property of the input trace is used, to read all events.

            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.

                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.

                writer = write_trace.event_writer_from_location(location)

And finally the usual Python magic to create an executable script from the code.

if __name__ == "__main__":