// MIT License // // Copyright (c) 2022 Advanced Micro Devices, Inc. All Rights Reserved. // // Permission is hereby granted, free of charge, to any person obtaining a copy // of this software and associated documentation files (the "Software"), to deal // in the Software without restriction, including without limitation the rights // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell // copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in all // copies or substantial portions of the Software. // // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE // SOFTWARE. #include "library/components/backtrace_metrics.hpp" #include "core/components/fwd.hpp" #include "core/config.hpp" #include "core/debug.hpp" #include "core/perfetto.hpp" #include "library/components/ensure_storage.hpp" #include "library/ptl.hpp" #include "library/runtime.hpp" #include "library/thread_info.hpp" #include "library/tracing.hpp" #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include namespace tracing { using namespace ::omnitrace::tracing; } namespace omnitrace { namespace component { using hw_counters = typename backtrace_metrics::hw_counters; using signal_type_instances = thread_data, category::sampling>; using backtrace_metrics_init_instances = thread_data; using sampler_running_instances = thread_data; using papi_vector_instances = thread_data; using papi_label_instances = thread_data, category::sampling>; namespace { struct perfetto_rusage {}; unique_ptr_t>& get_papi_labels(int64_t _tid) { return papi_label_instances::instance(construct_on_thread{ _tid }); } unique_ptr_t& get_papi_vector(int64_t _tid) { return papi_vector_instances::instance(construct_on_thread{ _tid }); } unique_ptr_t& get_backtrace_metrics_init(int64_t _tid) { return backtrace_metrics_init_instances::instance(construct_on_thread{ _tid }); } unique_ptr_t& get_sampler_running(int64_t _tid) { return sampler_running_instances::instance(construct_on_thread{ _tid }, false); } } // namespace std::string backtrace_metrics::label() { return "backtrace_metrics"; } std::string backtrace_metrics::description() { return "Records sampling data"; } std::vector backtrace_metrics::get_hw_counter_labels(int64_t _tid) { auto& _v = get_papi_labels(_tid); return (_v) ? *_v : std::vector{}; } void backtrace_metrics::start() {} void backtrace_metrics::stop() {} namespace { template auto get_enabled(tim::type_list) { constexpr size_t N = sizeof...(Tp); auto _v = std::bitset{}; size_t _n = 0; (_v.set(_n++, trait::runtime_enabled::get()), ...); return _v; } } // namespace void backtrace_metrics::sample(int) { if(!get_enabled(type_list{}).all()) { m_valid.reset(); return; } m_valid = get_enabled(categories_t{}); // return if everything is disabled if(!m_valid.any()) return; auto _cache = tim::rusage_cache{ RUSAGE_THREAD }; m_cpu = tim::get_clock_thread_now(); m_mem_peak = _cache.get_peak_rss(); m_ctx_swch = _cache.get_num_priority_context_switch() + _cache.get_num_voluntary_context_switch(); m_page_flt = _cache.get_num_major_page_faults() + _cache.get_num_minor_page_faults(); if constexpr(tim::trait::is_available::value) { constexpr auto hw_counters_idx = tim::index_of::value; constexpr auto hw_category_idx = tim::index_of::value; auto _tid = threading::get_id(); if(m_valid.test(hw_category_idx) && m_valid.test(hw_counters_idx)) { assert(get_papi_vector(_tid).get() != nullptr); m_hw_counter = get_papi_vector(_tid)->record(); } } } void backtrace_metrics::configure(bool _setup, int64_t _tid) { auto& _running = get_sampler_running(_tid); bool _is_running = (!_running) ? false : *_running; ensure_storage{}(); if(_setup && !_is_running) { (void) get_debug_sampling(); // make sure query in sampler does not allocate assert(_tid == threading::get_id()); if constexpr(tim::trait::is_available::value) { perfetto_counter_track::init(); OMNITRACE_DEBUG("HW COUNTER: starting...\n"); if(get_papi_vector(_tid)) { get_papi_vector(_tid)->start(); *get_papi_labels(_tid) = get_papi_vector(_tid)->get_config()->labels; } } } else if(!_setup && _is_running) { OMNITRACE_DEBUG("Destroying sampler for thread %lu...\n", _tid); *_running = false; if constexpr(tim::trait::is_available::value) { if(_tid == threading::get_id()) { if(get_papi_vector(_tid)) get_papi_vector(_tid)->stop(); OMNITRACE_DEBUG("HW COUNTER: stopped...\n"); } } OMNITRACE_DEBUG("Sampler destroyed for thread %lu\n", _tid); } } void backtrace_metrics::init_perfetto(int64_t _tid, valid_array_t _valid) { auto _hw_cnt_labels = *get_papi_labels(_tid); auto _tid_name = JOIN("", '[', _tid, ']'); if(!perfetto_counter_track::exists(_tid)) { if(get_valid(category::thread_cpu_time{}, _valid)) perfetto_counter_track::emplace( _tid, JOIN(' ', "Thread CPU time", _tid_name, "(S)"), "sec"); if(get_valid(category::thread_peak_memory{}, _valid)) perfetto_counter_track::emplace( _tid, JOIN(' ', "Thread Peak Memory Usage", _tid_name, "(S)"), "MB"); if(get_valid(category::thread_context_switch{}, _valid)) perfetto_counter_track::emplace( _tid, JOIN(' ', "Thread Context Switches", _tid_name, "(S)")); if(get_valid(category::thread_page_fault{}, _valid)) perfetto_counter_track::emplace( _tid, JOIN(' ', "Thread Page Faults", _tid_name, "(S)")); } if(!perfetto_counter_track::exists(_tid) && get_valid(type_list{}, _valid) && get_valid(category::thread_hardware_counter{}, _valid)) { for(auto& itr : _hw_cnt_labels) { std::string _desc = tim::papi::get_event_info(itr).short_descr; if(_desc.empty()) _desc = itr; OMNITRACE_CI_THROW(_desc.empty(), "Empty description for %s\n", itr.c_str()); perfetto_counter_track::emplace( _tid, JOIN(' ', "Thread", _desc, _tid_name, "(S)")); } } } void backtrace_metrics::fini_perfetto(int64_t _tid, valid_array_t _valid) { auto _hw_cnt_labels = *get_papi_labels(_tid); const auto& _thread_info = thread_info::get(_tid, SequentTID); OMNITRACE_CI_THROW(!_thread_info, "Error! missing thread info for tid=%li\n", _tid); if(!_thread_info) return; uint64_t _ts = _thread_info->get_stop(); uint64_t _rusage_idx = 0; if(get_valid(category::thread_cpu_time{}, _valid)) { TRACE_COUNTER(trait::name::value, perfetto_counter_track::at(_tid, _rusage_idx++), _ts, 0); } if(get_valid(category::thread_peak_memory{}, _valid)) { TRACE_COUNTER(trait::name::value, perfetto_counter_track::at(_tid, _rusage_idx++), _ts, 0); } if(get_valid(category::thread_context_switch{}, _valid)) { TRACE_COUNTER(trait::name::value, perfetto_counter_track::at(_tid, _rusage_idx++), _ts, 0); } if(get_valid(category::thread_page_fault{}, _valid)) { TRACE_COUNTER(trait::name::value, perfetto_counter_track::at(_tid, _rusage_idx++), _ts, 0); } if(get_valid(type_list{}, _valid) && get_valid(category::thread_hardware_counter{}, _valid)) { for(size_t i = 0; i < perfetto_counter_track::size(_tid); ++i) { if(i < _hw_cnt_labels.size()) { TRACE_COUNTER(trait::name::value, perfetto_counter_track::at(_tid, i), _ts, 0.0); } } } } backtrace_metrics& backtrace_metrics::operator-=(const backtrace_metrics& _rhs) { auto& _lhs = *this; if(_lhs(category::thread_cpu_time{})) { _lhs.m_cpu -= _rhs.m_cpu; } if(_lhs(category::thread_peak_memory{})) { _lhs.m_mem_peak -= _rhs.m_mem_peak; } if(_lhs(category::thread_context_switch{})) { _lhs.m_ctx_swch -= _rhs.m_ctx_swch; } if(_lhs(category::thread_page_fault{})) { _lhs.m_page_flt -= _rhs.m_page_flt; } if(_lhs(type_list{}) && _lhs(category::thread_hardware_counter{})) { for(size_t i = 0; i < _lhs.m_hw_counter.size(); ++i) _lhs.m_hw_counter.at(i) -= _rhs.m_hw_counter.at(i); } return _lhs; } void backtrace_metrics::post_process_perfetto(int64_t _tid, uint64_t _ts) const { uint64_t _rusage_idx = 0; if((*this)(category::thread_cpu_time{})) { TRACE_COUNTER(trait::name::value, perfetto_counter_track::at(_tid, _rusage_idx++), _ts, m_cpu / units::sec); } if((*this)(category::thread_peak_memory{})) { TRACE_COUNTER(trait::name::value, perfetto_counter_track::at(_tid, _rusage_idx++), _ts, m_mem_peak / units::megabyte); } if((*this)(category::thread_context_switch{})) { TRACE_COUNTER(trait::name::value, perfetto_counter_track::at(_tid, _rusage_idx++), _ts, m_ctx_swch); } if((*this)(category::thread_page_fault{})) { TRACE_COUNTER(trait::name::value, perfetto_counter_track::at(_tid, _rusage_idx++), _ts, m_page_flt); } if((*this)(type_list{}) && (*this)(category::thread_hardware_counter{})) { for(size_t i = 0; i < perfetto_counter_track::size(_tid); ++i) { if(i < m_hw_counter.size()) { TRACE_COUNTER(trait::name::value, perfetto_counter_track::at(_tid, i), _ts, m_hw_counter.at(i)); } } } } } // namespace component } // namespace omnitrace OMNITRACE_INSTANTIATE_EXTERN_COMPONENT( TIMEMORY_ESC(data_tracker), true, double) OMNITRACE_INSTANTIATE_EXTERN_COMPONENT( TIMEMORY_ESC(data_tracker), true, double) OMNITRACE_INSTANTIATE_EXTERN_COMPONENT( TIMEMORY_ESC(data_tracker), true, double) TIMEMORY_INITIALIZE_STORAGE(omnitrace::component::backtrace_metrics)