Skip to content

Latest commit

 

History

History
384 lines (287 loc) · 12.7 KB

README.md

File metadata and controls

384 lines (287 loc) · 12.7 KB

Using libaftermath-trace

States

A state is just a labelled interval that does not hold any extra information, but allows quick prototyping without extending the Aftermath type system. The easiest way to use it is to use am_state_stack and am_buffered_event_collection. When the state is entered it is pushed to the stack. On exit the state is popped. Popping the state from the stack adds a new interval with the corresponding state name into the event collection.

Intervals types that associate specific information with the interval should be modeled as separate data structure with an embedded interval and additional fields carrying the extra information (e.g., intervals representing OpenMP loop iteration periods are represented as instances of am_dsk_openmp_iteration_period).

Extending the type system will be covered later in this document.

Tracing

Initialization and destruction

All trace data is first accumulated in main memory before being written out to a trace file. The root data structure that serves as an entry point to all data held in memory is am_buffered_trace. Trace-wide data is stored directly in a dedicated buffer embedded into the buffered trace object. Dynamic events (e.g., events generated by a thread) are associated to streams of events, called event collections. In order to avoid contention on the trace-wide buffer, each event collection has its own buffer that can be written concurrently with buffers from other event collections.

A trace can be associated with an arbitrary number of event collections and events from the collections can be associated to nodes of arbitrary hierarchies representing the logical and physical structure of the system (e.g., tasks grouped into threads grouped into processes or e.g., cores grouped into physical packages grouped into machines).k

New event collections are created by invoking am_buffered_trace_new_collection and are added to the trace by calling am_buffered_trace_add_collection(). However, before any collection is added, the data structure for event collections must be registered once using am_dsk_event_collection_write_to_buffer_defid().

Before tracing starts, the trace has to be initialized and the header including data structures associating types from the type system with numeric IDs needs to be written to the main, trace-wide buffer. Afterwards, hierarchies can be initialized and added to the trace buffer. Once all hierarchies have been established and all event collections have been created, the actual mappings of event streams to nodes from these hierarchies for execution intervals represented as instances of am_dsk_event_mapping can to be written to the trace buffer using am_dsk_event_mapping_write_to_buffer_defid().

Please see the instrumented pthreads library or AfterOMPT for examples of creating and destroying traces.

Adding events to trace

Assuming an appropriate type was added to the type system, tracing events is relatively straightforward. Writing an event to the buffer of an event collection is done by calling an appropriate *_write_to_buffer_defid() function from on_disk_write_to_buffer.h. Please note that each event type must be registered once at the trace-wide buffer using the corresponding *_write_default_id_to_buffer() function.

Type System

For readers interesting more in the architecture of the Aftermath we refer to the read me file in libaftermath-core/doc/TYPE_SYSTEM. It explains in details a difference between on-disk and in-memory models.

For the purpose of this tutorial it is enough to know that on-disk types are ones written to the trace file and stored on disk in the file. In-memory types, on the other hand, are the memory representation of on-disk types when they are loaded from disk to memory to be analysed, displayed, etc. Definitions of on-disk and in-memory types are stored in separate directories.

Adding new on-disk types

In order to allow an event of a new type to be written to trace files, the new type has to be added to the type system. The type definitions can be found in libaftermath-core/src/defs/aftermath/types and on-disk types are defined in on_disk.py. Adding new types consists of adding a new object of type EventFrame for event collections events and Frame for trace wide events. Then the event has to be added to TypeList at the end of the file.

Adding new in-memory types

Python types

To enable reading and writing data from and to on-disk representation, so it can be processed and displayed, a corresponding in-memory representation has to be added. This can be done by creating a new InMemoryCompoundType object and adding it to the TypeList at the end of an appropriate in-memory.py file.

After the in-memory type is created the link to the on-disk representation has to be created. This is done by adding following lines to the appropriate on_disk.py file.

Within the trace can be stored either in the trace-wide array or an event collection. Again for the more detailed description we refer to libaftermath-core/doc/TYPE_SYSTEM.

For frames to be stored in an event collection, the syntax is:

tags.dsk.tomem.add_per_event_collection_tags(
  on_disk_type,
  aftermath.types.x.y.z.in_memory.in_memory_type,
  "collection_id")

And for trace-wide frames:

tags.dsk.tomem.add_per_trace_array_tags(
  on_disk_type,
  aftermath.type.x.y.z.in_memory.in_memory_type)

Source files

Next, some .c and .h files have to be updated so appropriate classes and functions are generated for the new type. It can be achieved with following steps:

  1. Add an array definition for the in-memory type by creating a new file called libaftermath-core/src/<type>_array.h:

    #ifndef AM_<TYPE>_ARRAY_H
    #define AM_<TYPE>_ARRAY_H
    
    #include <aftermath/core/typed_array.h>
    #include <aftermath/core/in_memory.h>
    
    AM_DECL_TYPED_ARRAY_WITH_ELEMENT_DESTRUCTOR(
      am_<type>_array,
      struct am_<type>,
      am_<type>_destroy)
    
    #endif

    If the type has no destructor then AM_DECL_TYPED_ARRAY(am_<type>_array, struct am_<type>) is sufficient. The destructor is required if the type is not a plain data type, e.g., it contains a string field.

  2. Include the new header file in libaftermath-core/src/defs/aftermath/templates/toplevel/on_disk.tpl.c .

  3. Add the array definition to the default array registry as follow in libaftermath-core/src/default_trace_array_registry.c:

    #include <aftermath/core/<type>_array.h>
    
    ...
    
    AM_DECL_DEFAULT_ARRAY_REGISTRY_FUNCTIONS(am_<type>_array)

    And then in the same file add following in am_build_default_trace_array_registry():

    AM_DEFAULT_ARRAY_REGISTRY_REGISTER(r, am_<type>_array,
                                       "am::<model>::<type>")
  4. Add a symbolic link from libaftermath-core/headers/aftermath/core/<type>_array.h to libaftermath-core/src/<type>_array.h:

    ln -s --relative libaftermath-core/src/<group>_<type>_array.h libaftermath-core/headers/aftermath/core/<group>_<type>_array.h
    
  5. Add aftermath/core/<type>_array.h to nobase_include_HEADERS in libaftermath-core/headers/Makefile.am

  6. Add src/<type>_array.h to libaftermath_core_la_SOURCES in libaftermath-core/Makefile.am

Using libaftermath-render

Add a renderer for a new type

To enable rendering of a newly-defined type in the Aftermath GUI, a new renderer needs to be added to libaftermath-render. This can be done as follows:

  1. Add new nodes in libaftermath-render/src/dfg/nodes/timeline/layers/<group>.h by adding:

    AM_RENDER_DFG_DECL_TIMELINE_LAYER_FILTER_NODE_TYPE(
      <group>_<type>,
      "<group>::<type>",
      "Some short description")
    
    AM_RENDER_DFG_DECL_TIMELINE_LAYER_ENABLE_CONFIGURATION_NODE_TYPE(
      <group>_<type>,
      "<group>::<type>",
      "Some short description")
    
    int am_render_dfg_timeline_<group>_<type>_layer_configuration_node_process(
      struct am_dfg_node* n);

    And then following lines in AM_DFG_BUILTIN_NODE_TYPES:

    &am_render_dfg_timeline_<group>_<type>_layer_filter_node_type,
    &am_render_dfg_timeline_<group>_<type>_layer_configuration_node_type
  2. Add an implementation for the new nodes in libaftermath-render/src/dfg/nodes/timeline/layers/<group>.c:

    AM_RENDER_DFG_IMPL_TIMELINE_LAYER_FILTER_NODE_TYPE(
      <group>_<type>,
      "<group>::<type>",
      struct am_timeline_<group>_<type>_layer)
    
    AM_RENDER_DFG_IMPL_TIMELINE_LAYER_ENABLE_CONFIGURATION_NODE_TYPE(
      <group>_<type>,
      "<group>::<type>")
  3. Update the list of types in libaftermath-render/src/dfg/types/builtin_types.c:

    AM_RENDER_DFG_DECL_TIMELINE_LAYER_TYPE(<group>_<type>, "<group>::<type>")

    And in builtin_defs[] add:

    &am_render_dfg_type_timeline_<group>_<type>_layer
  4. Declare a new timeline lane renderer in libaftermath-render/src/timeline/layers/lane/<group>/<group>.h:

    struct am_timeline_render_layer_type*
    am_timeline_<group>_<type>_layer_instantiate_type(void);
  5. Implement the lane rendering functions in libaftermath-render/src/timeline/layers/lane/<group>/<group>.c:

    static int trace_changed_<type>(struct am_timeline_render_layer* l,
                struct am_trace* t)
    {
      return trace_changed_per_ecoll_array(l, t);
    }
    
    static int renderer_changed_<type>(struct am_timeline_render_layer* l,
                   struct am_timeline_renderer* r)
    {
      if(r->trace)
        return trace_changed_<type>(l, r->trace);
      else
        return 0;
    }
    
    static size_t calculate_index_<type>(
      struct am_timeline_interval_layer* l,
      void* arg)
    {
      struct am_<group>_<type>* m = arg;
    
      // This value determines the colour in the GUI
      return 0;
    }
    
    struct am_timeline_render_layer_type*
    am_timeline_<group>_<type>_layer_instantiate_type(void)
    {
      struct am_timeline_render_layer_type* t;
    
      t = am_timeline_interval_layer_instantiate_type_index_fun(
        "<group>::<type>",
        "am::<group>::<type>",
        sizeof(struct am_<group>_<type>),
        offsetof(struct am_<group>_<type>, interval),
        calculate_index_<type>);
    
      t->trace_changed = trace_changed_<type>;
      t->renderer_changed = renderer_changed_<type>;
    
      return t;
    }

    For discrete events use the following code instead:

    struct am_timeline_render_layer_type*
    am_timeline_<group>_<type>_acquire_layer_instantiate_type(void)
    {
      struct am_timeline_render_layer_type* t;
    
      t = am_timeline_discrete_layer_instantiate_type_index_fun(
        "<group>::<type>",
        "am::<group>::<type>",
        sizeof(struct am_<group>_<type>),
        offsetof(struct am_<group>_<type>, timestamp),
        calculate_index_<type>,
        render_event);
    
      t->trace_changed = trace_changed_<type>;
      t->renderer_changed = renderer_changed_<type>;
    
      return t;
    }

    Where the render_event function can be freely implemented.

  6. Add the timeline type to libaftermath-render/src/timeline/common_layers.c by adding am_timeline_<group>_<type>_layer_instantiate_type to (*inst_functions[])(void).

Using the Aftermath GUI

Making a renderer accessible from the GUI

Extending existing profiles

The steps below explain how to add new type to one of the existing profiles in Aftermath:

  1. Add a new icon to aftermath/share/icons/.

  2. Add the path to the new icon to ICONS in aftermath/Makefile.am as share/icons/draw_<group>_<type>.svg \.

  3. Add new button to aftermath/share/profiles/<profile>/interface.amgui.in, for example:

    amgui_toolbar_togglebutton {
                  tooltip: "Some short description",
                  icon: "@ICON_BASEDIR@/draw_<group>_<type>.svg",
                  checked: 0u64,
                  id: "toolbutton_draw_<group>_<type>"
    

    And add the type to list of layers:

    amgui_timeline {
      id: "tl1",
      layers: [..., "<group>_<type>", ...]
    
  4. Update the data-flow graph in aftermath/share/profiles/<profile>/graph.dfg with nodes and connections of the new type.

    To the list of nodes, add:

    am::render::timeline::layer::<group>::<type>::filter {
      id: XXu64
    },
    am::render::timeline::layer::<group>::<type>::configuration {
      id: YYu64
    },
    am::gui::toolbar_togglebutton {
      id: ZZu64,
      widget_id: "toolbutton_draw_<group>_<type>"
    }
    

    To the list of list of connections, add:

    [AAu64, "layers", XXu64, "in"],
    [XXu64, "out", YYu64, "layer"],
    [ZZu64, "toggled", YYu64, "enable"]
    

    And indicate the location of the nodes as follows:

    [XXu64, x1, y1],
    [YYu64, x2, y2],
    [ZZu64, x3, y3]