Basics
======

.. Note:: This manual focuses on the python bindings. For a definite reference to attributes of
          definitions or events, please read the documentation of the C interface:
          `manual (available if built correctly)`_

.. _manual (available if built correctly): ../html/index.html

Definitions
-----------


OTF2 specifies over twenty different definitions, which are used to represent the state of the
application at the start of the tracing. Most definitions have an reference number.
This number is unique between all instances of definitions with the same type.
Some types of definitions may share the same space of possible reference numbers, e.g.,
:py:class:`otf2.definitions.MetricClass` definitions and :py:class:`otf2.definitions.MetricInstance`
definitions.
A definition can reference definitions of the same type and definitions of another type using the
reference numbers.
Definitions with a reference number inherit from the internal class
`otf2.definitions._RefDefinition` or its subclass `otf2.definitions._NamedRefDefinition`.

Some definitions are used to represent the structure of the system, e.g.,
:py:class:`otf2.definitions.SystemTreeNode`, :py:class:`otf2.definitions.SystemTreeNodeDomain`, and
:py:class:`otf2.definitions.SystemTreeNodeProperty`.

A :py:class:`otf2.definitions.Region` definition represents a code segment, which can be
distinguished by name. In case of common code running on a processor, this represents functions.
In a GPGPU context a region would represent a kernel.

A :py:class:`otf2.definitions.Location`
represents a place, where events can occur. This can be a thread running on a common processor or a
thread running in a GPGPU environment. A :py:class:`otf2.definitions.Location` can also be of the
special type :py:obj:`otf2.LocationType.METRIC`, which is used in conjunction with
asynchronous metrics (see :ref:`metrics`).

In order to represent metrics, there are three types of
definitions, i.e., :py:class:`otf2.definitions.MetricClass`,
:py:class:`otf2.definitions.MetricInstance`, and :py:class:`otf2.definitions.MetricMember`
(see :ref:`metrics`).


.. _def-handling:

Creating definitions
_______________________

Normally, definitions are not constructed directly, but rather through the
:py:class:`otf2.registry.DefinitionRegistry` of a given :py:class:`otf2.writer.Writer`. The
`DefinitionRegistry` provides a method for each definition. E.g.
:py:func:`otf2.registry.DefinitionRegistry.system_tree_node` generates a
:py:class:`otf2.definitions.SystemTreeNode`.
The registry will not generate duplicates: Instead of creating a definition that would be equal
to an existing definition, the existing one is returned.


Fields of definitions
_______________________

In general, definitions are nice Python objects with their expected member fields. With definitions
referring to each other, they form an acyclic graph. All definition fields have a specific type.
Possible types can be one following:

-
    Simple immutable types such as int, :py:mod:`otf2.enums` (typesafe).

    .. warning:: Never directly change the .value of an enum - game over.

- Strings are denoted as type :py:class:`String`, but they are stored as native :py:class:`str`
  objects within the definitions. Their references are retrieved only when the definition is
  written.
-
    A definition that refers to another definition simply has the other definition object as
    attribute.

    .. note:: Only definition classes marked by the protected base class `_RefDefinition` or
              its subclass `_NamedRefDefinition` may be referred to by other definitions.

- Aggregates (e.g. List of Definitions) are internally tuples (must be immutable).
  If you want to change/add/remove an element, you have to set it to a new tuple.
  You may pass any iterable to construct an aggregate attribute.


- Other types, especially mutable types, are not supported.

Definitions know the IdRegistry that is responsible for them. Whenever any attribute of a
Definition is changed, it informs it's IdRegistry of the change.


Events
------

Events are used to represent the state changes, which happen during the execution of the traced
application. As :py:class:`otf2.definitions.Location` definitions represent all places, where an
event can occur, every event is associated with a :py:class:`otf2.definitions.Location` definition,
i.e., the event has occurred at the associated location. Every event has a time stamp, which denotes
the time point, when the event occurred at the associated location. All events, which occurred at
the same location, are chronologically ordered by their timestamp in a monotonically increasing
fashion. Certain events reference some definitions using their reference number, to represent
stateless semantics.

OTF2 specifies over 50 different :py:mod:`otf2.events`. The majority of the
specified events are used to represent the different supported parallelism paradigms, e.g., MPI,
and OpenMP. All traces contain at least two types of events. These are :py:class:`otf2.events.Enter`
and :py:class:`otf2.events.Leave` events. Enter and leave events reference a
:py:class:`otf2.definitions.Region` definition, denoting the begin and the end of the execution of
the referenced region on the associated location. Therefore, they always occur as a pair and in this
particular sequence. Another type of events is the :py:class:`otf2.events.Metric` event. Metric
events denote a measurement value of a metric.

.. _metrics:

Metrics
-------

In OTF2 a metric comprises one or more different measurement points, e.g., an energy metric could
comprise the measurement for current and voltage. These measurements are called
:py:class:`otf2.definitions.MetricMember`. They can have different units, value types, and
measurement methods, but they have to be measured at the same time. The representation of these
measurements is done with :py:class:`otf2.definitions.MetricMember` definitions. Each metric
is represented by a :py:class:`otf2.definitions.MetricClass` definition. The
:py:class:`otf2.definitions.MetricClass` definition references all
:py:class:`otf2.definitions.MetricMember` definitions, which the metric consists of.

The recorded metric values are stored using :py:class:`otf2.events.Metric` events associated to
certain locations, which depends on the type of metric in question. Metrics are categorized in two
categories. The first category defines, how many locations record the metric.
This can be represented with a matching number of locations, which contain associated metric events.
The second category is given by the fact, that some measurements are recorded externally, i.e.,
the recorded values of the metric are not available on the recording locations during the execution
of the traced application. Therefore, a asynchronous metric requires, that the metric events are
stored differently compared to synchronous metrics.

In case of an synchronous metric, the metric events are coupled to :py:class:`otf2.events.Enter` and
:py:class:`otf2.events.Leave` events. The metric events always occur right before the enter and
leave, though the metric events, and the enter and leave event share the same timestamp. The
recorder and the scope of a synchronous metric is given implicitly by the associated locations of
the metric events, which are referencing the metric class definition.

Asynchronous metrics require two additional definitions, i.e., a
:py:class:`otf2.definitions.MetricInstance` and a :py:class:`otf2.definitions.Location` of type
:py:obj:`otf2.LocationType.METRIC`. As asynchronous metrics are externally recorded, they
cannot be recorded at the location to which the metric belongs, therefore, they have to explicit
specify their scope and their recording location. Also the metric events of the asynchronous
metrics has to be associated to a location of type instead :py:obj:`otf2.LocationType.METRIC`
of the recorder.
