#include #include #include #include #include #include #include #include #include #include #include "timemory/backends/process.hpp" #include "timemory/environment.hpp" #include "timemory/mpl/apply.hpp" #if !defined(JOIN) # define JOIN(...) tim::mpl::apply::join(__VA_ARGS__) #endif PERFETTO_DEFINE_CATEGORIES( perfetto::Category("hosttrace").SetDescription("Function trace")); extern "C" void hosttrace_trace_finalize(); namespace { bool get_debug() { static bool _v = tim::get_env("HOSTTRACE_DEBUG", false); return _v; } auto ensure_finalization() { if(get_debug()) fprintf(stderr, "[%s]\n", __FUNCTION__); return tim::scope::destructor{ []() { hosttrace_trace_finalize(); } }; } auto& get_trace_session() { static std::unique_ptr _session{}; return _session; } enum class State : unsigned short { PreInit = 0, Active, Finalized }; auto& get_state() { static State _v{ State::PreInit }; return _v; } auto& get_output_filename() { static auto _v = []() { auto _tmp = tim::get_env( "HOSTTRACE_OUTPUT_FILE", JOIN('/', tim::get_env("PWD", ".", false), "hosttrace.perfetto-trace-%p")); auto _pos = _tmp.find("%p"); if(_pos != std::string::npos) _tmp.replace(_pos, 2, std::to_string(tim::process::get_id())); return _tmp; }(); return _v; } auto& get_backend() { // select inprocess, system, or both (i.e. all) static auto _v = tim::get_env_choice( "HOSTTRACE_BACKEND", tim::get_env("HOSTTRACE_BACKEND_SYSTEM", false, false) ? "system" // if HOSTTRACE_BACKEND_SYSTEM is true, default to system. : "inprocess", // Otherwise, default to inprocess { "inprocess", "system", "all" }); return _v; } auto is_system_backend() { // if get_backend() returns 'system' or 'all', this is true return (get_backend() != "inprocess"); } bool hosttrace_init_perfetto() { if(get_debug()) fprintf(stderr, "[%s]\n", __FUNCTION__); if(get_state() != State::PreInit) return false; // environment settings auto shmem_size_hint = tim::get_env("HOSTTRACE_SHMEM_SIZE_HINT_KB", 40960); auto buffer_size = tim::get_env("HOSTTRACE_BUFFER_SIZE_KB", 1024000); perfetto::TracingInitArgs args{}; perfetto::TraceConfig cfg{}; perfetto::protos::gen::TrackEventConfig track_event_cfg{}; cfg.add_buffers()->set_size_kb(buffer_size); auto* ds_cfg = cfg.add_data_sources()->mutable_config(); ds_cfg->set_name("track_event"); ds_cfg->set_track_event_config_raw(track_event_cfg.SerializeAsString()); args.shmem_size_hint_kb = shmem_size_hint; if(get_backend() != "inprocess") args.backends |= perfetto::kSystemBackend; if(get_backend() != "system") args.backends |= perfetto::kInProcessBackend; perfetto::Tracing::Initialize(args); perfetto::TrackEvent::Register(); (void) get_output_filename(); tim::print_env(std::cerr); if(!is_system_backend()) { auto& tracing_session = get_trace_session(); tracing_session = perfetto::Tracing::NewTrace(); tracing_session->Setup(cfg); tracing_session->StartBlocking(); } get_state() = State::Active; // if static objects are destroyed in the inverse order of when they are // created this should ensure that finalization is called before perfetto // ends the tracing session static auto _ensure_finalization = ensure_finalization(); return true; } } // namespace extern "C" { void hosttrace_push_trace(const char* name) { if(get_debug()) fprintf(stderr, "[%s] %s\n", __FUNCTION__, name); // return if not active if(get_state() != State::Active && !hosttrace_init_perfetto()) return; TRACE_EVENT_BEGIN("hosttrace", perfetto::StaticString(name)); } void hosttrace_pop_trace(const char* name) { if(get_debug()) fprintf(stderr, "[%s] %s\n", __FUNCTION__, name); // return if not active if(get_state() != State::Active) return; TRACE_EVENT_END("hosttrace"); } void hosttrace_trace_init(const char*, bool, const char*) { if(get_debug()) fprintf(stderr, "[%s]\n", __FUNCTION__); hosttrace_init_perfetto(); } void hosttrace_trace_finalize(void) { if(get_debug()) fprintf(stderr, "[%s]\n", __FUNCTION__); if(get_state() != State::Active) return; get_state() = State::Finalized; if(!is_system_backend()) { // Make sure the last event is closed for this example. perfetto::TrackEvent::Flush(); auto& tracing_session = get_trace_session(); tracing_session->StopBlocking(); std::vector trace_data{ tracing_session->ReadTraceBlocking() }; if(trace_data.empty()) { fprintf(stderr, "[%s]> trace data is empty. File '%s' will not be written...\n", __FUNCTION__, get_output_filename().c_str()); return; } // Write the trace into a file. fprintf(stderr, "[%s]> Outputting '%s'. Trace data: %lu bytes...\n", __FUNCTION__, get_output_filename().c_str(), (unsigned long) trace_data.size()); std::ofstream output{}; output.open(get_output_filename(), std::ios::out | std::ios::binary); if(!output) fprintf(stderr, "[%s]> Error opening '%s'...\n", __FUNCTION__, get_output_filename().c_str()); else output.write(&trace_data[0], trace_data.size()); output.close(); } } void hosttrace_trace_set_env(const char* env_name, const char* env_val) { if(get_debug()) fprintf(stderr, "[%s] Setting env: %s=%s\n", __FUNCTION__, env_name, env_val); tim::set_env(env_name, env_val, 0); } } namespace { // if static objects are destroyed randomly (relatively uncommon behavior) // this might call finalization before perfetto ends the tracing session // but static variable in hosttrace_init_perfetto is more likely auto _ensure_finalization = ensure_finalization(); } // namespace PERFETTO_TRACK_EVENT_STATIC_STORAGE();