Custom Components¶
Timemory supports user-specifed components. A custom component can utilize the static polymorphic base class
tim::component::base
to inherit many features but ultimately, the goal is to not require a specific
base class. The tim::component::base
class provides the integration into the API and requires two template
parameters:
component type (i.e. itself)
the data type being stored
//
// generic static polymorphic base class
//
template <typename _Tp, typename value_type>
struct base;
//
// create cpu_clock component and use int64_t as data type
//
struct cpu_clock
: public base<cpu_clock, int64_t>
{
// ...
};
//
// create cpu_util component and specify data type as pair of 64-bit integers
//
struct cpu_util
: public base<cpu_util, std::pair<int64_t, int64_t>>
{
// ...
};
//
// create papi_tuple component and specify data type as an array of long long types
//
template <int... Events>
struct papi_tuple
: public base<papi_tuple<Events...>, // self type
std::array<long long, sizeof...(Events)>> // data type
{
// ...
};
Type Traits¶
Type traits are provided to customize how operations on the data type are handled. For example, adding two wall_clock
components together is a simple a += b
operation but adding two peak_rss
components is better defined as a
max operation: a = max(a, b)
since +=
a “peak” memory value could return a result larger than the total amount of
memory on the system. Additionally, certain components are not available on certain systems, e.g. components that
provide data via the POSIX rusage struct on Windows. The most update-to-date list of type-traits can be found in
timemory/mpl/types.hpp
in the trait
namespace. A subset of the available type-traits is provided below.
Namespace:
tim::trait
Type Trait | Description | Default Setting |
---|---|---|
is_available |
Specify the availablity of the component's implementation | std::true_type |
record_max |
Specify that addition operations should use comparisions | std::false_type |
custom_label_printing |
Specify the component provides its own labeling for output (typically multiple labels) | std::false_type |
custom_unit_printing |
Specify the component provides its own units for output (typically multiple units) | std::false_type |
custom_laps_printing |
Specify the component provides its own "lap" printing for output | std::false_type |
is_timing_category |
Designates the width and precision should apply environment-specified timing settings | std::false_type |
is_memory_category |
Designates the width and precision should apply environment-specified memory settings | std::false_type |
uses_timing_units |
Designates the width and precision should apply environment-specified timing units | std::false_type |
uses_memory_units |
Designates the width and precision should apply environment-specified memory units | std::false_type |
requires_json |
Specify the component should always output the JSON file format | std::false_type |
start_priority |
Specify the component should be started before non-prioritized components | std::false_type |
stop_priority |
Specify the component should be stopped before non-prioritized components | std::false_type |
Type Traits Example¶
template <typename _Tp> struct is_available : std::true_type { };
// on Windows, stack_rss is not available because stack_rss requires POSIX rusage struct
#if defined(_WIN32) || defined(_WIN64)
//
// using macro to specialize type-trait
//
TIMEMORY_DEFINE_CONCRETE_TRAIT(is_available, component::stack_rss, false_type)
//
// explicit specialization of type-trait without macro
//
namespace tim
{
namespace trait
{
template <>
struct is_available<component::stack_rss> : std::false_type
{};
} // namespace trait
} // namespace tim
#endif
The is_available
Trait¶
tim::trait::is_available
is a very important type-trait. Marking a component as not available results in this type
being filtered out of the variadic wrapper classes auto_tuple
, auto_list
, component_tuple
, and component_list
.
This means that with the above example, on Windows, the example_real_stack_type
type below will implement the
same behavor as the example_real_type
type:
using example_real_type = tim::auto_tuple< wall_clock >;
using example_real_stack_type = tim::auto_tuple< wall_clock, stack_rss >;
// On Windows, example_real_stack_type type will filter out stack_rss at compile time
// and will produce the exact same result and performance as example_real_type
In addition to a performance benefit (no operations on a component that does not provide anything) and a much
cleaner code base (significant reduction of #ifdef
blocks), this can be used to enable “add-on” features for
performance analysis that won’t be included in production code, e.g. including cpu_roofline_dp_flops
in a specification
but not enabling the PAPI backend in a production code.
Custom Component Example¶
Timemory uses SFINAE (substitution-failure-is-not-an-error) to query for the presence of member functions at compile-time.
Adding new capabilities to a component is commonly as simple as just providing the member function for the capability.
For example, if a component needs a string to forward on to another tool, the component can just add a void set_prefix(const char* str)
and this member function will be detected by operation::set_prefix<T>
(invoked by the variadic bundler). If this member
function does not exist, then operation::set_prefix<T>
will be a no-op for all instances of component T
.
Field | Required | Type | Description |
---|---|---|---|
value_type |
Yes | Alias/typedef | Data type used for storage |
base_type |
No | Alias/typedef | Base class directly inheriting from |
precision |
No | Constant integer | Default output precision |
width |
No | Constant integer | Default output width |
format_flags |
No | std::ios_base::fmtflags |
Bitset of formatting flags |
unit() |
No | numerical value | Units of data type |
label() |
No | string | Short-hand description without spaces |
description() |
No | string | Full description of component |
display_unit() |
No | string | String representation of unit() |
get() const |
No | const member function | Returns the current measurement |
get_display() const |
No | const member function | Returns the current measurement in format desired for reporting |
start() |
No | member function | Defines operation when component recording is started |
stop() |
No | member function | Defines operation when component recording is stopped |
set_prefix(T) |
No | member function | Sets label before start() is called. T can be std::string , const char* , or size_t |
struct wall_clock : public base<wall_clock, int64_t>
{
// alias to the type of data being recorded is stored
using value_type = int64_t;
// alias to base type that implements a lot of the functionality
using base_type = base<wall_clock, value_type>;
// this is specific to this particular component
using ratio_t = std::nano;
// these are formatting guidelines
static const short precision = 3;
static const short width = 6;
static const std::ios_base::fmtflags format_flags =
std::ios_base::fixed | std::ios_base::dec;
// these handle units conversions and descriptions
static int64_t unit() { return units::sec; }
static std::string label() { return "real"; }
static std::string description() { return "wall time"; }
static std::string display_unit() { return "sec"; }
// this defines how to record a value for the component
static value_type record()
{
return tim::get_clock_real_now<int64_t, ratio_t>();
}
// this defines how to get the relevant data from the component and can
// return any type
float get() const
{
auto val = (is_transient) ? accum : value;
return static_cast<double>(val / static_cast<double>(ratio_t::den) *
base_type::get_unit());
}
// this defines how the value is represented in '<<' and can return any type
float get_display()() const
{
return this->get();
}
// this defines what happens when the components is started
void start()
{
base_type::set_started();
value = record();
}
// this defines what happens when the components is stopped
void stop()
{
value = (record() - value);
accum += value;
base_type::set_stopped();
}
};