/*
 * This file is part of the Score-P software (http://www.score-p.org)
 *
 * Copyright (c) 2009-2013,
 * RWTH Aachen University, Germany
 *
 * Copyright (c) 2009-2013,
 * Gesellschaft fuer numerische Simulation mbH Braunschweig, Germany
 *
 * Copyright (c) 2009-2018, 2021, 2023,
 * Technische Universitaet Dresden, Germany
 *
 * Copyright (c) 2009-2013,
 * University of Oregon, Eugene, USA
 *
 * Copyright (c) 2009-2013, 2026,
 * Forschungszentrum Juelich GmbH, Germany
 *
 * Copyright (c) 2009-2013,
 * German Research School for Simulation Sciences GmbH, Juelich/Aachen, Germany
 *
 * Copyright (c) 2009-2013,
 * Technische Universitaet Muenchen, Germany
 *
 * This software may be modified and distributed under the terms of
 * a BSD-style license.  See the COPYING file in the package base
 * directory for details.
 *
 */


/**
 *  @file
 *
 *  @brief      This tool prints out all event files of an archive to console.
 *
 *  @return                  Returns EXIT_SUCCESS if successful, EXIT_FAILURE
 *                           if an error occurs.
 */

#include <config.h>

#include <stdio.h>
#include <stdarg.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
#include <stdint.h>
#include <stdarg.h>
#include <inttypes.h>
#include <assert.h>
#include <time.h>

#include <otf2/otf2.h>

#include "otf2_hash_table.h"
#include "otf2_vector.h"

#include <UTILS_IO.h>

/* ___ Shorthand macros. ____________________________________________________ */


#define PRIUint8      PRIu8
#define PRIInt8       PRId8
#define PRIUint16     PRIu16
#define PRIInt16      PRId16
#define PRIUint32     PRIu32
#define PRIInt32      PRId32
#define PRIUint64     PRIu64
#define PRIUint64Full PRIu64
#define PRIInt64      PRId64
#define PRIFloat      "g"
#define PRIDouble     "g"

#define BUFFER_SIZE 128


/** @internal
 *  @brief Type used to indicate a reference to a @eref{String} definition */
typedef uint64_t OTF2_StringRef64;

/* ___ Global variables. ____________________________________________________ */


/** @internal
 *  @brief Name of the program. */
static const char* otf2_NAME;

/** @internal
 *  @brief Defines if debug is turned on (1) or off (0). */
static bool otf2_DEBUG;

/** @internal
 *  @brief Treat warnings as errors. */
static bool otf2_WERROR;

/** @internal
 *  @brief Defines if all data is printed (1) or not (0). */
static bool otf2_ALL;

/** @internal
 *  @brief Defines if global definitions are printed (1) or not (0). */
static bool otf2_GLOBDEFS;

/** @internal
 *  @brief Defines if information from anchor file are printed (1) or not (0). */
static bool otf2_ANCHORFILE_INFO;

/** @internal
 *  @brief Defines if thumbnail headers should be printed. */
static bool otf2_THUMBNAIL_INFO;

/** @internal
 *  @brief Defines if thumbnail headers should be printed. */
static bool otf2_THUMBNAIL_SAMPLES;

/** @internal
 *  @brief Defines if a single location is selected. */
static uint64_t otf2_LOCAL = OTF2_UNDEFINED_LOCATION;

/** @internal
 *  @brief Tell if a local location was found (1) or not (0). */
static bool otf2_LOCAL_FOUND;

/** @internal
 *  @brief Defines lower bound of selected time interval. */
static uint64_t otf2_MINTIME;

/** @internal
 *  @brief Defines upper bound of selected time interval. */
static uint64_t otf2_MAXTIME = OTF2_UNDEFINED_UINT64;

/** @internal
 *  @brief Defines number of printed events in each step (UINT64_MAX means unlimited). */
static uint64_t otf2_STEP = OTF2_UNDEFINED_UINT64;

/** @internal
 *  @brief Defines if events are printed or not. */
static bool otf2_SILENT;

/** @internal
 *  @brief Defines if dot output is selected. */
static bool otf2_DOT;

/** @internal
 *  @brief Defines if we want to see the mapping tables. */
static bool otf2_MAPPINGS;

/** @internal
 *  @brief Defines if we want to see timer synchronizations. */
static bool otf2_CLOCK_OFFSETS;

/** @internal
 *  @brief Don't read local defs, to prevent the reader to apply mappings
 *         and clock corrections. */
static bool otf2_NOLOCALDEFS;

/** @internal
 *  @brief Print also any snapshots. */
static bool otf2_NOSNAPSHOTS;

/** @internal
 *  @brief width of the column with the anchor file information. */
static int otf2_ANCHOR_FILE_INFO_COLUMN_WIDTH = 30;


/** @internal
 *  @brief width of the column with the anchor file information. */
static int otf2_THUMBNAIL_HEADER_COLUMN_WIDTH = 16;


/** @internal
 *  @brief print the backtrace for a calling context sample. */
static bool otf2_UNWIND_CALLING_CONTEXT = false;


static enum otf2_print_timestamp_format
{
    TIMESTAMP_PLAIN,
    TIMESTAMP_OFFSET
} otf2_TIMESTAMP_FORMAT;


/* ___ Structures. __________________________________________________________ */


struct otf2_print_rank_list_element
{
    struct otf2_print_rank_list_element* next;
    OTF2_Paradigm                        paradigm;
    uint64_t                             rank;
};


struct otf2_print_rank_list
{
    struct otf2_print_rank_list_element*  head;
    struct otf2_print_rank_list_element** tail;
};


struct otf2_print_metric_list_element
{
    struct otf2_print_metric_list_element* next;
    OTF2_MetricRef                         metricRef;
    OTF2_LocationRef                       recorderRef;
};


struct otf2_print_metric_list
{
    struct otf2_print_metric_list_element*  head;
    struct otf2_print_metric_list_element** tail;
};


/** @internal
 *  @brief Keeps all data for the callbacks. */
struct otf2_print_data
{
    /** @brief Reader handle. */
    OTF2_Reader* reader;
    /** @brief List of locations to process. */
    otf2_vector* locations_to_read;

    /** @brief Collected definitions. */
    struct otf2_print_defs* defs;

    /** @brief Clock properties. */
    uint64_t timer_resolution;
    uint64_t global_offset;
    uint64_t trace_length;

    /** @brief Number of the artifical string refs in the hash table. */
    OTF2_StringRef64 artificial_string_refs;

    /** @brief Defined COMM_LOCATIONS groups. Indexed via OTF2_Paradigm. */
    otf2_vector* comm_paradigms;

    /* OTF2_RECORDER_KIND_CPU */
    struct otf2_print_metric_list recorded_cpu_metrics;

    /* OTF2_RECORDER_KIND_GPU */
    struct otf2_print_metric_list recorded_gpu_metrics;
};

/** @internal
 *  @brief Paradigm definition element. */
struct otf2_print_paradigm_def
{
    OTF2_Paradigm      paradigm;
    char*              name;
    OTF2_ParadigmClass paradigmClass;
};


/** @internal
 *  @brief Region definition element. */
struct otf2_print_def_name
{
    /** @brief The ID of the definition. */
    uint64_t def;
    /** @brief The name if the definition. */
    char*    name;
};


/** @internal
 *  @brief I/O paradigm property element. */
struct otf2_print_io_paradigm_property
{
    OTF2_IoParadigmProperty property;
    OTF2_Type               type;
    OTF2_AttributeValue     value;
};


/** @internal
 *  @brief I/O paradigm definition element. */
struct otf2_print_io_paradigm_def
{
    struct otf2_print_def_name             def;
    OTF2_IoParadigmClass                   ioParadigmClass;
    OTF2_IoParadigmFlag                    ioParadigmFlags;
    uint8_t                                numberOfProperties;
    struct otf2_print_io_paradigm_property properties[];
};


/** @internal
 *  @brief Untyped definition property element. */
struct otf2_print_def_property
{
    struct otf2_print_def_property* next;
    OTF2_StringRef                  name;
    OTF2_Type                       type;
    OTF2_AttributeValue             value;
};


/** @internal
 *  @brief SystemTreeDomain element. */
struct otf2_print_system_tree_node_domain
{
    struct otf2_print_system_tree_node_domain* next;
    OTF2_SystemTreeDomain                      domain;
};


/** @internal
 *  @brief SystemTreeNode definition element. */
struct otf2_print_system_tree_node_def
{
    struct otf2_print_def_name                  def;

    OTF2_StringRef                              name;
    OTF2_StringRef                              className;
    OTF2_SystemTreeNodeRef                      parent;

    struct otf2_print_system_tree_node_domain*  domains_head;
    struct otf2_print_system_tree_node_domain** domains_tail;
    struct otf2_print_def_property*             properties_head;
    struct otf2_print_def_property**            properties_tail;

    struct otf2_print_metric_list               recorded_metrics;
};


/** @internal
 *  @brief LocationGroup definition element. */
struct otf2_print_location_group_def
{
    struct otf2_print_def_name       def;

    OTF2_StringRef                   name;
    OTF2_LocationGroupType           locationGroupType;
    OTF2_SystemTreeNodeRef           systemTreeParent;
    OTF2_LocationGroupRef            creatingLocationGroup;

    struct otf2_print_def_property*  properties_head;
    struct otf2_print_def_property** properties_tail;

    struct otf2_print_rank_list      paradigm_ranks;

    struct otf2_print_metric_list    recorded_metrics;
};


/** @internal
 *  @brief Location definition element. */
struct otf2_print_location_def
{
    struct otf2_print_def_name       def;

    OTF2_StringRef                   name;
    OTF2_LocationType                locationType;
    OTF2_LocationGroupRef            locationGroup;

    struct otf2_print_def_property*  properties_head;
    struct otf2_print_def_property** properties_tail;

    struct otf2_print_rank_list      paradigm_ranks;

    struct otf2_print_metric_list    recorded_metrics;
};


/** @internal
 *  @brief Metric definition element. */
struct otf2_print_metric_member_def
{
    struct otf2_print_def_name def;
    OTF2_Type                  type;
    const char*                unit;
};


/** @internal
 *  @brief Metric definition element. */
struct otf2_print_metric_def
{
    struct otf2_print_def_name def;
    OTF2_RecorderKind          recorder_kind;
    uint8_t                    number_of_members;
    OTF2_MetricMemberRef       members[];
};


/** @internal
 *  @brief Group definition element. */
struct otf2_print_group_def
{
    struct otf2_print_def_name def;
    OTF2_GroupType             type;
    OTF2_GroupFlag             flags;
    OTF2_Paradigm              paradigm;
    /** COMM_LOCATIONS/COMM_LOCATION_GROUPS => pointer to the COMM_SELF group
        COMM_GROUP,COMM_SELF => pointer to the COMM_LOCATIONS/COMM_LOCATION_GROUPS group */
    struct otf2_print_group_def* comm_data;
    uint32_t                     number_of_members;
    uint64_t                     members[];
};


/** @internal
 *  @brief Comm/InterComm definition element. */
struct otf2_print_comm_def
{
    struct otf2_print_def_name         def;
    const struct otf2_print_group_def* comm_group_a;
    const struct otf2_print_group_def* comm_group_b;
};


/** @internal
 *  @brief RmaWin definition element. */
struct otf2_print_rma_win_def
{
    struct otf2_print_def_name        def;
    const struct otf2_print_comm_def* comm;
};


/** @internal
 *  @brief CartTopology definition element. */
struct otf2_print_cart_topology_def
{
    struct otf2_print_def_name        def;
    const struct otf2_print_comm_def* comm;
};


/** @internal
 *  @brief Calling context definition element. */
struct otf2_print_calling_context_def
{
    struct otf2_print_def_name def;
    uint64_t                   ip;
    OTF2_RegionRef             region;
    OTF2_SourceCodeLocationRef scl;
    OTF2_CallingContextRef     parent;

    /* property list */
    struct otf2_print_def_property*  properties_head;
    struct otf2_print_def_property** properties_tail;
};


/* ___ Prototypes for static functions. _____________________________________ */

static void
otf2_print_die( const char* fmt,
                ... )
{
    if ( fmt )
    {
        va_list va;
        fprintf( stderr, "%s: ", otf2_NAME );
        va_start( va, fmt );
        vfprintf( stderr, fmt, va );
        va_end( va );
    }
    fprintf( stderr, "Try '%s --help' for more information.\n", otf2_NAME );
    exit( EXIT_FAILURE );
}

static void
otf2_print_warn( const char* fmt,
                 ... )
{
    va_list va;
    fprintf( stderr, "%s: %s: ", otf2_NAME, otf2_WERROR ? "error" : "warning" );
    va_start( va, fmt );
    vfprintf( stderr, fmt, va );
    fflush( stderr );
    va_end( va );
    if ( otf2_WERROR )
    {
        fprintf( stderr, "%s: all warnings being treated as errors\n", otf2_NAME );
        exit( EXIT_FAILURE );
    }
}

static void
otf2_print_anchor_file_information( OTF2_Reader* reader );

static void
otf2_print_thumbnails( OTF2_Reader* reader );

static void
otf2_get_parameters( int    argc,
                     char** argv,
                     char** anchorFile );

static void
check_pointer( void* pointer,
               char* description,
               ... );

static void
check_status( OTF2_ErrorCode status,
              char*          description,
              ... );

static void
check_condition( bool  condition,
                 char* description,
                 ... );

static void
otf2_print_add_clock_properties( struct otf2_print_data* data,
                                 uint64_t                timerResolution,
                                 uint64_t                globalOffset,
                                 uint64_t                traceLength );

static const char*
otf2_print_get_timestamp( struct otf2_print_data* data,
                          OTF2_TimeStamp          time );

static void
otf2_print_add_location_to_read( struct otf2_print_data* data,
                                 OTF2_LocationRef        location );

static void
otf2_print_add_string( otf2_hash_table* strings,
                       OTF2_StringRef64 string,
                       size_t           content_len,
                       const char*      content_fmt,
                       ... );

static void
otf2_print_add_def64_name( const char*      def_name,
                           otf2_hash_table* defs,
                           otf2_hash_table* strings,
                           uint64_t         def,
                           OTF2_StringRef64 string,
                           bool             checkOrder );

void
otf2_print_add_metric_member( struct otf2_print_defs* defs,
                              OTF2_MetricMemberRef    metricMember,
                              OTF2_StringRef64        name,
                              OTF2_Type               valueType,
                              OTF2_StringRef          unit );

static const struct otf2_print_metric_member_def*
otf2_print_get_metric_member( otf2_hash_table*     metricMembers,
                              OTF2_MetricMemberRef metricMember );

static void
otf2_print_add_metric( otf2_hash_table*            metrics,
                       OTF2_MetricRef              metric,
                       OTF2_MetricRef              metricClass,
                       OTF2_RecorderKind           recorderKind,
                       uint8_t                     numberOfMembers,
                       const OTF2_MetricMemberRef* metricMembers );

static const struct otf2_print_metric_def*
otf2_print_get_metric( otf2_hash_table* metrics,
                       OTF2_MetricRef   metric );

static void
otf2_print_add_group( struct otf2_print_data* data,
                      OTF2_GroupRef           group,
                      OTF2_StringRef          name,
                      OTF2_GroupType          type,
                      OTF2_Paradigm           paradigm,
                      OTF2_GroupFlag          flags,
                      uint32_t                numberOfMembers,
                      const uint64_t*         members );

static const struct otf2_print_group_def*
otf2_print_get_group( otf2_hash_table* groups,
                      OTF2_GroupRef    group );

static void
otf2_print_add_comm( struct otf2_print_data* data,
                     OTF2_CommRef            comm,
                     OTF2_StringRef          name,
                     OTF2_GroupRef           groupA,
                     OTF2_GroupRef           groupB,
                     OTF2_CommRef            parent );

static const struct otf2_print_comm_def*
otf2_print_get_comm( otf2_hash_table* comms,
                     OTF2_CommRef     comm );

static const char*
otf2_print_comm_get_rank_name( struct otf2_print_defs* defs,
                               OTF2_LocationRef        location,
                               OTF2_CommRef            comm,
                               uint32_t                rank );

static const char*
otf2_print_comm_get_root_name( struct otf2_print_defs* defs,
                               OTF2_LocationRef        location,
                               OTF2_CommRef            comm,
                               uint32_t                root );

static const char*
otf2_print_task_get_generation_num( uint32_t generationNumber );

static void
otf2_print_add_rma_win( struct otf2_print_data* data,
                        OTF2_RmaWinRef          rmaWin,
                        OTF2_StringRef          name,
                        OTF2_CommRef            comm );

static const struct otf2_print_rma_win_def*
otf2_print_get_rma_win( otf2_hash_table* rmaWins,
                        OTF2_RmaWinRef   rmaWin );

static const char*
otf2_print_rma_win_get_rank_name( struct otf2_print_defs* defs,
                                  OTF2_LocationRef        location,
                                  OTF2_RmaWinRef          rmaWin,
                                  uint32_t                rank );

static const char*
otf2_print_rma_win_get_root_name( struct otf2_print_defs* defs,
                                  OTF2_LocationRef        location,
                                  OTF2_RmaWinRef          rmaWin,
                                  uint32_t                rank );

static void
otf2_print_add_cart_topology( struct otf2_print_data* data,
                              OTF2_CartTopologyRef    cartTopology,
                              OTF2_StringRef          name,
                              OTF2_CommRef            comm );

static const struct otf2_print_cart_topology_def*
otf2_print_get_cart_topology( otf2_hash_table*     cartTopologies,
                              OTF2_CartTopologyRef cartTopology );

static const char*
otf2_print_cart_topology_get_rank_name( struct otf2_print_defs* defs,
                                        OTF2_LocationRef        location,
                                        OTF2_CartTopologyRef    cartTopology,
                                        uint32_t                rank );

static void
otf2_print_add_calling_context( struct otf2_print_data*    data,
                                OTF2_CallingContextRef     self,
                                OTF2_RegionRef             region,
                                OTF2_SourceCodeLocationRef sourceCodeLocation,
                                OTF2_CallingContextRef     parent );

static struct otf2_print_calling_context_def*
otf2_print_get_calling_context( otf2_hash_table*       callingContexts,
                                OTF2_CallingContextRef callingContext );

static void
otf2_print_add_calling_context_property( struct otf2_print_defs* defs,
                                         OTF2_CallingContextRef  callingContext,
                                         OTF2_StringRef          name,
                                         OTF2_Type               type,
                                         OTF2_AttributeValue     value );

static void
otf2_print_add_system_tree_node( struct otf2_print_data* data,
                                 OTF2_SystemTreeNodeRef  systemTreeNode,
                                 OTF2_StringRef          name,
                                 OTF2_StringRef          className,
                                 OTF2_SystemTreeNodeRef  parent );

static struct otf2_print_system_tree_node_def*
otf2_print_get_system_tree_node( otf2_hash_table*       systemTreeNodes,
                                 OTF2_SystemTreeNodeRef systemTreeNode );

static void
otf2_print_add_system_tree_node_property( struct otf2_print_defs* defs,
                                          OTF2_SystemTreeNodeRef  systemTreeNode,
                                          OTF2_StringRef          name,
                                          OTF2_Type               type,
                                          OTF2_AttributeValue     value );

static void
otf2_print_add_system_tree_node_domain( struct otf2_print_defs* defs,
                                        OTF2_SystemTreeNodeRef  systemTreeNode,
                                        OTF2_SystemTreeDomain   domain );

static void
otf2_print_add_location_group( struct otf2_print_data* data,
                               OTF2_LocationGroupRef   locationGroup,
                               OTF2_StringRef          name,
                               OTF2_LocationGroupType  locationGroupType,
                               OTF2_SystemTreeNodeRef  systemTreeParent,
                               OTF2_LocationGroupRef   creatingLocationGroup );

static struct otf2_print_location_group_def*
otf2_print_get_location_group( otf2_hash_table*      locationGroups,
                               OTF2_LocationGroupRef locationGroup );

static void
otf2_print_add_location_group_property( struct otf2_print_defs* defs,
                                        OTF2_LocationGroupRef   locationGroup,
                                        OTF2_StringRef          name,
                                        OTF2_Type               type,
                                        OTF2_AttributeValue     value );

static void
otf2_print_add_location( struct otf2_print_data* data,
                         OTF2_LocationRef        location,
                         OTF2_StringRef          name,
                         OTF2_LocationType       locationType,
                         OTF2_LocationGroupRef   locationGroup );

static struct otf2_print_location_def*
otf2_print_get_location( otf2_hash_table* locations,
                         OTF2_LocationRef location );

static void
otf2_print_add_location_property( struct otf2_print_defs* defs,
                                  OTF2_LocationRef        location,
                                  OTF2_StringRef          name,
                                  OTF2_Type               type,
                                  OTF2_AttributeValue     value );

static const char*
otf2_print_get_rank_name( uint64_t    rank,
                          const char* rankName );

static char*
otf2_print_get_buffer( size_t len );

static const char*
otf2_print_get_id( uint32_t ID );

static const char*
otf2_print_get_id64( uint64_t ID );

static const char*
otf2_print_get_int64( int64_t value );

static const char*
otf2_print_get_name( const char* name,
                     uint64_t    ID );

static const char*
otf2_print_get_def64_name( const otf2_hash_table* defs,
                           uint64_t               def );

static const char*
otf2_print_get_def_name( const otf2_hash_table* defs,
                         uint32_t               def );

static const char*
otf2_print_get_def64_raw_name( const otf2_hash_table* defs,
                               uint64_t               def );

static const char*
otf2_print_get_def_raw_name( const otf2_hash_table* defs,
                             uint32_t               def );

static const char*
otf2_print_get_paradigm_name( const otf2_hash_table* paradigms,
                              OTF2_Paradigm          paradigm );

static char*
otf2_print_get_string( const otf2_hash_table* strings,
                       OTF2_StringRef64       string );

static const char*
otf2_print_get_attribute_value( struct otf2_print_defs* defs,
                                OTF2_Type               type,
                                OTF2_AttributeValue     value,
                                bool                    raw );

static const char*
otf2_print_get_paradigm_property_value( struct otf2_print_defs* defs,
                                        OTF2_ParadigmProperty   property,
                                        OTF2_Type               type,
                                        OTF2_AttributeValue     attributeValue,
                                        const char**            typeString );

static const char*
otf2_print_get_invalid( uint64_t ID );

static void
otf2_print_rank_list_init( struct otf2_print_rank_list* list );

static void
otf2_print_rank_list_append( struct otf2_print_rank_list* list,
                             OTF2_ParadigmProperty        paradigm,
                             uint64_t                     rank );

static void
otf2_print_metric_list_init( struct otf2_print_metric_list* list );

static void
otf2_print_metric_list_append( struct otf2_print_metric_list* list,
                               OTF2_MetricRef                 metricRef,
                               OTF2_LocationRef               recorderRef );

#include "otf2_print_types.h"


/* ___ Prototypes for event callbacks. ______________________________________ */


static OTF2_CallbackCode
print_unknown( OTF2_LocationRef    location,
               OTF2_TimeStamp      time,
               void*               userData,
               OTF2_AttributeList* attributes );


static OTF2_CallbackCode
print_global_def_unknown( void* userData );


#include "otf2_print_inc.c"


static OTF2_CallbackCode
print_def_mapping_table( void*             userData,
                         OTF2_MappingType  mapType,
                         const OTF2_IdMap* iDMap );

static OTF2_CallbackCode
print_def_clock_offset( void*    userData,
                        uint64_t time,
                        int64_t  offset,
                        double   stddev );

struct otf2_dot_data
{
    FILE*                   out;
    struct otf2_print_data* data;
};

static void
print_dot_system_tree_node( otf2_hash_table_entry* entry,
                            void*                  arg );

static void
print_dot_location_group( otf2_hash_table_entry* entry,
                          void*                  arg );

static void
print_dot_location( otf2_hash_table_entry* entry,
                    void*                  arg );


/* ___ main _________________________________________________________________ */


int
main( int    argc,
      char** argv )
{
    otf2_NAME = strrchr( argv[ 0 ], '/' );
    if ( otf2_NAME )
    {
        otf2_NAME++;
    }
    else
    {
        otf2_NAME = argv[ 0 ];
    }

    char* anchor_file = NULL;
    otf2_get_parameters( argc, argv, &anchor_file );

    if ( otf2_NOLOCALDEFS && ( otf2_MAPPINGS || otf2_CLOCK_OFFSETS ) )
    {
        otf2_print_die( "--no-local-defs is mutual exclusive to --show-mappings and --show-clock-offsets\n" );
    }

    printf( "\n=== OTF2-PRINT ===\n" );

    /* Get a reader handle. */
    OTF2_Reader* reader = OTF2_Reader_Open( anchor_file );
    check_pointer( reader, "Create new reader handle." );

    OTF2_ErrorCode status = OTF2_Reader_SetSerialCollectiveCallbacks( reader );
    check_status( status, "Set serial mode." );

    OTF2_Boolean global_reader_hint = OTF2_TRUE;
    status = OTF2_Reader_SetHint( reader,
                                  OTF2_HINT_GLOBAL_READER,
                                  &global_reader_hint );
    check_status( status, "Setting global-reader hint." );

    if ( otf2_ANCHORFILE_INFO )
    {
        otf2_print_anchor_file_information( reader );
    }


    if ( otf2_THUMBNAIL_INFO )
    {
        otf2_print_thumbnails( reader );
    }

    /* Only exit if --show-info was given. */
    if ( ( otf2_ANCHORFILE_INFO || otf2_THUMBNAIL_INFO ) && !otf2_ALL )
    {
        OTF2_Reader_Close( reader );

        /* This is just to add a message to the debug output. */
        check_status( OTF2_SUCCESS, "Delete reader handle." );
        check_status( OTF2_SUCCESS, "Program finished." );

        return EXIT_SUCCESS;
    }
/* ___ Read Global Definitions _______________________________________________*/

    uint32_t number_of_snapshots;
    status = OTF2_Reader_GetNumberOfSnapshots( reader, &number_of_snapshots );
    check_status( status, "Read number of snapshots." );

    /* Add a nice table header. */
    if ( otf2_GLOBDEFS )
    {
        printf( "\n" );
        printf( "=== Global Definitions =========================================================" );
        printf( "\n\n" );
        printf( "%-*s %12s  Attributes\n", otf2_DEF_COLUMN_WIDTH, "Definition", "ID" );
        printf( "--------------------------------------------------------------------------------\n" );
    }
    /* Define definition callbacks. */
    OTF2_GlobalDefReaderCallbacks* def_callbacks = otf2_print_create_global_def_callbacks();

    /* Get number of locations from the anchor file. */
    uint64_t num_locations = 0;
    status = OTF2_SUCCESS;
    status = OTF2_Reader_GetNumberOfLocations( reader, &num_locations );
    check_status( status, "Get number of locations. Number of locations: %" PRIu64,
                  num_locations );


    /* User data for callbacks. */
    struct otf2_print_data user_data;
    struct otf2_print_defs user_defs;
    memset( &user_data, 0, sizeof( user_data ) );
    memset( &user_defs, 0, sizeof( user_defs ) );
    user_data.reader            = reader;
    user_data.locations_to_read = otf2_vector_create();
    user_data.defs              = &user_defs;
    otf2_print_def_create_hash_tables( user_data.defs );
    user_data.comm_paradigms = otf2_vector_create();
    otf2_vector_resize( user_data.comm_paradigms, otf2_max_known_paradigm );
    user_data.artificial_string_refs = ( OTF2_StringRef64 )OTF2_UNDEFINED_STRING;
    otf2_print_metric_list_init( &user_data.recorded_cpu_metrics );
    otf2_print_metric_list_init( &user_data.recorded_gpu_metrics );

    /* Read global definitions. */
    uint64_t              definitions_read  = 0;
    OTF2_GlobalDefReader* global_def_reader = OTF2_Reader_GetGlobalDefReader( reader );
    check_pointer( global_def_reader, "Create global definition reader handle." );

    status = OTF2_Reader_RegisterGlobalDefCallbacks( reader, global_def_reader,
                                                     def_callbacks,
                                                     &user_data );
    check_status( status, "Register global definition callbacks." );
    OTF2_GlobalDefReaderCallbacks_Delete( def_callbacks );

    status = OTF2_Reader_ReadGlobalDefinitions( reader, global_def_reader,
                                                OTF2_UNDEFINED_UINT64,
                                                &definitions_read );
    check_status( status, "Read global definitions. Number of definitions: %" PRIu64,
                  definitions_read );

    OTF2_Reader_CloseGlobalDefReader( reader,
                                      global_def_reader );

    /* Check if number of global definitions read matches the number of global
     * definitions defined in the anchor file. */
    uint64_t defs_anchor = 0;
    status = OTF2_Reader_GetNumberOfGlobalDefinitions( reader, &defs_anchor );
    check_status( status, "Get number of global definitions: %" PRIu64,
                  defs_anchor );

    if ( defs_anchor != definitions_read )
    {
        check_status( OTF2_ERROR_INTEGRITY_FAULT,
                      "Number of global definitions read and defined in anchor file do not match!" );
    }

    /* Check if a requested local trace file was found. */
    if ( otf2_LOCAL == OTF2_UNDEFINED_LOCATION )
    {
        check_condition( otf2_LOCAL_FOUND, "Find location definitions in global definition file." );
    }
    else
    {
        check_condition( otf2_LOCAL_FOUND, "Find event file for selected location." );
    }

    OTF2_DefReaderCallbacks* local_def_callbacks = OTF2_DefReaderCallbacks_New();
    check_pointer( def_callbacks, "Create global definition callback handle." );
    if ( otf2_MAPPINGS )
    {
        OTF2_DefReaderCallbacks_SetMappingTableCallback( local_def_callbacks, print_def_mapping_table );
    }
    if ( otf2_CLOCK_OFFSETS )
    {
        OTF2_DefReaderCallbacks_SetClockOffsetCallback( local_def_callbacks, print_def_clock_offset );
    }

    /* Open a new local event reader for each found location ID. */
    if ( otf2_MAPPINGS || otf2_CLOCK_OFFSETS )
    {
        printf( "\n" );
        printf( "=== Per Location Definitions ===================================================" );
        printf( "\n\n" );
        printf( "%-*s %12s  Attributes\n", otf2_DEF_COLUMN_WIDTH, "Definition", "Location" );
        printf( "--------------------------------------------------------------------------------\n" );
    }

    for ( size_t i = 0; i < otf2_vector_size( user_data.locations_to_read ); i++ )
    {
        uint64_t* location_item = otf2_vector_at( user_data.locations_to_read, i );
        status = OTF2_Reader_SelectLocation( reader, *location_item );
        check_status( status, "Select location to read." );
    }

    bool local_def_files_exists = false;
    if ( !otf2_NOLOCALDEFS && ( !otf2_GLOBDEFS || otf2_ALL ) )
    {
        status = OTF2_Reader_OpenDefFiles( reader );
        /* Will fail if no local def files were written. Remember it and skip
           reading the local def files and closing them. */
        local_def_files_exists = OTF2_SUCCESS == status;
        if ( OTF2_ERROR_ENOENT == status )
        {
            /* Ignore ENOENT in the next check */
            status = OTF2_SUCCESS;
        }
        check_status( status, "Open local definition files for reading." );
    }
    if ( !otf2_GLOBDEFS || otf2_ALL )
    {
        status = OTF2_Reader_OpenEvtFiles( reader );
        check_status( status, "Open event files for reading." );
    }
    for ( size_t i = 0; i < otf2_vector_size( user_data.locations_to_read ); i++ )
    {
        uint64_t* location_item      = otf2_vector_at( user_data.locations_to_read, i );
        uint64_t  locationIdentifier = *location_item;

        /* Do not open the event reader, when only showing the global defs */
        if ( !otf2_GLOBDEFS || otf2_ALL )
        {
            OTF2_EvtReader* evt_reader = OTF2_Reader_GetEvtReader( reader,
                                                                   locationIdentifier );
            check_pointer( evt_reader, "Create local event reader for location %" PRIu64 ".",
                           locationIdentifier );
        }

        if ( !otf2_NOSNAPSHOTS && number_of_snapshots > 0 )
        {
            OTF2_SnapReader* snap_reader = OTF2_Reader_GetSnapReader( reader,
                                                                      locationIdentifier );
            check_pointer( snap_reader, "Create local snapshot reader for location %" PRIu64 ".",
                           locationIdentifier );
        }

        if ( otf2_NOLOCALDEFS || ( otf2_GLOBDEFS && !otf2_ALL ) )
        {
            continue;
        }

        if ( local_def_files_exists )
        {
            /* Also open a definition reader and read all local definitions. */
            OTF2_DefReader* def_reader = OTF2_Reader_GetDefReader( reader,
                                                                   locationIdentifier );
            /* a local def file is not mandatory */
            if ( def_reader )
            {
                if ( otf2_MAPPINGS || otf2_CLOCK_OFFSETS )
                {
                    status = OTF2_Reader_RegisterDefCallbacks( reader,
                                                               def_reader,
                                                               local_def_callbacks,
                                                               &locationIdentifier );
                    check_status( status, "Register local definition callbacks." );
                }

                uint64_t definitions_read = 0;
                status = OTF2_SUCCESS;
                do
                {
                    uint64_t def_reads = 0;
                    status = OTF2_Reader_ReadAllLocalDefinitions( reader,
                                                                  def_reader,
                                                                  &def_reads );
                    definitions_read += def_reads;

                    /* continue reading, if we have a duplicate mapping table */
                    if ( OTF2_ERROR_DUPLICATE_MAPPING_TABLE != status )
                    {
                        break;
                    }
                }
                while ( true );
                check_status( status,
                              "Read %" PRIu64 " definitions for location %" PRIu64,
                              definitions_read,
                              locationIdentifier );

                /* Close def reader, it is no longer useful and occupies memory */
                status = OTF2_Reader_CloseDefReader( reader, def_reader );
                check_status( status, "Close local definition reader." );
            }
        }
    }
    OTF2_DefReaderCallbacks_Delete( local_def_callbacks );
    if ( !otf2_NOLOCALDEFS && !( otf2_GLOBDEFS && !otf2_ALL ) && local_def_files_exists )
    {
        status = OTF2_Reader_CloseDefFiles( reader );
        check_status( status, "Close local definition files for reading." );
    }

    if ( otf2_DOT )
    {
        /* OTF2_Reader_Open ensures a '.' extension and only supports '.otf2' */
        int    anchor_prefix_length = ( int )strlen( anchor_file ) - 5;
        size_t dot_path_size        = anchor_prefix_length + strlen( ".SystemTree.dot" ) + 1;
        char*  dot_path             = malloc( dot_path_size );
        check_pointer( dot_path, "Allocating path for DOT file path." );
        snprintf( dot_path, dot_path_size, "%.*s.SystemTree.dot",
                  anchor_prefix_length, anchor_file );

        struct otf2_dot_data dot_data;
        dot_data.data = &user_data;
        dot_data.out  = fopen( dot_path, "w" );
        if ( dot_data.out == NULL )
        {
            fprintf( stderr,
                     "%s: cannot open dot file for system tree\n",
                     otf2_NAME );
            return EXIT_FAILURE;
        }

        fprintf( dot_data.out,
                 "/* This is the graph representation of the system tree. */\n"
                 "digraph SystemTree\n"
                 "{\n"
                 "    node [shape=plaintext]\n" );

        otf2_hash_table_foreach( user_data.defs->system_tree_nodes,
                                 print_dot_system_tree_node,
                                 &dot_data );

        otf2_hash_table_foreach( user_data.defs->location_groups,
                                 print_dot_location_group,
                                 &dot_data );

        otf2_hash_table_foreach( user_data.defs->locations,
                                 print_dot_location,
                                 &dot_data );

        fprintf( dot_data.out,
                 "}\n" );
        fclose( dot_data.out );

        printf( "\nGenerated system tree graph for \"%s\".\n\n", anchor_file );
        printf( "Dot file written to \"%s\".\n\n", dot_path );
        printf( "To generate an image from the dot file run any of:\n\n" );
        printf( "    dot -Tpng '%s' -o '%.*s.SystemTree.png'\n\n",
                dot_path, anchor_prefix_length, anchor_file );
        printf( "    dot -Tsvg '%s' -o '%.*s.SystemTree.svg'\n\n",
                dot_path, anchor_prefix_length, anchor_file );
        free( dot_path );
    }

    if ( ( otf2_GLOBDEFS || otf2_MAPPINGS || otf2_CLOCK_OFFSETS ) && !otf2_ALL )
    {
        OTF2_Reader_Close( reader );

        /* This is just to add a message to the debug output. */
        check_status( OTF2_SUCCESS, "Delete reader handle." );
        check_status( OTF2_SUCCESS, "Program finished." );

        return EXIT_SUCCESS;
    }



/* ___ Read Event Records ____________________________________________________*/



    /* Add a nice table header. */
    if ( !otf2_SILENT )
    {
        printf( "=== Events =====================================================================\n" );
    }

    /* Define event callbacks. */
    OTF2_GlobalEvtReaderCallbacks* evt_callbacks = otf2_print_create_global_evt_callbacks();

    /* Get global event reader. */
    OTF2_GlobalEvtReader* global_evt_reader = OTF2_Reader_GetGlobalEvtReader( reader );
    check_pointer( global_evt_reader, "Create global event reader." );


    /* Register the above defined callbacks to the global event reader. */
    if ( !otf2_SILENT )
    {
        status = OTF2_Reader_RegisterGlobalEvtCallbacks( reader,
                                                         global_evt_reader,
                                                         evt_callbacks,
                                                         &user_data );
        check_status( status, "Register global event callbacks." );
    }
    OTF2_GlobalEvtReaderCallbacks_Delete( evt_callbacks );


    /* Read until events are all read. */
    uint64_t events_read = otf2_STEP;

    while ( events_read == otf2_STEP )
    {
        if ( !otf2_SILENT )
        {
            printf( "%-*s %15s %20s  Attributes\n",
                    otf2_EVENT_COLUMN_WIDTH, "Event", "Location", "Timestamp" );
            printf( "--------------------------------------------------------------------------------\n" );
        }

        status = OTF2_Reader_ReadGlobalEvents( reader,
                                               global_evt_reader,
                                               otf2_STEP,
                                               &events_read );
        check_status( status, "Read %" PRIu64 " events.", events_read );

        /* Step through output if otf2_STEP is defined. */
        if ( otf2_STEP != OTF2_UNDEFINED_UINT64 )
        {
            printf( "Press ENTER to print next %" PRIu64 " events.", otf2_STEP );
            getchar();
        }
    }
    status = OTF2_Reader_CloseGlobalEvtReader( reader,
                                               global_evt_reader );
    check_status( status, "Close global definition reader." );
    status = OTF2_Reader_CloseEvtFiles( reader );
    check_status( status, "Close event files for reading." );

/* ___ Read Snapshot Records ____________________________________________________*/



    if ( !otf2_NOSNAPSHOTS && number_of_snapshots > 0 )
    {
        /* Add a nice table header. */
        if ( !otf2_SILENT )
        {
            printf( "=== Snapshots ==================================================================\n" );
        }

        /* Define snapshot callbacks. */
        OTF2_GlobalSnapReaderCallbacks* snap_callbacks = otf2_print_create_global_snap_callbacks();

        /* Get global snapshots reader. */
        OTF2_GlobalSnapReader* global_snap_reader = OTF2_Reader_GetGlobalSnapReader( reader );
        check_pointer( global_snap_reader, "Create global snapshots reader." );


        /* Register the above defined callbacks to the global snapshots reader. */
        if ( !otf2_SILENT )
        {
            status = OTF2_Reader_RegisterGlobalSnapCallbacks( reader,
                                                              global_snap_reader,
                                                              snap_callbacks,
                                                              &user_data );
            check_status( status, "Register global snapshots callbacks." );
        }
        OTF2_GlobalSnapReaderCallbacks_Delete( snap_callbacks );


        /* Read until snapshots are all read. */
        uint64_t records_read = otf2_STEP;
        while ( records_read == otf2_STEP )
        {
            if ( !otf2_SILENT )
            {
                printf( "%-*s %15s %20s  Attributes\n",
                        otf2_EVENT_COLUMN_WIDTH, "Snapshot", "Location", "Timestamp" );
                printf( "--------------------------------------------------------------------------------\n" );
            }

            status = OTF2_Reader_ReadGlobalSnapshots( reader,
                                                      global_snap_reader,
                                                      otf2_STEP,
                                                      &records_read );
            check_status( status, "Read %" PRIu64 " snapshot records.", records_read );

            /* Step through output if otf2_STEP is defined. */
            if ( otf2_STEP != OTF2_UNDEFINED_UINT64 )
            {
                printf( "Press ENTER to print next %" PRIu64 " snapshot records.", otf2_STEP );
                getchar();
            }
        }
    }

    OTF2_Reader_Close( reader );

    /* This is just to add a message to the debug output. */
    check_status( OTF2_SUCCESS, "Delete reader handle." );
    check_status( OTF2_SUCCESS, "Program finished." );

    otf2_vector_foreach( user_data.locations_to_read, free );
    otf2_vector_free( user_data.locations_to_read );
    otf2_print_def_destroy_hash_tables( user_data.defs );
    otf2_vector_free( user_data.comm_paradigms );

    return EXIT_SUCCESS;
}



/* ___ Implementation of static functions ___________________________________ */

static void
otf2_parse_number_argument( const char* option,
                            const char* argument,
                            uint64_t*   number )
{
    const char* p = argument;
    *number = 0;
    while ( *p )
    {
        if ( ( *p < '0' ) || ( *p > '9' ) )
        {
            otf2_print_die( "invalid number argument for %s: %s\n",
                            option, argument );
        }
        uint64_t new_number = *number * 10 + *p - '0';
        if ( new_number < *number )
        {
            otf2_print_die( "number argument to large for '%s': %s\n",
                            option, argument );
        }
        *number = new_number;
        p++;
    }
    if ( p == argument )
    {
        otf2_print_die( "empty number argument for '%s'\n", option );
    }
}

/** @internal
 *  @brief Get command line parameters.
 *
 *  Parses command line parameters and checks for their existence.
 *  Prints help for parameters '-h' or '--help'.
 *
 *  @param argc             Programs argument counter.
 *  @param argv             Programs argument values.
 *  @param anchorFile       Return pointer for the anchor file path.
 */
void
otf2_get_parameters( int    argc,
                     char** argv,
                     char** anchorFile )
{
    bool process_options = true;
    int  i;
    for ( i = 1; process_options && i < argc; i++ )
    {
        if ( !strcmp( argv[ i ], "--help" ) || !strcmp( argv[ i ], "-h" ) )
        {
            const char usage[] =
            #include "otf2_print_usage.h"
            ;
            printf( "%s\nReport bugs to <%s>\n", usage, PACKAGE_BUGREPORT );
            exit( EXIT_SUCCESS );
        }

        else if ( !strcmp( argv[ i ], "--version" ) || !strcmp( argv[ i ], "-V" ) )
        {
            printf( "%s: version %s\n", otf2_NAME, OTF2_VERSION );
            exit( EXIT_SUCCESS );
        }

        else if ( !strcmp( argv[ i ], "--warnings-as-errors" ) || !strcmp( argv[ i ], "-Werror" ) )
        {
            otf2_WERROR = true;
        }

        else if ( !strcmp( argv[ i ], "--debug" ) || !strcmp( argv[ i ], "-d" ) )
        {
            otf2_DEBUG = true;
        }

        /* Check for requested system tree dot output. */
        else if ( !strcmp( argv[ i ], "--system-tree" ) )
        {
            otf2_DOT = true;
        }

        else if ( !strcmp( argv[ i ], "--show-all" ) || !strcmp( argv[ i ], "-A" ) )
        {
            otf2_ANCHORFILE_INFO = true;
            otf2_THUMBNAIL_INFO  = true;
            otf2_ALL             = true;
            otf2_GLOBDEFS        = true;
        }

        else if ( !strcmp( argv[ i ], "--show-global-defs" ) || !strcmp( argv[ i ], "-G" ) )
        {
            otf2_GLOBDEFS = true;
        }

        else if ( !strcmp( argv[ i ], "--show-info" ) || !strcmp( argv[ i ], "-I" ) )
        {
            otf2_ANCHORFILE_INFO = true;
        }

        else if ( !strcmp( argv[ i ], "--show-thumbnails" ) || !strcmp( argv[ i ], "-T" ) )
        {
            otf2_THUMBNAIL_INFO = true;
        }

        else if ( !strcmp( argv[ i ], "--show-thumbnail-samples" ) )
        {
            otf2_THUMBNAIL_INFO    = true;
            otf2_THUMBNAIL_SAMPLES = true;
        }

        else if ( !strcmp( argv[ i ], "--show-mappings" ) || !strcmp( argv[ i ], "-M" ) )
        {
            otf2_MAPPINGS = true;
        }

        else if ( !strcmp( argv[ i ], "--show-clock-offsets" ) || !strcmp( argv[ i ], "-C" ) )
        {
            otf2_CLOCK_OFFSETS = true;
        }

        else if ( !strcmp( argv[ i ], "--no-local-defs" ) )
        {
            otf2_NOLOCALDEFS = true;
        }

        else if ( !strcmp( argv[ i ], "--no-snapshots" ) )
        {
            otf2_NOSNAPSHOTS = true;
        }

        else if ( !strcmp( argv[ i ], "--silent" ) )
        {
            otf2_SILENT = true;
        }

        else if ( !strcmp( argv[ i ], "--timestamps" )
                  || !strncmp( argv[ i ], "--timestamps=", 13 ) )
        {
            char* opt = argv[ i ];
            char* arg = &opt[ 12 ]; /* points to '=' or '\0' */
            if ( !*arg++ )
            {
                if ( i + 1 >= argc )
                {
                    otf2_print_die( "missing argument for '%s'\n", opt );
                }
                arg = argv[ i + 1 ];
                i++;
            }
            if ( !strcmp( "plain", arg ) )
            {
                otf2_TIMESTAMP_FORMAT = TIMESTAMP_PLAIN;
            }
            else if ( !strcmp( "offset", arg ) )
            {
                otf2_TIMESTAMP_FORMAT = TIMESTAMP_OFFSET;
            }
            else
            {
                otf2_print_die( "invalid argument for option '%.12s': %s\n", opt, arg );
            }
        }

        else if ( !strcmp( argv[ i ], "--location" )
                  || !strncmp( argv[ i ], "--location=", 11 )
                  || !strcmp( argv[ i ], "-L" ) )
        {
            char* opt = argv[ i ];
            char* arg;
            if ( opt[ 1 ] == '-' && opt[ 10 ] == '=' )
            {
                opt[ 10 ] = '\0';
                arg       = &opt[ 11 ];
            }
            else
            {
                if ( i + 1 >= argc )
                {
                    otf2_print_die( "missing argument for '%s'\n", opt );
                }
                arg = argv[ i + 1 ];
                i++;
            }

            otf2_parse_number_argument( opt, arg, &otf2_LOCAL );
        }

        else if ( !strcmp( argv[ i ], "--time" )
                  || !strncmp( argv[ i ], "--time=", 7 ) )
        {
            char* opt = argv[ i ];
            char* arg1;
            char* arg2;
            if ( opt[ 6 ] == '=' )
            {
                opt[ 6 ] = '\0';
                arg1     = &opt[ 7 ];
                arg2     = strchr( arg1, ',' );
                if ( !arg2 )
                {
                    otf2_print_die( "missing argument for '%s'\n", opt );
                }
                *arg2++ = '\0';
            }
            else
            {
                if ( i + 2 >= argc )
                {
                    otf2_print_die( "missing argument for '%s'\n", opt );
                }
                arg1 = argv[ i + 1 ];
                arg2 = argv[ i + 2 ];
                i   += 2;
            }

            otf2_parse_number_argument( opt, arg1, &otf2_MINTIME );
            otf2_parse_number_argument( opt, arg2, &otf2_MAXTIME );
        }

        else if ( !strcmp( argv[ i ], "--step" )
                  || !strncmp( argv[ i ], "--step=", 7 )
                  || !strcmp( argv[ i ], "-s" ) )
        {
            char* opt = argv[ i ];
            char* arg;
            if ( opt[ 1 ] == '-' && opt[ 6 ] == '=' )
            {
                opt[ 6 ] = '\0';
                arg      = &opt[ 7 ];
            }
            else
            {
                if ( i + 1 >= argc )
                {
                    otf2_print_die( "missing argument for '%s'\n", opt );
                }
                arg = argv[ i + 1 ];
                i++;
            }

            otf2_parse_number_argument( opt, arg, &otf2_STEP );
        }

        else if ( !strcmp( argv[ i ], "--unwind-calling-context" ) )
        {
            otf2_UNWIND_CALLING_CONTEXT = true;
        }

        else if ( !strcmp( argv[ i ], "--" ) )
        {
            process_options = false;
        }

        else if ( argv[ i ][ 0 ] == '-' )
        {
            otf2_print_die( "unrecognized option '%s'\n", argv[ i ] );
        }

        else
        {
            break;
        }
    }

    if ( 1 != argc - i )
    {
        otf2_print_die( "missing or too many anchorfile argument(s)\n" );
    }

    *anchorFile = argv[ i ];
}

static void
otf2_print_anchor_file_information( OTF2_Reader* reader )
{
    OTF2_ErrorCode status;

    uint8_t major;
    uint8_t minor;
    uint8_t bugfix;
    status = OTF2_Reader_GetVersion( reader, &major, &minor, &bugfix );
    check_status( status, "Read version." );

    printf( "\nContent of OTF2 anchor file:\n" );
    printf( "%-*s %u.%u.%u\n",
            otf2_ANCHOR_FILE_INFO_COLUMN_WIDTH, "Version",
            major, minor, bugfix );

    uint64_t chunk_size_events;
    uint64_t chunk_size_definitions;
    status = OTF2_Reader_GetChunkSize( reader,
                                       &chunk_size_events,
                                       &chunk_size_definitions );
    check_status( status, "Read chunk size." );

    printf( "%-*s %" PRIu64 "\n",
            otf2_ANCHOR_FILE_INFO_COLUMN_WIDTH, "Chunk size events",
            chunk_size_events );
    printf( "%-*s %" PRIu64 "\n",
            otf2_ANCHOR_FILE_INFO_COLUMN_WIDTH, "Chunk size definitions",
            chunk_size_definitions );

    OTF2_FileSubstrate substrate;
    status = OTF2_Reader_GetFileSubstrate( reader, &substrate );
    check_status( status, "Read file substrate." );

    printf( "%-*s %s\n",
            otf2_ANCHOR_FILE_INFO_COLUMN_WIDTH,
            "File substrate",
            otf2_print_get_file_substrate( substrate ) );

    OTF2_Compression compression;
    status = OTF2_Reader_GetCompression( reader, &compression );
    check_status( status, "Read compression mode." );

    printf( "%-*s ", otf2_ANCHOR_FILE_INFO_COLUMN_WIDTH, "Compression" );
    switch ( compression )
    {
        case OTF2_COMPRESSION_NONE:
            printf( "NONE\n" );
            break;
        case OTF2_COMPRESSION_ZLIB:
            printf( "ZLIB\n" );
            break;
        default:
            printf( "%s\n", otf2_print_get_invalid( compression ) );
    }

    uint64_t number_of_locations;
    status = OTF2_Reader_GetNumberOfLocations( reader, &number_of_locations );
    check_status( status, "Read number of locations." );

    printf( "%-*s %" PRIu64 "\n",
            otf2_ANCHOR_FILE_INFO_COLUMN_WIDTH, "Number of locations",
            number_of_locations );

    uint64_t number_of_global_definitions;
    status = OTF2_Reader_GetNumberOfGlobalDefinitions(
        reader,
        &number_of_global_definitions );
    check_status( status, "Read number of global definitions." );

    printf( "%-*s %" PRIu64 "\n",
            otf2_ANCHOR_FILE_INFO_COLUMN_WIDTH, "Number of global definitions",
            number_of_global_definitions );

    char* string_buffer;
    status = OTF2_Reader_GetMachineName( reader, &string_buffer );
    check_status( status, "Read machine name." );

    printf( "%-*s %s\n",
            otf2_ANCHOR_FILE_INFO_COLUMN_WIDTH, "Machine name",
            string_buffer );
    free( string_buffer );

    status = OTF2_Reader_GetCreator( reader, &string_buffer );
    check_status( status, "Read creator." );

    printf( "%-*s %s\n",
            otf2_ANCHOR_FILE_INFO_COLUMN_WIDTH, "Creator",
            string_buffer );
    free( string_buffer );

    status = OTF2_Reader_GetDescription( reader, &string_buffer );
    check_status( status, "Read description." );

    printf( "%-*s %s\n",
            otf2_ANCHOR_FILE_INFO_COLUMN_WIDTH, "Description",
            string_buffer );
    free( string_buffer );

    uint32_t number_of_properties;
    char**   property_names;
    status = OTF2_Reader_GetPropertyNames( reader, &number_of_properties, &property_names );
    check_status( status, "Read names of properties." );

    printf( "%-*s %" PRIu32 "\n",
            otf2_ANCHOR_FILE_INFO_COLUMN_WIDTH, "Number of properties",
            number_of_properties );

    for ( uint32_t property_index = 0; property_index < number_of_properties; property_index++ )
    {
        printf( "%-*s %s\n",
                otf2_ANCHOR_FILE_INFO_COLUMN_WIDTH, "Property name",
                property_names[ property_index ] );

        status = OTF2_Reader_GetProperty( reader, property_names[ property_index ], &string_buffer );
        check_status( status, "Read value of property." );

        printf( "%-*s %s\n",
                otf2_ANCHOR_FILE_INFO_COLUMN_WIDTH, "Property value",
                string_buffer );
        free( string_buffer );
    }
    free( property_names );

    uint64_t trace_id;
    status = OTF2_Reader_GetTraceId( reader, &trace_id );
    check_status( status, "Read trace identifier." );

    printf( "%-*s %" PRIx64 "\n",
            otf2_ANCHOR_FILE_INFO_COLUMN_WIDTH, "Trace identifier",
            trace_id );

    uint32_t number;
    status = OTF2_Reader_GetNumberOfSnapshots( reader,  &number );
    check_status( status, "Read number of snapshots." );

    printf( "%-*s %" PRIu32 "\n",
            otf2_ANCHOR_FILE_INFO_COLUMN_WIDTH, "Number of snapshots",
            number );

    status = OTF2_Reader_GetNumberOfThumbnails( reader,  &number );
    check_status( status, "Read Number of thumbnails." );

    printf( "%-*s %" PRIu32 "\n",
            otf2_ANCHOR_FILE_INFO_COLUMN_WIDTH, "Number of thumbnails",
            number );
}

void
otf2_print_thumbnails( OTF2_Reader* reader )
{
    /* Do we have thumbnails at all? */
    uint32_t number_of_thumbnails;
    if ( OTF2_Reader_GetNumberOfThumbnails( reader,
                                            &number_of_thumbnails ) != OTF2_SUCCESS
         || number_of_thumbnails == 0 )
    {
        return;
    }

    printf( "\nThumbnail headers:\n" );
    OTF2_ErrorCode status;
    for ( uint32_t i = 0; i < number_of_thumbnails; i++ )
    {
        OTF2_ThumbReader* thumb_reader = OTF2_Reader_GetThumbReader( reader, i );
        if ( !thumb_reader )
        {
            continue;
        }

        char*              name        = NULL;
        char*              description = NULL;
        OTF2_ThumbnailType type;
        uint32_t           number_of_samples;
        uint32_t           number_of_metrics;
        uint64_t*          refs_to_defs = NULL;

        status = OTF2_ThumbReader_GetHeader( thumb_reader,
                                             &name,
                                             &description,
                                             &type,
                                             &number_of_samples,
                                             &number_of_metrics,
                                             &refs_to_defs );
        check_status( status, "Reading thumbnail header." );

        printf( "%-*s "
                "%12" PRIUint32
                "  "
                "Name: \"%s\", "
                "Description: \"%s\", "
                "Type: %s, "
                "Samples: %" PRIUint32 ", "
                "%" PRIUint32 " Metric%s",
                otf2_THUMBNAIL_HEADER_COLUMN_WIDTH, "THUMBNAIL_HEADER",
                i,
                name,
                description,
                otf2_print_get_thumbnail_type( type ),
                number_of_samples,
                number_of_metrics,
                number_of_metrics == 1 ? "" : "s" );

        const char* sep = ": ";
        for ( uint32_t ref = 0; ref < number_of_metrics; ref++ )
        {
            printf( "%s%" PRIUint64, sep, refs_to_defs[ ref ] );
            sep = ", ";
        }
        printf( "\n" );

        free( name );
        free( description );
        free( refs_to_defs );

        if ( !otf2_THUMBNAIL_SAMPLES )
        {
            OTF2_Reader_CloseThumbReader( reader, thumb_reader );
            continue;
        }

        uint64_t* one_sample;
        one_sample = malloc( number_of_metrics * sizeof( *one_sample ) );
        check_pointer( one_sample, "Allocating sample array." );

        uint64_t baseline;
        for ( uint32_t sample = 0; sample < number_of_samples; sample++ )
        {
            status = OTF2_ThumbReader_ReadSample( thumb_reader,
                                                  &baseline,
                                                  number_of_metrics,
                                                  one_sample );
            check_status( status, "Reading thumbnail sample." );

            printf( "%-*s "
                    "%12" PRIUint32
                    "  "
                    "Baseline: %s, "
                    "Metric%s",
                    otf2_THUMBNAIL_HEADER_COLUMN_WIDTH, "THUMBNAIL_SAMPLE",
                    sample,
                    otf2_print_get_id64( baseline ),
                    number_of_metrics == 1 ? "" : "s" );

            const char* sep = ": ";
            for ( uint32_t metric = 0; metric < number_of_metrics; metric++ )
            {
                printf( "%s%" PRIUint64, sep, one_sample[ metric ] );
                sep = ", ";
            }
            printf( "\n" );
        }
        free( one_sample );

        OTF2_Reader_CloseThumbReader( reader, thumb_reader );
    }
}


/** @internal
 *  @brief Check if pointer is NULL.
 *
 *  Checks if a pointer is NULL. If so it prints an error with the passed
 *  description and exits the program.
 *  If in debug mode, it prints a debug message with the passed description.
 *  It is possible to passed a variable argument list like e.g. in printf.
 *
 *  @param pointer          Pointer to be checked.
 *  @param description      Description for error/debug message.
 *  @param ...              Variable argument list like e.g. in printf.
 */
void
check_pointer( void* pointer,
               char* description,
               ... )
{
    va_list va;
    va_start( va, description );

    if ( pointer == NULL )
    {
        printf( "==ERROR== " );
        vfprintf( stdout, description, va );
        printf( "\n" );
        exit( EXIT_FAILURE );
    }

    if ( otf2_DEBUG )
    {
        printf( "==DEBUG== " );
        vfprintf( stdout, description, va );
        printf( "\n" );
    }

    va_end( va );
}


/** @internal
 *  @brief Check if status is not OTF2_SUCCESS.
 *
 *  Checks if status is not OTF2_SUCCESS. If so it prints an error with the
 *  passed description and exits the program.
 *  If in debug mode, it prints a debug message with the passed description.
 *  It is possible to passed a variable argument list like e.g. in printf.
 *
 *  @param status           Status to be checked.
 *  @param description      Description for error/debug message.
 *  @param ...              Variable argument list like e.g. in printf.
 */
void
check_status( OTF2_ErrorCode status,
              char*          description,
              ... )
{
    va_list va;
    va_start( va, description );

    if ( status != OTF2_SUCCESS )
    {
        printf( "==ERROR== %s ", OTF2_Error_GetName( status ) );
        vfprintf( stdout, description, va );
        printf( "\n" );
        exit( EXIT_FAILURE );
    }

    if ( otf2_DEBUG )
    {
        printf( "==DEBUG== " );
        vfprintf( stdout, description, va );
        printf( "\n" );
    }

    va_end( va );
}


/** @internal
 *  @brief Check if condition is true. Otherwise fail.
 *
 *  Checks if condition is true. If not it prints an error with the
 *  passed description and exits the program.
 *  If in debug mode, it prints a debug message with the passed description.
 *  It is possible to passed a variable argument list like e.g. in printf.
 *
 *  @param condition        Condition which must hold true.
 *  @param description      Description for error/debug message.
 *  @param ...              Variable argument list like e.g. in printf.
 */
void
check_condition( bool  condition,
                 char* description,
                 ... )
{
    va_list va;
    va_start( va, description );

    if ( !condition )
    {
        fprintf( stderr, "==ERROR== " );
        vfprintf( stderr, description, va );
        fprintf( stderr, "\n" );
        exit( EXIT_FAILURE );
    }

    if ( otf2_DEBUG )
    {
        printf( "==DEBUG== " );
        vfprintf( stdout, description, va );
        printf( "\n" );
    }

    va_end( va );
}


void
otf2_print_add_clock_properties( struct otf2_print_data* data,
                                 uint64_t                timerResolution,
                                 uint64_t                globalOffset,
                                 uint64_t                traceLength )
{
    if ( data->timer_resolution != 0 )
    {
        otf2_print_warn( "duplicate ClockProperties\n" );
        /* overwriting the current definition */
    }

    if ( timerResolution == 0 )
    {
        otf2_print_warn( "invalid timer resolution in ClockProperties: %" PRIu64 "\n",
                         timerResolution );
        return;
    }

    data->timer_resolution = timerResolution;
    data->global_offset    = globalOffset;
    data->trace_length     = traceLength;
}


const char*
otf2_print_get_timestamp( struct otf2_print_data* data,
                          OTF2_TimeStamp          time )
{
    if ( time == OTF2_UNDEFINED_TIMESTAMP )
    {
        return "UNDEFINED";
    }

    char* buffer = otf2_print_get_buffer( 0 );

    if ( otf2_TIMESTAMP_FORMAT == TIMESTAMP_OFFSET &&
         data->timer_resolution != 0 )
    {
        if ( time < data->global_offset )
        {
            snprintf( buffer, BUFFER_SIZE, "-%" PRIu64, data->global_offset - time );
        }
        else
        {
            snprintf( buffer, BUFFER_SIZE, "%" PRIu64, time - data->global_offset );
        }
    }
    else
    {
        snprintf( buffer, BUFFER_SIZE, "%" PRIu64, time );
    }

    return buffer;
}


/** @internal
 *  @brief Add a locations to the list of locations to read events from.
 *
 *  @param locations        List of regions.
 *  @param location         Location ID of the location.
 */
void
otf2_print_add_location_to_read( struct otf2_print_data* data,
                                 OTF2_LocationRef        location )
{
    uint64_t* location_item = malloc( sizeof( *location_item ) );
    assert( location_item );

    *location_item = location;

    otf2_vector_push_back( data->locations_to_read, location_item );
}


/** @internal
 *  @brief Add a string to the set of strings.
 *
 *  @param strings          Set of strings.
 *  @param string           String ID of new element.
 *  @param content          Content of the new element.
 */
void
otf2_print_add_string( otf2_hash_table* strings,
                       OTF2_StringRef64 string,
                       size_t           content_len,
                       const char*      content_fmt,
                       ... )
{
    if ( string == OTF2_UNDEFINED_STRING )
    {
        return;
    }

    bool use_vl = true;
    if ( content_len == 0 )
    {
        content_len = strlen( content_fmt ) + 1;
        use_vl      = false;
    }

    size_t                 hint;
    otf2_hash_table_entry* entry =
        otf2_hash_table_find( strings, &string, &hint );
    if ( entry )
    {
        otf2_print_warn( "duplicate String definition: %s\n",
                         otf2_print_get_def_name( strings, string ) );
        /* overwriting the current definition */
    }

    struct otf2_print_def_name* new_string = malloc( sizeof( *new_string )
                                                     + content_len );
    check_pointer( new_string, "Allocating memory for String definition." );

    new_string->def  = string;
    new_string->name = ( char* )new_string + sizeof( *new_string );

    if ( use_vl )
    {
        va_list vl;
        va_start( vl, content_fmt );

        vsnprintf( new_string->name, content_len, content_fmt, vl );

        va_end( vl );
    }
    else
    {
        memcpy( new_string->name, content_fmt, strlen( content_fmt ) + 1 );
    }

    otf2_hash_table_insert( strings,
                            &new_string->def,
                            new_string,
                            &hint );

    entry = otf2_hash_table_find( strings, &string, &hint );
    assert( entry );
}


/** @internal
 *  @brief Add a def with id tye uint64_t to the set of defs.
 *
 *  @param regions          Set of regions.
 *  @param region           Region ID of new region.
 *  @param string           String ID of new region.
 */
void
otf2_print_add_def64_name( const char*      defClass,
                           otf2_hash_table* defs,
                           otf2_hash_table* strings,
                           uint64_t         def,
                           OTF2_StringRef64 string,
                           bool             checkOrder )
{
    if ( def == OTF2_UNDEFINED_UINT64 )
    {
        return;
    }

    size_t                 hint;
    otf2_hash_table_entry* entry =
        otf2_hash_table_find( defs, &def, &hint );
    if ( entry )
    {
        otf2_print_warn( "duplicate %s definition: %s\n",
                         defClass,
                         otf2_print_get_def64_name( defs, def ) );
        /* overwriting the current definition */
    }

    struct otf2_print_def_name* new_def = malloc( sizeof( *new_def ) );
    check_pointer( new_def, "Allocating memory for %s definition.", defClass );

    new_def->def  = def;
    new_def->name = otf2_print_get_string( strings, string );

    bool out_of_order = checkOrder
                        && def != otf2_hash_table_size( defs );
    otf2_hash_table_insert( defs, &new_def->def, new_def, &hint );
    if ( out_of_order )
    {
        otf2_print_warn( "out of order %s definition: %s\n",
                         defClass,
                         otf2_print_get_def64_name( defs, def ) );
    }
}

/** @internal
 *  @brief Add a def with id type uint32_t to the set of defs.
 *
 *  @param regions          Set of regions.
 *  @param region           Region ID of new region.
 *  @param string           String ID of new region.
 */
static void
otf2_print_add_def_name( const char*      defClass,
                         otf2_hash_table* defs,
                         otf2_hash_table* strings,
                         uint32_t         def,
                         OTF2_StringRef64 string,
                         bool             checkOrder )
{
    if ( def == OTF2_UNDEFINED_UINT32 )
    {
        return;
    }
    otf2_print_add_def64_name( defClass, defs, strings, def, string, checkOrder );
}


/** @internal
 *  @brief Add a metric class or metric instances to the defs.
 */
void
otf2_print_add_metric_member( struct otf2_print_defs* defs,
                              OTF2_MetricMemberRef    metricMember,
                              OTF2_StringRef64        name,
                              OTF2_Type               valueType,
                              OTF2_StringRef          unit )
{
    if ( metricMember == OTF2_UNDEFINED_METRIC )
    {
        return;
    }

    uint64_t               metric_member64 = metricMember;
    size_t                 hint;
    otf2_hash_table_entry* entry =
        otf2_hash_table_find( defs->metric_members, &metric_member64, &hint );
    if ( entry )
    {
        otf2_print_warn( "duplicate MetricMember definition: %s\n",
                         otf2_print_get_def_name( defs->metric_members, metricMember ) );
        /* overwriting the current definition */
    }

    struct otf2_print_metric_member_def* new_def = calloc( 1, sizeof( *new_def ) );
    check_pointer( new_def, "Allocating memory for MetricMember definition." );

    new_def->def.def  = metric_member64;
    new_def->def.name = otf2_print_get_string( defs->strings, name );

    new_def->type = valueType;
    new_def->unit = otf2_print_get_string( defs->strings, unit );

    bool out_of_order = metricMember != otf2_hash_table_size( defs->metric_members );
    otf2_hash_table_insert( defs->metric_members, &new_def->def.def, new_def, &hint );
    if ( out_of_order )
    {
        otf2_print_warn( "out of order MetricMember definition: %s\n",
                         otf2_print_get_def_name( defs->metric_members, metricMember ) );
    }
}

static const struct otf2_print_metric_member_def*
otf2_print_get_metric_member( otf2_hash_table*     metricMembers,
                              OTF2_MetricMemberRef metricMember )
{
    if ( metricMember == OTF2_UNDEFINED_METRIC_MEMBER )
    {
        return NULL;
    }

    uint64_t               metric_member64 = metricMember;
    otf2_hash_table_entry* entry           =
        otf2_hash_table_find( metricMembers, &metric_member64, NULL );
    if ( entry )
    {
        return entry->value;
    }

    return NULL;
}

/** @internal
 *  @brief Add a metric class or metric instances to the defs.
 */
void
otf2_print_add_metric( otf2_hash_table*            metrics,
                       OTF2_MetricRef              metric,
                       OTF2_MetricRef              metricClass,
                       OTF2_RecorderKind           recorderKind,
                       uint8_t                     numberOfMembers,
                       const OTF2_MetricMemberRef* metricMembers )
{
    if ( metric == OTF2_UNDEFINED_METRIC )
    {
        return;
    }

    uint64_t               metric64 = metric;
    size_t                 hint;
    otf2_hash_table_entry* entry =
        otf2_hash_table_find( metrics, &metric64, &hint );
    if ( entry )
    {
        otf2_print_warn( "duplicate Metric definition: %s\n",
                         otf2_print_get_def_name( metrics, metric ) );
        /* overwriting the current definition */
    }

    /* Resolve metric class in case of metric instance */
    if ( metricClass != OTF2_UNDEFINED_METRIC )
    {
        uint64_t               metric_class64 = metricClass;
        size_t                 class_hint;
        otf2_hash_table_entry* class_entry =
            otf2_hash_table_find( metrics, &metric_class64, &class_hint );
        if ( class_entry )
        {
            struct otf2_print_metric_def* class_def = class_entry->value;
            numberOfMembers = class_def->number_of_members;
            metricMembers   = class_def->members;
        }
    }

    struct otf2_print_metric_def* new_def =
        malloc( sizeof( *new_def ) +
                ( numberOfMembers * sizeof( OTF2_MetricMemberRef ) ) );
    check_pointer( new_def, "Allocating memory for Metric definition." );

    new_def->def.def  = metric64;
    new_def->def.name = NULL;

    new_def->recorder_kind     = recorderKind;
    new_def->number_of_members = numberOfMembers;
    memcpy( new_def->members,
            metricMembers,
            numberOfMembers * sizeof( OTF2_MetricMemberRef ) );

    bool out_of_order = metric != otf2_hash_table_size( metrics );
    otf2_hash_table_insert( metrics, &new_def->def.def, new_def, &hint );
    if ( out_of_order )
    {
        otf2_print_warn( "out of order Metric definition: %s\n",
                         otf2_print_get_def_name( metrics, metric ) );
    }
}

/** @internal
 *  @brief Get a metric class or metric instances defs.
 */
const struct otf2_print_metric_def*
otf2_print_get_metric( otf2_hash_table* metrics,
                       OTF2_MetricRef   metric )
{
    if ( metric == OTF2_UNDEFINED_METRIC )
    {
        return NULL;
    }

    uint64_t               metric64 = metric;
    otf2_hash_table_entry* entry    =
        otf2_hash_table_find( metrics, &metric64, NULL );
    if ( entry )
    {
        return entry->value;
    }

    return NULL;
}


static void
otf2_print_rank_list_init( struct otf2_print_rank_list* list )
{
    list->head = NULL;
    list->tail = &list->head;
}


static void
otf2_print_rank_list_append( struct otf2_print_rank_list* list,
                             OTF2_ParadigmProperty        paradigm,
                             uint64_t                     rank )
{
    struct otf2_print_rank_list_element* new_rank = calloc( 1, sizeof( *new_rank ) );
    check_pointer( new_rank, "Allocating memory for recorded Metric." );

    new_rank->paradigm = paradigm;
    new_rank->rank     = rank;
    *list->tail        = new_rank;
    list->tail         = &new_rank->next;
}


static void
otf2_print_metric_list_init( struct otf2_print_metric_list* list )
{
    list->head = NULL;
    list->tail = &list->head;
}


static void
otf2_print_metric_list_append( struct otf2_print_metric_list* list,
                               OTF2_MetricRef                 metricRef,
                               OTF2_LocationRef               recorderRef )
{
    struct otf2_print_metric_list_element* new_metric = calloc( 1, sizeof( *new_metric ) );
    check_pointer( new_metric, "Allocating memory for recorded Metric." );

    new_metric->metricRef   = metricRef;
    new_metric->recorderRef = recorderRef;
    *list->tail             = new_metric;
    list->tail              = &new_metric->next;
}


void
otf2_print_add_group( struct otf2_print_data* data,
                      OTF2_GroupRef           group,
                      OTF2_StringRef          name,
                      OTF2_GroupType          type,
                      OTF2_Paradigm           paradigm,
                      OTF2_GroupFlag          flags,
                      uint32_t                numberOfMembers,
                      const uint64_t*         members )
{
    struct otf2_print_defs* defs = data->defs;

    if ( group == OTF2_UNDEFINED_GROUP )
    {
        return;
    }

    uint64_t               group64 = group;
    size_t                 hint;
    otf2_hash_table_entry* entry =
        otf2_hash_table_find( defs->groups, &group64, &hint );
    if ( entry )
    {
        otf2_print_warn( "duplicate Group definition: %s\n",
                         otf2_print_get_def_name( defs->groups, group ) );
        /* overwriting the current definition */
    }

    struct otf2_print_group_def* new_def =
        malloc( sizeof( *new_def )
                + numberOfMembers * sizeof( *new_def->members ) );
    check_pointer( new_def, "Allocating memory for Group definition." );

    /* initialize base definition */
    new_def->def.def           = group64;
    new_def->def.name          = otf2_print_get_string( defs->strings, name );
    new_def->type              = type;
    new_def->flags             = flags;
    new_def->paradigm          = paradigm;
    new_def->comm_data         = NULL;
    new_def->number_of_members = numberOfMembers;
    memcpy( new_def->members,
            members,
            numberOfMembers * sizeof( *members ) );

    bool out_of_order = group != otf2_hash_table_size( defs->groups );
    otf2_hash_table_insert( defs->groups, &new_def->def.def, new_def, &hint );
    if ( out_of_order )
    {
        otf2_print_warn( "out of order Group definition: %s\n",
                         otf2_print_get_def_name( defs->groups, group ) );
    }

    /* handle the special communication group definions */
    if ( type == OTF2_GROUP_TYPE_COMM_LOCATIONS ||
         type == OTF2_GROUP_TYPE_COMM_LOCATION_GROUPS )
    {
        if ( otf2_vector_size( data->comm_paradigms ) <= ( size_t )paradigm )
        {
            otf2_vector_resize( data->comm_paradigms, ( size_t )paradigm + 1 );
        }

        const struct otf2_print_group_def* group_def =
            otf2_vector_at( data->comm_paradigms, ( size_t )paradigm );
        if ( group_def )
        {
            otf2_print_warn( "duplicate Group(COMM_LOCATIONS/COMM_LOCATION_GROUPS) for paradigm %s\n",
                             otf2_print_get_paradigm_name( defs->paradigms, paradigm ) );
        }
        otf2_vector_set( data->comm_paradigms, ( size_t )paradigm, new_def );

        if ( !new_def->def.name || strlen( new_def->def.name ) == 0 )
        {
            otf2_hash_table_entry* entry =
                otf2_hash_table_find( defs->paradigms, &paradigm, NULL );
            if ( entry )
            {
                struct otf2_print_paradigm_def* paradigm_def = entry->value;
                new_def->def.name = paradigm_def->name;
            }
        }

        for ( uint32_t i = 0; i < numberOfMembers; ++i )
        {
            if ( type == OTF2_GROUP_TYPE_COMM_LOCATIONS )
            {
                struct otf2_print_location_def* location = otf2_print_get_location( defs->locations, members[ i ] );
                if ( location )
                {
                    otf2_print_rank_list_append( &location->paradigm_ranks, paradigm, i );
                }
                else
                {
                    otf2_print_warn( "undefined Location %s in Group(COMM_LOCATIONS) %s for paradigm %s "
                                     "for Group(COMM_GROUP): %s\n",
                                     otf2_print_get_def64_name( defs->locations, members[ i ] ),
                                     otf2_print_get_def64_name( defs->groups, group_def->def.def ),
                                     otf2_print_get_paradigm_name( defs->paradigms, paradigm ) );
                }
            }
            else /* if ( type == OTF2_GROUP_TYPE_COMM_LOCATION_GROUPS ) */
            {
                struct otf2_print_location_group_def* location_group =
                    otf2_print_get_location_group( defs->location_groups, members[ i ] );
                if ( location_group )
                {
                    otf2_print_rank_list_append( &location_group->paradigm_ranks, paradigm, i );
                }
                else
                {
                    otf2_print_warn( "undefined LocationGroup %s in Group(COMM_LOCATION_GROUPS) %s for paradigm %s "
                                     "for Group(COMM_GROUP): %s\n",
                                     otf2_print_get_def64_name( defs->location_groups, members[ i ] ),
                                     otf2_print_get_def64_name( defs->groups, group_def->def.def ),
                                     otf2_print_get_paradigm_name( defs->paradigms, paradigm ) );
                }
            }
        }
    }

    if ( type == OTF2_GROUP_TYPE_COMM_GROUP )
    {
        if ( otf2_vector_size( data->comm_paradigms ) <= ( size_t )paradigm )
        {
            otf2_vector_resize( data->comm_paradigms, ( size_t )paradigm + 1 );
        }

        struct otf2_print_group_def* comm_paradigm =
            otf2_vector_at( data->comm_paradigms, ( size_t )paradigm );
        if ( !comm_paradigm )
        {
            otf2_print_warn( "undefined Group(COMM_LOCATIONS/COMM_LOCATION_GROUPS) for paradigm %s "
                             "for Group(COMM_GROUP): %s\n",
                             otf2_print_get_paradigm_name( defs->paradigms, paradigm ),
                             otf2_print_get_def_name( defs->groups, group ) );
            return;
        }
        new_def->comm_data = comm_paradigm;

        for ( uint32_t i = 0; i < numberOfMembers; ++i )
        {
            if ( members[ i ] >= comm_paradigm->number_of_members )
            {
                otf2_print_warn( "invalid rank in Group(COMM_GROUP) %s: %s\n",
                                 otf2_print_get_def_name( defs->groups, group ),
                                 otf2_print_get_id64( members[ i ] ) );
            }
        }
    }

    if ( type == OTF2_GROUP_TYPE_COMM_SELF )
    {
        if ( otf2_vector_size( data->comm_paradigms ) <= ( size_t )paradigm )
        {
            otf2_vector_resize( data->comm_paradigms, ( size_t )paradigm + 1 );
        }

        struct otf2_print_group_def* comm_paradigm =
            otf2_vector_at( data->comm_paradigms, ( size_t )paradigm );
        if ( !comm_paradigm )
        {
            otf2_print_warn( "undefined Group(COMM_LOCATIONS/COMM_LOCATION_GROUPS) for paradigm %s "
                             "for Group(COMM_SELF): %s\n",
                             otf2_print_get_paradigm_name( defs->paradigms, paradigm ),
                             otf2_print_get_def_name( defs->groups, group ) );
        }
        else
        {
            if ( comm_paradigm->comm_data )
            {
                otf2_print_warn( "duplicate Group(COMM_SELF) for paradigm %s: %s\n",
                                 otf2_print_get_paradigm_name( defs->paradigms, paradigm ),
                                 otf2_print_get_def_name( defs->groups, group ) );
                /* overwriting the COMM_SELF group */
            }
            new_def->comm_data       = comm_paradigm;
            comm_paradigm->comm_data = new_def;
        }
    }
}

const struct otf2_print_group_def*
otf2_print_get_group( otf2_hash_table* groups,
                      OTF2_GroupRef    group )
{
    if ( group == OTF2_UNDEFINED_GROUP )
    {
        return NULL;
    }

    uint64_t               group64 = group;
    otf2_hash_table_entry* entry   =
        otf2_hash_table_find( groups, &group64, NULL );
    if ( entry )
    {
        return entry->value;
    }

    return NULL;
}


void
otf2_print_add_comm( struct otf2_print_data* data,
                     OTF2_CommRef            comm,
                     OTF2_StringRef          name,
                     OTF2_GroupRef           groupA,
                     OTF2_GroupRef           groupB,
                     OTF2_CommRef            parent )
{
    struct otf2_print_defs* defs = data->defs;

    if ( comm == OTF2_UNDEFINED_COMM )
    {
        return;
    }

    uint64_t comm64         = parent;
    bool     invalid_parent = parent != OTF2_UNDEFINED_COMM
                              && !otf2_hash_table_find( defs->comms,
                                                        &comm64,
                                                        NULL );

    comm64 = comm;
    size_t                 hint;
    otf2_hash_table_entry* entry =
        otf2_hash_table_find( defs->comms, &comm64, &hint );
    if ( entry )
    {
        otf2_print_warn( "duplicate Comm/InterComm definition: %s\n",
                         otf2_print_get_def_name( defs->comms, comm ) );
        /* overwriting the current definition */
    }

    struct otf2_print_comm_def* new_def = calloc( 1, sizeof( *new_def ) );
    check_pointer( new_def, "Allocating memory for Comm definition." );

    new_def->def.def  = comm64;
    new_def->def.name = otf2_print_get_string( defs->strings, name );
    bool out_of_order = comm != otf2_hash_table_size( defs->comms );
    otf2_hash_table_insert( defs->comms, &new_def->def.def, new_def, &hint );

    if ( out_of_order )
    {
        otf2_print_warn( "out of order Comm/InterComm definition: %s\n",
                         otf2_print_get_def_name( defs->comms, comm ) );
    }

    new_def->comm_group_a = otf2_print_get_group( defs->groups, groupA );
    if ( ( new_def->comm_group_a &&
           ( OTF2_GROUP_TYPE_COMM_GROUP != new_def->comm_group_a->type &&
             OTF2_GROUP_TYPE_COMM_SELF != new_def->comm_group_a->type ) ) ||
         !new_def->comm_group_a )
    {
        otf2_print_warn( "invalid Group reference in Comm/InterComm %s definition: %s\n",
                         otf2_print_get_def_name( defs->comms, comm ),
                         otf2_print_get_def_name( defs->groups, groupA ) );
        new_def->comm_group_a = NULL;
    }

    if ( groupB != OTF2_UNDEFINED_GROUP )
    {
        new_def->comm_group_b = otf2_print_get_group( defs->groups, groupB );
        if ( ( new_def->comm_group_b &&
               ( OTF2_GROUP_TYPE_COMM_GROUP != new_def->comm_group_b->type &&
                 OTF2_GROUP_TYPE_COMM_SELF != new_def->comm_group_b->type ) ) ||
             !new_def->comm_group_b )
        {
            otf2_print_warn( "invalid Group reference in Comm/InterComm %s definition: %s\n",
                             otf2_print_get_def_name( defs->comms, comm ),
                             otf2_print_get_def_name( defs->groups, groupB ) );
            new_def->comm_group_b = NULL;
        }
    }

    if ( invalid_parent )
    {
        otf2_print_warn( "invalid parent Comm/InterComm for %s definition: %s\n",
                         otf2_print_get_def_name( defs->comms, comm ),
                         otf2_print_get_def_name( defs->comms, parent ) );
    }
}

const struct otf2_print_comm_def*
otf2_print_get_comm( otf2_hash_table* comms,
                     OTF2_CommRef     comm )
{
    if ( comm == OTF2_UNDEFINED_COMM )
    {
        return NULL;
    }

    uint64_t               comm64 = comm;
    otf2_hash_table_entry* entry  =
        otf2_hash_table_find( comms, &comm64, NULL );
    if ( entry )
    {
        return entry->value;
    }

    return NULL;
}

static const char*
otf2_print_group_get_rank_name_( struct otf2_print_defs*            defs,
                                 const struct otf2_print_group_def* groupDef,
                                 uint32_t                           rank )
{
    const struct otf2_print_group_def* comm_paradigm =
        groupDef->comm_data;
    if ( groupDef->flags & OTF2_GROUP_FLAG_GLOBAL_MEMBERS )
    {
        if ( rank < comm_paradigm->number_of_members )
        {
            if ( comm_paradigm->type == OTF2_GROUP_TYPE_COMM_LOCATIONS )
            {
                return otf2_print_get_def64_name(
                    defs->locations,
                    comm_paradigm->members[ rank ] );
            }
            else /* if ( comm_paradigm->type == OTF2_GROUP_TYPE_COMM_LOCATION_GROUPS ) */
            {
                return otf2_print_get_def_name(
                    defs->location_groups,
                    comm_paradigm->members[ rank ] );
            }
        }
    }
    else if ( rank < groupDef->number_of_members
              && groupDef->members[ rank ] < comm_paradigm->number_of_members )
    {
        if ( comm_paradigm->type == OTF2_GROUP_TYPE_COMM_LOCATIONS )
        {
            return otf2_print_get_def64_name(
                defs->locations,
                comm_paradigm->members[ groupDef->members[ rank ] ] );
        }
        else /* if ( comm_paradigm->type == OTF2_GROUP_TYPE_COMM_LOCATION_GROUPS ) */
        {
            return otf2_print_get_def_name(
                defs->location_groups,
                comm_paradigm->members[ groupDef->members[ rank ] ] );
        }
    }

    return "INVALID";
}


static const char*
otf2_print_group_get_rank_name( struct otf2_print_defs* defs,
                                OTF2_GroupRef           group,
                                uint32_t                rank )
{
    return otf2_print_group_get_rank_name_(
        defs,
        otf2_print_get_group( defs->groups, group ),
        rank );
}


const char*
otf2_print_comm_get_rank_name( struct otf2_print_defs* defs,
                               OTF2_LocationRef        location,
                               OTF2_CommRef            communicator,
                               uint32_t                rank )
{
    if ( rank == OTF2_UNDEFINED_UINT32 )
    {
        /* used in collectives without a root rank */
        return "UNDEFINED";
    }

    const char* rank_name = "INVALID";

    const struct otf2_print_comm_def* comm_def =
        otf2_print_get_comm( defs->comms, communicator );
    if ( comm_def == NULL )
    {
        goto out;
    }

    const struct otf2_print_group_def* group_def = comm_def->comm_group_a;
    if ( comm_def->comm_group_b )
    {
        /* Intercomm, determine in which group 'location` is, and select the
           _remote_ group */
        const struct otf2_print_group_def* comm_paradigm =
            group_def->comm_data;
        uint64_t i;
        if ( comm_paradigm->type == OTF2_GROUP_TYPE_COMM_LOCATIONS )
        {
            for ( i = 0; i < group_def->number_of_members; i++ )
            {
                if ( comm_paradigm->members[ group_def->members[ i ] ] == location )
                {
                    break;
                }
            }
        }
        else /* if ( comm_paradigm->type == OTF2_GROUP_TYPE_COMM_LOCATION_GROUPS ) */
        {
            OTF2_LocationGroupRef locationGroup =
                otf2_print_get_location( defs->locations, location )->locationGroup;
            for ( i = 0; i < group_def->number_of_members; i++ )
            {
                if ( comm_paradigm->members[ group_def->members[ i ] ] == locationGroup )
                {
                    break;
                }
            }
        }
        if ( i < group_def->number_of_members )
        {
            /* location is in group A, thus _remote_ is group B */
            group_def = comm_def->comm_group_b;
        }
    }

    if ( group_def == NULL )
    {
        goto out;
    }

    if ( group_def->type == OTF2_GROUP_TYPE_COMM_SELF )
    {
        if ( 0 == rank )
        {
            rank_name = otf2_print_get_def64_name( defs->locations, location );
        }
    }

    if ( group_def->type == OTF2_GROUP_TYPE_COMM_GROUP
         && group_def->comm_data )
    {
        rank_name = otf2_print_group_get_rank_name_( defs, group_def, rank );
    }

out:
    return otf2_print_get_rank_name( rank, rank_name );
}


static const char*
otf2_print_comm_get_root_name( struct otf2_print_defs* defs,
                               OTF2_LocationRef        location,
                               OTF2_CommRef            comm,
                               uint32_t                root )
{
    const char* result = otf2_print_get_raw_collective_root( root );
    if ( result == NULL )
    {
        result = otf2_print_comm_get_rank_name( defs, location, comm, root );
    }

    return result;
}


const char*
otf2_print_task_get_generation_num( uint32_t generationNumber )
{
    const char* result = otf2_print_get_raw_task_generation_number( generationNumber );
    if ( result == NULL )
    {
        result = otf2_print_get_id( generationNumber );
    }

    return result;
}


void
otf2_print_add_rma_win( struct otf2_print_data* data,
                        OTF2_RmaWinRef          rmaWin,
                        OTF2_StringRef          name,
                        OTF2_CommRef            comm )
{
    struct otf2_print_defs* defs = data->defs;

    if ( rmaWin == OTF2_UNDEFINED_RMA_WIN )
    {
        return;
    }

    uint64_t               rma_win64 = rmaWin;
    size_t                 hint;
    otf2_hash_table_entry* entry =
        otf2_hash_table_find( defs->rma_wins, &rma_win64, &hint );
    if ( entry )
    {
        otf2_print_warn( "duplicate RmaWin definition: %s\n",
                         otf2_print_get_def_name( defs->rma_wins, rmaWin ) );
        /* overwriting the current definition */
    }

    struct otf2_print_rma_win_def* new_def = malloc( sizeof( *new_def ) );
    check_pointer( new_def, "Allocating memory for RmaWin definition." );

    new_def->def.def  = rma_win64;
    new_def->def.name = otf2_print_get_string( defs->strings, name );
    bool out_of_order = rmaWin != otf2_hash_table_size( defs->rma_wins );
    otf2_hash_table_insert( defs->rma_wins, &new_def->def.def, new_def, &hint );
    if ( out_of_order )
    {
        otf2_print_warn( "out of order RmaWin definition: %s\n",
                         otf2_print_get_def_name( defs->rma_wins, rmaWin ) );
    }

    new_def->comm = otf2_print_get_comm( defs->comms, comm );
    if ( !new_def->comm )
    {
        otf2_print_warn( "undefined Comm definition for RmaWin %s: %s\n",
                         otf2_print_get_def_name( defs->rma_wins, rmaWin ),
                         otf2_print_get_def_name( defs->comms, comm ) );
    }
}


const struct otf2_print_rma_win_def*
otf2_print_get_rma_win( otf2_hash_table* rmaWins,
                        OTF2_RmaWinRef   rmaWin )
{
    if ( rmaWin == OTF2_UNDEFINED_RMA_WIN )
    {
        return NULL;
    }

    uint64_t               rma_win64 = rmaWin;
    otf2_hash_table_entry* entry     =
        otf2_hash_table_find( rmaWins, &rma_win64, NULL );
    if ( entry )
    {
        return entry->value;
    }

    return NULL;
}


const char*
otf2_print_rma_win_get_rank_name( struct otf2_print_defs* defs,
                                  OTF2_LocationRef        location,
                                  OTF2_RmaWinRef          rmaWin,
                                  uint32_t                rank )
{
    if ( rank == OTF2_UNDEFINED_UINT32 )
    {
        /* used in collectives without a root rank */
        return "UNDEFINED";
    }

    const struct otf2_print_rma_win_def* rma_win_def =
        otf2_print_get_rma_win( defs->rma_wins, rmaWin );
    if ( rma_win_def && rma_win_def->comm )
    {
        return otf2_print_comm_get_rank_name( defs,
                                              location,
                                              rma_win_def->comm->def.def,
                                              rank );
    }

    return otf2_print_get_rank_name( rank, "INVALID" );
}


static const char*
otf2_print_rma_win_get_root_name( struct otf2_print_defs* defs,
                                  OTF2_LocationRef        location,
                                  OTF2_RmaWinRef          rmaWin,
                                  uint32_t                root )
{
    const char* result = otf2_print_get_raw_collective_root( root );
    if ( result == NULL )
    {
        result = otf2_print_rma_win_get_rank_name( defs, location, rmaWin, root );
    }

    return result;
}


void
otf2_print_add_cart_topology( struct otf2_print_data* data,
                              OTF2_CartTopologyRef    cartTopology,
                              OTF2_StringRef          name,
                              OTF2_CommRef            comm )
{
    struct otf2_print_defs* defs = data->defs;

    if ( cartTopology == OTF2_UNDEFINED_CART_TOPOLOGY )
    {
        return;
    }

    uint64_t               cart_topology64 = cartTopology;
    size_t                 hint;
    otf2_hash_table_entry* entry =
        otf2_hash_table_find( defs->cart_topologys, &cart_topology64, &hint );
    if ( entry )
    {
        otf2_print_warn( "duplicate CartTopology definition: %s\n",
                         otf2_print_get_def_name( defs->cart_topologys, cartTopology ) );
        /* overwriting the current definition */
    }

    struct otf2_print_cart_topology_def* new_def = malloc( sizeof( *new_def ) );
    check_pointer( new_def, "Allocating memory for CartTopology definition." );

    new_def->def.def  = cart_topology64;
    new_def->def.name = otf2_print_get_string( defs->strings, name );
    bool out_of_order = cartTopology != otf2_hash_table_size( defs->cart_topologys );
    otf2_hash_table_insert( defs->cart_topologys, &new_def->def.def, new_def, &hint );
    if ( out_of_order )
    {
        otf2_print_warn( "out of order CartTopology definition: %s\n",
                         otf2_print_get_def_name( defs->cart_topologys, cartTopology ) );
    }

    new_def->comm = otf2_print_get_comm( defs->comms, comm );
    if ( !new_def->comm )
    {
        otf2_print_warn( "undefined Comm definition for CartTopology %s: %s\n",
                         otf2_print_get_def_name( defs->cart_topologys, cartTopology ),
                         otf2_print_get_def_name( defs->comms, comm ) );
    }
}

const struct otf2_print_cart_topology_def*
otf2_print_get_cart_topology( otf2_hash_table*     cartTopologies,
                              OTF2_CartTopologyRef cartTopology )
{
    if ( cartTopology == OTF2_UNDEFINED_CART_TOPOLOGY )
    {
        return NULL;
    }

    uint64_t               cart_topology64 = cartTopology;
    otf2_hash_table_entry* entry           =
        otf2_hash_table_find( cartTopologies, &cart_topology64, NULL );
    if ( entry )
    {
        return entry->value;
    }

    return NULL;
}

const char*
otf2_print_cart_topology_get_rank_name( struct otf2_print_defs* defs,
                                        OTF2_LocationRef        location,
                                        OTF2_CartTopologyRef    cartTopology,
                                        uint32_t                rank )
{
    if ( rank == OTF2_UNDEFINED_UINT32 )
    {
        /* used in collectives without a root rank */
        return "UNDEFINED";
    }

    const struct otf2_print_cart_topology_def* cart_topology_def =
        otf2_print_get_cart_topology( defs->cart_topologys, cartTopology );
    if ( cart_topology_def && cart_topology_def->comm )
    {
        return otf2_print_comm_get_rank_name( defs,
                                              location,
                                              cart_topology_def->comm->def.def,
                                              rank );
    }

    return otf2_print_get_rank_name( rank, "INVALID" );
}


/** @internal
 *  @brief Add a calling context def.
 */
void
otf2_print_add_calling_context( struct otf2_print_data*    data,
                                OTF2_CallingContextRef     callingContext,
                                OTF2_RegionRef             region,
                                OTF2_SourceCodeLocationRef sourceCodeLocation,
                                OTF2_CallingContextRef     parent )
{
    if ( callingContext == OTF2_UNDEFINED_CALLING_CONTEXT )
    {
        return;
    }

    struct otf2_print_defs* defs = data->defs;

    uint64_t               region64    = region;
    const char*            region_name = "UNDEFINED";
    otf2_hash_table_entry* entry       =
        otf2_hash_table_find( defs->regions, &region64, NULL );
    if ( entry )
    {
        struct otf2_print_def_name* def = entry->value;
        region_name = def->name;
    }
    size_t length = strlen( region_name ) + 1;

    const char* scl_prefix = "";
    const char* scl        = "";
    if ( sourceCodeLocation != OTF2_UNDEFINED_SOURCE_CODE_LOCATION )
    {
        const char* full_scl = otf2_print_get_def_raw_name( defs->source_code_locations,
                                                            sourceCodeLocation );
        if ( full_scl )
        {
            scl_prefix = "@";
            scl        = UTILS_IO_GetWithoutPath( full_scl );
        }
    }
    length += strlen( scl_prefix ) + strlen( scl );

    OTF2_StringRef64 cct_name_id = ++data->artificial_string_refs;
    otf2_print_add_string( defs->strings,
                           cct_name_id,
                           length, "%s%s%s", region_name, scl_prefix, scl );

    uint64_t calling_context64 = parent;
    bool     invalid_parent    = parent != OTF2_UNDEFINED_CALLING_CONTEXT
                                 && !otf2_hash_table_find( defs->calling_contexts,
                                                           &calling_context64,
                                                           NULL );

    calling_context64 = callingContext;
    size_t hint;
    entry = otf2_hash_table_find( defs->calling_contexts, &calling_context64, &hint );
    if ( entry )
    {
        otf2_print_warn( "duplicate CallingContext definition: %s\n",
                         otf2_print_get_def_name( defs->calling_contexts,
                                                  callingContext ) );
        /* overwriting the current definition */
    }

    struct otf2_print_calling_context_def* new_def = calloc( 1, sizeof( *new_def ) );
    check_pointer( new_def, "Out of memory for a CallingContext definition." );

    new_def->def.def  = calling_context64;
    new_def->def.name = otf2_print_get_string( defs->strings, cct_name_id );
    new_def->region   = region;
    new_def->scl      = sourceCodeLocation;
    new_def->parent   = parent;

    new_def->properties_tail = &new_def->properties_head;

    bool out_of_order = callingContext != otf2_hash_table_size( defs->calling_contexts );
    otf2_hash_table_insert( defs->calling_contexts, &new_def->def.def, new_def, &hint );

    if ( invalid_parent )
    {
        otf2_print_warn( "invalid parent CallingContext for %s definition: %s\n",
                         otf2_print_get_def_name( defs->calling_contexts, callingContext ),
                         otf2_print_get_def_name( defs->calling_contexts, parent ) );
    }

    if ( out_of_order )
    {
        otf2_print_warn( "out of order CallingContext definition: %s\n",
                         otf2_print_get_def_name( defs->calling_contexts, callingContext ) );
    }
}

/** @internal
 *  @brief Get a calling context def by ref.
 */
struct otf2_print_calling_context_def*
otf2_print_get_calling_context( otf2_hash_table*       callingContexts,
                                OTF2_CallingContextRef callingContext )
{
    if ( callingContext == OTF2_UNDEFINED_CALLING_CONTEXT )
    {
        return NULL;
    }

    uint64_t               calling_context64 = callingContext;
    otf2_hash_table_entry* entry             =
        otf2_hash_table_find( callingContexts, &calling_context64, NULL );
    if ( entry )
    {
        return entry->value;
    }

    return NULL;
}


static void
add_def_property( const char*                       defName,
                  OTF2_StringRef                    name,
                  OTF2_Type                         type,
                  OTF2_AttributeValue               value,
                  struct otf2_print_def_property*** tail )
{
    struct otf2_print_def_property* property = calloc( 1, sizeof( *property ) );
    check_pointer( property, "Out of memory for a %sProperty.", defName );

    property->next  = NULL;
    property->name  = name;
    property->type  = type;
    property->value = value;

    **tail = property;
    *tail  = &property->next;
}


/** @internal
 *  @brief Add a calling context property.
 */
void
otf2_print_add_calling_context_property( struct otf2_print_defs* defs,
                                         OTF2_CallingContextRef  callingContext,
                                         OTF2_StringRef          name,
                                         OTF2_Type               type,
                                         OTF2_AttributeValue     value )
{
    struct otf2_print_calling_context_def* def =
        otf2_print_get_calling_context( defs->calling_contexts,
                                        callingContext );
    if ( !def )
    {
        otf2_print_warn( "invalid CallingContext reference: %s\n",
                         otf2_print_get_def_name( defs->calling_contexts,
                                                  callingContext ) );
        return;
    }

    add_def_property( "CallingContext", name, type, value, &def->properties_tail );
}

/** @internal
 *  @brief Add a system tree node def.
 */
void
otf2_print_add_system_tree_node( struct otf2_print_data* data,
                                 OTF2_SystemTreeNodeRef  systemTreeNode,
                                 OTF2_StringRef          name,
                                 OTF2_StringRef          className,
                                 OTF2_SystemTreeNodeRef  parent )
{
    if ( systemTreeNode == OTF2_UNDEFINED_SYSTEM_TREE_NODE )
    {
        return;
    }

    struct otf2_print_defs* defs = data->defs;

    uint64_t node_name_id = ++data->artificial_string_refs;
    char*    node_name    = otf2_print_get_string( defs->strings, name );
    char*    class_name   = otf2_print_get_string( defs->strings, className );
    size_t   length       = strlen( class_name ) + 2 + strlen( node_name ) + 1;

    otf2_print_add_string( defs->strings,
                           node_name_id,
                           length, "%s::%s", class_name, node_name );

    uint64_t id64           = parent;
    bool     invalid_parent = parent != OTF2_UNDEFINED_SYSTEM_TREE_NODE
                              && !otf2_hash_table_find( defs->system_tree_nodes,
                                                        &id64, NULL );

    id64 = systemTreeNode;
    size_t                 hint;
    otf2_hash_table_entry* entry =
        otf2_hash_table_find( defs->system_tree_nodes, &id64, &hint );
    if ( entry )
    {
        otf2_print_warn( "duplicate SystemTreeNode definition: %s\n",
                         otf2_print_get_def_name( defs->system_tree_nodes, systemTreeNode ) );
        /* overwriting the current definition */
    }

    struct otf2_print_system_tree_node_def* new_def = calloc( 1, sizeof( *new_def ) );
    check_pointer( new_def, "Out of memory for a SystemTreeNode definition." );

    new_def->def.def   = id64;
    new_def->def.name  = otf2_print_get_string( defs->strings, node_name_id );
    new_def->name      = name;
    new_def->className = className;
    new_def->parent    = parent;

    new_def->domains_tail    = &new_def->domains_head;
    new_def->properties_tail = &new_def->properties_head;

    otf2_print_metric_list_init( &new_def->recorded_metrics );

    bool out_of_order = systemTreeNode != otf2_hash_table_size( defs->system_tree_nodes );
    otf2_hash_table_insert( defs->system_tree_nodes, &new_def->def.def, new_def, &hint );

    if ( invalid_parent )
    {
        otf2_print_warn( "invalid parent SystemTreeNode for %s definition: %s\n",
                         otf2_print_get_def_name( defs->system_tree_nodes, systemTreeNode ),
                         otf2_print_get_def_name( defs->system_tree_nodes, parent ) );
    }

    if ( out_of_order )
    {
        otf2_print_warn( "out of order SystemTreeNode definition: %s\n",
                         otf2_print_get_def_name( defs->system_tree_nodes, systemTreeNode ) );
    }
}

/** @internal
 *  @brief Get a system tree node def by ref.
 */
struct otf2_print_system_tree_node_def*
otf2_print_get_system_tree_node( otf2_hash_table*       systemTreeNodes,
                                 OTF2_SystemTreeNodeRef systemTreeNode )
{
    if ( systemTreeNode == OTF2_UNDEFINED_SYSTEM_TREE_NODE )
    {
        return NULL;
    }

    uint64_t               id64  = systemTreeNode;
    otf2_hash_table_entry* entry =
        otf2_hash_table_find( systemTreeNodes, &id64, NULL );
    if ( entry )
    {
        return entry->value;
    }

    return NULL;
}


/** @internal
 *  @brief Add a system tree node property.
 */
void
otf2_print_add_system_tree_node_property( struct otf2_print_defs* defs,
                                          OTF2_SystemTreeNodeRef  systemTreeNode,
                                          OTF2_StringRef          name,
                                          OTF2_Type               type,
                                          OTF2_AttributeValue     value )
{
    struct otf2_print_system_tree_node_def* def =
        otf2_print_get_system_tree_node( defs->system_tree_nodes, systemTreeNode );
    if ( !def )
    {
        otf2_print_warn( "invalid SystemTreeNode reference: %s\n",
                         otf2_print_get_def_name( defs->system_tree_nodes, systemTreeNode ) );
        return;
    }

    add_def_property( "SystemTreeNode", name, type, value, &def->properties_tail );
}

/** @internal
 *  @brief Add a system tree node domain.
 */
void
otf2_print_add_system_tree_node_domain( struct otf2_print_defs* defs,
                                        OTF2_SystemTreeNodeRef  systemTreeNode,
                                        OTF2_SystemTreeDomain   domain )
{
    struct otf2_print_system_tree_node_def* def =
        otf2_print_get_system_tree_node( defs->system_tree_nodes, systemTreeNode );
    if ( !def )
    {
        otf2_print_warn( "invalid SystemTreeNode reference: %s\n",
                         otf2_print_get_def_name( defs->system_tree_nodes, systemTreeNode ) );
        return;
    }

    struct otf2_print_system_tree_node_domain* new_domain = calloc( 1, sizeof( *new_domain ) );
    check_pointer( new_domain, "Out of memory for a SystemTreeDomain." );

    new_domain->next   = NULL;
    new_domain->domain = domain;

    *def->domains_tail = new_domain;
    def->domains_tail  = &new_domain->next;
}

/** @internal
 *  @brief Add a location group def.
 */
void
otf2_print_add_location_group( struct otf2_print_data* data,
                               OTF2_LocationGroupRef   locationGroup,
                               OTF2_StringRef          name,
                               OTF2_LocationGroupType  locationGroupType,
                               OTF2_SystemTreeNodeRef  systemTreeParent,
                               OTF2_LocationGroupRef   creatingLocationGroup )
{
    if ( locationGroup == OTF2_UNDEFINED_LOCATION_GROUP )
    {
        return;
    }

    struct otf2_print_defs* defs = data->defs;

    uint64_t id64           = systemTreeParent;
    bool     invalid_parent = systemTreeParent != OTF2_UNDEFINED_SYSTEM_TREE_NODE
                              && !otf2_hash_table_find( defs->system_tree_nodes,
                                                        &id64, NULL );

    id64 = creatingLocationGroup;
    bool invalid_creator = creatingLocationGroup != OTF2_UNDEFINED_LOCATION_GROUP
                           && !otf2_hash_table_find( defs->location_groups,
                                                     &id64, NULL );

    id64 = locationGroup;
    size_t                 hint;
    otf2_hash_table_entry* entry =
        otf2_hash_table_find( defs->location_groups, &id64, &hint );
    if ( entry )
    {
        otf2_print_warn( "duplicate LocationGroup definition: %s\n",
                         otf2_print_get_def_name( defs->location_groups, locationGroup ) );
        /* overwriting the current definition */
    }

    struct otf2_print_location_group_def* new_def = calloc( 1, sizeof( *new_def ) );
    check_pointer( new_def, "Out of memory for a LocationGroup definition." );

    new_def->def.def               = id64;
    new_def->def.name              = otf2_print_get_string( defs->strings, name );
    new_def->name                  = name;
    new_def->locationGroupType     = locationGroupType;
    new_def->systemTreeParent      = systemTreeParent;
    new_def->creatingLocationGroup = creatingLocationGroup;

    new_def->properties_tail = &new_def->properties_head;

    otf2_print_rank_list_init( &new_def->paradigm_ranks );
    otf2_print_metric_list_init( &new_def->recorded_metrics );

    bool out_of_order = locationGroup != otf2_hash_table_size( defs->location_groups );
    otf2_hash_table_insert( defs->location_groups, &new_def->def.def, new_def, &hint );

    if ( invalid_parent )
    {
        otf2_print_warn( "invalid parent SystemTreeNode for %s definition: %s\n",
                         otf2_print_get_def_name( defs->location_groups, locationGroup ),
                         otf2_print_get_def_name( defs->system_tree_nodes, systemTreeParent ) );
    }

    if ( invalid_creator )
    {
        otf2_print_warn( "invalid creator LocationGroup for %s definition: %s\n",
                         otf2_print_get_def_name( defs->location_groups, locationGroup ),
                         otf2_print_get_def_name( defs->location_groups, creatingLocationGroup ) );
    }

    if ( out_of_order )
    {
        otf2_print_warn( "out of order LocationGroup definition: %s\n",
                         otf2_print_get_def_name( defs->location_groups, locationGroup ) );
    }
}

/** @internal
 *  @brief Get a location group def by ref.
 */
struct otf2_print_location_group_def*
otf2_print_get_location_group( otf2_hash_table*      locationGroups,
                               OTF2_LocationGroupRef locationGroup )
{
    if ( locationGroup == OTF2_UNDEFINED_LOCATION_GROUP )
    {
        return NULL;
    }

    uint64_t               id64  = locationGroup;
    otf2_hash_table_entry* entry =
        otf2_hash_table_find( locationGroups, &id64, NULL );
    if ( entry )
    {
        return entry->value;
    }

    return NULL;
}


/** @internal
 *  @brief Add a location group property.
 */
static void
otf2_print_add_location_group_property( struct otf2_print_defs* defs,
                                        OTF2_LocationGroupRef   locationGroup,
                                        OTF2_StringRef          name,
                                        OTF2_Type               type,
                                        OTF2_AttributeValue     value )
{
    struct otf2_print_location_group_def* def =
        otf2_print_get_location_group( defs->location_groups, locationGroup );
    if ( !def )
    {
        otf2_print_warn( "invalid LocationGroup reference: %s\n",
                         otf2_print_get_def_name( defs->location_groups, locationGroup ) );
        return;
    }

    add_def_property( "LocationGroup", name, type, value, &def->properties_tail );
}

/** @internal
 *  @brief Add a location def.
 */
void
otf2_print_add_location( struct otf2_print_data* data,
                         OTF2_LocationRef        location,
                         OTF2_StringRef          name,
                         OTF2_LocationType       locationType,
                         OTF2_LocationGroupRef   locationGroup )
{
    if ( location == OTF2_UNDEFINED_LOCATION )
    {
        return;
    }

    struct otf2_print_defs* defs = data->defs;

    uint64_t id64           = locationGroup;
    bool     invalid_parent = locationGroup != OTF2_UNDEFINED_LOCATION_GROUP
                              && !otf2_hash_table_find( defs->location_groups,
                                                        &id64, NULL );

    size_t                 hint;
    otf2_hash_table_entry* entry =
        otf2_hash_table_find( defs->locations, &location, &hint );
    if ( entry )
    {
        otf2_print_warn( "duplicate Location definition: %s\n",
                         otf2_print_get_def64_name( defs->locations, location ) );
        /* overwriting the current definition */
    }

    struct otf2_print_location_def* new_def = calloc( 1, sizeof( *new_def ) );
    check_pointer( new_def, "Out of memory for a Location definition." );

    new_def->def.def       = location;
    new_def->def.name      = otf2_print_get_string( defs->strings, name );
    new_def->name          = name;
    new_def->locationType  = locationType;
    new_def->locationGroup = locationGroup;

    new_def->properties_tail = &new_def->properties_head;

    otf2_print_rank_list_init( &new_def->paradigm_ranks );
    otf2_print_metric_list_init( &new_def->recorded_metrics );

    otf2_hash_table_insert( defs->locations, &new_def->def.def, new_def, &hint );

    if ( invalid_parent )
    {
        otf2_print_warn( "invalid parent LocationGroup for %s definition: %s\n",
                         otf2_print_get_def_name( defs->locations, location ),
                         otf2_print_get_def_name( defs->location_groups, locationGroup ) );
    }
}

/** @internal
 *  @brief Get a location def by ref.
 */
struct otf2_print_location_def*
otf2_print_get_location( otf2_hash_table* locations,
                         OTF2_LocationRef location )
{
    if ( location == OTF2_UNDEFINED_LOCATION )
    {
        return NULL;
    }

    otf2_hash_table_entry* entry =
        otf2_hash_table_find( locations, &location, NULL );
    if ( entry )
    {
        return entry->value;
    }

    return NULL;
}


/** @internal
 *  @brief Add a location property.
 */
static void
otf2_print_add_location_property( struct otf2_print_defs* defs,
                                  OTF2_LocationRef        location,
                                  OTF2_StringRef          name,
                                  OTF2_Type               type,
                                  OTF2_AttributeValue     value )
{
    struct otf2_print_location_def* def =
        otf2_print_get_location( defs->locations, location );
    if ( !def )
    {
        otf2_print_warn( "invalid Location reference: %s\n",
                         otf2_print_get_def_name( defs->locations, location ) );
        return;
    }

    add_def_property( "Location", name, type, value, &def->properties_tail );
}

const char*
otf2_print_get_rank_name( uint64_t    rank,
                          const char* rankName )
{
    if ( rank == OTF2_UNDEFINED_UINT64 )
    {
        return "UNDEFINED";
    }

    const char* rank_str = otf2_print_get_id64( rank );
    size_t      len      = strlen( rank_str ) + 4 + strlen( rankName );
    char*       buffer   = otf2_print_get_buffer( len );
    snprintf( buffer, len, "%s (%s)", rank_str, rankName );

    return buffer;
}


/** The returned buffer has a size of at least BUFFER_SIZE */
char*
otf2_print_get_buffer( size_t len )
{
    #define NR_ENTRIES 8
    static uint32_t next_idx;
    static struct otf2_print_buffer
    {
        char*  buffer;
        size_t size;
    } buffers[ NR_ENTRIES ];
    struct otf2_print_buffer* next = &buffers[ next_idx ];

    next_idx++;
    if ( next_idx == NR_ENTRIES )
    {
        next_idx = 0;
    }

    if ( len < BUFFER_SIZE )
    {
        len = BUFFER_SIZE;
    }
    if ( next->size <= len )
    {
        next->buffer = realloc( next->buffer, len );
        assert( next->buffer );
        next->size = len;
    }

    *next->buffer = '\0';

    return next->buffer;
}


static const char*
otf2_print_get_id( uint32_t ID )
{
    uint64_t id64 = ID;
    if ( ID == OTF2_UNDEFINED_UINT32 )
    {
        id64 = OTF2_UNDEFINED_UINT64;
    }
    return otf2_print_get_id64( id64 );
}


const char*
otf2_print_get_id64( uint64_t ID )
{
    ( void )otf2_print_get_id;

    if ( ID == OTF2_UNDEFINED_UINT64 )
    {
        return "UNDEFINED";
    }

    char* buffer = otf2_print_get_buffer( 0 );

    snprintf( buffer, BUFFER_SIZE, "%" PRIu64, ID );

    return buffer;
}


const char*
otf2_print_get_int64( int64_t value )
{
    if ( value == OTF2_UNDEFINED_INT64 )
    {
        return "UNDEFINED";
    }

    char* buffer = otf2_print_get_buffer( 0 );

    snprintf( buffer, BUFFER_SIZE, "%" PRIi64, value );

    return buffer;
}


const char*
otf2_print_get_invalid_( const char* invalid, uint64_t ID )
{
    size_t len = strlen( invalid ) + 1;
    /* add size for id */
    len += 32;

    char* buffer = otf2_print_get_buffer( len );

    snprintf( buffer, len, "%s <%" PRIu64 ">", invalid, ID );
    return buffer;
}


const char*
otf2_print_get_invalid( uint64_t ID )
{
    return otf2_print_get_invalid_( "INVALID", ID );
}


const char*
otf2_print_get_name( const char* name,
                     uint64_t    ID )
{
    if ( !name )
    {
        return otf2_print_get_id64( ID );
    }

    size_t len = strlen( name ) + 1;
    /* add size for id */
    len += 32;

    char* buffer = otf2_print_get_buffer( len );

    snprintf( buffer, len, "\"%s\" <%" PRIu64 ">", name, ID );

    return buffer;
}


/** @internal
 *  @brief Get the name of a definition.
 *
 *  @param regions          Set of regions.
 *  @param strings          Set of strings.
 *  @param region           Region ID.
 *
 *  @return                 Returns the name of a region if successful, NULL
 *                          otherwise.
 */
const char*
otf2_print_get_def64_name( const otf2_hash_table* defs,
                           uint64_t               def )
{
    if ( def == OTF2_UNDEFINED_UINT64 )
    {
        return "UNDEFINED";
    }

    otf2_hash_table_entry* entry =
        otf2_hash_table_find( defs, &def, NULL );
    if ( !entry )
    {
        return otf2_print_get_invalid( def );
    }

    struct otf2_print_def_name* any_def = entry->value;

    return otf2_print_get_name( any_def->name, def );
}


/** @internal
 *  @brief Get the name of a definition.
 *
 *  @param regions          Set of regions.
 *  @param strings          Set of strings.
 *  @param region           Region ID.
 *
 *  @return                 Returns the name of a region if successful, NULL
 *                          otherwise.
 */
static const char*
otf2_print_get_def_name( const otf2_hash_table* defs,
                         uint32_t               def )
{
    uint64_t def64 = def;
    if ( def == OTF2_UNDEFINED_UINT32 )
    {
        def64 = OTF2_UNDEFINED_UINT64;
    }
    return otf2_print_get_def64_name( defs, def64 );
}


const char*
otf2_print_get_def64_raw_name( const otf2_hash_table* defs,
                               uint64_t               def )
{
    if ( def == OTF2_UNDEFINED_UINT64 )
    {
        return "UNDEFINED";
    }

    otf2_hash_table_entry* entry =
        otf2_hash_table_find( defs, &def, NULL );
    if ( !entry )
    {
        return NULL;
    }

    struct otf2_print_def_name* any_def = entry->value;

    return any_def->name;
}


static const char*
otf2_print_get_def_raw_name( const otf2_hash_table* defs,
                             uint32_t               def )
{
    uint64_t def64 = def;
    if ( def == OTF2_UNDEFINED_UINT32 )
    {
        def64 = OTF2_UNDEFINED_UINT64;
    }
    return otf2_print_get_def64_raw_name( defs, def64 );
}


/** @internal
 *  @brief Add a paradigm def.
 */
static void
otf2_print_add_paradigm( struct otf2_print_data* data,
                         OTF2_Paradigm           paradigm,
                         OTF2_StringRef          string,
                         OTF2_ParadigmClass      paradigmClass )
{
    struct otf2_print_defs* defs = data->defs;

    size_t                 hint;
    otf2_hash_table_entry* entry =
        otf2_hash_table_find( defs->paradigms, &paradigm, &hint );
    if ( entry )
    {
        otf2_print_warn( "duplicate Paradigm definition: %s\n",
                         otf2_print_get_paradigm_name( defs->paradigms, paradigm ) );
        /* overwriting the current definition */
    }

    struct otf2_print_paradigm_def* new_paradigm = malloc( sizeof( *new_paradigm ) );
    check_pointer( new_paradigm, "Allocating memory for Paradigm definition." );

    new_paradigm->paradigm      = paradigm;
    new_paradigm->name          = otf2_print_get_string( defs->strings, string );
    new_paradigm->paradigmClass = paradigmClass;
    if ( !new_paradigm->name )
    {
        new_paradigm->name = ( char* )otf2_print_get_raw_paradigm( paradigm );
    }

    otf2_hash_table_insert( defs->paradigms, &new_paradigm->paradigm, new_paradigm, &hint );

    if ( otf2_vector_size( data->comm_paradigms ) <= ( size_t )paradigm )
    {
        otf2_vector_resize( data->comm_paradigms, ( size_t )paradigm + 1 );
    }
}


/** @internal
 *  @brief Get the name of a paradigm.
 *
 *  @return                 Returns the name of a paradigm if successful, NULL
 *                          otherwise.
 */
const char*
otf2_print_get_paradigm_name( const otf2_hash_table* paradigms,
                              OTF2_Paradigm          paradigm )
{
    otf2_hash_table_entry* entry =
        otf2_hash_table_find( paradigms, &paradigm, NULL );
    if ( !entry )
    {
        return otf2_print_get_paradigm( paradigm );
    }

    struct otf2_print_paradigm_def* paradigm_def = entry->value;

    return otf2_print_get_name( paradigm_def->name, paradigm );
}


const char*
otf2_print_get_paradigm_property_value( struct otf2_print_defs* defs,
                                        OTF2_ParadigmProperty   property,
                                        OTF2_Type               type,
                                        OTF2_AttributeValue     attributeValue,
                                        const char**            typeString )
{
    *typeString = otf2_print_get_type( type );

    switch ( property )
    {
        case OTF2_PARADIGM_PROPERTY_COMM_NAME_TEMPLATE:
        case OTF2_PARADIGM_PROPERTY_RMA_WIN_NAME_TEMPLATE:
            if ( type != OTF2_TYPE_STRING )
            {
                return otf2_print_get_invalid_( "TYPE MISMATCH", attributeValue.uint64 );
            }
            return otf2_print_get_def_name( defs->strings, attributeValue.stringRef );

        case OTF2_PARADIGM_PROPERTY_RMA_ONLY:
        {
            OTF2_Boolean   property_value;
            OTF2_ErrorCode ret = OTF2_AttributeValue_GetBoolean(
                type,
                attributeValue,
                &property_value );
            if ( OTF2_ERROR_INVALID_ATTRIBUTE_TYPE == ret )
            {
                return otf2_print_get_invalid_( "TYPE MISMATCH", attributeValue.uint64 );
            }
            *typeString = "BOOLEAN";
            return otf2_print_get_boolean( property_value );
        }

        default:
            return otf2_print_get_invalid_( "UNKNOWN PROPERTY",  attributeValue.uint64 );
    }
}

/** @internal
 *  @brief Add a I/O paradigm def.
 */
static void
otf2_print_add_io_paradigm( struct otf2_print_data*        data,
                            OTF2_IoParadigmRef             ioParadigm,
                            OTF2_StringRef                 name,
                            OTF2_IoParadigmClass           ioParadigmClass,
                            OTF2_IoParadigmFlag            ioParadigmFlags,
                            uint8_t                        numberOfProperties,
                            const OTF2_IoParadigmProperty* properties,
                            const OTF2_Type*               types,
                            const OTF2_AttributeValue*     values )
{
    struct otf2_print_defs* defs = data->defs;

    if ( ioParadigm == OTF2_UNDEFINED_IO_PARADIGM )
    {
        return;
    }

    size_t                 hint;
    uint64_t               io_paradigm64 = ioParadigm;
    otf2_hash_table_entry* entry         =
        otf2_hash_table_find( defs->io_paradigms, &io_paradigm64, &hint );
    if ( entry )
    {
        otf2_print_warn( "duplicate IoParadigm definition: %s\n",
                         otf2_print_get_def_name( defs->io_paradigms, ioParadigm ) );
        /* overwriting the current definition */
    }

    struct otf2_print_io_paradigm_def* new_io_paradigm = malloc( sizeof( *new_io_paradigm )
                                                                 + numberOfProperties * sizeof( new_io_paradigm->properties[ 0 ] ) );
    check_pointer( new_io_paradigm, "Allocating memory for IoParadigm definition." );

    new_io_paradigm->def.def            = ioParadigm;
    new_io_paradigm->def.name           = otf2_print_get_string( defs->strings, name );
    new_io_paradigm->ioParadigmClass    = ioParadigmClass;
    new_io_paradigm->ioParadigmFlags    = ioParadigmFlags;
    new_io_paradigm->numberOfProperties = numberOfProperties;

    for ( uint8_t i = 0; i < numberOfProperties; i++ )
    {
        new_io_paradigm->properties[ i ].property = properties[ i ];
        new_io_paradigm->properties[ i ].type     = types[ i ];
        new_io_paradigm->properties[ i ].value    = values[ i ];
    }

    bool out_of_order = ioParadigm != otf2_hash_table_size( defs->io_paradigms );
    otf2_hash_table_insert( defs->io_paradigms, &new_io_paradigm->def.def, new_io_paradigm, &hint );
    if ( out_of_order )
    {
        otf2_print_warn( "out of order IoParadigm definition: %s\n",
                         otf2_print_get_def_name( defs->io_paradigms, ioParadigm ) );
    }
}


/** @internal
 *  @brief Get a I/O paradigm.
 *
 *  @return Returns the name of a I/O paradigm if successful, NULL
 *          otherwise.
 */
const struct otf2_print_io_paradigm_def*
otf2_print_get_io_paradigm( const otf2_hash_table* ioParadigms,
                            OTF2_IoParadigmRef     ioParadigm )
{
    if ( ioParadigm == OTF2_UNDEFINED_IO_PARADIGM )
    {
        return NULL;
    }

    uint64_t               io_paradigm64 = ioParadigm;
    otf2_hash_table_entry* entry         =
        otf2_hash_table_find( ioParadigms, &io_paradigm64, NULL );
    if ( !entry )
    {
        return NULL;
    }

    return entry->value;
}


/** @internal
 *  @brief Type check and converts an attribute value of an I/O paradigm property.
 *
 *  @return Returns the value of a I/O paradigm property if successful, NULL
 *          otherwise.
 */
const char*
otf2_print_get_io_paradigm_property_value( struct otf2_print_defs* defs,
                                           OTF2_IoParadigmProperty property,
                                           OTF2_Type               type,
                                           OTF2_AttributeValue     attributeValue,
                                           const char**            typeString )
{
    *typeString = otf2_print_get_type( type );

    switch ( property )
    {
        case OTF2_IO_PARADIGM_PROPERTY_VERSION:
            if ( type != OTF2_TYPE_STRING )
            {
                return otf2_print_get_invalid_( "TYPE MISMATCH", attributeValue.uint64 );
            }
            return otf2_print_get_def_name( defs->strings, attributeValue.stringRef );

        default:
            return otf2_print_get_invalid_( "UNKNOWN I/O PROPERTY",  attributeValue.uint64 );
    }
}

/** @internal
 *  @brief Get the content of a string.
 *
 *  @param strings          Set of strings.
 *  @param string           String ID.
 *
 *  @return                 Returns the content of a string if successful, NULL
 *                          otherwise.
 */
char*
otf2_print_get_string( const otf2_hash_table* strings,
                       OTF2_StringRef64       string )
{
    if ( string == OTF2_UNDEFINED_STRING )
    {
        return NULL;
    }

    otf2_hash_table_entry* entry =
        otf2_hash_table_find( strings, &string, NULL );
    if ( !entry )
    {
        return NULL;
    }

    struct otf2_print_def_name* def = entry->value;

    return def->name;
}


static inline const char*
otf2_print_get_scope_name( struct otf2_print_defs* defs,
                           OTF2_MetricScope        scopeType,
                           uint64_t                scope )
{
    switch ( scopeType )
    {
        #define scope_case( SCOPE_TYPE, scope_type ) \
    case OTF2_SCOPE_ ## SCOPE_TYPE: \
        return otf2_print_get_def64_name( defs->scope_type, scope )

        scope_case( LOCATION, locations );
        scope_case( LOCATION_GROUP, location_groups );
        scope_case( SYSTEM_TREE_NODE, system_tree_nodes );
        scope_case( GROUP, groups );

        #undef scope_case

        default:
            return otf2_print_get_id64( scope );
    }
}


static void
otf2_print_attribute_list( struct otf2_print_data* data,
                           OTF2_AttributeList*     attributes )
{
    struct otf2_print_defs* defs = data->defs;

    /* Print additional attributes. */
    uint32_t count = OTF2_AttributeList_GetNumberOfElements( attributes );
    if ( count == 0 )
    {
        return;
    }

    const char* sep = "";
    printf( "%-*s ADDITIONAL ATTRIBUTES: ", otf2_EVENT_COLUMN_WIDTH + 38, "" );
    for ( uint32_t i = 0; i < count; i++ )
    {
        uint32_t            id;
        OTF2_Type           type;
        OTF2_AttributeValue value;

        OTF2_AttributeList_PopAttribute( attributes, &id, &type, &value );

        printf( "%s(%s; %s; %s)",
                sep,
                otf2_print_get_def_name( defs->attributes, id ),
                otf2_print_get_type( type ),
                otf2_print_get_attribute_value( defs, type, value, false ) );
        sep = ", ";
    }
    printf( "\n" );
}


/* ___ Implementation of callbacks __________________________________________ */



OTF2_CallbackCode
print_unknown( OTF2_LocationRef    location,
               OTF2_TimeStamp      time,
               void*               userData,
               OTF2_AttributeList* attributes )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;

    printf( "%-*s %15" PRIu64 " %20" PRIu64 "\n",
            otf2_EVENT_COLUMN_WIDTH, "UNKNOWN",
            location, time );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}

/** @internal
 *  @name Callbacks for events.
 *
 *  @param location         Location ID.
 *  @param time             Timestamp of the event.
 *  @param userData         Optional user data.
 *
 *  @return                 Returns OTF2_SUCCESS if successful, an error code
 *                          if an error occurs.
 *  @{
 */
OTF2_CallbackCode
print_buffer_flush( OTF2_LocationRef    location,
                    OTF2_TimeStamp      time,
                    void*               userData,
                    OTF2_AttributeList* attributes,
                    OTF2_TimeStamp      stopTime )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Stop Time: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "BUFFER_FLUSH",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_timestamp( data, stopTime ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_measurement_on_off( OTF2_LocationRef     location,
                          OTF2_TimeStamp       time,
                          void*                userData,
                          OTF2_AttributeList*  attributes,
                          OTF2_MeasurementMode measurementMode )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Mode: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "MEASUREMENT_ON_OFF",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_measurement_mode( measurementMode ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_enter( OTF2_LocationRef    location,
             OTF2_TimeStamp      time,
             void*               userData,
             OTF2_AttributeList* attributes,
             OTF2_RegionRef      region )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Region: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "ENTER",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_def_name( defs->regions, region ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_leave( OTF2_LocationRef    location,
             OTF2_TimeStamp      time,
             void*               userData,
             OTF2_AttributeList* attributes,
             OTF2_RegionRef      region )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Region: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "LEAVE",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_def_name( defs->regions, region ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_mpi_send( OTF2_LocationRef    location,
                OTF2_TimeStamp      time,
                void*               userData,
                OTF2_AttributeList* attributes,
                uint32_t            receiver,
                OTF2_CommRef        communicator,
                uint32_t            msgTag,
                uint64_t            msgLength )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Receiver: %s, "
            "Communicator: %s, "
            "Tag: %s, "
            "Length: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "MPI_SEND",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_comm_get_rank_name( defs, location, communicator, receiver ),
            otf2_print_get_def_name( defs->comms, communicator ),
            otf2_print_get_id( msgTag ),
            otf2_print_get_id64( msgLength ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_mpi_isend( OTF2_LocationRef    location,
                 OTF2_TimeStamp      time,
                 void*               userData,
                 OTF2_AttributeList* attributes,
                 uint32_t            receiver,
                 OTF2_CommRef        communicator,
                 uint32_t            msgTag,
                 uint64_t            msgLength,
                 uint64_t            requestID )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Receiver: %s, "
            "Communicator: %s, "
            "Tag: %s, "
            "Length: %s, "
            "Request: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "MPI_ISEND",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_comm_get_rank_name( defs, location, communicator, receiver ),
            otf2_print_get_def_name( defs->comms, communicator ),
            otf2_print_get_id( msgTag ),
            otf2_print_get_id64( msgLength ),
            otf2_print_get_id64( requestID ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_mpi_isend_complete( OTF2_LocationRef    location,
                          OTF2_TimeStamp      time,
                          void*               userData,
                          OTF2_AttributeList* attributes,
                          uint64_t            requestID )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Request: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "MPI_ISEND_COMPLETE",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_id64( requestID ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_mpi_irecv_request( OTF2_LocationRef    location,
                         OTF2_TimeStamp      time,
                         void*               userData,
                         OTF2_AttributeList* attributes,
                         uint64_t            requestID )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Request: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "MPI_IRECV_REQUEST",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_id64( requestID ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_mpi_recv( OTF2_LocationRef    location,
                OTF2_TimeStamp      time,
                void*               userData,
                OTF2_AttributeList* attributes,
                uint32_t            sender,
                OTF2_CommRef        communicator,
                uint32_t            msgTag,
                uint64_t            msgLength )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Sender: %s, "
            "Communicator: %s, "
            "Tag: %s, "
            "Length: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "MPI_RECV",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_comm_get_rank_name( defs, location, communicator, sender ),
            otf2_print_get_def_name( defs->comms, communicator ),
            otf2_print_get_id( msgTag ),
            otf2_print_get_id64( msgLength ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_mpi_irecv( OTF2_LocationRef    location,
                 OTF2_TimeStamp      time,
                 void*               userData,
                 OTF2_AttributeList* attributes,
                 uint32_t            sender,
                 OTF2_CommRef        communicator,
                 uint32_t            msgTag,
                 uint64_t            msgLength,
                 uint64_t            requestID )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Sender: %s, "
            "Communicator: %s, "
            "Tag: %s, "
            "Length: %s, "
            "Request: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "MPI_IRECV",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_comm_get_rank_name( defs, location, communicator, sender ),
            otf2_print_get_def_name( defs->comms, communicator ),
            otf2_print_get_id( msgTag ),
            otf2_print_get_id64( msgLength ),
            otf2_print_get_id64( requestID ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_mpi_request_test( OTF2_LocationRef    location,
                        OTF2_TimeStamp      time,
                        void*               userData,
                        OTF2_AttributeList* attributes,
                        uint64_t            requestID )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Request: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "MPI_REQUEST_TEST",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_id64( requestID ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_mpi_request_cancelled( OTF2_LocationRef    location,
                             OTF2_TimeStamp      time,
                             void*               userData,
                             OTF2_AttributeList* attributes,
                             uint64_t            requestID )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Request: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "MPI_REQUEST_CANCELLED",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_id64( requestID ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_mpi_collective_begin( OTF2_LocationRef    location,
                            OTF2_TimeStamp      time,
                            void*               userData,
                            OTF2_AttributeList* attributes )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;

    printf( "%-*s %15" PRIu64 " %20s  "
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "MPI_COLLECTIVE_BEGIN",
            location,
            otf2_print_get_timestamp( data, time ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_mpi_collective_end( OTF2_LocationRef    location,
                          OTF2_TimeStamp      time,
                          void*               userData,
                          OTF2_AttributeList* attributes,
                          OTF2_CollectiveOp   collectiveOp,
                          OTF2_CommRef        communicator,
                          uint32_t            root,
                          uint64_t            sizeSent,
                          uint64_t            sizeReceived )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Operation: %s, "
            "Communicator: %s, "
            "Root: %s, "
            "Sent: %s, "
            "Received: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "MPI_COLLECTIVE_END",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_collective_op( collectiveOp ),
            otf2_print_get_def_name( defs->comms, communicator ),
            otf2_print_comm_get_root_name( defs, location, communicator, root ),
            otf2_print_get_id64( sizeSent ),
            otf2_print_get_id64( sizeReceived ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_omp_fork( OTF2_LocationRef    location,
                OTF2_TimeStamp      time,
                void*               userData,
                OTF2_AttributeList* attributes,
                uint32_t            numberOfRequestedThreads )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;

    printf( "%-*s %15" PRIu64 " %20s  "
            "# Requested Threads: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "OMP_FORK",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_id( numberOfRequestedThreads ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_omp_join( OTF2_LocationRef    location,
                OTF2_TimeStamp      time,
                void*               userData,
                OTF2_AttributeList* attributes )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;

    printf( "%-*s %15" PRIu64 " %20s  "
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "OMP_JOIN",
            location,
            otf2_print_get_timestamp( data, time ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_omp_acquire_lock( OTF2_LocationRef    location,
                        OTF2_TimeStamp      time,
                        void*               userData,
                        OTF2_AttributeList* attributes,
                        uint32_t            lockID,
                        uint32_t            acquisitionOrder )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Lock: %s, "
            "Acquisition Order: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "OMP_ACQUIRE_LOCK",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_id( lockID ),
            otf2_print_get_id( acquisitionOrder ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_omp_release_lock( OTF2_LocationRef    location,
                        OTF2_TimeStamp      time,
                        void*               userData,
                        OTF2_AttributeList* attributes,
                        uint32_t            lockID,
                        uint32_t            acquisitionOrder )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Lock: %s, "
            "Acquisition Order: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "OMP_RELEASE_LOCK",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_id( lockID ),
            otf2_print_get_id( acquisitionOrder ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_omp_task_create( OTF2_LocationRef    location,
                       OTF2_TimeStamp      time,
                       void*               userData,
                       OTF2_AttributeList* attributes,
                       uint64_t            taskID )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Task: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "OMP_TASK_CREATE",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_id64( taskID ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_omp_task_switch( OTF2_LocationRef    location,
                       OTF2_TimeStamp      time,
                       void*               userData,
                       OTF2_AttributeList* attributes,
                       uint64_t            taskID )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Task: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "OMP_TASK_SWITCH",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_id64( taskID ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_omp_task_complete( OTF2_LocationRef    location,
                         OTF2_TimeStamp      time,
                         void*               userData,
                         OTF2_AttributeList* attributes,
                         uint64_t            taskID )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Task: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "OMP_TASK_COMPLETE",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_id64( taskID ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_metric( OTF2_LocationRef        location,
              OTF2_TimeStamp          time,
              void*                   userData,
              OTF2_AttributeList*     attributes,
              OTF2_MetricRef          metric,
              uint8_t                 numberOfMetrics,
              const OTF2_Type*        typeIDs,
              const OTF2_MetricValue* metricValues )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Metric: %s, "
            "%" PRIUint8 " Value%s",
            otf2_EVENT_COLUMN_WIDTH, "METRIC",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_def_name( defs->metrics, metric ),
            numberOfMetrics,
            numberOfMetrics == 1 ? "" : "s" );

    const struct otf2_print_metric_def* metric_def =
        otf2_print_get_metric( defs->metrics, metric );

    const char* sep = ": ";
    for ( uint8_t i = 0; i < numberOfMetrics; i++ )
    {
        const char* metric_member_name = "INVALID";
        if ( metric_def && i < metric_def->number_of_members )
        {
            metric_member_name = otf2_print_get_def_name(
                defs->metric_members,
                metric_def->members[ i ] );
        }
        switch ( typeIDs[ i ] )
        {
            case OTF2_TYPE_INT64:
                printf( "%s(%s; INT64; %" PRId64 ")", sep,
                        metric_member_name,
                        metricValues[ i ].signed_int );
                break;
            case OTF2_TYPE_UINT64:
                printf( "%s(%s; UINT64; %" PRIu64 ")", sep,
                        metric_member_name,
                        metricValues[ i ].unsigned_int );
                break;
            case OTF2_TYPE_DOUBLE:
                printf( "%s(%s; DOUBLE; %g)", sep,
                        metric_member_name,
                        metricValues[ i ].floating_point );
                break;
            default:
            {
                printf( "%s(%s; %s; %08" PRIx64 ")", sep,
                        metric_member_name,
                        otf2_print_get_invalid( typeIDs[ i ] ),
                        metricValues[ i ].unsigned_int );
            }
        }
        sep = ", ";
    }
    printf( "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_parameter_string( OTF2_LocationRef    location,
                        OTF2_TimeStamp      time,
                        void*               userData,
                        OTF2_AttributeList* attributes,
                        OTF2_ParameterRef   parameter,
                        OTF2_StringRef      string )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Parameter: %s, "
            "Value: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "PARAMETER_STRING",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_def_name( defs->parameters, parameter ),
            otf2_print_get_def_name( defs->strings, string ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_parameter_int( OTF2_LocationRef    location,
                     OTF2_TimeStamp      time,
                     void*               userData,
                     OTF2_AttributeList* attributes,
                     OTF2_ParameterRef   parameter,
                     int64_t             value )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Parameter: %s, "
            "Value: %" PRIInt64
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "PARAMETER_INT64",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_def_name( defs->parameters, parameter ),
            value,
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_parameter_unsigned_int( OTF2_LocationRef    location,
                              OTF2_TimeStamp      time,
                              void*               userData,
                              OTF2_AttributeList* attributes,
                              OTF2_ParameterRef   parameter,
                              uint64_t            value )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Parameter: %s, "
            "Value: %" PRIUint64
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "PARAMETER_UINT64",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_def_name( defs->parameters, parameter ),
            value,
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}

OTF2_CallbackCode
print_rma_win_create( OTF2_LocationRef    location,
                      OTF2_TimeStamp      time,
                      void*               userData,
                      OTF2_AttributeList* attributes,
                      OTF2_RmaWinRef      win )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Window: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "RMA_WIN_CREATE",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_def_name( defs->rma_wins, win ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_rma_win_destroy( OTF2_LocationRef    location,
                       OTF2_TimeStamp      time,
                       void*               userData,
                       OTF2_AttributeList* attributes,
                       OTF2_RmaWinRef      win )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Window: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "RMA_WIN_DESTROY",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_def_name( defs->rma_wins, win ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_rma_collective_begin( OTF2_LocationRef    location,
                            OTF2_TimeStamp      time,
                            void*               userData,
                            OTF2_AttributeList* attributes )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;

    printf( "%-*s %15" PRIu64 " %20s  "
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "RMA_COLLECTIVE_BEGIN",
            location,
            otf2_print_get_timestamp( data, time ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_rma_collective_end( OTF2_LocationRef    location,
                          OTF2_TimeStamp      time,
                          void*               userData,
                          OTF2_AttributeList* attributes,
                          OTF2_CollectiveOp   collectiveOp,
                          OTF2_RmaSyncLevel   syncLevel,
                          OTF2_RmaWinRef      win,
                          uint32_t            root,
                          uint64_t            bytesSent,
                          uint64_t            bytesReceived )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Operation: %s, "
            "Window: %s, "
            "Level of Synchronicity: %s, "
            "Root: %s, "
            "Sent: %s, "
            "Received: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "RMA_COLLECTIVE_END",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_collective_op( collectiveOp ),
            otf2_print_get_def_name( defs->rma_wins, win ),
            otf2_print_get_rma_sync_level( syncLevel ),
            otf2_print_rma_win_get_root_name( defs,
                                              location,
                                              win,
                                              root ),
            otf2_print_get_id64( bytesSent ),
            otf2_print_get_id64( bytesReceived ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_rma_group_sync( OTF2_LocationRef    location,
                      OTF2_TimeStamp      time,
                      void*               userData,
                      OTF2_AttributeList* attributes,
                      OTF2_RmaSyncLevel   syncLevel,
                      OTF2_RmaWinRef      win,
                      OTF2_GroupRef       group )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Level of Synchronicity: %s, "
            "Window: %s, "
            "Group: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "RMA_GROUP_SYNC",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_rma_sync_level( syncLevel ),
            otf2_print_get_def_name( defs->rma_wins, win ),
            otf2_print_get_def_name( defs->groups, group ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_rma_request_lock( OTF2_LocationRef    location,
                        OTF2_TimeStamp      time,
                        void*               userData,
                        OTF2_AttributeList* attributes,
                        OTF2_RmaWinRef      win,
                        uint32_t            remote,
                        uint64_t            lockId,
                        OTF2_LockType       lockType )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Window: %s, "
            "Remote: %s, "
            "Lock: %s, "
            "Type: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "RMA_REQUEST_LOCK",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_def_name( defs->rma_wins, win ),
            otf2_print_rma_win_get_rank_name( defs,
                                              location,
                                              win,
                                              remote ),
            otf2_print_get_id64( lockId ),
            otf2_print_get_lock_type( lockType ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_rma_acquire_lock( OTF2_LocationRef    location,
                        OTF2_TimeStamp      time,
                        void*               userData,
                        OTF2_AttributeList* attributes,
                        OTF2_RmaWinRef      win,
                        uint32_t            remote,
                        uint64_t            lockId,
                        OTF2_LockType       lockType )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Window: %s, "
            "Remote: %s, "
            "Lock: %s, "
            "Type: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "RMA_ACQUIRE_LOCK",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_def_name( defs->rma_wins, win ),
            otf2_print_rma_win_get_rank_name( defs,
                                              location,
                                              win,
                                              remote ),
            otf2_print_get_id64( lockId ),
            otf2_print_get_lock_type( lockType ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_rma_try_lock( OTF2_LocationRef    location,
                    OTF2_TimeStamp      time,
                    void*               userData,
                    OTF2_AttributeList* attributes,
                    OTF2_RmaWinRef      win,
                    uint32_t            remote,
                    uint64_t            lockId,
                    OTF2_LockType       lockType )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Window: %s, "
            "Remote: %s, "
            "Lock: %s, "
            "Type: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "RMA_TRY_LOCK",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_def_name( defs->rma_wins, win ),
            otf2_print_rma_win_get_rank_name( defs,
                                              location,
                                              win,
                                              remote ),
            otf2_print_get_id64( lockId ),
            otf2_print_get_lock_type( lockType ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_rma_release_lock( OTF2_LocationRef    location,
                        OTF2_TimeStamp      time,
                        void*               userData,
                        OTF2_AttributeList* attributes,
                        OTF2_RmaWinRef      win,
                        uint32_t            remote,
                        uint64_t            lockId )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Window: %s, "
            "Remote: %s, "
            "Lock: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "RMA_RELEASE_LOCK",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_def_name( defs->rma_wins, win ),
            otf2_print_rma_win_get_rank_name( defs,
                                              location,
                                              win,
                                              remote ),
            otf2_print_get_id64( lockId ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_rma_sync( OTF2_LocationRef    location,
                OTF2_TimeStamp      time,
                void*               userData,
                OTF2_AttributeList* attributes,
                OTF2_RmaWinRef      win,
                uint32_t            remote,
                OTF2_RmaSyncType    syncType )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Window: %s, "
            "Remote: %s, "
            "Sync Type: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "RMA_SYNC",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_def_name( defs->rma_wins, win ),
            otf2_print_rma_win_get_rank_name( defs,
                                              location,
                                              win,
                                              remote ),
            otf2_print_get_rma_sync_type( syncType ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_rma_wait_change( OTF2_LocationRef    location,
                       OTF2_TimeStamp      time,
                       void*               userData,
                       OTF2_AttributeList* attributes,
                       OTF2_RmaWinRef      win )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Window: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "RMA_WAIT_CHANGE",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_def_name( defs->rma_wins, win ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_rma_put( OTF2_LocationRef    location,
               OTF2_TimeStamp      time,
               void*               userData,
               OTF2_AttributeList* attributes,
               OTF2_RmaWinRef      win,
               uint32_t            remote,
               uint64_t            bytes,
               uint64_t            matchingId )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Window: %s, "
            "Remote: %s, "
            "Bytes: %s, "
            "Matching: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "RMA_PUT",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_def_name( defs->rma_wins, win ),
            otf2_print_rma_win_get_rank_name( defs,
                                              location,
                                              win,
                                              remote ),
            otf2_print_get_id64( bytes ),
            otf2_print_get_id64( matchingId ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_rma_get( OTF2_LocationRef    location,
               OTF2_TimeStamp      time,
               void*               userData,
               OTF2_AttributeList* attributes,
               OTF2_RmaWinRef      win,
               uint32_t            remote,
               uint64_t            bytes,
               uint64_t            matchingId )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Window: %s, "
            "Remote: %s, "
            "Bytes: %s, "
            "Matching: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "RMA_GET",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_def_name( defs->rma_wins, win ),
            otf2_print_rma_win_get_rank_name( defs,
                                              location,
                                              win,
                                              remote ),
            otf2_print_get_id64( bytes ),
            otf2_print_get_id64( matchingId ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_rma_atomic( OTF2_LocationRef    location,
                  OTF2_TimeStamp      time,
                  void*               userData,
                  OTF2_AttributeList* attributes,
                  OTF2_RmaWinRef      win,
                  uint32_t            remote,
                  OTF2_RmaAtomicType  type,
                  uint64_t            bytesSent,
                  uint64_t            bytesReceived,
                  uint64_t            matchingId )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Window: %s, "
            "Remote: %s, "
            "Type: %s, "
            "Sent: %s, "
            "Received: %s, "
            "Matching: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "RMA_ATOMIC",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_def_name( defs->rma_wins, win ),
            otf2_print_rma_win_get_rank_name( defs,
                                              location,
                                              win,
                                              remote ),
            otf2_print_get_rma_atomic_type( type ),
            otf2_print_get_id64( bytesSent ),
            otf2_print_get_id64( bytesReceived ),
            otf2_print_get_id64( matchingId ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_rma_op_complete_blocking( OTF2_LocationRef    location,
                                OTF2_TimeStamp      time,
                                void*               userData,
                                OTF2_AttributeList* attributes,
                                OTF2_RmaWinRef      win,
                                uint64_t            matchingId )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Window: %s, "
            "Matching: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "RMA_OP_COMPLETE_BLOCKING",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_def_name( defs->rma_wins, win ),
            otf2_print_get_id64( matchingId ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_rma_op_complete_non_blocking( OTF2_LocationRef    location,
                                    OTF2_TimeStamp      time,
                                    void*               userData,
                                    OTF2_AttributeList* attributes,
                                    OTF2_RmaWinRef      win,
                                    uint64_t            matchingId )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Window: %s, "
            "Matching: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "RMA_OP_COMPLETE_NON_BLOCKING",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_def_name( defs->rma_wins, win ),
            otf2_print_get_id64( matchingId ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_rma_op_test( OTF2_LocationRef    location,
                   OTF2_TimeStamp      time,
                   void*               userData,
                   OTF2_AttributeList* attributes,
                   OTF2_RmaWinRef      win,
                   uint64_t            matchingId )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Window: %s, "
            "Matching: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "RMA_OP_TEST",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_def_name( defs->rma_wins, win ),
            otf2_print_get_id64( matchingId ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_rma_op_complete_remote( OTF2_LocationRef    location,
                              OTF2_TimeStamp      time,
                              void*               userData,
                              OTF2_AttributeList* attributes,
                              OTF2_RmaWinRef      win,
                              uint64_t            matchingId )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Window: %s, "
            "Matching: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "RMA_OP_COMPLETE_REMOTE",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_def_name( defs->rma_wins, win ),
            otf2_print_get_id64( matchingId ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}

OTF2_CallbackCode
print_thread_fork( OTF2_LocationRef    location,
                   OTF2_TimeStamp      time,
                   void*               userData,
                   OTF2_AttributeList* attributes,
                   OTF2_Paradigm       model,
                   uint32_t            numberOfRequestedThreads )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Model: %s, "
            "# Requested Threads: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "THREAD_FORK",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_paradigm_name( defs->paradigms, model ),
            otf2_print_get_id( numberOfRequestedThreads ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_thread_join( OTF2_LocationRef    location,
                   OTF2_TimeStamp      time,
                   void*               userData,
                   OTF2_AttributeList* attributes,
                   OTF2_Paradigm       model )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Model: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "THREAD_JOIN",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_paradigm_name( defs->paradigms, model ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_thread_team_begin( OTF2_LocationRef    location,
                         OTF2_TimeStamp      time,
                         void*               userData,
                         OTF2_AttributeList* attributes,
                         OTF2_CommRef        threadTeam )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Thread Team: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "THREAD_TEAM_BEGIN",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_def_name( defs->comms, threadTeam ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_thread_team_end( OTF2_LocationRef    location,
                       OTF2_TimeStamp      time,
                       void*               userData,
                       OTF2_AttributeList* attributes,
                       OTF2_CommRef        threadTeam )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Thread Team: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "THREAD_TEAM_END",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_def_name( defs->comms, threadTeam ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_thread_acquire_lock( OTF2_LocationRef    location,
                           OTF2_TimeStamp      time,
                           void*               userData,
                           OTF2_AttributeList* attributes,
                           OTF2_Paradigm       model,
                           uint32_t            lockID,
                           uint32_t            acquisitionOrder )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Model: %s, "
            "Lock: %s, "
            "Acquisition Order: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "THREAD_ACQUIRE_LOCK",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_paradigm_name( defs->paradigms, model ),
            otf2_print_get_id( lockID ),
            otf2_print_get_id( acquisitionOrder ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_thread_release_lock( OTF2_LocationRef    location,
                           OTF2_TimeStamp      time,
                           void*               userData,
                           OTF2_AttributeList* attributes,
                           OTF2_Paradigm       model,
                           uint32_t            lockID,
                           uint32_t            acquisitionOrder )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Model: %s, "
            "Lock: %s, "
            "Acquisition Order: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "THREAD_RELEASE_LOCK",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_paradigm_name( defs->paradigms, model ),
            otf2_print_get_id( lockID ),
            otf2_print_get_id( acquisitionOrder ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_thread_task_create( OTF2_LocationRef    location,
                          OTF2_TimeStamp      time,
                          void*               userData,
                          OTF2_AttributeList* attributes,
                          OTF2_CommRef        threadTeam,
                          uint32_t            creatingThread,
                          uint32_t            generationNumber )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Thread Team: %s, "
            "Creating Thread: %s, "
            "Generation Number: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "THREAD_TASK_CREATE",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_def_name( defs->comms, threadTeam ),
            otf2_print_comm_get_rank_name( defs,
                                           location,
                                           threadTeam,
                                           creatingThread ),
            otf2_print_task_get_generation_num( generationNumber ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_thread_task_switch( OTF2_LocationRef    location,
                          OTF2_TimeStamp      time,
                          void*               userData,
                          OTF2_AttributeList* attributes,
                          OTF2_CommRef        threadTeam,
                          uint32_t            creatingThread,
                          uint32_t            generationNumber )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Thread Team: %s, "
            "Creating Thread: %s, "
            "Generation Number: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "THREAD_TASK_SWITCH",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_def_name( defs->comms, threadTeam ),
            otf2_print_comm_get_rank_name( defs,
                                           location,
                                           threadTeam,
                                           creatingThread ),
            otf2_print_task_get_generation_num( generationNumber ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_thread_task_complete( OTF2_LocationRef    location,
                            OTF2_TimeStamp      time,
                            void*               userData,
                            OTF2_AttributeList* attributes,
                            OTF2_CommRef        threadTeam,
                            uint32_t            creatingThread,
                            uint32_t            generationNumber )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Thread Team: %s, "
            "Creating Thread: %s, "
            "Generation Number: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "THREAD_TASK_COMPLETE",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_def_name( defs->comms, threadTeam ),
            otf2_print_comm_get_rank_name( defs,
                                           location,
                                           threadTeam,
                                           creatingThread ),
            otf2_print_task_get_generation_num( generationNumber ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_thread_create( OTF2_LocationRef    location,
                     OTF2_TimeStamp      time,
                     void*               userData,
                     OTF2_AttributeList* attributes,
                     OTF2_CommRef        threadContingent,
                     uint64_t            sequenceCount )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Thread Contingent: %s, "
            "Sequence Count: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "THREAD_CREATE",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_def_name( defs->comms, threadContingent ),
            otf2_print_get_id64( sequenceCount ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_thread_begin( OTF2_LocationRef    location,
                    OTF2_TimeStamp      time,
                    void*               userData,
                    OTF2_AttributeList* attributes,
                    OTF2_CommRef        threadContingent,
                    uint64_t            sequenceCount )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Thread Contingent: %s, "
            "Sequence Count: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "THREAD_BEGIN",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_def_name( defs->comms, threadContingent ),
            otf2_print_get_id64( sequenceCount ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_thread_wait( OTF2_LocationRef    location,
                   OTF2_TimeStamp      time,
                   void*               userData,
                   OTF2_AttributeList* attributes,
                   OTF2_CommRef        threadContingent,
                   uint64_t            sequenceCount )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Thread Contingent: %s, "
            "Sequence Count: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "THREAD_WAIT",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_def_name( defs->comms, threadContingent ),
            otf2_print_get_id64( sequenceCount ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_thread_end( OTF2_LocationRef    location,
                  OTF2_TimeStamp      time,
                  void*               userData,
                  OTF2_AttributeList* attributes,
                  OTF2_CommRef        threadContingent,
                  uint64_t            sequenceCount )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Thread Contingent: %s, "
            "Sequence Count: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "THREAD_END",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_def_name( defs->comms, threadContingent ),
            otf2_print_get_id64( sequenceCount ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


static void
unwind_calling_context( struct otf2_print_defs* defs,
                        OTF2_CallingContextRef  callingContext,
                        uint32_t                unwindDistance )
{
    while ( callingContext != OTF2_UNDEFINED_CALLING_CONTEXT )
    {
        const struct otf2_print_calling_context_def* def =
            otf2_print_get_calling_context( defs->calling_contexts,
                                            callingContext );
        if ( !def )
        {
            printf( "%-*s %15s %20s  "
                    "%s"
                    "%s",
                    otf2_EVENT_COLUMN_WIDTH, "", "", "",
                    otf2_print_get_invalid( callingContext ),
                    "\n" );
            break;
        }

        printf( "%-*s %15s %20s  "
                "%s%s",
                otf2_EVENT_COLUMN_WIDTH, "", "", "",
                unwindDistance == OTF2_UNDEFINED_UINT32
                ? "?"
                : unwindDistance > 1
                ? "+"
                : unwindDistance == 1
                ? "*"
                : " ",
                otf2_print_get_def_name( defs->calling_contexts, callingContext ) );

        printf( "\n" );

        callingContext = def->parent;

        if ( unwindDistance != OTF2_UNDEFINED_UINT32 && unwindDistance > 0 )
        {
            unwindDistance--;
        }
    }
}


OTF2_CallbackCode
print_calling_context_sample( OTF2_LocationRef           location,
                              OTF2_TimeStamp             time,
                              void*                      userData,
                              OTF2_AttributeList*        attributes,
                              OTF2_CallingContextRef     callingContext,
                              uint32_t                   unwindDistance,
                              OTF2_InterruptGeneratorRef interruptGenerator )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Calling Context: %s, "
            "Unwind Distance: %s, "
            "Interrupt Generator: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "CALLING_CONTEXT_SAMPLE",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_def_name( defs->calling_contexts, callingContext ),
            otf2_print_get_id( unwindDistance ),
            otf2_print_get_def_name( defs->interrupt_generators, interruptGenerator ),
            "\n" );

    if ( otf2_UNWIND_CALLING_CONTEXT )
    {
        unwind_calling_context( defs,
                                callingContext,
                                unwindDistance );
    }

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


static OTF2_CallbackCode
print_calling_context_enter( OTF2_LocationRef       location,
                             OTF2_TimeStamp         time,
                             void*                  userData,
                             OTF2_AttributeList*    attributes,
                             OTF2_CallingContextRef callingContext,
                             uint32_t               unwindDistance )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Calling Context: %s, "
            "Unwind Distance: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "CALLING_CONTEXT_ENTER",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_def_name( defs->calling_contexts, callingContext ),
            otf2_print_get_id( unwindDistance ),
            "\n" );

    if ( otf2_UNWIND_CALLING_CONTEXT )
    {
        unwind_calling_context( defs,
                                callingContext,
                                unwindDistance );
    }

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


static OTF2_CallbackCode
print_calling_context_leave( OTF2_LocationRef       location,
                             OTF2_TimeStamp         time,
                             void*                  userData,
                             OTF2_AttributeList*    attributes,
                             OTF2_CallingContextRef callingContext )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Calling Context: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "CALLING_CONTEXT_LEAVE",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_def_name( defs->calling_contexts, callingContext ),
            "\n" );

    if ( otf2_UNWIND_CALLING_CONTEXT )
    {
        unwind_calling_context( defs,
                                callingContext,
                                1 /* there is progress in the function we leave */ );
    }

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


static OTF2_CallbackCode
print_io_create_handle( OTF2_LocationRef    location,
                        OTF2_TimeStamp      time,
                        void*               userData,
                        OTF2_AttributeList* attributes,
                        OTF2_IoHandleRef    handle,
                        OTF2_IoAccessMode   mode,
                        OTF2_IoCreationFlag creationFlags,
                        OTF2_IoStatusFlag   statusFlags )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Handle: %s, "
            "Access Mode: %s, "
            "Creation Flags: %s, "
            "Status Flags: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "IO_CREATE_HANDLE",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_def_name( defs->io_handles, handle ),
            otf2_print_get_io_access_mode( mode ),
            otf2_print_get_io_creation_flag( creationFlags ),
            otf2_print_get_io_status_flag( statusFlags ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


static OTF2_CallbackCode
print_io_destroy_handle( OTF2_LocationRef    location,
                         OTF2_TimeStamp      time,
                         void*               userData,
                         OTF2_AttributeList* attributes,
                         OTF2_IoHandleRef    handle )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Handle: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "IO_DESTROY_HANDLE",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_def_name( defs->io_handles, handle ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


static OTF2_CallbackCode
print_io_duplicate_handle( OTF2_LocationRef    location,
                           OTF2_TimeStamp      time,
                           void*               userData,
                           OTF2_AttributeList* attributes,
                           OTF2_IoHandleRef    oldHandle,
                           OTF2_IoHandleRef    newHandle,
                           OTF2_IoStatusFlag   statusFlags )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Old Handle: %s, "
            "New Handle: %s, "
            "Status Flags: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "IO_DUPLICATE_HANDLE",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_def_name( defs->io_handles, oldHandle ),
            otf2_print_get_def_name( defs->io_handles, newHandle ),
            otf2_print_get_io_status_flag( statusFlags ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


static OTF2_CallbackCode
print_io_seek( OTF2_LocationRef    location,
               OTF2_TimeStamp      time,
               void*               userData,
               OTF2_AttributeList* attributes,
               OTF2_IoHandleRef    handle,
               int64_t             offsetRequest,
               OTF2_IoSeekOption   whence,
               uint64_t            offsetResult )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Handle: %s, "
            "Offset Request: %s, "
            "Whence: %s, "
            "Offset Result: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "IO_SEEK",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_def_name( defs->io_handles, handle ),
            otf2_print_get_int64( offsetRequest ),
            otf2_print_get_io_seek_option( whence ),
            otf2_print_get_id64( offsetResult ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


static OTF2_CallbackCode
print_io_change_status_flags( OTF2_LocationRef    location,
                              OTF2_TimeStamp      time,
                              void*               userData,
                              OTF2_AttributeList* attributes,
                              OTF2_IoHandleRef    handle,
                              OTF2_IoStatusFlag   statusFlags )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Handle: %s, "
            "Status Flags: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "IO_CHANGE_FLAGS",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_def_name( defs->io_handles, handle ),
            otf2_print_get_io_status_flag( statusFlags ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


static OTF2_CallbackCode
print_io_delete_file( OTF2_LocationRef    location,
                      OTF2_TimeStamp      time,
                      void*               userData,
                      OTF2_AttributeList* attributes,
                      OTF2_IoParadigmRef  ioParadigm,
                      OTF2_IoFileRef      file )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "I/O Paradigm: %s, "
            "File: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "IO_DELETE_FILE",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_def_name( defs->io_paradigms, ioParadigm ),
            otf2_print_get_def_name( defs->io_files, file ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


static OTF2_CallbackCode
print_io_operation_begin( OTF2_LocationRef     location,
                          OTF2_TimeStamp       time,
                          void*                userData,
                          OTF2_AttributeList*  attributes,
                          OTF2_IoHandleRef     handle,
                          OTF2_IoOperationMode mode,
                          OTF2_IoOperationFlag operationFlags,
                          uint64_t             bytesRequest,
                          uint64_t             matchingId )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Handle: %s, "
            "Mode: %s, "
            "Operation Flags: %s, "
            "Bytes Request: %s, "
            "Matching Id: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "IO_OPERATION_BEGIN",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_def_name( defs->io_handles, handle ),
            otf2_print_get_io_operation_mode( mode ),
            otf2_print_get_io_operation_flag( operationFlags ),
            otf2_print_get_id64( bytesRequest ),
            otf2_print_get_id64( matchingId ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


static OTF2_CallbackCode
print_io_operation_issued( OTF2_LocationRef    location,
                           OTF2_TimeStamp      time,
                           void*               userData,
                           OTF2_AttributeList* attributes,
                           OTF2_IoHandleRef    handle,
                           uint64_t            matchingId )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Handle: %s, "
            "Matching Id: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "IO_OPERATION_ISSUED",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_def_name( defs->io_handles, handle ),
            otf2_print_get_id64( matchingId ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


static OTF2_CallbackCode
print_io_operation_test( OTF2_LocationRef    location,
                         OTF2_TimeStamp      time,
                         void*               userData,
                         OTF2_AttributeList* attributes,
                         OTF2_IoHandleRef    handle,
                         uint64_t            matchingId )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Handle: %s, "
            "Matching Id: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "IO_OPERATION_TEST",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_def_name( defs->io_handles, handle ),
            otf2_print_get_id64( matchingId ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


static OTF2_CallbackCode
print_io_operation_complete( OTF2_LocationRef    location,
                             OTF2_TimeStamp      time,
                             void*               userData,
                             OTF2_AttributeList* attributes,
                             OTF2_IoHandleRef    handle,
                             uint64_t            bytesResult,
                             uint64_t            matchingId )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Handle: %s, "
            "Bytes Result: %s, "
            "Matching Id: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "IO_OPERATION_COMPLETE",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_def_name( defs->io_handles, handle ),
            otf2_print_get_id64( bytesResult ),
            otf2_print_get_id64( matchingId ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


static OTF2_CallbackCode
print_io_operation_cancelled( OTF2_LocationRef    location,
                              OTF2_TimeStamp      time,
                              void*               userData,
                              OTF2_AttributeList* attributes,
                              OTF2_IoHandleRef    handle,
                              uint64_t            matchingId )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Handle: %s, "
            "Matching Id: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "IO_OPERATION_CANCELLED",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_def_name( defs->io_handles, handle ),
            otf2_print_get_id64( matchingId ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


static OTF2_CallbackCode
print_io_acquire_lock( OTF2_LocationRef    location,
                       OTF2_TimeStamp      time,
                       void*               userData,
                       OTF2_AttributeList* attributes,
                       OTF2_IoHandleRef    handle,
                       OTF2_LockType       lockType )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Handle: %s, "
            "Type: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "IO_ACQUIRE_LOCK",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_def_name( defs->io_handles, handle ),
            otf2_print_get_lock_type( lockType ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


static OTF2_CallbackCode
print_io_release_lock( OTF2_LocationRef    location,
                       OTF2_TimeStamp      time,
                       void*               userData,
                       OTF2_AttributeList* attributes,
                       OTF2_IoHandleRef    handle,
                       OTF2_LockType       lockType )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Handle: %s, "
            "Type: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "IO_RELEASE_LOCK",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_def_name( defs->io_handles, handle ),
            otf2_print_get_lock_type( lockType ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


static OTF2_CallbackCode
print_io_try_lock( OTF2_LocationRef    location,
                   OTF2_TimeStamp      time,
                   void*               userData,
                   OTF2_AttributeList* attributes,
                   OTF2_IoHandleRef    handle,
                   OTF2_LockType       lockType )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Handle: %s, "
            "Type: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "IO_TRY_LOCK",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_def_name( defs->io_handles, handle ),
            otf2_print_get_lock_type( lockType ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


static OTF2_CallbackCode
print_program_begin( OTF2_LocationRef      location,
                     OTF2_TimeStamp        time,
                     void*                 userData,
                     OTF2_AttributeList*   attributes,
                     OTF2_StringRef        programName,
                     uint32_t              numberOfArguments,
                     const OTF2_StringRef* programArguments )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Name: %s, "
            "%" PRIUint32 " Argument%s",
            otf2_EVENT_COLUMN_WIDTH, "PROGRAM_BEGIN",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_def_name( defs->strings, programName ),
            numberOfArguments,
            numberOfArguments == 1 ? "" : "s" );

    const char* sep = ": ";
    for ( uint32_t i = 0; i < numberOfArguments; i++ )
    {
        printf( "%s%s", sep, otf2_print_get_def_name( defs->strings, programArguments[ i ] ) );
        sep = ", ";
    }
    printf( "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


static OTF2_CallbackCode
print_program_end( OTF2_LocationRef    location,
                   OTF2_TimeStamp      time,
                   void*               userData,
                   OTF2_AttributeList* attributes,
                   int64_t             exitStatus )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Exit status: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "PROGRAM_END",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_int64( exitStatus ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


static OTF2_CallbackCode
print_non_blocking_collective_request( OTF2_LocationRef    location,
                                       OTF2_TimeStamp      time,
                                       void*               userData,
                                       OTF2_AttributeList* attributes,
                                       uint64_t            requestID )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Request: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "NON_BLOCKING_COLLECTIVE_REQUEST",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_id64( requestID ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


static OTF2_CallbackCode
print_non_blocking_collective_complete( OTF2_LocationRef    location,
                                        OTF2_TimeStamp      time,
                                        void*               userData,
                                        OTF2_AttributeList* attributes,
                                        OTF2_CollectiveOp   collectiveOp,
                                        OTF2_CommRef        communicator,
                                        uint32_t            root,
                                        uint64_t            sizeSent,
                                        uint64_t            sizeReceived,
                                        uint64_t            requestID )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Operation: %s, "
            "Communicator: %s, "
            "Root: %s, "
            "Sent: %s, "
            "Received: %s, "
            "Request: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "NON_BLOCKING_COLLECTIVE_COMPLETE",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_collective_op( collectiveOp ),
            otf2_print_get_def_name( defs->comms, communicator ),
            otf2_print_comm_get_root_name( defs, location, communicator, root ),
            otf2_print_get_id64( sizeSent ),
            otf2_print_get_id64( sizeReceived ),
            otf2_print_get_id64( requestID ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


static OTF2_CallbackCode
print_comm_create( OTF2_LocationRef    location,
                   OTF2_TimeStamp      time,
                   void*               userData,
                   OTF2_AttributeList* attributes,
                   OTF2_CommRef        communicator )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Communicator: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "COMM_CREATE",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_def_name( defs->comms, communicator ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


static OTF2_CallbackCode
print_comm_destroy( OTF2_LocationRef    location,
                    OTF2_TimeStamp      time,
                    void*               userData,
                    OTF2_AttributeList* attributes,
                    OTF2_CommRef        communicator )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Communicator: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "COMM_DESTROY",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_def_name( defs->comms, communicator ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


/** @} */


static OTF2_CallbackCode
print_mpi_imrecv( OTF2_LocationRef    location,
                  OTF2_TimeStamp      time,
                  void*               userData,
                  OTF2_AttributeList* attributes,
                  uint64_t            requestId,
                  uint64_t            msgLength )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Request: %s, "
            "Length: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "MPI_IMRECV",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_id64( requestId ),
            otf2_print_get_id64( msgLength ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


static OTF2_CallbackCode
print_mpi_imrecv_request( OTF2_LocationRef    location,
                          OTF2_TimeStamp      time,
                          void*               userData,
                          OTF2_AttributeList* attributes,
                          uint64_t            messageId,
                          uint64_t            requestId )

{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Message: %s, "
            "Request: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "MPI_IMRECV_REQUEST",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_id64( messageId ),
            otf2_print_get_id64( requestId ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


static OTF2_CallbackCode
print_mpi_mrecv( OTF2_LocationRef    location,
                 OTF2_TimeStamp      time,
                 void*               userData,
                 OTF2_AttributeList* attributes,
                 uint64_t            messageId,
                 uint64_t            msgLength )

{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Message: %s, "
            "Length: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "MPI_MRECV",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_get_id64( messageId ),
            otf2_print_get_id64( msgLength ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


static OTF2_CallbackCode
print_mpi_probe( OTF2_LocationRef    location,
                 OTF2_TimeStamp      time,
                 void*               userData,
                 OTF2_AttributeList* attributes,
                 uint32_t            sender,
                 OTF2_CommRef        communicator,
                 uint32_t            tag,
                 uint64_t            messageId )
{
    if ( time < otf2_MINTIME || time > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Sender: %s, "
            "Communicator : %s, "
            "Tag: %s, "
            "Message: %s"
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "MPI_PROBE",
            location,
            otf2_print_get_timestamp( data, time ),
            otf2_print_comm_get_rank_name( defs, location, communicator, sender ),
            otf2_print_get_def_name( defs->comms, communicator ),
            otf2_print_get_id( tag ),
            otf2_print_get_id64( messageId ),
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


static OTF2_CallbackCode
print_snap_snapshot_start( OTF2_LocationRef    location,
                           OTF2_TimeStamp      snapTime,
                           void*               userData,
                           OTF2_AttributeList* attributes,
                           uint64_t            numberOfRecords )
{
    if ( snapTime < otf2_MINTIME || snapTime > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;

    printf( "%-*s %15" PRIu64 " %20s  "
            "# Events: %" PRIUint64
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "SNAPSHOT_START",
            location,
            otf2_print_get_timestamp( data, snapTime ),
            numberOfRecords,
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


static OTF2_CallbackCode
print_snap_snapshot_end( OTF2_LocationRef    location,
                         OTF2_TimeStamp      snapTime,
                         void*               userData,
                         OTF2_AttributeList* attributes,
                         uint64_t            contReadPos )
{
    if ( snapTime < otf2_MINTIME || snapTime > otf2_MAXTIME )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    struct otf2_print_data* data = userData;

    printf( "%-*s %15" PRIu64 " %20s  "
            "Cont. Read Position: %" PRIUint64
            "%s",
            otf2_EVENT_COLUMN_WIDTH, "SNAPSHOT_END",
            location,
            otf2_print_get_timestamp( data, snapTime ),
            contReadPos,
            "\n" );

    otf2_print_attribute_list( data, attributes );

    return OTF2_CALLBACK_SUCCESS;
}


/* ___ Global definitions ____________________________________________________*/


OTF2_CallbackCode
print_global_def_unknown( void* userData )
{
    /* Print definition if selected. */
    if ( !otf2_GLOBDEFS )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    printf( "%-*s\n",
            otf2_DEF_COLUMN_WIDTH, "UNKNOWN" );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_global_def_clock_properties( void*    userData,
                                   uint64_t timerResolution,
                                   uint64_t globalOffset,
                                   uint64_t traceLength,
                                   uint64_t realtimeTimestamp )
{
    struct otf2_print_data* data = userData;

    otf2_print_add_clock_properties( data,
                                     timerResolution,
                                     globalOffset,
                                     traceLength );

    /* Print definition if selected. */
    if ( !otf2_GLOBDEFS )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    char* trace_date_buffer = "UNDEFINED";
    if ( realtimeTimestamp != OTF2_UNDEFINED_TIMESTAMP )
    {
        trace_date_buffer = otf2_print_get_buffer( BUFFER_SIZE );
        time_t     seconds    = ( time_t )( realtimeTimestamp / 1000000000 );
        struct tm* trace_date = localtime( &seconds );
        /* YYYY-MM-DD HH:MM:SS.NNNNNNNNN TIMEZONE */
        strftime( trace_date_buffer, BUFFER_SIZE, "%F %T.XXXXXXXXX %z", trace_date );
        /* print into the XXXXXXXXX the nanoseconds */
        snprintf( &trace_date_buffer[ 20 ], 10, "%09" PRIu64, realtimeTimestamp % 1000000000 );
        /* replace '\0' after nanoseconds with ' ' again */
        trace_date_buffer[ 29 ] = ' ';
    }

    printf( "%-*s %12s  "
            "Ticks per Seconds: %" PRIUint64 ", "
            "Global Offset: %" PRIUint64 ", "
            "Length: %" PRIUint64 ", "
            "Date: %s"
            "%s",
            otf2_DEF_COLUMN_WIDTH, "CLOCK_PROPERTIES",
            "",
            timerResolution,
            globalOffset,
            traceLength,
            trace_date_buffer,
            "\n" );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_global_def_paradigm( void*              userData,
                           OTF2_Paradigm      paradigm,
                           OTF2_StringRef     name,
                           OTF2_ParadigmClass paradigmClass )
{
    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    otf2_print_add_paradigm( data,
                             paradigm,
                             name,
                             paradigmClass );

    /* Print definition if selected. */
    if ( !otf2_GLOBDEFS )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    printf( "%-*s "
            "%12" PRIUint8
            "  "
            "%s, "
            "Name: %s, "
            "Class: %s"
            "%s",
            otf2_DEF_COLUMN_WIDTH, "PARADIGM",
            paradigm,
            otf2_print_get_paradigm( paradigm ),
            otf2_print_get_def_name( defs->strings, name ),
            otf2_print_get_paradigm_class( paradigmClass ),
            "\n" );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_global_def_paradigm_property( void*                 userData,
                                    OTF2_Paradigm         paradigm,
                                    OTF2_ParadigmProperty property,
                                    OTF2_Type             type,
                                    OTF2_AttributeValue   attributeValue )
{
    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    /* Print definition if selected. */
    if ( !otf2_GLOBDEFS )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    const char* type_string;
    const char* value_string =
        otf2_print_get_paradigm_property_value( defs,
                                                property,
                                                type,
                                                attributeValue,
                                                &type_string );

    printf( "%-*s "
            "%12s"
            "  "
            "Paradigm: %s, "
            "Property: %s, "
            "Type: %s, "
            "Value: %s"
            "%s",
            otf2_DEF_COLUMN_WIDTH, "PARADIGM_PROPERTY",
            "",
            otf2_print_get_paradigm_name( defs->paradigms, paradigm ),
            otf2_print_get_paradigm_property( property ),
            type_string,
            value_string,
            "\n" );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_global_def_io_paradigm( void*                          userData,
                              OTF2_IoParadigmRef             self,
                              OTF2_StringRef                 identification,
                              OTF2_StringRef                 name,
                              OTF2_IoParadigmClass           ioParadigmClass,
                              OTF2_IoParadigmFlag            ioParadigmFlags,
                              uint8_t                        numberOfProperties,
                              const OTF2_IoParadigmProperty* properties,
                              const OTF2_Type*               types,
                              const OTF2_AttributeValue*     values )
{
    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    otf2_print_add_io_paradigm( data,
                                self,
                                name,
                                ioParadigmClass,
                                ioParadigmFlags,
                                numberOfProperties,
                                properties,
                                types,
                                values );

    /* Print definition if selected. */
    if ( !otf2_GLOBDEFS )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    printf( "%-*s "
            "%12" PRIUint8
            "  "
            "Identification: %s, "
            "Name: %s, "
            "Class: %s, "
            "Flags: %s, "
            "%" PRIUint8 " Propert%s",
            otf2_DEF_COLUMN_WIDTH, "IO_PARADIGM",
            self,
            otf2_print_get_def_name( defs->strings, identification ),
            otf2_print_get_def_name( defs->strings, name ),
            otf2_print_get_io_paradigm_class( ioParadigmClass ),
            otf2_print_get_io_paradigm_flag( ioParadigmFlags ),
            numberOfProperties,
            numberOfProperties == 1 ? "y" : "ies" );

    char sep[ 3 ] = ": ";
    for ( uint8_t i = 0; i < numberOfProperties; i++ )
    {
        const char* type_string;
        const char* value_string =
            otf2_print_get_io_paradigm_property_value( defs,
                                                       properties[ i ],
                                                       types[ i ],
                                                       values[ i ],
                                                       &type_string );

        printf( "%s(%s; %s; %s)",
                sep,
                otf2_print_get_io_paradigm_property( properties[ i ] ),
                type_string,
                value_string );
        sep[ 0 ] = ',';
    }
    printf( "\n" );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_global_def_string( void*          userData,
                         OTF2_StringRef self,
                         const char*    string )
{
    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    otf2_print_add_string( defs->strings, self, 0, string );

    /* Print definition if selected. */
    if ( !otf2_GLOBDEFS )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    printf( "%-*s %12u  \"%s\""
            "%s",
            otf2_DEF_COLUMN_WIDTH, "STRING",
            self,
            string,
            "\n" );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_global_def_attribute( void*             userData,
                            OTF2_AttributeRef self,
                            OTF2_StringRef    name,
                            OTF2_StringRef    description,
                            OTF2_Type         type )
{
    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    otf2_print_add_def_name( "Attribute",
                             defs->attributes,
                             defs->strings,
                             self,
                             name,
                             true );

    /* Print definition if selected. */
    if ( !otf2_GLOBDEFS )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    printf( "%-*s "
            "%12" PRIUint32
            "  "
            "Name: %s, "
            "Description: %s, "
            "Type: %s"
            "%s",
            otf2_DEF_COLUMN_WIDTH, "ATTRIBUTE",
            self,
            otf2_print_get_def_name( defs->strings, name ),
            otf2_print_get_def_name( defs->strings, description ),
            otf2_print_get_type( type ),
            "\n" );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_global_def_system_tree_node( void*                  userData,
                                   OTF2_SystemTreeNodeRef self,
                                   OTF2_StringRef         name,
                                   OTF2_StringRef         className,
                                   OTF2_SystemTreeNodeRef parent )
{
    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    otf2_print_add_system_tree_node( data,
                                     self,
                                     name,
                                     className,
                                     parent );

    /* Print definition if selected. */
    if ( !otf2_GLOBDEFS )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    printf( "%-*s "
            "%12" PRIUint32
            "  "
            "Name: %s, "
            "Class: %s, "
            "Parent: %s"
            "%s",
            otf2_DEF_COLUMN_WIDTH, "SYSTEM_TREE_NODE",
            self,
            otf2_print_get_def_name( defs->strings, name ),
            otf2_print_get_def_name( defs->strings, className ),
            otf2_print_get_def_name( defs->system_tree_nodes, parent ),
            "\n" );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_global_def_location_group( void*                  userData,
                                 OTF2_LocationGroupRef  self,
                                 OTF2_StringRef         name,
                                 OTF2_LocationGroupType locationGroupType,
                                 OTF2_SystemTreeNodeRef systemTreeParent,
                                 OTF2_LocationGroupRef  creatingLocationGroup )
{
    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    otf2_print_add_location_group( data,
                                   self,
                                   name,
                                   locationGroupType,
                                   systemTreeParent,
                                   creatingLocationGroup );

    /* Print definition if selected. */
    if ( !otf2_GLOBDEFS )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    printf( "%-*s "
            "%12" PRIUint32
            "  "
            "Name: %s, "
            "Type: %s, "
            "Parent: %s, "
            "Creator: %s"
            "%s",
            otf2_DEF_COLUMN_WIDTH, "LOCATION_GROUP",
            self,
            otf2_print_get_def_name( defs->strings, name ),
            otf2_print_get_location_group_type( locationGroupType ),
            otf2_print_get_def_name( defs->system_tree_nodes, systemTreeParent ),
            otf2_print_get_def_name( defs->location_groups, creatingLocationGroup ),
            "\n" );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_global_def_location( void*                 userData,
                           OTF2_LocationRef      self,
                           OTF2_StringRef        name,
                           OTF2_LocationType     locationType,
                           uint64_t              numberOfEvents,
                           OTF2_LocationGroupRef locationGroup )
{
    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    otf2_print_add_location( data,
                             self,
                             name,
                             locationType,
                             locationGroup );

    /* Print definition if selected. */
    if ( otf2_GLOBDEFS )
    {
        printf( "%-*s "
                "%12" PRIUint64
                "  "
                "Name: %s, "
                "Type: %s, "
                "# Events: %s, "
                "Group: %s"
                "%s",
                otf2_DEF_COLUMN_WIDTH, "LOCATION",
                self,
                otf2_print_get_def_name( defs->strings, name ),
                otf2_print_get_location_type( locationType ),
                otf2_print_get_id64( numberOfEvents ),
                otf2_print_get_def_name( defs->location_groups, locationGroup ),
                "\n" );
    }

    /* Only proceed if either no local location is selected (i.e. read all) or
     * location ID matches provided location ID. */
    if ( otf2_LOCAL != OTF2_UNDEFINED_LOCATION && otf2_LOCAL != self )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    /* add location to the list of locations to read events from */
    otf2_print_add_location_to_read( data, self );

    otf2_LOCAL_FOUND = true;

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_global_def_region( void*           userData,
                         OTF2_RegionRef  self,
                         OTF2_StringRef  name,
                         OTF2_StringRef  canonicalName,
                         OTF2_StringRef  description,
                         OTF2_RegionRole regionRole,
                         OTF2_Paradigm   paradigm,
                         OTF2_RegionFlag regionFlags,
                         OTF2_StringRef  sourceFile,
                         uint32_t        beginLineNumber,
                         uint32_t        endLineNumber )
{
    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    otf2_print_add_def_name( "Region",
                             defs->regions,
                             defs->strings,
                             self,
                             name,
                             true );

    /* Print definition if selected. */
    if ( !otf2_GLOBDEFS )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    printf( "%-*s "
            "%12" PRIUint32
            "  "
            "Name: %s "
            "(Aka. %s), "
            "Descr.: %s, "
            "Role: %s, "
            "Paradigm: %s, "
            "Flags: %s, "
            "File: %s, "
            "Begin: %" PRIUint32 ", "
            "End: %" PRIUint32
            "%s",
            otf2_DEF_COLUMN_WIDTH, "REGION",
            self,
            otf2_print_get_def_name( defs->strings, name ),
            otf2_print_get_def_name( defs->strings, canonicalName ),
            otf2_print_get_def_name( defs->strings, description ),
            otf2_print_get_region_role( regionRole ),
            otf2_print_get_paradigm_name( defs->paradigms, paradigm ),
            otf2_print_get_region_flag( regionFlags ),
            otf2_print_get_def_name( defs->strings, sourceFile ),
            beginLineNumber,
            endLineNumber,
            "\n" );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_global_def_callsite( void*            userData,
                           OTF2_CallsiteRef self,
                           OTF2_StringRef   sourceFile,
                           uint32_t         lineNumber,
                           OTF2_RegionRef   enteredRegion,
                           OTF2_RegionRef   leftRegion )
{
    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    otf2_print_add_def_name( "Callsite",
                             defs->callsites,
                             defs->strings,
                             self,
                             OTF2_UNDEFINED_STRING,
                             true );

    /* Print definition if selected. */
    if ( !otf2_GLOBDEFS )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    printf( "%-*s "
            "%12" PRIUint32
            "  "
            "File: %s, "
            "Line Number: %" PRIUint32 ", "
            "Entered Region: %s, "
            "Left Region: %s"
            "%s",
            otf2_DEF_COLUMN_WIDTH, "CALLSITE",
            self,
            otf2_print_get_def_name( defs->strings, sourceFile ),
            lineNumber,
            otf2_print_get_def_name( defs->regions, enteredRegion ),
            otf2_print_get_def_name( defs->regions, leftRegion ),
            "\n" );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_global_def_callpath( void*            userData,
                           OTF2_CallpathRef self,
                           OTF2_CallpathRef parent,
                           OTF2_RegionRef   region )
{
    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    otf2_print_add_def_name( "Callpath",
                             defs->callpaths,
                             defs->strings,
                             self,
                             OTF2_UNDEFINED_STRING,
                             true );

    /* Print definition if selected. */
    if ( !otf2_GLOBDEFS )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    printf( "%-*s "
            "%12" PRIUint32
            "  "
            "Parent: %s, "
            "Region: %s"
            "%s",
            otf2_DEF_COLUMN_WIDTH, "CALLPATH",
            self,
            otf2_print_get_def_name( defs->callpaths, parent ),
            otf2_print_get_def_name( defs->regions, region ),
            "\n" );

    return OTF2_CALLBACK_SUCCESS;
}

OTF2_CallbackCode
print_global_def_group( void*           userData,
                        OTF2_GroupRef   self,
                        OTF2_StringRef  name,
                        OTF2_GroupType  groupType,
                        OTF2_Paradigm   paradigm,
                        OTF2_GroupFlag  groupFlags,
                        uint32_t        numberOfMembers,
                        const uint64_t* members )
{
    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    otf2_print_add_group( data,
                          self,
                          name,
                          groupType,
                          paradigm,
                          groupFlags,
                          numberOfMembers,
                          members );

    /* Print definition if selected. */
    if ( !otf2_GLOBDEFS )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    printf( "%-*s "
            "%12" PRIUint32
            "  "
            "Name: %s, "
            "Type: %s, "
            "Paradigm: %s, "
            "Flags: %s, "
            "%" PRIUint32 " Member%s",
            otf2_DEF_COLUMN_WIDTH, "GROUP",
            self,
            otf2_print_get_def_name( defs->strings, name ),
            otf2_print_get_group_type( groupType ),
            otf2_print_get_paradigm_name( defs->paradigms, paradigm ),
            otf2_print_get_group_flag( groupFlags ),
            numberOfMembers,
            numberOfMembers == 1 ? "" : "s" );

    struct otf2_print_group_def* comm_paradigm = NULL;
    if ( groupType == OTF2_GROUP_TYPE_COMM_GROUP )
    {
        comm_paradigm = otf2_vector_at( data->comm_paradigms, ( size_t )paradigm );
    }

    const char* sep = ": ";
    for ( uint32_t i = 0; i < numberOfMembers; i++ )
    {
        switch ( groupType )
        {
            case OTF2_GROUP_TYPE_UNKNOWN:
                printf( "%s%s", sep, otf2_print_get_id64( members[ i ] ) );
                break;

            case OTF2_GROUP_TYPE_LOCATIONS:
                printf( "%s%s", sep, otf2_print_get_def64_name( defs->locations,
                                                                members[ i ] ) );
                break;

            case OTF2_GROUP_TYPE_REGIONS:
                printf( "%s%s", sep, otf2_print_get_def64_name( defs->regions,
                                                                members[ i ] ) );
                break;

            case OTF2_GROUP_TYPE_METRIC:
                printf( "%s%s", sep, otf2_print_get_def64_name( defs->metric_members,
                                                                members[ i ] ) );
                break;

            case OTF2_GROUP_TYPE_COMM_LOCATIONS:
                printf( "%s%s", sep, otf2_print_get_def64_name( defs->locations,
                                                                members[ i ] ) );
                break;

            case OTF2_GROUP_TYPE_COMM_LOCATION_GROUPS:
                printf( "%s%s", sep, otf2_print_get_def64_name( defs->location_groups,
                                                                members[ i ] ) );
                break;

            case OTF2_GROUP_TYPE_COMM_GROUP:
            {
                const char* rank_name = "INVALID";
                if ( comm_paradigm )
                {
                    rank_name = otf2_print_group_get_rank_name(
                        defs, self, i );
                }
                printf( "%s%s", sep,
                        otf2_print_get_rank_name( members[ i ],
                                                  rank_name ) );
                break;
            }

            case OTF2_GROUP_TYPE_COMM_SELF: /* should have 0 members */
            default:
                printf( "%s%s", sep, otf2_print_get_id64( members[ i ] ) );
                break;
        }
        sep = ", ";
    }
    printf( "\n" );

    return OTF2_CALLBACK_SUCCESS;
}


static inline const char*
otf2_print_get_metric_value_type( OTF2_Type type )
{
    switch ( type )
    {
        case OTF2_TYPE_UINT64:
            return "UINT64";
        case OTF2_TYPE_INT64:
            return "INT64";
        case OTF2_TYPE_DOUBLE:
            return "DOUBLE";
        default:
            return otf2_print_get_invalid( type );
    }
}


OTF2_CallbackCode
print_global_def_metric_member( void*                userData,
                                OTF2_MetricMemberRef self,
                                OTF2_StringRef       name,
                                OTF2_StringRef       description,
                                OTF2_MetricType      metricType,
                                OTF2_MetricMode      metricMode,
                                OTF2_Type            valueType,
                                OTF2_Base            base,
                                int64_t              exponent,
                                OTF2_StringRef       unit )
{
    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    otf2_print_add_metric_member( defs,
                                  self,
                                  name,
                                  valueType,
                                  unit );

    /* Print definition if selected. */
    if ( !otf2_GLOBDEFS )
    {
        return OTF2_CALLBACK_SUCCESS;
    }


    printf( "%-*s "
            "%12" PRIUint32
            "  "
            "Name: %s, "
            "Descr.: %s, "
            "Type: %s, "
            "Mode: %s, "
            "Value Type: %s, "
            "Base: %s, "
            "Exponent: %" PRIInt64 ", "
            "Unit: %s"
            "%s",
            otf2_DEF_COLUMN_WIDTH, "METRIC_MEMBER",
            self,
            otf2_print_get_def_name( defs->strings, name ),
            otf2_print_get_def_name( defs->strings, description ),
            otf2_print_get_metric_type( metricType ),
            otf2_print_get_metric_mode( metricMode ),
            otf2_print_get_metric_value_type( valueType ),
            otf2_print_get_base( base ),
            exponent,
            otf2_print_get_def_name( defs->strings, unit ),
            "\n" );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_global_def_metric_class( void*                       userData,
                               OTF2_MetricRef              self,
                               uint8_t                     numberOfMetrics,
                               const OTF2_MetricMemberRef* metricMembers,
                               OTF2_MetricOccurrence       metricOccurrence,
                               OTF2_RecorderKind           recorderKind )
{
    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    otf2_print_add_metric( defs->metrics,
                           self,
                           OTF2_UNDEFINED_METRIC,
                           recorderKind,
                           numberOfMetrics,
                           metricMembers );

    /* Print definition if selected. */
    if ( !otf2_GLOBDEFS )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    printf( "%-*s "
            "%12" PRIUint32
            "  "
            "Occurrence: %s, "
            "Kind: %s, "
            "%" PRIUint8 " Member%s",
            otf2_DEF_COLUMN_WIDTH, "METRIC_CLASS",
            self,
            otf2_print_get_metric_occurrence( metricOccurrence ),
            otf2_print_get_recorder_kind( recorderKind ),
            numberOfMetrics,
            numberOfMetrics == 1 ? "" : "s" );

    const char* sep = ": ";
    for ( uint8_t i = 0; i < numberOfMetrics; i++ )
    {
        printf( "%s%s",
                sep,
                otf2_print_get_def_name( defs->metric_members, metricMembers[ i ] ) );
        sep = ", ";
    }
    printf( "\n" );

    if ( recorderKind == OTF2_RECORDER_KIND_CPU )
    {
        otf2_print_metric_list_append( &data->recorded_cpu_metrics, self, OTF2_UNDEFINED_LOCATION );
    }
    else if ( recorderKind == OTF2_RECORDER_KIND_GPU )
    {
        otf2_print_metric_list_append( &data->recorded_gpu_metrics, self, OTF2_UNDEFINED_LOCATION );
    }

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_global_def_metric_instance( void*            userData,
                                  OTF2_MetricRef   self,
                                  OTF2_MetricRef   metricClass,
                                  OTF2_LocationRef recorder,
                                  OTF2_MetricScope metricScope,
                                  uint64_t         scope )
{
    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    otf2_print_add_metric( defs->metrics,
                           self,
                           metricClass,
                           OTF2_RECORDER_KIND_UNKNOWN,
                           0,
                           NULL );

    const struct otf2_print_metric_def* class_ref = otf2_print_get_metric( defs->metrics, metricClass );
    if ( class_ref != NULL )
    {
        if ( class_ref->recorder_kind != OTF2_RECORDER_KIND_ABSTRACT )
        {
            otf2_print_warn( "MetricInstance %s references the non-abstract MetricClass %s\n",
                             otf2_print_get_def_name( defs->metrics, self ),
                             otf2_print_get_def_name( defs->metrics, metricClass ) );
        }
    }
    else
    {
        otf2_print_warn( "invalid reference to MetricClass %s in MetricInstance %s\n",
                         otf2_print_get_def_name( defs->metrics, metricClass ),
                         otf2_print_get_def_name( defs->metrics, self ) );
    }

    struct otf2_print_location_def* recorder_location = otf2_print_get_location( defs->locations, recorder );
    if ( recorder_location == NULL )
    {
        otf2_print_warn( "MetricInstance %s references an unknown Location recorder %s\n",
                         otf2_print_get_def_name( defs->metrics, self ),
                         otf2_print_get_def64_name( defs->locations, recorder ) );
    }

    switch ( metricScope )
    {
#define case_scope( SCOPE_TYPE, ScopeType, scope_type, ... ) \
    case OTF2_SCOPE_ ## SCOPE_TYPE: \
    { \
        struct otf2_print_ ## scope_type ## _def* scope_ ## scope_type \
            = otf2_print_get_ ## scope_type( defs->scope_type ## s, scope ); \
        if ( scope_ ## scope_type != NULL ) \
        { \
            __VA_ARGS__ \
            otf2_print_metric_list_append( &scope_ ## scope_type->recorded_metrics, self, recorder ); \
        } \
        else \
        { \
            otf2_print_warn( "invalid reference to " #ScopeType " %s in scope of MetricInstance %s\n", \
                             otf2_print_get_def64_name( defs->scope_type ## s, scope ), \
                             otf2_print_get_def_name( defs->metrics, self ) ); \
        } \
    } \
    break

        case_scope(
            LOCATION, Location, location,
            if ( scope_location->locationType == OTF2_LOCATION_TYPE_METRIC )
        {
            otf2_print_warn( "referenced Location %s in scope of MetricInstance %s is of type METRIC\n",
                             otf2_print_get_def64_name( defs->locations, scope ),
                             otf2_print_get_def_name( defs->metrics, self ) );
        }
            );

        case_scope( LOCATION_GROUP, LocationGroup, location_group );
        case_scope( SYSTEM_TREE_NODE, SystemTreeNode, system_tree_node );

#undef case_scope

        case OTF2_SCOPE_GROUP:
        default:
            break;
    }

    /* Print definition if selected. */
    if ( !otf2_GLOBDEFS )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    printf( "%-*s "
            "%12" PRIUint32
            "  "
            "Class: %s, "
            "Recorder: %s, "
            "Scope: %s %s"
            "%s",
            otf2_DEF_COLUMN_WIDTH, "METRIC_INSTANCE",
            self,
            otf2_print_get_def_name( defs->metrics, metricClass ),
            otf2_print_get_def64_name( defs->locations, recorder ),
            otf2_print_get_metric_scope( metricScope ),
            otf2_print_get_scope_name( defs, metricScope, scope ),
            "\n" );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_global_def_comm( void*          userData,
                       OTF2_CommRef   self,
                       OTF2_StringRef name,
                       OTF2_GroupRef  group,
                       OTF2_CommRef   parent,
                       OTF2_CommFlag  flags )
{
    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    otf2_print_add_comm( data,
                         self,
                         name,
                         group,
                         OTF2_UNDEFINED_GROUP,
                         parent );

    /* Print definition if selected. */
    if ( !otf2_GLOBDEFS )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    printf( "%-*s "
            "%12" PRIUint32
            "  "
            "Name: %s, "
            "Group: %s, "
            "Parent: %s, "
            "Flags: %s"
            "%s",
            otf2_DEF_COLUMN_WIDTH, "COMM",
            self,
            otf2_print_get_def_name( defs->strings, name ),
            otf2_print_get_def_name( defs->groups, group ),
            otf2_print_get_def_name( defs->comms, parent ),
            otf2_print_get_comm_flag( flags ),
            "\n" );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_global_def_inter_comm( void*          userData,
                             OTF2_CommRef   self,
                             OTF2_StringRef name,
                             OTF2_GroupRef  groupA,
                             OTF2_GroupRef  groupB,
                             OTF2_CommRef   commonCommunicator,
                             OTF2_CommFlag  flags )
{
    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    otf2_print_add_comm( data,
                         self,
                         name,
                         groupA,
                         groupB,
                         commonCommunicator );

    /* Print definition if selected. */
    if ( !otf2_GLOBDEFS )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    printf( "%-*s "
            "%12" PRIUint32
            "  "
            "name: %s, "
            "Group A: %s, "
            "Group B: %s, "
            "Common Communicator: %s, "
            "Flags: %s"
            "%s",
            otf2_DEF_COLUMN_WIDTH, "INTER_COMM",
            self,
            otf2_print_get_def_name( defs->strings, name ),
            otf2_print_get_def_name( defs->groups, groupA ),
            otf2_print_get_def_name( defs->groups, groupB ),
            otf2_print_get_def_name( defs->comms, commonCommunicator ),
            otf2_print_get_comm_flag( flags ),
            "\n" );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_global_def_parameter( void*              userData,
                            OTF2_ParameterRef  self,
                            OTF2_StringRef     name,
                            OTF2_ParameterType parameterType )
{
    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    otf2_print_add_def_name( "Parameter",
                             defs->parameters,
                             defs->strings,
                             self,
                             name,
                             true );

    /* Print definition if selected. */
    if ( !otf2_GLOBDEFS )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    printf( "%-*s "
            "%12" PRIUint32
            "  "
            "Name: %s, "
            "Type: %s"
            "%s",
            otf2_DEF_COLUMN_WIDTH, "PARAMETER",
            self,
            otf2_print_get_def_name( defs->strings, name ),
            otf2_print_get_parameter_type( parameterType ),
            "\n" );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_global_def_rma_win( void*           userData,
                          OTF2_RmaWinRef  self,
                          OTF2_StringRef  name,
                          OTF2_CommRef    comm,
                          OTF2_RmaWinFlag flags )
{
    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    otf2_print_add_rma_win( data,
                            self,
                            name,
                            comm );

    /* Print definition if selected. */
    if ( !otf2_GLOBDEFS )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    printf( "%-*s "
            "%12" PRIUint32
            "  "
            "Name: %s, "
            "Communicator: %s, "
            "Flags: %s"
            "%s",
            otf2_DEF_COLUMN_WIDTH, "RMA_WIN",
            self,
            otf2_print_get_def_name( defs->strings, name ),
            otf2_print_get_def_name( defs->comms, comm ),
            otf2_print_get_rma_win_flag( flags ),
            "\n" );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_global_def_metric_class_recorder( void*            userData,
                                        OTF2_MetricRef   metric,
                                        OTF2_LocationRef recorder )
{
    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    /* Print definition if selected. */
    if ( !otf2_GLOBDEFS )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    printf( "%-*s "
            "%12s"
            "  "
            "Class: %s, "
            "Recorder: %s"
            "%s",
            otf2_DEF_COLUMN_WIDTH, "METRIC_CLASS_RECORDER",
            "",
            otf2_print_get_def_name( defs->metrics, metric ),
            otf2_print_get_def64_name( defs->locations, recorder ),
            "\n" );

    return OTF2_CALLBACK_SUCCESS;
}


static OTF2_CallbackCode
print_global_def_system_tree_node_property( void*                  userData,
                                            OTF2_SystemTreeNodeRef systemTreeNode,
                                            OTF2_StringRef         name,
                                            OTF2_Type              type,
                                            OTF2_AttributeValue    value )
{
    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    otf2_print_add_system_tree_node_property( defs,
                                              systemTreeNode,
                                              name,
                                              type,
                                              value );

    /* Print definition if selected. */
    if ( !otf2_GLOBDEFS )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    printf( "%-*s "
            "%12s"
            "  "
            "System Tree Node: %s, "
            "Name: %s, "
            "Type: %s, "
            "Value: %s"
            "%s",
            otf2_DEF_COLUMN_WIDTH, "SYSTEM_TREE_NODE_PROPERTY",
            "",
            otf2_print_get_def_name( defs->system_tree_nodes, systemTreeNode ),
            otf2_print_get_def_name( defs->strings, name ),
            otf2_print_get_type( type ),
            otf2_print_get_attribute_value( defs, type, value, false ),
            "\n" );

    return OTF2_CALLBACK_SUCCESS;
}


static OTF2_CallbackCode
print_global_def_system_tree_node_domain( void*                  userData,
                                          OTF2_SystemTreeNodeRef systemTreeNode,
                                          OTF2_SystemTreeDomain  systemTreeDomain )
{
    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    otf2_print_add_system_tree_node_domain( defs,
                                            systemTreeNode,
                                            systemTreeDomain );

    /* Print definition if selected. */
    if ( !otf2_GLOBDEFS )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    printf( "%-*s "
            "%12s"
            "  "
            "System Tree Node: %s, "
            "Domain: %s"
            "%s",
            otf2_DEF_COLUMN_WIDTH, "SYSTEM_TREE_NODE_DOMAIN",
            "",
            otf2_print_get_def_name( defs->system_tree_nodes, systemTreeNode ),
            otf2_print_get_system_tree_domain( systemTreeDomain ),
            "\n" );

    return OTF2_CALLBACK_SUCCESS;
}


static OTF2_CallbackCode
print_global_def_location_group_property( void*                 userData,
                                          OTF2_LocationGroupRef locationGroup,
                                          OTF2_StringRef        name,
                                          OTF2_Type             type,
                                          OTF2_AttributeValue   value )
{
    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    otf2_print_add_location_group_property( defs,
                                            locationGroup,
                                            name,
                                            type,
                                            value );

    /* Print definition if selected. */
    if ( !otf2_GLOBDEFS )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    printf( "%-*s "
            "%12s"
            "  "
            "Location Group: %s, "
            "Name: %s, "
            "Type: %s, "
            "Value: %s"
            "%s",
            otf2_DEF_COLUMN_WIDTH, "LOCATION_GROUP_PROPERTY",
            "",
            otf2_print_get_def_name( defs->location_groups, locationGroup ),
            otf2_print_get_def_name( defs->strings, name ),
            otf2_print_get_type( type ),
            otf2_print_get_attribute_value( defs, type, value, false ),
            "\n" );

    return OTF2_CALLBACK_SUCCESS;
}


static OTF2_CallbackCode
print_global_def_location_property( void*               userData,
                                    OTF2_LocationRef    location,
                                    OTF2_StringRef      name,
                                    OTF2_Type           type,
                                    OTF2_AttributeValue value )
{
    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    otf2_print_add_location_property( defs,
                                      location,
                                      name,
                                      type,
                                      value );

    /* Print definition if selected. */
    if ( !otf2_GLOBDEFS )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    printf( "%-*s "
            "%12s"
            "  "
            "Location: %s, "
            "Name: %s, "
            "Type: %s, "
            "Value: %s"
            "%s",
            otf2_DEF_COLUMN_WIDTH, "LOCATION_PROPERTY",
            "",
            otf2_print_get_def64_name( defs->locations, location ),
            otf2_print_get_def_name( defs->strings, name ),
            otf2_print_get_type( type ),
            otf2_print_get_attribute_value( defs, type, value, false ),
            "\n" );

    return OTF2_CALLBACK_SUCCESS;
}


static OTF2_CallbackCode
print_global_def_cart_dimension( void*                 userData,
                                 OTF2_CartDimensionRef self,
                                 OTF2_StringRef        name,
                                 uint32_t              size,
                                 OTF2_CartPeriodicity  cartPeriodicity )
{
    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    otf2_print_add_def_name( "CartDimension",
                             defs->cart_dimensions,
                             defs->strings,
                             self,
                             name,
                             true );

    /* Print definition if selected. */
    if ( !otf2_GLOBDEFS )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    printf( "%-*s "
            "%12" PRIUint32
            "  "
            "Name: %s, "
            "Size: %" PRIUint32 ", "
            "Periodicity: %s"
            "%s",
            otf2_DEF_COLUMN_WIDTH, "CART_DIMENSION",
            self,
            otf2_print_get_def_name( defs->strings, name ),
            size,
            otf2_print_get_cart_periodicity( cartPeriodicity ),
            "\n" );

    return OTF2_CALLBACK_SUCCESS;
}


static OTF2_CallbackCode
print_global_def_cart_topology( void*                        userData,
                                OTF2_CartTopologyRef         self,
                                OTF2_StringRef               name,
                                OTF2_CommRef                 communicator,
                                uint8_t                      numberOfDimensions,
                                const OTF2_CartDimensionRef* cartDimensions )
{
    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    otf2_print_add_cart_topology( data,
                                  self,
                                  name,
                                  communicator );

    /* Print definition if selected. */
    if ( !otf2_GLOBDEFS )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    printf( "%-*s "
            "%12" PRIUint32
            "  "
            "Name: %s, "
            "Communicator: %s, "
            "%" PRIUint8 " Dimension%s: (",
            otf2_DEF_COLUMN_WIDTH, "CART_TOPOLOGY",
            self,
            otf2_print_get_def_name( defs->strings, name ),
            otf2_print_get_def_name( defs->comms, communicator ),
            numberOfDimensions,
            numberOfDimensions == 1 ? "" : "s" );

    const char* sep = "";
    for ( uint8_t i = 0; i < numberOfDimensions; i++ )
    {
        printf( "%s%s",
                sep,
                otf2_print_get_def_name( defs->cart_dimensions,
                                         cartDimensions[ i ] ) );
        sep = ", ";
    }
    printf( ")\n" );

    return OTF2_CALLBACK_SUCCESS;
}


static OTF2_CallbackCode
print_global_def_cart_coordinate( void*                userData,
                                  OTF2_CartTopologyRef cartTopology,
                                  uint32_t             rank,
                                  uint8_t              numberOfDimensions,
                                  const uint32_t*      coordinates )
{
    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    /* Print definition if selected. */
    if ( !otf2_GLOBDEFS )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    printf( "%-*s "
            "%12s"
            "  "
            "Cartesian Topology: %s, "
            "Rank: %s, "
            "Coordinate%s: (",
            otf2_DEF_COLUMN_WIDTH, "CART_COORDINATE",
            "",
            otf2_print_get_def_name( defs->cart_topologys, cartTopology ),
            otf2_print_cart_topology_get_rank_name( defs,
                                                    OTF2_UNDEFINED_LOCATION,
                                                    cartTopology,
                                                    rank ),
            numberOfDimensions == 1 ? "" : "s" );

    const char* sep = "";
    for ( uint8_t i = 0; i < numberOfDimensions; i++ )
    {
        printf( "%s%" PRIUint32, sep, coordinates[ i ] );
        sep = ", ";
    }
    printf( ")\n" );

    return OTF2_CALLBACK_SUCCESS;
}

static OTF2_CallbackCode
print_global_def_source_code_location( void*                      userData,
                                       OTF2_SourceCodeLocationRef self,
                                       OTF2_StringRef             file,
                                       uint32_t                   lineNumber )
{
    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    uint64_t scl_name_id = ++data->artificial_string_refs;
    char*    file_name   = otf2_print_get_string( defs->strings, file );
    size_t   length      = strlen( file_name ) + strlen( ":" ) + 10 + 1;

    otf2_print_add_string( defs->strings,
                           scl_name_id,
                           length, "%s:%d", file_name, lineNumber );

    otf2_print_add_def_name( "SourceCodeLocation",
                             defs->source_code_locations,
                             defs->strings,
                             self,
                             scl_name_id,
                             true );

    /* Print definition if selected. */
    if ( !otf2_GLOBDEFS )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    printf( "%-*s "
            "%12" PRIUint32
            "  "
            "File: %s, "
            "Line Number: %" PRIUint32
            "%s",
            otf2_DEF_COLUMN_WIDTH, "SOURCE_CODE_LOCATION",
            self,
            otf2_print_get_def_name( defs->strings, file ),
            lineNumber,
            "\n" );

    return OTF2_CALLBACK_SUCCESS;
}

static OTF2_CallbackCode
print_global_def_calling_context_property( void*                  userData,
                                           OTF2_CallingContextRef callingContext,
                                           OTF2_StringRef         name,
                                           OTF2_Type              type,
                                           OTF2_AttributeValue    value )
{
    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    otf2_print_add_calling_context_property( defs,
                                             callingContext,
                                             name,
                                             type,
                                             value );

    /* Print definition if selected. */
    if ( !otf2_GLOBDEFS )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    printf( "%-*s "
            "%12s"
            "  "
            "Calling Context: %s, "
            "Name: %s, "
            "Type: %s, "
            "Value: %s"
            "%s",
            otf2_DEF_COLUMN_WIDTH, "CALLING_CONTEXT_PROPERTY",
            "",
            otf2_print_get_def_name( defs->calling_contexts, callingContext ),
            otf2_print_get_def_name( defs->strings, name ),
            otf2_print_get_type( type ),
            otf2_print_get_attribute_value( defs, type, value, false ),
            "\n" );

    return OTF2_CALLBACK_SUCCESS;
}


static OTF2_CallbackCode
print_global_def_calling_context( void*                      userData,
                                  OTF2_CallingContextRef     self,
                                  OTF2_RegionRef             region,
                                  OTF2_SourceCodeLocationRef sourceCodeLocation,
                                  OTF2_CallingContextRef     parent )
{
    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    otf2_print_add_calling_context( data,
                                    self,
                                    region,
                                    sourceCodeLocation,
                                    parent );

    /* Print definition if selected. */
    if ( !otf2_GLOBDEFS )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    printf( "%-*s "
            "%12" PRIUint32
            "  "
            "Region: %s, "
            "Source code location: %s, "
            "Parent: %s"
            "%s",
            otf2_DEF_COLUMN_WIDTH, "CALLING_CONTEXT",
            self,
            otf2_print_get_def_name( defs->regions, region ),
            otf2_print_get_def_name( defs->source_code_locations, sourceCodeLocation ),
            otf2_print_get_def_name( defs->calling_contexts, parent ),
            "\n" );

    return OTF2_CALLBACK_SUCCESS;
}


static OTF2_CallbackCode
print_global_def_interrupt_generator( void*                       userData,
                                      OTF2_InterruptGeneratorRef  self,
                                      OTF2_StringRef              name,
                                      OTF2_InterruptGeneratorMode interruptGeneratorMode,
                                      OTF2_Base                   base,
                                      int64_t                     exponent,
                                      uint64_t                    period )
{
    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    otf2_print_add_def_name( "InterruptGenerator",
                             defs->interrupt_generators,
                             defs->strings,
                             self,
                             name,
                             true );

    /* Print definition if selected. */
    if ( !otf2_GLOBDEFS )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    printf( "%-*s "
            "%12" PRIUint32
            "  "
            "Name: %s, "
            "Mode: %s, "
            "Base: %s, "
            "Exponent: %" PRIInt64 ", "
            "Period: %" PRIUint64
            "%s",
            otf2_DEF_COLUMN_WIDTH, "INTERRUPT_GENERATOR",
            self,
            otf2_print_get_def_name( defs->strings, name ),
            otf2_print_get_interrupt_generator_mode( interruptGeneratorMode ),
            otf2_print_get_base( base ),
            exponent,
            period,
            "\n" );

    return OTF2_CALLBACK_SUCCESS;
}


static OTF2_CallbackCode
print_global_def_io_file_property( void*               userData,
                                   OTF2_IoFileRef      ioFile,
                                   OTF2_StringRef      name,
                                   OTF2_Type           type,
                                   OTF2_AttributeValue value )
{
    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    /* Print definition if selected. */
    if ( !otf2_GLOBDEFS )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    printf( "%-*s "
            "%12s"
            "  "
            "File: %s, "
            "Name: %s, "
            "Type: %s, "
            "Value: %s"
            "%s",
            otf2_DEF_COLUMN_WIDTH, "IO_FILE_PROPERTY",
            "",
            otf2_print_get_def_name( defs->io_files, ioFile ),
            otf2_print_get_def_name( defs->strings, name ),
            otf2_print_get_type( type ),
            otf2_print_get_attribute_value( defs, type, value, false ),
            "\n" );

    return OTF2_CALLBACK_SUCCESS;
}


static OTF2_CallbackCode
print_global_def_io_regular_file( void*                  userData,
                                  OTF2_IoFileRef         self,
                                  OTF2_StringRef         name,
                                  OTF2_SystemTreeNodeRef scope )
{
    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    uint64_t io_file_name_id = ++data->artificial_string_refs;
    char*    file_name       = otf2_print_get_string( defs->strings, name );
    size_t   length          = strlen( "file://" ) + strlen( file_name ) + 1;

    otf2_print_add_string( defs->strings,
                           io_file_name_id,
                           length, "file://%s", file_name );

    otf2_print_add_def_name( "IoFile",
                             defs->io_files,
                             defs->strings,
                             self,
                             io_file_name_id,
                             true );

    /* Print definition if selected. */
    if ( !otf2_GLOBDEFS )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    printf( "%-*s "
            "%12" PRIUint32
            "  "
            "Name: %s, "
            "Scope: %s"
            "%s",
            otf2_DEF_COLUMN_WIDTH, "IO_REGULAR_FILE",
            self,
            otf2_print_get_def_name( defs->strings, name ),
            otf2_print_get_def_name( defs->system_tree_nodes, scope ),
            "\n" );

    return OTF2_CALLBACK_SUCCESS;
}


static OTF2_CallbackCode
print_global_def_io_directory( void*                  userData,
                               OTF2_IoFileRef         self,
                               OTF2_StringRef         name,
                               OTF2_SystemTreeNodeRef scope )
{
    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    uint64_t io_file_name_id = ++data->artificial_string_refs;
    char*    directory_name  = otf2_print_get_string( defs->strings, name );
    size_t   length          = strlen( "directory://" ) + strlen( directory_name ) + 1;

    otf2_print_add_string( defs->strings,
                           io_file_name_id,
                           length, "directory://%s", directory_name );

    otf2_print_add_def_name( "IoFile",
                             defs->io_files,
                             defs->strings,
                             self,
                             io_file_name_id,
                             true );

    /* Print definition if selected. */
    if ( !otf2_GLOBDEFS )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    printf( "%-*s "
            "%12" PRIUint32
            "  "
            "Name: %s, "
            "Scope: %s"
            "%s",
            otf2_DEF_COLUMN_WIDTH, "IO_DIRECTORY",
            self,
            otf2_print_get_def_name( defs->strings, name ),
            otf2_print_get_def_name( defs->system_tree_nodes, scope ),
            "\n" );

    return OTF2_CALLBACK_SUCCESS;
}


static OTF2_CallbackCode
print_global_def_io_handle( void*              userData,
                            OTF2_IoHandleRef   self,
                            OTF2_StringRef     name,
                            OTF2_IoFileRef     file,
                            OTF2_IoParadigmRef ioParadigm,
                            OTF2_IoHandleFlag  ioHandleFlags,
                            OTF2_CommRef       comm,
                            OTF2_IoHandleRef   parent )
{
    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    uint64_t io_handle_name_id = name;
    if ( name == OTF2_UNDEFINED_STRING
         || strcmp( otf2_print_get_def_raw_name( defs->strings, name ), "" ) == 0 )
    {
        io_handle_name_id = ++data->artificial_string_refs;
        const char* paradigm_name = otf2_print_get_def_raw_name( defs->io_paradigms, ioParadigm );
        const char* file_name     = otf2_print_get_def_raw_name( defs->io_files, file );
        size_t      length        = strlen( "[]" ) + strlen( paradigm_name ) + strlen( file_name ) + 1;

        otf2_print_add_string( defs->strings,
                               io_handle_name_id,
                               length, "[%s]%s", paradigm_name, file_name );
    }

    otf2_print_add_def_name( "IoHandle",
                             defs->io_handles,
                             defs->strings,
                             self,
                             io_handle_name_id,
                             true );

    /* Print definition if selected. */
    if ( !otf2_GLOBDEFS )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    printf( "%-*s "
            "%12" PRIUint32
            "  "
            "Name: %s, "
            "File: %s, "
            "I/O Paradigm: %s, "
            "Flags: %s, "
            "Communicator: %s, "
            "Parent: %s"
            "%s",
            otf2_DEF_COLUMN_WIDTH, "IO_HANDLE",
            self,
            otf2_print_get_def_name( defs->strings, name ),
            otf2_print_get_def_name( defs->io_files, file ),
            otf2_print_get_def_name( defs->io_paradigms, ioParadigm ),
            otf2_print_get_io_handle_flag( ioHandleFlags ),
            otf2_print_get_def_name( defs->comms, comm ),
            otf2_print_get_def_name( defs->io_handles, parent ),
            "\n" );

    return OTF2_CALLBACK_SUCCESS;
}


static OTF2_CallbackCode
print_global_def_io_pre_created_handle_state( void*             userData,
                                              OTF2_IoHandleRef  ioHandle,
                                              OTF2_IoAccessMode mode,
                                              OTF2_IoStatusFlag statusFlags )
{
    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    /* Print definition if selected. */
    if ( !otf2_GLOBDEFS )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    printf( "%-*s "
            "%12s"
            "  "
            "Handle: %s, "
            "Access Mode: %s, "
            "Status Flags: %s"
            "%s",
            otf2_DEF_COLUMN_WIDTH, "IO_PRE_CREATED_HANDLE_STATE",
            "",
            otf2_print_get_def_name( defs->io_handles, ioHandle ),
            otf2_print_get_io_access_mode( mode ),
            otf2_print_get_io_status_flag( statusFlags ),
            "\n" );

    return OTF2_CALLBACK_SUCCESS;
}


static void
map_traverse_dense( uint64_t localId,
                    uint64_t globalId,
                    void*    userData )
{
    const char* sep = *( char** )userData;
    printf( "%s%s", sep, otf2_print_get_id64( globalId ) );
    *( char** )userData = ",";
}

static void
map_traverse_sparse( uint64_t localId,
                     uint64_t globalId,
                     void*    userData )
{
    const char* sep = *( char** )userData;
    printf( "%s%" PRIu64 "=>%s", sep, localId, otf2_print_get_id64( globalId ) );
    *( char** )userData = ",";
}

OTF2_CallbackCode
print_def_mapping_table( void*             userData,
                         OTF2_MappingType  mapType,
                         const OTF2_IdMap* iDMap )
{
    uint64_t* location_ptr = userData;

    printf( "%-*s %12" PRIu64 "  Type: %s, ",
            otf2_DEF_COLUMN_WIDTH, "MAPPING_TABLE",
            *location_ptr,
            otf2_print_get_mapping_type( mapType ) );

    OTF2_IdMapMode map_mode;
    OTF2_IdMap_GetMode( iDMap, &map_mode );

    const char*                 sep;
    OTF2_IdMap_TraverseCallback traverse_cb;
    const char*                 end;
    if ( map_mode == OTF2_ID_MAP_DENSE )
    {
        sep         = "[";
        traverse_cb = map_traverse_dense;
        end         = "]";
    }
    else
    {
        sep         = "{";
        traverse_cb = map_traverse_sparse;
        end         = "}";
    }

    OTF2_IdMap_Traverse( iDMap, traverse_cb, &sep );

    /* includes "\n" */
    puts( end );

    return OTF2_CALLBACK_SUCCESS;
}


OTF2_CallbackCode
print_def_clock_offset( void*    userData,
                        uint64_t time,
                        int64_t  offset,
                        double   stddev )
{
    uint64_t* location_ptr = userData;

    printf( "%-*s %12" PRIu64 "  Time: %" PRIu64 ", Offset: %+" PRIi64 ", "
            "StdDev: %g\n",
            otf2_DEF_COLUMN_WIDTH, "CLOCK_OFFSET",
            *location_ptr, time, offset, stddev );

    return OTF2_CALLBACK_SUCCESS;
}

static OTF2_CallbackCode
print_global_def_callpath_parameter( void*               userData,
                                     OTF2_CallpathRef    callpath,
                                     OTF2_ParameterRef   parameter,
                                     OTF2_Type           type,
                                     OTF2_AttributeValue value )
{
    struct otf2_print_data* data = userData;
    struct otf2_print_defs* defs = data->defs;

    /* Print definition if selected. */
    if ( !otf2_GLOBDEFS )
    {
        return OTF2_CALLBACK_SUCCESS;
    }

    printf( "%-*s "
            "%12s"
            "  "
            "Callpath: %s, "
            "Parameter: %s, "
            "Type: %s, "
            "Value: %s"
            "%s",
            otf2_DEF_COLUMN_WIDTH, "CALLPATH_PARAMETER",
            "",
            otf2_print_get_def_name( defs->callpaths, callpath ),
            otf2_print_get_def_name( defs->parameters, parameter ),
            otf2_print_get_type( type ),
            otf2_print_get_attribute_value( defs, type, value, false ),
            "\n" );

    return OTF2_CALLBACK_SUCCESS;
}

static bool
type_is_numeric( OTF2_Type type )
{
    switch ( type )
    {
        case OTF2_TYPE_UINT8:
        case OTF2_TYPE_UINT16:
        case OTF2_TYPE_UINT32:
        case OTF2_TYPE_UINT64:
        case OTF2_TYPE_INT8:
        case OTF2_TYPE_INT16:
        case OTF2_TYPE_INT32:
        case OTF2_TYPE_INT64:
        case OTF2_TYPE_FLOAT:
        case OTF2_TYPE_DOUBLE:
            return true;
        default:
            return false;
    }
}

static inline void
print_dot_label_begin( struct otf2_dot_data* dotData,
                       const char*           defName,
                       uint64_t              defRef,
                       const char*           kind,
                       const char*           name )
{
    fprintf( dotData->out,
             "        label=<\n"
             "            <table border='0' cellborder='0' cellspacing='0' cellpadding='0'>\n"
             "            <tr><td><table border='0' cellborder='1' cellspacing='0'>\n"
             "                <tr><td align='left' sides='ltb'><b>%s</b></td><td align='right' sides='tbr'>%s</td></tr>\n"
             "            </table></td></tr>\n"
             "            <tr><td><table border='0' cellborder='1' cellspacing='0'>\n"
             "                <tr><td align='left'>%s</td><td align='left'>%s</td></tr>\n"
             "            </table></td></tr>\n",
             defName, otf2_print_get_id64( defRef ), kind, name );
}

static inline void
print_dot_label_end( struct otf2_dot_data* dotData )
{
    fprintf( dotData->out,
             "            </table>\n"
             "        >\n" );
}

static inline void
print_dot_label_section_begin( struct otf2_dot_data* dotData,
                               uint32_t              columnCount,
                               const char*           sectionName )
{
    fprintf( dotData->out,
             "            <tr><td><table border='0' cellborder='1' cellspacing='0'>\n"
             "                <tr><td align='left' colspan='%u'><b>%s</b></td></tr>\n",
             columnCount, sectionName );
}

static inline void
print_dot_label_section_end( struct otf2_dot_data* dotData )
{
    fprintf( dotData->out,
             "            </table></td></tr>\n" );
}

enum print_dot_label_alignment
{
    LEFT  = 0,
    RIGHT = 1,
};

static inline void
print_dot_label_section_row( struct otf2_dot_data* dotData,
                             const char*           port,
                             uint32_t              columnCount,
                             ... )
{
    fprintf( dotData->out, "                <tr>" );

    va_list vl;
    va_start( vl, columnCount );
    for ( int column = 0; column < columnCount; column++ )
    {
        enum print_dot_label_alignment align = va_arg( vl, int );
        const char*                    value = va_arg( vl, char* );

        fprintf( dotData->out,
                 "<td %s%s%salign='%s'>%s</td>",
                 port ? "port='" : "",
                 port ? port : "",
                 port ? "' " : "",
                 align == LEFT ? "left" : "right",
                 value );
        port = NULL;
    }
    va_end( vl );

    fprintf( dotData->out, "</tr>\n" );
}

static void
print_dot_def_properties( struct otf2_dot_data*           dotData,
                          struct otf2_print_def_property* properties )
{
    struct otf2_print_data* data = dotData->data;
    struct otf2_print_defs* defs = data->defs;

    if ( !properties )
    {
        return;
    }

    print_dot_label_section_begin( dotData, 3, "Properties" );

    for (; properties; properties = properties->next )
    {
        print_dot_label_section_row(
            dotData, NULL, 3,
            LEFT, otf2_print_get_string( defs->strings, properties->name ),
            LEFT, otf2_print_get_type( properties->type ),
            type_is_numeric( properties->type ) ? RIGHT : LEFT,
            otf2_print_get_attribute_value( defs, properties->type, properties->value, true ) );
    }

    print_dot_label_section_end( dotData );
}

static void
print_dot_paradigm_ranks( struct otf2_dot_data*        dotData,
                          struct otf2_print_rank_list* ranks )
{
    struct otf2_print_data* data = dotData->data;
    struct otf2_print_defs* defs = data->defs;

    if ( ranks && !ranks->head )
    {
        return;
    }

    print_dot_label_section_begin( dotData, 2, "Paradigm Ranks" );

    for ( struct otf2_print_rank_list_element* rank = ranks->head;
          rank;
          rank = rank->next )
    {
        const struct otf2_print_group_def* group_def =
            otf2_vector_at( data->comm_paradigms, rank->paradigm );

        print_dot_label_section_row( dotData, NULL, 2,
                                     LEFT, group_def->def.name,
                                     RIGHT, otf2_print_get_id64( rank->rank ) );
    }

    print_dot_label_section_end( dotData );
}

static void
print_dot_recorded_metrics( struct otf2_dot_data*          dotData,
                            struct otf2_print_metric_list* metrics )
{
    struct otf2_print_data* data = dotData->data;
    struct otf2_print_defs* defs = data->defs;

    if ( metrics && !metrics->head )
    {
        return;
    }

    print_dot_label_section_begin( dotData, 3, "Recorded Metrics" );

    for ( struct otf2_print_metric_list_element* metric = metrics->head;
          metric;
          metric = metric->next )
    {
        const struct otf2_print_metric_def* metric_class = otf2_print_get_metric( defs->metrics, metric->metricRef );
        for ( uint32_t member = 0; member < metric_class->number_of_members; member++ )
        {
            const struct otf2_print_metric_member_def* metric_member
                = otf2_print_get_metric_member( defs->metric_members,
                                                metric_class->members[ member ] );

            char* port = otf2_print_get_buffer( 0 );
            snprintf( port, BUFFER_SIZE, "Cx%X_Mx%X", metric->metricRef, metric_class->members[ member ] );

            print_dot_label_section_row( dotData, port, 3,
                                         LEFT, metric_member->def.name,
                                         LEFT, otf2_print_get_type( metric_member->type ),
                                         LEFT, metric_member->unit );
        }
    }

    print_dot_label_section_end( dotData );
}

static void
print_dot_recorded_metrics_spliced( struct otf2_dot_data*          dotData,
                                    struct otf2_print_metric_list* metrics1,
                                    struct otf2_print_metric_list* metrics2 )
{
    if ( !metrics1 )
    {
        return print_dot_recorded_metrics( dotData, metrics2 );
    }

    *metrics1->tail = metrics2->head;
    print_dot_recorded_metrics( dotData, metrics1 );
    *metrics1->tail = NULL;
}

static void
print_dot_recorded_metrics_edges( struct otf2_dot_data*          dotData,
                                  uint64_t                       self,
                                  const char*                    source,
                                  struct otf2_print_metric_list* metrics )
{
    struct otf2_print_data* data = dotData->data;
    struct otf2_print_defs* defs = data->defs;

    if ( metrics && !metrics->head )
    {
        return;
    }

    for ( struct otf2_print_metric_list_element* metric = metrics->head;
          metric;
          metric = metric->next )
    {
        struct otf2_print_location_def* recorder = otf2_print_get_location( defs->locations, metric->recorderRef );
        if ( !recorder )
        {
            continue;
        }
        if ( recorder->locationType == OTF2_LOCATION_TYPE_METRIC
             && recorder->locationGroup == OTF2_UNDEFINED_LOCATION_GROUP )
        {
            continue;
        }

        const struct otf2_print_metric_def* metric_class = otf2_print_get_metric( defs->metrics, metric->metricRef );
        for ( uint32_t member = 0; member < metric_class->number_of_members; member++ )
        {
            if ( recorder->locationType == OTF2_LOCATION_TYPE_METRIC )
            {
                if ( recorder->locationGroup != self )
                {
                    fprintf( dotData->out,
                             "    Gx%X -> %s:Cx%X_Mx%X [dir=back, style=dotted, constraint=false];\n",
                             recorder->locationGroup, source, metric->metricRef, metric_class->members[ member ] );
                }
            }
            else
            {
                if ( metric->recorderRef != self )
                {
                    fprintf( dotData->out,
                             "    Lx%" PRIX64 " -> %s:Cx%X_Mx%X [dir=back, style=dotted, constraint=false];\n",
                             metric->recorderRef, source, metric->metricRef, metric_class->members[ member ] );
                }
            }
        }
    }
}

void
print_dot_system_tree_node( otf2_hash_table_entry* entry,
                            void*                  arg )
{
    struct otf2_dot_data*                   dot_data = arg;
    struct otf2_print_data*                 data     = dot_data->data;
    struct otf2_print_defs*                 defs     = data->defs;
    struct otf2_print_system_tree_node_def* def      = entry->value;

    fprintf( dot_data->out,
             "    Nx%" PRIX64 " [\n",
             def->def.def );

    print_dot_label_begin( dot_data,
                           "SYSTEM_TREE_NODE", def->def.def,
                           otf2_print_get_string( defs->strings, def->className ),
                           otf2_print_get_string( defs->strings, def->name ) );

    if ( def->domains_head )
    {
        print_dot_label_section_begin( dot_data, 1, "Domains" );

        for ( struct otf2_print_system_tree_node_domain* domain = def->domains_head;
              domain; domain = domain->next )
        {
            print_dot_label_section_row( dot_data, NULL, 1, LEFT, otf2_print_get_system_tree_domain( domain->domain ) );
        }

        print_dot_label_section_end( dot_data );
    }

    print_dot_def_properties( dot_data, def->properties_head );

    print_dot_recorded_metrics( dot_data, &def->recorded_metrics );

    print_dot_label_end( dot_data );

    fprintf( dot_data->out,
             "    ];\n" );

    /* Generate dot edge entry. */
    char* self = otf2_print_get_buffer( 0 );
    snprintf( self, BUFFER_SIZE, "Nx%" PRIX64, def->def.def );

    if ( def->parent != OTF2_UNDEFINED_SYSTEM_TREE_NODE )
    {
        fprintf( dot_data->out, "    Nx%X -> %s [dir=back];\n", def->parent, self );
    }

    print_dot_recorded_metrics_edges( dot_data, def->def.def, self, &def->recorded_metrics );
}

void
print_dot_location_group( otf2_hash_table_entry* entry,
                          void*                  arg )
{
    struct otf2_dot_data*                 dot_data = arg;
    struct otf2_print_data*               data     = dot_data->data;
    struct otf2_print_defs*               defs     = data->defs;
    struct otf2_print_location_group_def* def      = entry->value;

    fprintf( dot_data->out,
             "    Gx%" PRIX64 " [\n",
             def->def.def );

    print_dot_label_begin( dot_data,
                           "LOCATION_GROUP", def->def.def,
                           otf2_print_get_location_group_type( def->locationGroupType ),
                           otf2_print_get_string( defs->strings, def->name ) );

    print_dot_def_properties( dot_data, def->properties_head );

    print_dot_paradigm_ranks( dot_data, &def->paradigm_ranks );

    print_dot_recorded_metrics( dot_data, &def->recorded_metrics );

    print_dot_label_end( dot_data );

    fprintf( dot_data->out,
             "    ];\n" );

    char* self = otf2_print_get_buffer( 0 );
    snprintf( self, BUFFER_SIZE, "Gx%" PRIX64, def->def.def );

    if ( def->systemTreeParent != OTF2_UNDEFINED_SYSTEM_TREE_NODE )
    {
        fprintf( dot_data->out, "    Nx%X -> %s [dir=back];\n",
                 def->systemTreeParent, self );
    }

    if ( def->creatingLocationGroup != OTF2_UNDEFINED_LOCATION_GROUP )
    {
        fprintf( dot_data->out, "    Gx%X -> %s [dir=back, style=dashed, constraint=false];\n",
                 def->creatingLocationGroup, self );
    }

    print_dot_recorded_metrics_edges( dot_data, def->def.def, self, &def->recorded_metrics );
}

static void
print_dot_location( otf2_hash_table_entry* entry,
                    void*                  arg )
{
    struct otf2_dot_data*           dot_data = arg;
    struct otf2_print_data*         data     = dot_data->data;
    struct otf2_print_defs*         defs     = data->defs;
    struct otf2_print_location_def* def      = entry->value;

    if ( def->locationType == OTF2_LOCATION_TYPE_METRIC )
    {
        return;
    }

    fprintf( dot_data->out,
             "    Lx%" PRIX64 " [\n",
             def->def.def );

    print_dot_label_begin( dot_data,
                           "LOCATION", def->def.def,
                           otf2_print_get_location_type( def->locationType ),
                           otf2_print_get_string( defs->strings, def->name ) );

    print_dot_def_properties( dot_data, def->properties_head );

    print_dot_paradigm_ranks( dot_data, &def->paradigm_ranks );

    print_dot_recorded_metrics_spliced(
        dot_data,
        def->locationType == OTF2_LOCATION_TYPE_CPU_THREAD
        ? &data->recorded_cpu_metrics
        : def->locationType == OTF2_LOCATION_TYPE_ACCELERATOR_STREAM
        ? &data->recorded_gpu_metrics
        : NULL,
        &def->recorded_metrics );

    print_dot_label_end( dot_data );

    fprintf( dot_data->out,
             "    ];\n" );

    char* self = otf2_print_get_buffer( 0 );
    snprintf( self, BUFFER_SIZE, "Lx%" PRIX64, def->def.def );

    if ( def->locationGroup != OTF2_UNDEFINED_LOCATION_GROUP )
    {
        fprintf( dot_data->out, "    Gx%X -> %s [dir=back];\n",
                 def->locationGroup, self );
    }

    print_dot_recorded_metrics_edges( dot_data, def->def.def, self, &def->recorded_metrics );
}
