Improved analysis of functions to instrument + MPI support + timemory support (#2)
* various tweaks
* build updates + cleanup + overlap guard + min addr range
* Library source reorg + miscellaneous tweaks
* Removed unnecessary fwd decls
* Print address range in --print-X pair mode
- hosttrace modifications
- disable instrumenting functions with overlapping sections or multiple entry points by default (control via --allow-overlapping option)
- disable instrumenting functions whose address range < 512 bytes unless a loop is present by default (control via --min-address-range option)
- disable instrumenting functions w/ loops whose address range < 64 bytes (control via --min-loop-address-range)
- Support for wrapping MPI function calls even in binary rewrite mode
- e.g. use gotcha to wrap MPI functions with hosttrace_push_trace and hosttrace_pop_trace
- New timemory only mode --> HOSTTRACE_USE_TIMEMORY=ON
- New timemory + perfetto mode --> HOSTTRACE_USE_PERFETTO=ON + HOSTTRACE_USE_TIMEMORY=ON
- Full support for all timemory components
- parallel-overhead example for measuring the overhead in a MT-parallelized application with very small instrumentation functions
- improvements to output directories for hosttrace exe
- improvements to output directories for hosttrace library
- new hosttrace options
- --print-instrumented <type> prints out the instrumented entities and exits
- --print-available <type> prints out the available instrumentation entities and exits
- --print-overlapping <type> prints out the overlapping entities and exits
- NOTE: <type> above refers to the information printed out, e.g. module name vs. function name vs. module and function name, etc.
[ROCm/rocprofiler-systems commit: 1f15b3070f]
This commit is contained in:
committed by
GitHub
parent
1ff2dfed88
commit
6825578603
@@ -140,24 +140,25 @@ static int verbose_level = tim::get_env<int>("TIMEMORY_RUN_VERBOSE", 0);
|
||||
// string settings
|
||||
//
|
||||
static string_t main_fname = "main";
|
||||
static string_t argv0 = "";
|
||||
static string_t cmdv0 = "";
|
||||
static string_t argv0 = {};
|
||||
static string_t cmdv0 = {};
|
||||
static string_t default_components = "wall_clock";
|
||||
static string_t prefer_library = "";
|
||||
static string_t prefer_library = {};
|
||||
//
|
||||
// global variables
|
||||
//
|
||||
static patch_pointer_t bpatch;
|
||||
static call_expr_t* initialize_expr = nullptr;
|
||||
static call_expr_t* terminate_expr = nullptr;
|
||||
static snippet_vec_t init_names;
|
||||
static snippet_vec_t fini_names;
|
||||
static fmodset_t available_module_functions;
|
||||
static fmodset_t instrumented_module_functions;
|
||||
static regexvec_t func_include;
|
||||
static regexvec_t func_exclude;
|
||||
static regexvec_t file_include;
|
||||
static regexvec_t file_exclude;
|
||||
static patch_pointer_t bpatch = {};
|
||||
static call_expr_t* initialize_expr = nullptr;
|
||||
static call_expr_t* terminate_expr = nullptr;
|
||||
static snippet_vec_t init_names = {};
|
||||
static snippet_vec_t fini_names = {};
|
||||
static fmodset_t available_module_functions = {};
|
||||
static fmodset_t instrumented_module_functions = {};
|
||||
static fmodset_t overlapping_module_functions = {};
|
||||
static regexvec_t func_include = {};
|
||||
static regexvec_t func_exclude = {};
|
||||
static regexvec_t file_include = {};
|
||||
static regexvec_t file_exclude = {};
|
||||
static auto regex_opts = std::regex_constants::egrep | std::regex_constants::optimize;
|
||||
//
|
||||
//======================================================================================//
|
||||
@@ -219,17 +220,6 @@ error_func_real(error_level_t level, int num, const char* const* params);
|
||||
void
|
||||
error_func_fake(error_level_t level, int num, const char* const* params);
|
||||
|
||||
bool
|
||||
find_func_or_calls(std::vector<const char*> names, bpvector_t<point_t*>& points,
|
||||
image_t* appImage, procedure_loc_t loc = BPatch_locEntry);
|
||||
|
||||
bool
|
||||
find_func_or_calls(const char* name, bpvector_t<point_t*>& points, image_t* image,
|
||||
procedure_loc_t loc = BPatch_locEntry);
|
||||
|
||||
bool
|
||||
load_dependent_libraries(address_space_t* bedit, char* bindings);
|
||||
|
||||
bool
|
||||
c_stdlib_module_constraint(const string_t& file);
|
||||
|
||||
@@ -283,10 +273,10 @@ struct function_signature
|
||||
location_t m_row = { 0, 0 };
|
||||
location_t m_col = { 0, 0 };
|
||||
string_t m_return = "void";
|
||||
string_t m_name = "";
|
||||
string_t m_name = {};
|
||||
string_t m_params = "()";
|
||||
string_t m_file = "";
|
||||
mutable string_t m_signature = "";
|
||||
string_t m_file = {};
|
||||
mutable string_t m_signature = {};
|
||||
|
||||
TIMEMORY_DEFAULT_OBJECT(function_signature)
|
||||
|
||||
@@ -360,7 +350,10 @@ struct function_signature
|
||||
//
|
||||
struct module_function
|
||||
{
|
||||
using width_t = std::array<size_t, 3>;
|
||||
using width_t = std::array<size_t, 3>;
|
||||
using address_t = Dyninst::Address;
|
||||
|
||||
static constexpr size_t absolute_max_width = 80;
|
||||
|
||||
static auto& get_width()
|
||||
{
|
||||
@@ -399,6 +392,13 @@ struct module_function
|
||||
module = modname;
|
||||
function = fname;
|
||||
signature = get_func_file_line_info(mod, proc);
|
||||
assert(proc->isInstrumentable() == true);
|
||||
std::pair<address_t, address_t> _range{};
|
||||
if(proc->getAddressRange(_range.first, _range.second))
|
||||
address_range = _range.second - _range.first;
|
||||
auto _instructions = proc->findPoint(BPatch_locInstruction);
|
||||
if(_instructions)
|
||||
instr_count = _instructions->size();
|
||||
}
|
||||
|
||||
friend bool operator<(const module_function& lhs, const module_function& rhs)
|
||||
@@ -410,56 +410,85 @@ struct module_function
|
||||
: (lhs.module < rhs.module);
|
||||
}
|
||||
|
||||
static void write_header(std::ostream& os)
|
||||
{
|
||||
auto w0 = std::min<size_t>(get_width()[0], absolute_max_width);
|
||||
auto w1 = std::min<size_t>(get_width()[1], absolute_max_width);
|
||||
auto w2 = std::min<size_t>(get_width()[2], absolute_max_width);
|
||||
|
||||
std::stringstream ss;
|
||||
ss << std::setw(14) << "AddressRange"
|
||||
<< " " << std::setw(14) << "InstrCount"
|
||||
<< " " << std::setw(w0 + 8) << std::left << "Module"
|
||||
<< " " << std::setw(w1 + 8) << std::left << "Function"
|
||||
<< " " << std::setw(w2 + 8) << std::left << "FunctionSignature"
|
||||
<< "\n";
|
||||
os << ss.str();
|
||||
}
|
||||
|
||||
friend std::ostream& operator<<(std::ostream& os, const module_function& rhs)
|
||||
{
|
||||
std::stringstream ss;
|
||||
|
||||
static size_t absolute_max = 80;
|
||||
auto w0 = std::min<size_t>(get_width()[0], absolute_max);
|
||||
auto w1 = std::min<size_t>(get_width()[1], absolute_max);
|
||||
auto w2 = std::min<size_t>(get_width()[2], absolute_max);
|
||||
auto w0 = std::min<size_t>(get_width()[0], absolute_max_width);
|
||||
auto w1 = std::min<size_t>(get_width()[1], absolute_max_width);
|
||||
auto w2 = std::min<size_t>(get_width()[2], absolute_max_width);
|
||||
|
||||
auto _get_str = [](const std::string& _inc) {
|
||||
if(_inc.length() > absolute_max)
|
||||
return _inc.substr(0, absolute_max - 3) + "...";
|
||||
if(_inc.length() > absolute_max_width)
|
||||
return _inc.substr(0, absolute_max_width - 3) + "...";
|
||||
return _inc;
|
||||
};
|
||||
|
||||
ss << std::setw(w0 + 8) << std::left << _get_str(rhs.module) << " "
|
||||
// clang-format off
|
||||
ss << std::setw(14) << rhs.address_range << " "
|
||||
<< std::setw(14) << rhs.instr_count << " "
|
||||
<< std::setw(w0 + 8) << std::left << _get_str(rhs.module) << " "
|
||||
<< std::setw(w1 + 8) << std::left << _get_str(rhs.function) << " "
|
||||
<< std::setw(w2 + 8) << std::left << _get_str(rhs.signature.get());
|
||||
// clang-format on
|
||||
|
||||
os << ss.str();
|
||||
return os;
|
||||
}
|
||||
|
||||
string_t module = "";
|
||||
string_t function = "";
|
||||
size_t address_range = 0;
|
||||
size_t instr_count = 0;
|
||||
string_t module = {};
|
||||
string_t function = {};
|
||||
function_signature signature;
|
||||
};
|
||||
//
|
||||
//======================================================================================//
|
||||
//
|
||||
static inline void
|
||||
dump_info(const string_t& _oname, const fmodset_t& _data, int level)
|
||||
dump_info(std::ostream& _os, const fmodset_t& _data)
|
||||
{
|
||||
if(!debug_print && verbose_level < level)
|
||||
return;
|
||||
|
||||
module_function::reset_width();
|
||||
for(const auto& itr : _data)
|
||||
module_function::update_width(itr);
|
||||
|
||||
module_function::write_header(_os);
|
||||
for(const auto& itr : _data)
|
||||
_os << itr << '\n';
|
||||
|
||||
module_function::reset_width();
|
||||
}
|
||||
//
|
||||
static inline void
|
||||
dump_info(const string_t& _oname, const fmodset_t& _data, int _level)
|
||||
{
|
||||
if(!debug_print && verbose_level < _level)
|
||||
return;
|
||||
|
||||
std::ofstream ofs(_oname);
|
||||
if(ofs)
|
||||
{
|
||||
verbprintf(level, "Dumping '%s'... ", _oname.c_str());
|
||||
for(const auto& itr : _data)
|
||||
ofs << itr << '\n';
|
||||
verbprintf(level, "Done\n");
|
||||
verbprintf(_level, "Dumping '%s'... ", _oname.c_str());
|
||||
dump_info(ofs, _data);
|
||||
verbprintf(_level, "Done\n");
|
||||
}
|
||||
ofs.close();
|
||||
|
||||
module_function::reset_width();
|
||||
}
|
||||
//
|
||||
//======================================================================================//
|
||||
@@ -554,7 +583,7 @@ private:
|
||||
//
|
||||
static inline address_space_t*
|
||||
hosttrace_get_address_space(patch_pointer_t _bpatch, int _cmdc, char** _cmdv,
|
||||
bool _rewrite, int _pid = -1, string_t _name = "")
|
||||
bool _rewrite, int _pid = -1, string_t _name = {})
|
||||
{
|
||||
address_space_t* mutatee = nullptr;
|
||||
|
||||
|
||||
@@ -0,0 +1,209 @@
|
||||
|
||||
#pragma once
|
||||
|
||||
#if !defined(TIMEMORY_USE_PERFETTO)
|
||||
# include <perfetto.h>
|
||||
# define PERFETTO_CATEGORIES \
|
||||
perfetto::Category("hosttrace").SetDescription("Function trace")
|
||||
#else
|
||||
# define PERFETTO_CATEGORIES \
|
||||
perfetto::Category("hosttrace").SetDescription("Function trace"), \
|
||||
perfetto::Category("timemory") \
|
||||
.SetDescription("Events from the timemory API")
|
||||
# define TIMEMORY_PERFETTO_CATEGORIES PERFETTO_CATEGORIES
|
||||
#endif
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdint>
|
||||
#include <cstdio>
|
||||
#include <fstream>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <string>
|
||||
#include <sys/types.h>
|
||||
#include <thread>
|
||||
#include <unistd.h>
|
||||
#include <utility>
|
||||
#include <vector>
|
||||
|
||||
#include "timemory/api.hpp"
|
||||
#include "timemory/backends/mpi.hpp"
|
||||
#include "timemory/backends/process.hpp"
|
||||
#include "timemory/backends/threading.hpp"
|
||||
#include "timemory/components.hpp"
|
||||
#include "timemory/components/gotcha/mpip.hpp"
|
||||
#include "timemory/config.hpp"
|
||||
#include "timemory/environment.hpp"
|
||||
#include "timemory/manager.hpp"
|
||||
#include "timemory/mpl/apply.hpp"
|
||||
#include "timemory/operations.hpp"
|
||||
#include "timemory/runtime.hpp"
|
||||
#include "timemory/settings.hpp"
|
||||
#include "timemory/storage.hpp"
|
||||
#include "timemory/variadic.hpp"
|
||||
|
||||
// forward decl of the API
|
||||
extern "C"
|
||||
{
|
||||
void hosttrace_push_trace(const char* name) TIMEMORY_VISIBILITY("default");
|
||||
void hosttrace_pop_trace(const char* name) TIMEMORY_VISIBILITY("default");
|
||||
void hosttrace_trace_init(const char*, bool, const char*)
|
||||
TIMEMORY_VISIBILITY("default");
|
||||
void hosttrace_trace_finalize(void) TIMEMORY_VISIBILITY("default");
|
||||
void hosttrace_trace_set_env(const char* env_name, const char* env_val)
|
||||
TIMEMORY_VISIBILITY("default");
|
||||
void hosttrace_trace_set_mpi(bool use, bool attached) TIMEMORY_VISIBILITY("default");
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------//
|
||||
|
||||
// same sort of functionality as python's " ".join([...])
|
||||
#if !defined(JOIN)
|
||||
# define JOIN(...) tim::mpl::apply<std::string>::join(__VA_ARGS__)
|
||||
#endif
|
||||
|
||||
#define HOSTTRACE_DEBUG(...) \
|
||||
if(get_debug()) \
|
||||
{ \
|
||||
fprintf(stderr, __VA_ARGS__); \
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------------------//
|
||||
|
||||
namespace audit = tim::audit;
|
||||
namespace comp = tim::component;
|
||||
namespace quirk = tim::quirk;
|
||||
namespace threading = tim::threading;
|
||||
|
||||
// this is used to wrap fork()
|
||||
struct fork_gotcha : comp::base<fork_gotcha, void>
|
||||
{
|
||||
using gotcha_data_t = comp::gotcha_data;
|
||||
|
||||
TIMEMORY_DEFAULT_OBJECT(fork_gotcha)
|
||||
|
||||
// this will get called right before fork
|
||||
void audit(const gotcha_data_t& _data, audit::incoming);
|
||||
|
||||
// this will get called right after fork with the return value
|
||||
void audit(const gotcha_data_t& _data, audit::outgoing, pid_t _pid);
|
||||
};
|
||||
|
||||
// this is used to wrap MPI_Init and MPI_Init_thread
|
||||
struct mpi_gotcha : comp::base<mpi_gotcha, void>
|
||||
{
|
||||
using gotcha_data_t = comp::gotcha_data;
|
||||
|
||||
TIMEMORY_DEFAULT_OBJECT(mpi_gotcha)
|
||||
|
||||
// this will get called right before MPI_Init with that functions arguments
|
||||
void audit(const gotcha_data_t& _data, audit::incoming, int*, char***);
|
||||
|
||||
// this will get called right before MPI_Init_thread with that functions arguments
|
||||
void audit(const gotcha_data_t& _data, audit::incoming, int*, char***, int, int*);
|
||||
|
||||
// this will get called right after MPI_Init and MPI_Init_thread with the return value
|
||||
void audit(const gotcha_data_t& _data, audit::outgoing, int _retval);
|
||||
};
|
||||
|
||||
// timemory api struct
|
||||
struct hosttrace : tim::concepts::api
|
||||
{};
|
||||
|
||||
// timemory component which calls hosttrace functions
|
||||
// (used in gotcha wrappers)
|
||||
struct hosttrace_component : tim::component::base<hosttrace_component, void>
|
||||
{
|
||||
void start();
|
||||
void stop();
|
||||
void set_prefix(const char*);
|
||||
|
||||
private:
|
||||
const char* m_prefix = nullptr;
|
||||
};
|
||||
|
||||
using fork_gotcha_t = comp::gotcha<4, tim::component_tuple<fork_gotcha>, hosttrace>;
|
||||
using mpi_gotcha_t = comp::gotcha<4, tim::component_tuple<mpi_gotcha>, hosttrace>;
|
||||
using hosttrace_bundle_t =
|
||||
tim::lightweight_tuple<comp::wall_clock, comp::peak_rss, comp::cpu_clock,
|
||||
comp::cpu_util, comp::user_global_bundle, fork_gotcha_t,
|
||||
mpi_gotcha_t>;
|
||||
using bundle_t =
|
||||
tim::component_bundle<hosttrace, comp::wall_clock*, comp::user_global_bundle*>;
|
||||
using bundle_allocator_t = tim::data::ring_buffer_allocator<bundle_t>;
|
||||
|
||||
//--------------------------------------------------------------------------------------//
|
||||
|
||||
#if !defined(TIMEMORY_USE_PERFETTO)
|
||||
PERFETTO_DEFINE_CATEGORIES(PERFETTO_CATEGORIES);
|
||||
#endif
|
||||
|
||||
#if defined(CUSTOM_DATA_SOURCE)
|
||||
class CustomDataSource : public perfetto::DataSource<CustomDataSource>
|
||||
{
|
||||
public:
|
||||
void OnSetup(const SetupArgs&) override
|
||||
{
|
||||
// Use this callback to apply any custom configuration to your data source
|
||||
// based on the TraceConfig in SetupArgs.
|
||||
PRINT_HERE("%s", "setup");
|
||||
}
|
||||
|
||||
void OnStart(const StartArgs&) override
|
||||
{
|
||||
// This notification can be used to initialize the GPU driver, enable
|
||||
// counters, etc. StartArgs will contains the DataSourceDescriptor,
|
||||
// which can be extended.
|
||||
PRINT_HERE("%s", "start");
|
||||
}
|
||||
|
||||
void OnStop(const StopArgs&) override
|
||||
{
|
||||
// Undo any initialization done in OnStart.
|
||||
PRINT_HERE("%s", "stop");
|
||||
}
|
||||
|
||||
// Data sources can also have per-instance state.
|
||||
int my_custom_state = 0;
|
||||
};
|
||||
|
||||
PERFETTO_DECLARE_DATA_SOURCE_STATIC_MEMBERS(CustomDataSource);
|
||||
#endif
|
||||
|
||||
//--------------------------------------------------------------------------------------//
|
||||
|
||||
// used for specifying the state of hosttrace
|
||||
enum class State : unsigned short
|
||||
{
|
||||
DelayedInit = 0,
|
||||
PreInit,
|
||||
Active,
|
||||
Finalized
|
||||
};
|
||||
|
||||
bool
|
||||
get_debug();
|
||||
|
||||
State&
|
||||
get_state();
|
||||
|
||||
std::unique_ptr<hosttrace_bundle_t>&
|
||||
get_main_bundle();
|
||||
|
||||
//--------------------------------------------------------------------------------------//
|
||||
|
||||
// there are currently some strange things that happen with vector<bundle_t> so using
|
||||
// vector<bundle_t*> and timemory's ring_buffer_allocator to create contiguous memory-page
|
||||
// aligned instances of the bundle
|
||||
struct hosttrace_timemory_data
|
||||
{
|
||||
static constexpr size_t max_supported_threads = 1024;
|
||||
using instance_array_t = std::array<hosttrace_timemory_data, max_supported_threads>;
|
||||
|
||||
bundle_allocator_t allocator{};
|
||||
std::vector<bundle_t*> bundles{};
|
||||
|
||||
static instance_array_t& instances();
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------------------------//
|
||||
Reference in New Issue
Block a user