From 0449f3b1ebbc57f608c1eb6da29570d2b933adc5 Mon Sep 17 00:00:00 2001 From: kurnevichstanislav Date: Tue, 12 Sep 2023 17:36:14 +0300 Subject: [PATCH] [PT][Sampler] Integrate sampler in inspector * Add DefaultSamplerAgent to load shared library with callbacks * Add SampleSaver to support future possibility of save samples in various ways * Support Profiler.start, stop, setSamplingInterval in InspectorServer * Move logic of set segv status from sampler to thread_sampling_info Signed-off-by: kurnevichstanislav --- runtime/BUILD.gn | 1 + runtime/CMakeLists.txt | 1 + runtime/default_sampler_agent.cpp | 77 +++++++++++++++++++ runtime/default_sampler_agent.h | 43 +++++++++++ runtime/include/panda_vm.h | 12 +++ runtime/include/tooling/debug_interface.h | 7 ++ runtime/include/tooling/profile_interface.h | 45 +++++++++++ runtime/options.yaml | 10 +++ runtime/panda_vm.cpp | 10 +++ runtime/runtime.cpp | 21 +++-- runtime/signal_handler.cpp | 2 +- runtime/tooling/inspector/init.cpp | 36 ++++++++- runtime/tooling/inspector/inspector.cpp | 62 ++++++++++++--- runtime/tooling/inspector/inspector.h | 19 ++++- .../tooling/inspector/inspector_server.cpp | 40 ++++++++++ runtime/tooling/inspector/inspector_server.h | 5 ++ runtime/tooling/pt_hooks_wrapper.h | 12 +++ runtime/tooling/sampler/sample_saver.h | 66 ++++++++++++++++ runtime/tooling/sampler/sampling_profiler.cpp | 16 ++-- runtime/tooling/sampler/sampling_profiler.h | 45 ++++++++--- runtime/tooling/thread_sampling_info.h | 12 ++- runtime/tooling/tools.cpp | 23 ++++-- runtime/tooling/tools.h | 11 ++- 23 files changed, 523 insertions(+), 53 deletions(-) create mode 100644 runtime/default_sampler_agent.cpp create mode 100644 runtime/default_sampler_agent.h create mode 100644 runtime/include/tooling/profile_interface.h create mode 100644 runtime/tooling/sampler/sample_saver.h diff --git a/runtime/BUILD.gn b/runtime/BUILD.gn index a459759d7..8327adb96 100644 --- a/runtime/BUILD.gn +++ b/runtime/BUILD.gn @@ -122,6 +122,7 @@ source_set("libarkruntime_set_static") { "coretypes/array.cpp", "coretypes/string.cpp", "default_debugger_agent.cpp", + "default_sampler_agent.cpp", "deoptimization.cpp", "entrypoints/entrypoints.cpp", "exceptions.cpp", diff --git a/runtime/CMakeLists.txt b/runtime/CMakeLists.txt index d42316017..221036c9a 100644 --- a/runtime/CMakeLists.txt +++ b/runtime/CMakeLists.txt @@ -144,6 +144,7 @@ set(SOURCES relayout_profiler.cpp loadable_agent.cpp default_debugger_agent.cpp + default_sampler_agent.cpp coroutines/coroutine.cpp coroutines/threaded_coroutine.cpp coroutines/stackful_coroutine.cpp diff --git a/runtime/default_sampler_agent.cpp b/runtime/default_sampler_agent.cpp new file mode 100644 index 000000000..75ebf1067 --- /dev/null +++ b/runtime/default_sampler_agent.cpp @@ -0,0 +1,77 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "default_sampler_agent.h" +#include "runtime/include/runtime.h" + +namespace panda { +DefaultSamplerAgent::DefaultSamplerAgent(os::memory::Mutex &mutex) + : LibraryAgent(mutex, PandaString(Runtime::GetOptions().GetSamplerLibraryPath()), "StartSampler", "StopSampler") +{ +} + +bool DefaultSamplerAgent::Load() +{ + sampler_ = Runtime::GetCurrent()->GetTools().GetSamplingProfiler(); + if (!sampler_) { + LOG(ERROR, RUNTIME) << "Sampler was not created"; + return false; + } + + if (!LibraryAgent::Load()) { + std::cerr << "Destroy because cant Load" << std::endl; + return false; + } + + return true; +} + +bool DefaultSamplerAgent::Unload() +{ + return LibraryAgent::Unload(); +} + +bool DefaultSamplerAgent::CallLoadCallback(void *resolved_function) +{ + ASSERT(resolved_function); + ASSERT(sampler_); + + using StartSampler = int (*)(uint32_t); + + uint32_t port = Runtime::GetOptions().GetSamplerPort(); + + int res = reinterpret_cast(resolved_function)(port); + if (res != 0) { + LOG(ERROR, RUNTIME) << "'StartSampler' has failed with " << res; + return false; + } + + return true; +} + +bool DefaultSamplerAgent::CallUnloadCallback(void *resolved_function) +{ + ASSERT(resolved_function); + + using StopSampler = int (*)(); + int res = reinterpret_cast(resolved_function)(); + if (res != 0) { + LOG(ERROR, RUNTIME) << "'StopSampler' has failed with " << res; + return false; + } + + return true; +} +} // namespace panda diff --git a/runtime/default_sampler_agent.h b/runtime/default_sampler_agent.h new file mode 100644 index 000000000..e71faa30b --- /dev/null +++ b/runtime/default_sampler_agent.h @@ -0,0 +1,43 @@ +/* + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PANDA_RUNTIME_DEFAULT_SAMPLER_AGENT_H +#define PANDA_RUNTIME_DEFAULT_SAMPLER_AGENT_H + +#include "runtime/include/loadable_agent.h" +#include "runtime/include/tooling/profile_interface.h" + +namespace panda { + +namespace tooling::sampler { +class Sampler; +} + +class DefaultSamplerAgent : public LibraryAgent, public LibraryAgentLoader { +public: + explicit DefaultSamplerAgent(os::memory::Mutex &mutex); + + bool Load() override; + bool Unload() override; + +private: + bool CallLoadCallback(void *resolved_function) override; + bool CallUnloadCallback(void *resolved_function) override; + + tooling::ProfileInterface *sampler_ {nullptr}; +}; +} // namespace panda + +#endif // PANDA_RUNTIME_DEFAULT_SAMPLER_AGENT_H diff --git a/runtime/include/panda_vm.h b/runtime/include/panda_vm.h index 677eae55d..8920fc4df 100644 --- a/runtime/include/panda_vm.h +++ b/runtime/include/panda_vm.h @@ -210,6 +210,16 @@ public: debugger_agent_.reset(); } + void LoadSamplerAgent() + { + sampler_agent_ = CreateSamplingProfilerAgent(); + } + + void UnloadSamplerAgent() + { + sampler_agent_.reset(); + } + MutatorLock *GetMutatorLock() { return mutator_lock_; @@ -242,12 +252,14 @@ protected: } virtual LoadableAgentHandle CreateDebuggerAgent(); + virtual LoadableAgentHandle CreateSamplingProfilerAgent(); private: /// Lock used for preventing object heap modifications (for example at GC<->JIT,ManagedCode interaction during STW) MutatorLock *mutator_lock_; uint32_t frame_ext_size_ {EMPTY_EXT_FRAME_DATA_SIZE}; LoadableAgentHandle debugger_agent_; + LoadableAgentHandle sampler_agent_; // Intrusive GC test API PandaList mark_queue_ GUARDED_BY(mark_queue_lock_); diff --git a/runtime/include/tooling/debug_interface.h b/runtime/include/tooling/debug_interface.h index faf782472..4f9610a43 100644 --- a/runtime/include/tooling/debug_interface.h +++ b/runtime/include/tooling/debug_interface.h @@ -43,6 +43,10 @@ namespace panda::tooling { class PtLangExt; +namespace sampler { +struct SampleInfo; +} + class Error { public: enum class Type { @@ -217,6 +221,7 @@ enum class PtHookType { PT_HOOK_TYPE_MONITOR_CONTENDED_ENTER, PT_HOOK_TYPE_MONITOR_CONTENDED_ENTERED, PT_HOOK_TYPE_OBJECT_ALLOC, + PT_HOOK_TYPE_SAMPLE_CREATED, // The count of hooks. Don't move PT_HOOK_TYPE_COUNT }; @@ -361,6 +366,8 @@ public: virtual void SingleStep(PtThread /* thread */, Method * /* method */, const PtLocation & /* location */) {} + virtual void SampleCreated(sampler::SampleInfo && /* sample */) {} + // * * * * * // Deprecated hooks // * * * * * diff --git a/runtime/include/tooling/profile_interface.h b/runtime/include/tooling/profile_interface.h new file mode 100644 index 000000000..41d710ddc --- /dev/null +++ b/runtime/include/tooling/profile_interface.h @@ -0,0 +1,45 @@ +/** + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PANDA_TOOLING_PROFILE_INTERFACE_H +#define PANDA_TOOLING_PROFILE_INTERFACE_H + +#include "macros.h" + +namespace panda::tooling { + +class PtHooks; + +class ProfileInterface { +public: + NO_COPY_SEMANTIC(ProfileInterface); + NO_MOVE_SEMANTIC(ProfileInterface); + + ProfileInterface() = default; + virtual ~ProfileInterface() = default; + + PANDA_PUBLIC_API virtual void RegisterHooks(PtHooks *hooks) = 0; + PANDA_PUBLIC_API virtual void UnregisterHooks() = 0; + + PANDA_PUBLIC_API virtual void SetSamplingInterval(uint32_t us) = 0; + PANDA_PUBLIC_API virtual void SetTraceOutfile(const char * /* filename */) {}; + + PANDA_PUBLIC_API virtual void Start() = 0; + PANDA_PUBLIC_API virtual void Stop() = 0; +}; + +} // namespace panda::tooling + +#endif // PANDA_TOOLING_PROFILE_INTERFACE_H diff --git a/runtime/options.yaml b/runtime/options.yaml index b3a6f944b..2ac3bf9cf 100644 --- a/runtime/options.yaml +++ b/runtime/options.yaml @@ -133,6 +133,16 @@ options: default: "" description: Name of file to collect trace in .aspt format +- name: sampler-library-path + type: std::string + default: "" + description: Path to sampler library + +- name: sampler-port + type: uint32_t + default: 19015 + description: Port for serving sampler clients via socket + - name: debugger-port type: uint32_t default: 19015 diff --git a/runtime/panda_vm.cpp b/runtime/panda_vm.cpp index deaba84ad..94744d690 100644 --- a/runtime/panda_vm.cpp +++ b/runtime/panda_vm.cpp @@ -17,6 +17,7 @@ #include "mem/lock_config_helper.h" #include "runtime/default_debugger_agent.h" +#include "runtime/default_sampler_agent.h" #include "runtime/include/runtime.h" #include "runtime/include/runtime_options.h" #include "runtime/include/runtime_notification.h" @@ -136,6 +137,15 @@ LoadableAgentHandle PandaVM::CreateDebuggerAgent() return {}; } +LoadableAgentHandle PandaVM::CreateSamplingProfilerAgent() +{ + if (!Runtime::GetOptions().GetSamplerLibraryPath().empty()) { + return DefaultSamplerAgent::LoadInstance(); + } + + return {}; +} + PandaString PandaVM::GetClassesFootprint() const { ASSERT(GetLanguageContext().GetLanguageType() == LangTypeT::LANG_TYPE_STATIC); diff --git a/runtime/runtime.cpp b/runtime/runtime.cpp index 567b750c5..86aba7a6c 100644 --- a/runtime/runtime.cpp +++ b/runtime/runtime.cpp @@ -366,12 +366,6 @@ bool Runtime::Create(const RuntimeOptions &options) instance_->GetNotificationManager()->VmInitializationEvent(thread); instance_->GetNotificationManager()->ThreadStartEvent(thread); - if (options.IsSamplingProfilerEnable()) { - instance_->GetTools().CreateSamplingProfiler(); - instance_->GetTools().StartSamplingProfiler(options.GetSamplingProfilerOutputFile(), - options.GetSamplingProfilerInterval()); - } - return true; } @@ -422,7 +416,11 @@ bool Runtime::Destroy() trace::ScopedTrace scoped_trace("Runtime shutdown"); if (instance_->GetOptions().IsSamplingProfilerEnable()) { + if (!options_.GetSamplerLibraryPath().empty()) { + instance_->GetPandaVM()->UnloadSamplerAgent(); + } instance_->GetTools().StopSamplingProfiler(); + instance_->GetTools().DestroySamplingProfiler(); } // when signal start, but no signal stop tracing, should stop it @@ -913,6 +911,17 @@ bool Runtime::Initialize() panda_vm_->LoadDebuggerAgent(); } + if (options_.IsSamplingProfilerEnable()) { + instance_->GetTools().CreateSamplingProfiler(); + + if (!options_.GetSamplerLibraryPath().empty()) { + panda_vm_->LoadSamplerAgent(); + } else { + instance_->GetTools().StartSamplingProfiler(options_.GetSamplingProfilerOutputFile(), + options_.GetSamplingProfilerInterval()); + } + } + if (options_.WasSetMemAllocDumpExec()) { StartMemAllocDumper(ConvertToString(options_.GetMemAllocDumpFile())); } diff --git a/runtime/signal_handler.cpp b/runtime/signal_handler.cpp index 4301f9d56..c0dd51568 100644 --- a/runtime/signal_handler.cpp +++ b/runtime/signal_handler.cpp @@ -402,7 +402,7 @@ bool DetectSEGVFromSamplingProfilerHandler([[maybe_unused]] int sig, [[maybe_unu return false; } - if (sampler->IsSegvHandlerEnable() && sampling_info->IsThreadSampling()) { + if (sampling_info->IsSegvHandlerEnable() && sampling_info->IsThreadSampling()) { SignalContext signal_context(context); signal_context.SetPC(reinterpret_cast(&SamplerSigSegvHandler)); return true; diff --git a/runtime/tooling/inspector/init.cpp b/runtime/tooling/inspector/init.cpp index a2d073e40..6bc809226 100644 --- a/runtime/tooling/inspector/init.cpp +++ b/runtime/tooling/inspector/init.cpp @@ -44,7 +44,9 @@ extern "C" int StartDebugger(uint32_t port, bool break_on_start) } G_DEBUG_SESSION = panda::Runtime::GetCurrent()->StartDebugSession(); - G_INSPECTOR.emplace(G_SERVER, G_DEBUG_SESSION->GetDebugger(), break_on_start); + + panda::tooling::inspector::Inspector::InspectableInterfaces interfaces {&(G_DEBUG_SESSION->GetDebugger()), nullptr}; + G_INSPECTOR.emplace(G_SERVER, interfaces, break_on_start); return 0; } @@ -59,3 +61,35 @@ extern "C" int StopDebugger() G_DEBUG_SESSION.reset(); return static_cast(!G_SERVER.Stop()); } + +extern "C" int StartSampler(uint32_t port) +{ + if (G_INSPECTOR) { + LOG(ERROR, DEBUGGER) << "Sampler has already been started"; + return 1; + } + + if (!G_SERVER.Start(port)) { + return 1; + } + + auto *sampler = panda::Runtime::GetCurrent()->GetTools().GetSamplingProfiler(); + + panda::tooling::inspector::Inspector::InspectableInterfaces interfaces {nullptr, sampler}; + G_INSPECTOR.emplace(G_SERVER, interfaces, true); + return 0; +} + +extern "C" int StopSampler() +{ + if (!G_INSPECTOR) { + LOG(ERROR, DEBUGGER) << "Sampler has not been started"; + return 1; + } + + // We need to stop server first to not hang while joining server thread in Inspector destructor + auto ret = G_SERVER.Stop(); + G_INSPECTOR.reset(); + + return static_cast(!ret); +} diff --git a/runtime/tooling/inspector/inspector.cpp b/runtime/tooling/inspector/inspector.cpp index 963ce10e9..b444f126f 100644 --- a/runtime/tooling/inspector/inspector.cpp +++ b/runtime/tooling/inspector/inspector.cpp @@ -33,11 +33,18 @@ using namespace std::placeholders; // NOLINT(google-build-using-namespace) namespace panda::tooling::inspector { -Inspector::Inspector(Server &server, DebugInterface &debugger, bool break_on_start) - : break_on_start_(break_on_start), inspector_server_(server), debugger_(debugger) +Inspector::Inspector(Server &server, const InspectableInterfaces &interfaces, bool break_on_start) + : break_on_start_(break_on_start), + inspector_server_(server), + debugger_(interfaces.debugger), + sampler_(interfaces.sampler) { - if (!HandleError(debugger_.RegisterHooks(this))) { - return; + if (debugger_ != nullptr) { + if (!HandleError(debugger_->RegisterHooks(this))) { + return; + } + } else if (sampler_ != nullptr) { + sampler_->RegisterHooks(this); } inspector_server_.OnValidate([this]() NO_THREAD_SAFETY_ANALYSIS { @@ -82,6 +89,11 @@ Inspector::Inspector(Server &server, DebugInterface &debugger, bool break_on_sta inspector_server_.OnCallRuntimeEnable(std::bind(&Inspector::RuntimeEnable, this, _1)); inspector_server_.OnCallRuntimeGetProperties(std::bind(&Inspector::GetProperties, this, _1, _2, _3)); inspector_server_.OnCallRuntimeRunIfWaitingForDebugger(std::bind(&Inspector::RunIfWaitingForDebugger, this, _1)); + + inspector_server_.OnCallRuntimeGetIsolateId(std::bind(&Inspector::GetIsolateId, this)); + inspector_server_.OnCallProfilerSetSamplingInterval(std::bind(&Inspector::SetSamplingInterval, this, _1)); + inspector_server_.OnCallProfilerStart(std::bind(&Inspector::SamplerStart, this)); + inspector_server_.OnCallProfilerStop(std::bind(&Inspector::SamplerStop, this)); // NOLINTEND(modernize-avoid-bind) server_thread_ = std::thread(&InspectorServer::Run, &inspector_server_); @@ -92,7 +104,14 @@ Inspector::~Inspector() { inspector_server_.Kill(); server_thread_.join(); - HandleError(debugger_.UnregisterHooks()); + + if (debugger_ != nullptr) { + HandleError(debugger_->UnregisterHooks()); + } + + if (sampler_ != nullptr) { + sampler_->UnregisterHooks(); + } } void Inspector::ConsoleCall(PtThread thread, ConsoleCallType type, uint64_t timestamp, @@ -132,7 +151,7 @@ void Inspector::MethodEntry(PtThread thread, Method * /* method */) auto it = threads_.find(thread); ASSERT(it != threads_.end()); if (it->second.OnMethodEntry()) { - HandleError(debugger_.NotifyFramePop(thread, 0)); + HandleError(debugger_->NotifyFramePop(thread, 0)); } } @@ -181,8 +200,8 @@ void Inspector::ThreadStart(PtThread thread) thread, hit_breakpoints, exception_remote_object, [this, thread, &object_repository](auto &handler) { FrameId frame_id = 0; - HandleError(debugger_.EnumerateFrames(thread, [this, &object_repository, &handler, - &frame_id](const PtFrame &frame) { + HandleError(debugger_->EnumerateFrames(thread, [this, &object_repository, &handler, + &frame_id](const PtFrame &frame) { std::string_view source_file; std::string_view method_name; size_t line_number; @@ -296,7 +315,7 @@ void Inspector::StepInto(PtThread thread) { auto it = threads_.find(thread); if (it != threads_.end()) { - auto frame = debugger_.GetCurrentFrame(thread); + auto frame = debugger_->GetCurrentFrame(thread); if (!frame) { HandleError(frame.Error()); return; @@ -310,7 +329,7 @@ void Inspector::StepOver(PtThread thread) { auto it = threads_.find(thread); if (it != threads_.end()) { - auto frame = debugger_.GetCurrentFrame(thread); + auto frame = debugger_->GetCurrentFrame(thread); if (!frame) { HandleError(frame.Error()); return; @@ -324,7 +343,7 @@ void Inspector::StepOut(PtThread thread) { auto it = threads_.find(thread); if (it != threads_.end()) { - HandleError(debugger_.NotifyFramePop(thread, 0)); + HandleError(debugger_->NotifyFramePop(thread, 0)); it->second.StepOut(); } } @@ -341,7 +360,7 @@ void Inspector::RestartFrame(PtThread thread, FrameId frame_id) { auto it = threads_.find(thread); if (it != threads_.end()) { - if (auto error = debugger_.RestartFrame(thread, frame_id)) { + if (auto error = debugger_->RestartFrame(thread, frame_id)) { HandleError(*error); return; } @@ -374,4 +393,23 @@ std::string Inspector::GetSourceCode(std::string_view source_file) { return debug_info_cache_.GetSourceCode(source_file); } + +void Inspector::SetSamplingInterval(uint32_t microseconds) const +{ + Runtime::GetCurrent()->GetTools().GetSamplingProfiler()->SetSamplingInterval(microseconds); +} + +void Inspector::SamplerStart() const +{ + Runtime::GetCurrent()->GetTools().GetSamplingProfiler()->SetTraceOutfile("run_sampler_from_inspector.aspt"); + Runtime::GetCurrent()->GetTools().GetSamplingProfiler()->Start(); +} + +void Inspector::SamplerStop() const +{ + Runtime::GetCurrent()->GetTools().GetSamplingProfiler()->Stop(); +} + +void Inspector::GetIsolateId() const {} + } // namespace panda::tooling::inspector diff --git a/runtime/tooling/inspector/inspector.h b/runtime/tooling/inspector/inspector.h index 52eaa4cf6..dac462f02 100644 --- a/runtime/tooling/inspector/inspector.h +++ b/runtime/tooling/inspector/inspector.h @@ -22,6 +22,8 @@ #include "types/numeric_id.h" #include "tooling/debug_interface.h" +#include "tooling/profile_interface.h" +#include "tooling/sampler/sampling_profiler.h" #include "tooling/inspector/object_repository.h" #include "tooling/inspector/types/pause_on_exceptions_state.h" #include "tooling/inspector/types/property_descriptor.h" @@ -47,7 +49,13 @@ class Server; class Inspector : public PtHooks { public: - Inspector(Server &server, DebugInterface &debugger, bool break_on_start); + struct InspectableInterfaces { + DebugInterface *debugger; + ProfileInterface *sampler; + }; + +public: + Inspector(Server &server, const InspectableInterfaces &interfaces, bool break_on_start); ~Inspector() override; NO_COPY_SEMANTIC(Inspector); @@ -92,6 +100,11 @@ private: std::vector GetProperties(PtThread thread, RemoteObjectId object_id, bool generate_preview); std::string GetSourceCode(std::string_view source_file); + void SetSamplingInterval(uint32_t microseconds) const; + void SamplerStart() const; + void SamplerStop() const; + void GetIsolateId() const; + private: bool break_on_start_; @@ -99,10 +112,12 @@ private: bool connecting_ {false}; // Should be accessed only from the server thread InspectorServer inspector_server_; // NOLINT(misc-non-private-member-variables-in-classes) - DebugInterface &debugger_; + DebugInterface *debugger_ {nullptr}; DebugInfoCache debug_info_cache_; std::map threads_; + ProfileInterface *sampler_ {nullptr}; + std::thread server_thread_; }; } // namespace inspector diff --git a/runtime/tooling/inspector/inspector_server.cpp b/runtime/tooling/inspector/inspector_server.cpp index 0d61d8294..545c4d2dc 100644 --- a/runtime/tooling/inspector/inspector_server.cpp +++ b/runtime/tooling/inspector/inspector_server.cpp @@ -35,6 +35,7 @@ namespace panda::tooling::inspector { InspectorServer::InspectorServer(Server &server) : server_(server) { server_.OnCall("Debugger.enable", [](auto, auto &result, auto &) { result.AddProperty("debuggerId", "debugger"); }); + server_.OnCall("Profiler.enable", [](auto, auto &, auto &) {}); } void InspectorServer::Kill() @@ -561,4 +562,43 @@ void InspectorServer::SendTargetAttachedToTarget(const std::string &session_id) params.AddProperty("waitingForDebugger", true); }); } + +// Isolate id -- unique identifier of virual machine (isolate) +// Without a response to this request, the profiler in google chrome will not allow you to press start +void InspectorServer::OnCallRuntimeGetIsolateId(std::function &&handler) +{ + server_.OnCall("Runtime.getIsolateId", [this, handler = std::move(handler)](auto &, auto &result, auto &) { + result.AddProperty("id", ""); + }); +} + +void InspectorServer::OnCallProfilerSetSamplingInterval(std::function &&handler) +{ + server_.OnCall("Profiler.setSamplingInterval", + [this, handler = std::move(handler)](auto &, auto &, const JsonObject ¶ms) { + uint32_t sampling_interval = 0; + if (auto prop = params.GetValue("interval")) { + sampling_interval = *prop; + } else { + LOG(INFO, PROFILER) << "No 'interval' property"; + return; + } + + handler(sampling_interval); + }); +} + +void InspectorServer::OnCallProfilerStart(std::function &&handler) +{ + server_.OnCall("Profiler.start", [this, handler = std::move(handler)](auto &, auto &, auto &) { handler(); }); +} + +void InspectorServer::OnCallProfilerStop(std::function &&handler) +{ + server_.OnCall("Profiler.stop", [this, handler = std::move(handler)](auto &, auto &result, auto &) { + handler(); + result.AddProperty("profile", ""); + }); +} + } // namespace panda::tooling::inspector diff --git a/runtime/tooling/inspector/inspector_server.h b/runtime/tooling/inspector/inspector_server.h index 845867872..945500c35 100644 --- a/runtime/tooling/inspector/inspector_server.h +++ b/runtime/tooling/inspector/inspector_server.h @@ -91,6 +91,11 @@ public: std::function(PtThread, RemoteObjectId, bool)> &&handler); void OnCallRuntimeRunIfWaitingForDebugger(std::function &&handler); + void OnCallRuntimeGetIsolateId(std::function &&handler); + void OnCallProfilerSetSamplingInterval(std::function &&handler); + void OnCallProfilerStart(std::function &&handler); + void OnCallProfilerStop(std::function &&handler); + private: void SendTargetAttachedToTarget(const std::string &session_id); diff --git a/runtime/tooling/pt_hooks_wrapper.h b/runtime/tooling/pt_hooks_wrapper.h index 18ae06f91..c73da7874 100644 --- a/runtime/tooling/pt_hooks_wrapper.h +++ b/runtime/tooling/pt_hooks_wrapper.h @@ -443,6 +443,18 @@ public: loaded_hooks->ObjectAlloc(klass, object, thread, size); } + void SampleCreated(sampler::SampleInfo &&sample) override + { + // Atomic with acquire order reason: data race with hooks_ + auto *loaded_hooks = hooks_.load(std::memory_order_acquire); + if (loaded_hooks == nullptr || !HookIsEnabled(PtHookType::PT_HOOK_TYPE_SAMPLE_CREATED)) { + return; + } + // Atomic with acquire order reason: data race with vmdeath_did_not_happen_ + ASSERT(vmdeath_did_not_happen_.load(std::memory_order_acquire)); + loaded_hooks->SampleCreated(std::move(sample)); + } + private: bool GlobalHookIsEnabled(PtHookType type) const { diff --git a/runtime/tooling/sampler/sample_saver.h b/runtime/tooling/sampler/sample_saver.h new file mode 100644 index 000000000..6d4c4922f --- /dev/null +++ b/runtime/tooling/sampler/sample_saver.h @@ -0,0 +1,66 @@ +/** + * Copyright (c) 2023 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef PANDA_RUNTIME_TOOLING_SAMPLER_SAMPLE_SAVER_H +#define PANDA_RUNTIME_TOOLING_SAMPLER_SAMPLE_SAVER_H + +#include "runtime/include/tooling/debug_interface.h" +#include "runtime/tooling/sampler/thread_communicator.h" +#include "runtime/tooling/pt_hooks_wrapper.h" + +namespace panda::tooling::sampler { + +class SampleSaver { +public: + enum class SaveFormat { + NONE = -1, + ASPT_FILE_DUMP = 0, + PASS_SAMPLE_IN_HOOK = 1, + }; + +public: + NO_COPY_SEMANTIC(SampleSaver); + NO_MOVE_SEMANTIC(SampleSaver); + + SampleSaver(const ThreadCommunicator &communicator, PtHooksWrapper *hooks, SaveFormat save_format) + : save_format_(save_format), communicator_(communicator), hooks_(hooks) + { + } + + ~SampleSaver() = default; + + void SaveSample(SampleInfo &sample) + { + if (save_format_ == SaveFormat::ASPT_FILE_DUMP) { + communicator_.SendSample(sample); + } else if (save_format_ == SaveFormat::PASS_SAMPLE_IN_HOOK) { + if (hooks_ == nullptr) { + LOG(FATAL, PROFILER) << "Hooks is nullptr, cant send samples"; + } + hooks_->SampleCreated(std::move(sample)); + } + } + +private: + SaveFormat save_format_ {SaveFormat::NONE}; + + const ThreadCommunicator &communicator_; + + PtHooksWrapper *hooks_ {nullptr}; +}; + +} // namespace panda::tooling::sampler + +#endif // PANDA_RUNTIME_TOOLING_SAMPLER_SAMPLE_SAVER_H diff --git a/runtime/tooling/sampler/sampling_profiler.cpp b/runtime/tooling/sampler/sampling_profiler.cpp index 497b90aa0..643193f12 100644 --- a/runtime/tooling/sampler/sampling_profiler.cpp +++ b/runtime/tooling/sampler/sampling_profiler.cpp @@ -152,6 +152,7 @@ void Sampler::EraseThreadHandle(ManagedThread *thread) void Sampler::ThreadStart(ManagedThread *managed_thread) { AddThreadHandle(managed_thread); + hooks_.ThreadStart(PtThread(managed_thread)); } void Sampler::ThreadEnd(ManagedThread *managed_thread) @@ -180,28 +181,26 @@ void Sampler::LoadModule(std::string_view name) runtime_->GetClassLinker()->EnumeratePandaFiles(callback, false); } -bool Sampler::Start(const char *filename) +void Sampler::Start() { if (is_active_) { LOG(ERROR, PROFILER) << "Attemp to start sampling profiler while it's already started"; - return false; + return; } if (UNLIKELY(!communicator_.Init())) { LOG(ERROR, PROFILER) << "Failed to create pipes for sampling listener. Profiler cannot be started"; - return false; + return; } is_active_ = true; // Creating std::string instead of sending pointer to avoid UB stack-use-after-scope - listener_thread_ = std::make_unique(&Sampler::ListenerThreadEntry, this, std::string(filename)); + listener_thread_ = std::make_unique(&Sampler::ListenerThreadEntry, this, aspt_outfile_); listener_tid_ = listener_thread_->native_handle(); // All prepairing actions should be done before this thread is started sampler_thread_ = std::make_unique(&Sampler::SamplerThreadEntry, this); sampler_tid_ = sampler_thread_->native_handle(); - - return true; } void Sampler::Stop() @@ -437,8 +436,7 @@ void SigProfSamplingProfilerHandler([[maybe_unused]] int signum, [[maybe_unused] sample.stack_info.managed_stack_size = stack_counter; sample.thread_info.thread_id = os::thread::GetCurrentThreadId(); - const ThreadCommunicator &communicator = Sampler::GetSampleCommunicator(); - communicator.SendSample(sample); + Sampler::GetSampleSaver().SaveSample(sample); } void Sampler::SamplerThreadEntry() @@ -484,7 +482,7 @@ void Sampler::SamplerThreadEntry() // Sending last sample on finish to avoid of deadlock in listener SampleInfo last_sample; last_sample.stack_info.managed_stack_size = 0; - communicator_.SendSample(last_sample); + Sampler::GetSampleSaver().SaveSample(last_sample); --S_CURRENT_HANDLERS_COUNTER; diff --git a/runtime/tooling/sampler/sampling_profiler.h b/runtime/tooling/sampler/sampling_profiler.h index 8fd66d993..b02ccf19a 100644 --- a/runtime/tooling/sampler/sampling_profiler.h +++ b/runtime/tooling/sampler/sampling_profiler.h @@ -28,6 +28,8 @@ #include "runtime/tooling/sampler/sample_writer.h" #include "runtime/tooling/sampler/thread_communicator.h" #include "runtime/tooling/sampler/lock_free_queue.h" +#include "runtime/tooling/sampler/sample_saver.h" +#include "runtime/include/tooling/profile_interface.h" namespace panda::tooling::sampler { @@ -36,7 +38,7 @@ class SamplerTest; } // namespace test // Panda sampling profiler -class Sampler final : public RuntimeListener { +class Sampler final : public ProfileInterface, RuntimeListener { public: ~Sampler() override = default; @@ -55,6 +57,17 @@ public: return communicator_; } + static SampleSaver &GetSampleSaver() + { + ASSERT(instance_ != nullptr); + return instance_->GetSaver(); + } + + SampleSaver &GetSaver() + { + return sample_saver_; + } + static const LockFreeQueue &GetSampleQueuePF() { ASSERT(instance_ != nullptr); @@ -66,24 +79,30 @@ public: return loaded_pfs_queue_; } - void SetSampleInterval(uint32_t us) + // ProfileInterface methods + PANDA_PUBLIC_API void RegisterHooks(PtHooks *hooks) override { - ASSERT(is_active_ == false); - sample_interval_ = static_cast(us); + hooks_.SetHooks(hooks); + } + + PANDA_PUBLIC_API void UnregisterHooks() override + { + hooks_.SetHooks(nullptr); } - void SetSegvHandlerStatus(bool segv_handler_status) + PANDA_PUBLIC_API void SetSamplingInterval(uint32_t us) override { - is_segv_handler_enable_ = segv_handler_status; + ASSERT(is_active_ == false); + sample_interval_ = static_cast(us); } - bool IsSegvHandlerEnable() const + PANDA_PUBLIC_API void SetTraceOutfile(const char *filename) override { - return is_segv_handler_enable_; + aspt_outfile_ = std::string(filename); } - PANDA_PUBLIC_API bool Start(const char *filename); - PANDA_PUBLIC_API void Stop(); + PANDA_PUBLIC_API void Start() override; + PANDA_PUBLIC_API void Stop() override; // Events: Notify profiler that managed thread created or finished void ThreadStart(ManagedThread *managed_thread) override; @@ -126,10 +145,11 @@ private: os::thread::NativeHandleType sampler_tid_ {0}; std::unique_ptr sampler_thread_ {nullptr}; std::unique_ptr listener_thread_ {nullptr}; + ThreadCommunicator communicator_; + SampleSaver sample_saver_ {communicator_, &hooks_, SampleSaver::SaveFormat::ASPT_FILE_DUMP}; std::atomic is_active_ {false}; - bool is_segv_handler_enable_ {true}; PandaSet managed_threads_ GUARDED_BY(managed_threads_lock_); os::memory::Mutex managed_threads_lock_; @@ -140,6 +160,9 @@ private: os::memory::Mutex loaded_pfs_lock_; std::chrono::microseconds sample_interval_; + std::string aspt_outfile_; + + PtHooksWrapper hooks_; friend class test::SamplerTest; diff --git a/runtime/tooling/thread_sampling_info.h b/runtime/tooling/thread_sampling_info.h index 3a31c79f0..3120f3123 100644 --- a/runtime/tooling/thread_sampling_info.h +++ b/runtime/tooling/thread_sampling_info.h @@ -44,9 +44,19 @@ public: return sigsegv_jmp_env_; } + void SetSegvHandlerStatus(bool segv_handler_status) + { + is_segv_handler_enable_ = segv_handler_status; + } + + bool IsSegvHandlerEnable() const + { + return is_segv_handler_enable_; + } + private: bool is_thread_sampling_ {false}; - + bool is_segv_handler_enable_ {true}; // Environment that saved by setjmp in SIGPROF handler in Sampler // Used for longjmp in case of SIGSEGV during thread sampling jmp_buf sigsegv_jmp_env_ {}; diff --git a/runtime/tooling/tools.cpp b/runtime/tooling/tools.cpp index e97ff666d..0112a0458 100644 --- a/runtime/tooling/tools.cpp +++ b/runtime/tooling/tools.cpp @@ -18,7 +18,7 @@ namespace panda::tooling { -sampler::Sampler *Tools::GetSamplingProfiler() +ProfileInterface *Tools::GetSamplingProfiler() { // Singleton instance return sampler_; @@ -33,32 +33,41 @@ void Tools::CreateSamplingProfiler() if (sampler_segv_option != nullptr) { std::string_view option = sampler_segv_option; if (option == "1" || option == "true" || option == "ON") { + auto sampling_info = ManagedThread::GetCurrent()->GetPtThreadInfo()->GetSamplingInfo(); + ASSERT(sampling_info); // SEGV handler for sampler is enable by default - sampler_->SetSegvHandlerStatus(false); + sampling_info->SetSegvHandlerStatus(false); } } } -bool Tools::StartSamplingProfiler(const std::string &aspt_filename, uint32_t interval) +void Tools::StartSamplingProfiler(const std::string &aspt_filename, uint32_t interval) { ASSERT(sampler_ != nullptr); - sampler_->SetSampleInterval(interval); + sampler_->SetSamplingInterval(interval); if (aspt_filename.empty()) { std::time_t current_time = std::time(nullptr); std::tm *local_time = std::localtime(¤t_time); std::string aspt_filename_time = std::to_string(local_time->tm_hour) + "-" + std::to_string(local_time->tm_min) + "-" + std::to_string(local_time->tm_sec) + ".aspt"; - return sampler_->Start(aspt_filename_time.c_str()); + sampler_->SetTraceOutfile(aspt_filename_time.c_str()); + sampler_->Start(); + return; } - return sampler_->Start(aspt_filename.c_str()); + sampler_->SetTraceOutfile(aspt_filename.c_str()); + sampler_->Start(); } void Tools::StopSamplingProfiler() { ASSERT(sampler_ != nullptr); sampler_->Stop(); - sampler::Sampler::Destroy(sampler_); +} + +void Tools::DestroySamplingProfiler() +{ + sampler::Sampler::Destroy(static_cast(sampler_)); sampler_ = nullptr; } diff --git a/runtime/tooling/tools.h b/runtime/tooling/tools.h index 97f2ec71b..adec55eb6 100644 --- a/runtime/tooling/tools.h +++ b/runtime/tooling/tools.h @@ -20,6 +20,8 @@ namespace panda::tooling { +class ProfileInterface; + namespace sampler { class Sampler; } // namespace sampler @@ -29,16 +31,19 @@ public: Tools() = default; ~Tools() = default; + ProfileInterface *GetSamplingProfiler(); + void CreateSamplingProfiler(); - sampler::Sampler *GetSamplingProfiler(); - bool StartSamplingProfiler(const std::string &aspt_filename, uint32_t interval); + void DestroySamplingProfiler(); + + void StartSamplingProfiler(const std::string &aspt_filename, uint32_t interval); void StopSamplingProfiler(); private: NO_COPY_SEMANTIC(Tools); NO_MOVE_SEMANTIC(Tools); - sampler::Sampler *sampler_ {nullptr}; + ProfileInterface *sampler_ {nullptr}; }; } // namespace panda::tooling -- Gitee