Perfetto annotation from timemory components (#289)

* Annotate perfetto with timemory component data

- support perfetto annotations via timemory component data, e.g. use PAPI component for exact HW counter annotations

* Tests for perfetto annotation via timemory data

* Update omnitrace-instrument

- remove --default-components argument as this overrides any components set in configuration file
- required by perfetto annotation via timemory data tests

* filter unavailable timemory components

- filter out unavailable timemory components before attempting to invoke the annotate operation on the bundle

* update annotate tests

- account for no PAPI support

* update lulesh-timemory test

- replace '-d wall_clock peak_rss' with '--env OMNITRACE_TIMEMORY_COMPONENTS="wall_clock peak_rss"'

* annotate tests update

- fix misnamed test

* annotate tests update

- restrict binary rewrite to run function to force instrumentation despite heuristics

* annotate tests update

- print {available,overlapping,excluded,instrumented} functions during binary rewrite

* annotate tests update

- add allow-overlapping flag

* Support PAPI with CAP_SYS_ADMIN

- do not disable PAPI if perf_event_paranoid > 2 but has CAP_SYS_ADMIN capability

[ROCm/rocprofiler-systems commit: 1aca8c177b]
This commit is contained in:
Jonathan R. Madsen
2023-06-19 19:18:04 -05:00
committed by GitHub
parent a0812bfa0b
commit c6929f545d
11 changed files with 377 additions and 101 deletions
@@ -31,6 +31,7 @@
#include "perfetto.hpp"
#include "utility.hpp"
#include <timemory/backends/capability.hpp>
#include <timemory/backends/dmp.hpp>
#include <timemory/backends/mpi.hpp>
#include <timemory/backends/process.hpp>
@@ -40,6 +41,7 @@
#include <timemory/log/color.hpp>
#include <timemory/log/logger.hpp>
#include <timemory/manager.hpp>
#include <timemory/process/process.hpp>
#include <timemory/sampling/allocator.hpp>
#include <timemory/settings.hpp>
#include <timemory/settings/types.hpp>
@@ -59,6 +61,7 @@
#include <cstdlib>
#include <fstream>
#include <limits>
#include <linux/capability.h>
#include <numeric>
#include <ostream>
#include <sstream>
@@ -944,7 +947,13 @@ configure_settings(bool _init)
if(_fparanoid) _fparanoid >> _paranoid;
}
if(_paranoid > 2)
auto _cap_status = timemory::linux::capability::cap_read(process::get_id());
auto* _cap_data = &_cap_status.effective;
bool _has_cap_sys_admin = false;
for(auto itr : timemory::linux::capability::cap_decode(*_cap_data))
if(itr == CAP_SYS_ADMIN) _has_cap_sys_admin = true;
if(_paranoid > 2 && !_has_cap_sys_admin)
{
OMNITRACE_BASIC_VERBOSE(0,
"/proc/sys/kernel/perf_event_paranoid has a value of %i. "
@@ -180,14 +180,6 @@ category_region<CategoryT>::start(std::string_view name, Args&&... args)
}
}
if constexpr(_ct_use_perfetto)
{
if(get_use_perfetto())
{
tracing::push_perfetto(CategoryT{}, name.data(), std::forward<Args>(args)...);
}
}
if constexpr(_ct_use_timemory)
{
if(get_use_timemory())
@@ -196,6 +188,14 @@ category_region<CategoryT>::start(std::string_view name, Args&&... args)
}
}
if constexpr(_ct_use_perfetto)
{
if(get_use_perfetto())
{
tracing::push_perfetto(CategoryT{}, name.data(), std::forward<Args>(args)...);
}
}
if constexpr(is_one_of<CategoryT, critical_trace_categories_t>::value)
{
using Device = critical_trace::Device;
@@ -250,14 +250,6 @@ category_region<CategoryT>::stop(std::string_view name, Args&&... args)
++tracing::pop_count();
}
if constexpr(_ct_use_timemory)
{
if(get_use_timemory())
{
tracing::pop_timemory(CategoryT{}, name, std::forward<Args>(args)...);
}
}
if constexpr(_ct_use_perfetto)
{
if(get_use_perfetto())
@@ -267,6 +259,14 @@ category_region<CategoryT>::stop(std::string_view name, Args&&... args)
}
}
if constexpr(_ct_use_timemory)
{
if(get_use_timemory())
{
tracing::pop_timemory(CategoryT{}, name, std::forward<Args>(args)...);
}
}
if constexpr(_ct_use_causal)
{
if constexpr(is_one_of<CategoryT, causal_throughput_categories_t>::value)
@@ -38,10 +38,17 @@
#include "library/thread_data.hpp"
#include "library/tracing/annotation.hpp"
#include <timemory/components/io/components.hpp>
#include <timemory/components/network/types.hpp>
#include <timemory/components/papi/types.hpp>
#include <timemory/components/rusage/components.hpp>
#include <timemory/components/timing/backends.hpp>
#include <timemory/components/timing/components.hpp>
#include <timemory/enum.h>
#include <timemory/hash/types.hpp>
#include <timemory/mpl/concepts.hpp>
#include <timemory/mpl/type_traits.hpp>
#include <timemory/types.hpp>
#include <atomic>
#include <functional>
@@ -57,8 +64,15 @@ namespace omnitrace
{
namespace tracing
{
using interval_data_instances = thread_data<std::vector<bool>>;
using hash_value_t = tim::hash_value_t;
using interval_data_instances = thread_data<std::vector<bool>>;
using hash_value_t = tim::hash_value_t;
using perfetto_annotate_component_types = tim::mpl::available_t<type_list<
comp::cpu_clock, comp::cpu_util, comp::kernel_mode_time, comp::num_major_page_faults,
comp::num_minor_page_faults, comp::page_rss, comp::peak_rss, comp::papi_array_t,
comp::papi_vector, comp::priority_context_switch, comp::voluntary_context_switch,
comp::process_cpu_clock, comp::process_cpu_util, comp::system_clock,
comp::thread_cpu_clock, comp::thread_cpu_util, comp::user_clock, comp::user_mode_time,
comp::virtual_memory>>;
//
// declarations
@@ -148,7 +162,7 @@ get_perfetto_track(CategoryT, FuncT&& _desc_generator, Args&&... _args)
auto& _track_uuids = get_perfetto_track_uuids();
if(_track_uuids.find(_uuid) == _track_uuids.end())
{
const auto _track = TrackT(_uuid);
const auto _track = TrackT(_uuid, ::perfetto::ProcessTrack::Current());
auto _desc = _track.Serialize();
auto _name = std::forward<FuncT>(_desc_generator)(std::forward<Args>(_args)...);
@@ -172,7 +186,7 @@ get_perfetto_track(CategoryT, FuncT&& _desc_generator, Args&&... _args)
_uuid, _track_uuids.at(_uuid).c_str(), _name.c_str());
#endif
return TrackT(_uuid);
return TrackT(_uuid, ::perfetto::ProcessTrack::Current());
}
template <typename Tp = uint64_t>
@@ -283,12 +297,13 @@ push_timemory(CategoryT, std::string_view name, Args&&... args)
++get_profile_stack<CategoryT>();
}
template <typename CategoryT, typename... Args>
inline void
pop_timemory(CategoryT, std::string_view name, Args&&... args)
template <typename CategoryT>
inline std::pair<instrumentation_bundle_t*, size_t>
get_timemory(CategoryT, std::string_view name)
{
using return_type = std::pair<instrumentation_bundle_t*, size_t>;
// skip if category is disabled and not pushed on this thread
if(profile_pop_disabled<CategoryT>()) return;
if(profile_pop_disabled<CategoryT>()) return return_type{ nullptr, -1 };
auto _hash = tim::hash::get_hash_id(name);
auto& _data = tracing::get_instrumentation_bundles();
@@ -296,18 +311,13 @@ pop_timemory(CategoryT, std::string_view name, Args&&... args)
{
OMNITRACE_DEBUG("[%s] skipped %s :: empty bundle stack\n", "omnitrace_pop_trace",
name.data());
return;
return return_type{ nullptr, -1 };
}
auto*& _v_back = _data.bundles.back();
if(OMNITRACE_LIKELY(_v_back->get_hash() == _hash))
{
// decrement the profile stack
--get_profile_stack<CategoryT>();
_v_back->stop(std::forward<Args>(args)...);
_data.allocator.destroy(_v_back);
_data.allocator.deallocate(_v_back, 1);
_data.bundles.erase(--_data.bundles.end());
return std::make_pair(_v_back, _data.bundles.size() - 1);
}
else if(_data.bundles.size() > 1)
{
@@ -316,16 +326,52 @@ pop_timemory(CategoryT, std::string_view name, Args&&... args)
auto*& _v = _data.bundles.at(i - 1);
if(_v->get_hash() == _hash)
{
// decrement the profile stack
--get_profile_stack<CategoryT>();
_v->stop(std::forward<Args>(args)...);
_data.allocator.destroy(_v);
_data.allocator.deallocate(_v, 1);
_data.bundles.erase(_data.bundles.begin() + (i - 1));
break;
return std::make_pair(_v, i - 1);
}
}
}
return return_type{ nullptr, -1 };
}
template <typename CategoryT, typename... Args>
inline auto
stop_timemory(CategoryT, std::string_view name, Args&&... args)
{
using return_type = std::pair<instrumentation_bundle_t*, size_t>;
// skip if category is disabled and not pushed on this thread
if(profile_pop_disabled<CategoryT>()) return return_type{ nullptr, -1 };
auto&& _data = get_timemory(CategoryT{}, name);
if(_data.first)
{
_data.first->stop(std::forward<Args>(args)...);
}
return _data;
}
inline void
destroy_timemory(std::pair<instrumentation_bundle_t*, size_t> _data)
{
if(_data.first)
{
auto& _bundles = tracing::get_instrumentation_bundles();
_bundles.allocator.destroy(_data.first);
_bundles.allocator.deallocate(_data.first, 1);
_bundles.bundles.erase(_bundles.bundles.begin() + _data.second);
}
}
template <typename CategoryT, typename... Args>
inline void
pop_timemory(CategoryT, std::string_view name, Args&&... args)
{
// skip if category is disabled and not pushed on this thread
if(profile_pop_disabled<CategoryT>()) return;
auto _data = stop_timemory(CategoryT{}, name, std::forward<Args>(args)...);
if(_data.first) destroy_timemory(std::move(_data));
}
template <typename CategoryT, typename... Args>
@@ -384,6 +430,36 @@ push_perfetto(CategoryT, const char* name, Args&&... args)
}
}
/// \brief This function is used to take an existing lambda accepting a
/// perfetto::EventContext and append the timemory annotations. Examples
/// are seen in the pop_perfetto* functions
template <typename CategoryT, typename Arg>
inline decltype(auto)
perfetto_annotate_timemory_data(CategoryT, const char* name, Arg&& arg)
{
if constexpr(std::is_invocable<Arg, ::perfetto::EventContext>::value)
{
return [&arg, name](::perfetto::EventContext _ctx) {
if(config::get_perfetto_annotations())
{
auto _timemory_data = get_timemory(CategoryT{}, name);
if(_timemory_data.first)
{
_timemory_data.first->stop();
_timemory_data.first
->template invoke_with<tim::operation::perfetto_annotate>(
perfetto_annotate_component_types{}, _ctx);
}
}
std::forward<Arg>(arg)(std::move(_ctx));
};
}
else
{
return std::move(arg);
}
}
template <typename CategoryT, typename... Args>
inline void
pop_perfetto(CategoryT, const char* name, Args&&... args)
@@ -400,7 +476,8 @@ pop_perfetto(CategoryT, const char* name, Args&&... args)
if(config::get_perfetto_annotations())
{
TRACE_EVENT_END(trait::name<CategoryT>::value, _ts, "end_ns", _ts,
std::forward<Args>(args)...);
perfetto_annotate_timemory_data(CategoryT{}, name,
std::forward<Args>(args))...);
}
else
{
@@ -428,14 +505,15 @@ pop_perfetto(CategoryT, const char* name, Args&&... args)
// decrement tracing stack
--get_tracing_stack<CategoryT>();
uint64_t _ts = now();
TRACE_EVENT_END(trait::name<CategoryT>::value, _ts,
std::forward<Args>(args)...,
[&](::perfetto::EventContext ctx) {
if(config::get_perfetto_annotations())
{
tracing::add_perfetto_annotation(ctx, "end_ns", _ts);
}
});
TRACE_EVENT_END(
trait::name<CategoryT>::value, _ts, std::forward<Args>(args)...,
perfetto_annotate_timemory_data(
CategoryT{}, name, [&](::perfetto::EventContext ctx) {
if(config::get_perfetto_annotations())
{
tracing::add_perfetto_annotation(ctx, "end_ns", _ts);
}
}));
}
}
@@ -456,7 +534,7 @@ push_perfetto_ts(CategoryT, const char* name, uint64_t _ts, Args&&... args)
template <typename CategoryT, typename... Args>
inline void
pop_perfetto_ts(CategoryT, const char*, uint64_t _ts, Args&&... args)
pop_perfetto_ts(CategoryT, const char* name, uint64_t _ts, Args&&... args)
{
// skip if category is disabled and not pushed on this thread
if(tracing_pop_disabled<CategoryT>()) return;
@@ -464,7 +542,9 @@ pop_perfetto_ts(CategoryT, const char*, uint64_t _ts, Args&&... args)
// decrement tracing stack
--get_tracing_stack<CategoryT>();
TRACE_EVENT_END(trait::name<CategoryT>::value, _ts, std::forward<Args>(args)...);
TRACE_EVENT_END(
trait::name<CategoryT>::value, _ts,
perfetto_annotate_timemory_data(CategoryT{}, name, std::forward<Args>(args))...);
}
template <typename CategoryT, typename... Args>
@@ -482,7 +562,7 @@ push_perfetto_track(CategoryT, const char* name, ::perfetto::Track _track, uint6
template <typename CategoryT, typename... Args>
inline void
pop_perfetto_track(CategoryT, const char*, ::perfetto::Track _track, uint64_t _ts,
pop_perfetto_track(CategoryT, const char* name, ::perfetto::Track _track, uint64_t _ts,
Args&&... args)
{
// skip if category is disabled and not pushed on this thread
@@ -491,8 +571,9 @@ pop_perfetto_track(CategoryT, const char*, ::perfetto::Track _track, uint64_t _t
// decrement tracing stack
--get_tracing_stack<CategoryT>();
TRACE_EVENT_END(trait::name<CategoryT>::value, _track, _ts,
std::forward<Args>(args)...);
TRACE_EVENT_END(
trait::name<CategoryT>::value, _track, _ts,
perfetto_annotate_timemory_data(CategoryT{}, name, std::forward<Args>(args))...);
}
template <typename CategoryT, typename... Args>
@@ -32,6 +32,7 @@
#include "omnitrace/categories.h" // in omnitrace-user
#include <timemory/mpl/concepts.hpp>
#include <timemory/operations/types/get.hpp>
#include <type_traits>
@@ -206,3 +207,90 @@ add_perfetto_annotation(perfetto_event_context_t& ctx,
const omnitrace_annotation_t& _annotation);
} // namespace tracing
} // namespace omnitrace
#include <timemory/operations/types/annotate.hpp>
namespace tim
{
namespace operation
{
using perfetto_event_context_t = ::omnitrace::tracing::perfetto_event_context_t;
template <typename Tp>
struct annotate<perfetto_event_context_t, Tp>
{
TIMEMORY_DEFAULT_OBJECT(annotate)
auto operator()(Tp& obj, perfetto_event_context_t& _ctx) const
{
return sfinae(obj, 0, _ctx);
}
private:
// If the component has a annotate(...) member function
template <typename T>
static auto sfinae(T& obj, int, perfetto_event_context_t& _ctx)
-> decltype(obj.annotate(_ctx))
{
static_assert(std::is_same<T, Tp>::value, "Error T != Tp");
return obj.annotate(_ctx);
}
// If the component does not have a annotate(...) member function
template <typename T>
static void sfinae(T& obj, long, perfetto_event_context_t& _ctx)
{
static_assert(std::is_same<T, Tp>::value, "Error T != Tp");
using value_type = typename T::value_type;
if constexpr(!std::is_void<value_type>::value)
{
auto _obj_data = sfinae_data<Tp, decltype(obj.get())>(obj, 0);
for(size_t i = 0; i < std::get<0>(_obj_data); ++i)
{
auto&& _label = std::get<1>(_obj_data).at(i);
auto&& _value = std::get<2>(_obj_data).at(i);
::omnitrace::tracing::add_perfetto_annotation(_ctx, _label, _value);
}
}
(void) _ctx;
}
template <typename T, typename DataT>
static auto sfinae_data(T& obj, int)
-> decltype(std::tuple<size_t, std::vector<std::string>, DataT>(obj.get().size(),
obj.label_array(),
obj.get()))
{
static_assert(std::is_same<T, Tp>::value, "Error T != Tp");
auto _labels = obj.label_array();
auto _data = obj.get();
auto _size = std::min<size_t>(_labels.size(), _data.size());
return std::make_tuple(_size, _labels, _data);
}
template <typename T, typename DataT>
static auto sfinae_data(T& obj, long)
{
using strvec_t = std::vector<std::string>;
using datavec_t = std::vector<DataT>;
size_t _size = 1;
strvec_t _labels = { obj.get_label() };
datavec_t _data = { obj.get() };
return std::tuple<size_t, strvec_t, datavec_t>{ _size, _labels, _data };
}
};
template <typename Tp>
struct perfetto_annotate : annotate<perfetto_event_context_t, Tp>
{
using base_type = annotate<perfetto_event_context_t, Tp>;
TIMEMORY_DEFAULT_OBJECT(perfetto_annotate)
auto operator()(Tp& obj, perfetto_event_context_t& _ctx) const
{
return base_type::operator()(obj, _ctx);
}
};
} // namespace operation
} // namespace tim