diff --git a/runtime/BUILD.gn b/runtime/BUILD.gn index a459759d79a1db409ae84d2911216bd5ecaac3e6..8327adb966b610bc7f9d05b1d4ede4bcee7d6cb6 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 d42316017ea1517d1ae74df6d664c94abc2aa67d..221036c9aa43bbe17ddc67ce8fc863da440f78bc 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 0000000000000000000000000000000000000000..75ebf1067caa3d7f96850a6546edc7944d314a0c --- /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 0000000000000000000000000000000000000000..e71faa30bf4b69fa16b9f0a7f8e0ca9d89512153 --- /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 677eae55d97ea00e8f7af4ea49f0390789d763fb..8920fc4df6a037a76a0269c05485a85ac74ea0ed 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 faf782472fd7474d60648e42965443b3fbb755e8..4f9610a439b11c5b1f3427c5055d585e5bc1f5c9 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 0000000000000000000000000000000000000000..41d710ddc7a9a9159f93cbdddd9eef465a99bbb4 --- /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 b3a6f944b14898f00b354f7c498cdb51efd61212..2ac3bf9cfe80010d098a5431c73889baa77c52d8 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 deaba84adc82c3e6d1f420f9ed7a1c7d5563b17c..94744d690607910ba2a436f9e40519a127d50e6a 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 567b750c58f4b67e9f18472ee22810d58fcf1e5c..86aba7a6c99dea14666c382b084b0366aa7613cd 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 4301f9d565e5225b9606ad2c9e849a0dae3db8a0..c0dd515689d3167c6224b74597627e9537b7f49b 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 a2d073e40b1516d96cf116bbcfe9105202f76cd4..6bc809226bd74b6d44aeed76c9bdc367504e7af3 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 963ce10e94a95b3728ca3907a8d829a03b30007b..b444f126fe723e4b647825c70bd6b422074d6162 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 52eaa4cf63d6b394f13575677d933fdcba66f41c..dac462f023866f65dea4ef2df0f984a18b88430a 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 0d61d829431648454896de2f5cb9da6c8dbceb0d..545c4d2dcb1a32297bd5b288d187734abe056ec1 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 845867872fcff7b2d37d94152faf4789885e92c9..945500c354ae3b0ea29fe95eee162a956c71ad0f 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 18ae06f91dc9a1110586e1600bed56acb6ee16d4..c73da787436c2eb75db9a8465919c5a6daf881d2 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 0000000000000000000000000000000000000000..6d4c4922f987bbd83bc06f35bc8ca62588b26231 --- /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 497b90aa00267b45b6e66b656e178e3e489c6af8..643193f12f2c507ea5da8ad71893f0c9d8c80716 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 8fd66d993571d13617695cfeec4244e633e46c33..b02ccf19af70d39569907469ae882decfea2125a 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 3a31c79f0bd659db9a5ecb2ed0e79adab1da3e12..3120f312303d875ad43f84d5e7773ce7256bc91a 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 e97ff666d0123648eacd4a63f0e5364a31164ded..0112a0458c84f320d51eaf8eb050552450deacfc 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 97f2ec71bc82008fea8006862f88a8a64e4f00d6..adec55eb63dba7a50fe49ca53357421f7606dcd8 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