From ec61d4d188389b2af59d51b09319b13468ddccec Mon Sep 17 00:00:00 2001 From: David Anderson Date: Tue, 24 Jun 2014 01:04:13 -0700 Subject: [PATCH 1/4] Add a general profiling abstraction layer. --- AMBuildScript | 7 +- core/AMBuilder | 3 +- core/logic/AMBuilder | 2 +- core/logic/ProfileTools.cpp | 144 ++++++++ core/logic/ProfileTools.h | 83 +++++ core/logic/Profiler.cpp | 516 --------------------------- core/logic/Profiler.h | 136 ------- core/logic/common_logic.cpp | 9 +- core/logic/intercom.h | 2 +- core/logic/smn_profiler.cpp | 31 ++ core/logic_bridge.cpp | 1 - core/vprof_tool.cpp | 117 ++++++ core/vprof_tool.h | 66 ++++ plugins/include/profiler.inc | 23 ++ public/sourcepawn/sp_vm_api.h | 125 +++++-- sourcepawn/jit/BaseRuntime.cpp | 32 +- sourcepawn/jit/BaseRuntime.h | 1 + sourcepawn/jit/engine2.cpp | 14 +- sourcepawn/jit/engine2.h | 42 ++- sourcepawn/jit/jit_shared.h | 1 - sourcepawn/jit/sp_vm_basecontext.cpp | 47 +-- sourcepawn/jit/sp_vm_function.cpp | 17 +- sourcepawn/jit/sp_vm_function.h | 10 +- sourcepawn/jit/x86/jit_x86.cpp | 1 - 24 files changed, 664 insertions(+), 766 deletions(-) create mode 100644 core/logic/ProfileTools.cpp create mode 100644 core/logic/ProfileTools.h delete mode 100644 core/logic/Profiler.cpp delete mode 100644 core/logic/Profiler.h create mode 100644 core/vprof_tool.cpp create mode 100644 core/vprof_tool.h diff --git a/AMBuildScript b/AMBuildScript index a481451f..0f705ed0 100644 --- a/AMBuildScript +++ b/AMBuildScript @@ -184,6 +184,11 @@ class SMConfig(object): cxx.cflags += ['-Wno-narrowing'] if (have_gcc and cxx.version >= '4.7') or (have_clang and cxx.version >= '3'): cxx.cxxflags += ['-Wno-delete-non-virtual-dtor'] + if have_clang: + cxx.cxxflags += [ + '-Wno-implicit-exception-spec-mismatch', + '-Wno-deprecated-register', + ] cxx.linkflags += ['-m32'] cxx.cxxflags += [ @@ -191,8 +196,6 @@ class SMConfig(object): '-fno-threadsafe-statics', '-Wno-non-virtual-dtor', '-Wno-overloaded-virtual', - '-Wno-implicit-exception-spec-mismatch', - '-Wno-deprecated-register', ] if have_gcc: diff --git a/core/AMBuilder b/core/AMBuilder index 08a96d88..7eb37f84 100644 --- a/core/AMBuilder +++ b/core/AMBuilder @@ -41,7 +41,8 @@ project.sources += [ 'MenuStyle_Radio.cpp', 'sm_autonatives.cpp', 'sm_srvcmds.cpp', - 'ConsoleDetours.cpp' + 'ConsoleDetours.cpp', + 'vprof_tool.cpp', ] for sdk_name in SM.sdks: diff --git a/core/logic/AMBuilder b/core/logic/AMBuilder index 09096255..b38a0965 100644 --- a/core/logic/AMBuilder +++ b/core/logic/AMBuilder @@ -33,7 +33,6 @@ binary.sources += [ 'TextParsers.cpp', 'smn_textparse.cpp', 'smn_adt_trie.cpp', - 'Profiler.cpp', 'smn_functions.cpp', 'smn_timers.cpp', 'smn_players.cpp', @@ -66,6 +65,7 @@ binary.sources += [ 'AdminCache.cpp', 'sm_trie.cpp', 'smn_console.cpp', + 'ProfileTools.cpp', ] if builder.target_platform == 'windows': binary.sources += ['thread/WinThreads.cpp'] diff --git a/core/logic/ProfileTools.cpp b/core/logic/ProfileTools.cpp new file mode 100644 index 00000000..5cf9e5af --- /dev/null +++ b/core/logic/ProfileTools.cpp @@ -0,0 +1,144 @@ +// vim: set ts=4 sw=4 tw=99 noet : +// ============================================================================= +// SourceMod +// Copyright (C) 2004-2014 AlliedModders LLC. All rights reserved. +// ============================================================================= +// +// This program is free software; you can redistribute it and/or modify it under +// the terms of the GNU General Public License, version 3.0, as published by the +// Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +// details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . +// +// As a special exception, AlliedModders LLC gives you permission to link the +// code of this program (as well as its derivative works) to "Half-Life 2," the +// "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software +// by the Valve Corporation. You must obey the GNU General Public License in +// all respects for all other code used. Additionally, AlliedModders LLC grants +// this exception to all derivative works. AlliedModders LLC defines further +// exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), +// or . + +#include "ProfileTools.h" +#include + +ProfileToolManager g_ProfileToolManager; + +ProfileToolManager::ProfileToolManager() + : active_(nullptr) +{ +} + +void +ProfileToolManager::OnSourceModAllInitialized() +{ + rootmenu->AddRootConsoleCommand2("prof", "Profiling", this); +} + +void +ProfileToolManager::OnSourceModShutdown() +{ + rootmenu->RemoveRootConsoleCommand("prof", this); +} + +IProfilingTool * +ProfileToolManager::FindToolByName(const char *name) +{ + for (size_t i = 0; i < tools_.length(); i++) { + if (strcmp(tools_[i]->Name(), name) == 0) + return tools_[i]; + } + return nullptr; +} + +static void +render_help(const char *fmt, ...) +{ + char buffer[2048]; + + va_list ap; + va_start(ap, fmt); + smcore.FormatArgs(buffer, sizeof(buffer), fmt, ap); + va_end(ap); + + rootmenu->ConsolePrint("%s", buffer); +} + +void +ProfileToolManager::OnRootConsoleCommand2(const char *cmdname, const ICommandArgs *args) +{ + if (tools_.length() == 0) { + rootmenu->ConsolePrint("No profiling tools are enabled."); + return; + } + + if (args->ArgC() >= 3) { + cmdname = args->Arg(2); + + if (strcmp(cmdname, "list") == 0) { + rootmenu->ConsolePrint("Profiling tools:"); + for (size_t i = 0; i < tools_.length(); i++) { + rootmenu->DrawGenericOption(tools_[i]->Name(), tools_[i]->Description()); + } + return; + } + if (strcmp(cmdname, "stop") == 0) { + if (!active_) { + rootmenu->ConsolePrint("No profiler is active."); + return; + } + g_pSourcePawn2->DisableProfiling(); + g_pSourcePawn2->SetProfilingTool(nullptr); + active_->Stop(); + active_->RenderHelp(render_help); + return; + } + + if (args->ArgC() < 4) { + rootmenu->ConsolePrint("You must specify a profiling tool name."); + return; + } + + const char *toolname = args->Arg(3); + if (strcmp(cmdname, "start") == 0) { + if (active_) { + rootmenu->ConsolePrint("A profile is already active using %s.", active_->Name()); + return; + } + if ((active_ = FindToolByName(toolname)) == nullptr) { + rootmenu->ConsolePrint("No tool with the name \"%s\" was found.", toolname); + return; + } + if (!active_->Start()) { + rootmenu->ConsolePrint("Failed to attach to or start %s.", active_->Name()); + active_ = nullptr; + return; + } + g_pSourcePawn2->SetProfilingTool(active_); + g_pSourcePawn2->EnableProfiling(); + rootmenu->ConsolePrint("Started profiling with %s.", active_->Name()); + return; + } + if (strcmp(cmdname, "help") == 0) { + IProfilingTool *tool = FindToolByName(toolname); + if (!tool) { + rootmenu->ConsolePrint("No tool with the name \"%s\" was found.", toolname); + return; + } + tool->RenderHelp(render_help); + return; + } + } + + rootmenu->ConsolePrint("Profiling commands:"); + rootmenu->DrawGenericOption("list", "List all available profiling tools."); + rootmenu->DrawGenericOption("start", "Start a profile with a given tool."); + rootmenu->DrawGenericOption("stop", "Stop the current profile session."); + rootmenu->DrawGenericOption("help", "Display help text for a profiler."); +} diff --git a/core/logic/ProfileTools.h b/core/logic/ProfileTools.h new file mode 100644 index 00000000..2a760809 --- /dev/null +++ b/core/logic/ProfileTools.h @@ -0,0 +1,83 @@ +// vim: set ts=4 sw=4 tw=99 noet : +// ============================================================================= +// SourceMod +// Copyright (C) 2004-2014 AlliedModders LLC. All rights reserved. +// ============================================================================= +// +// This program is free software; you can redistribute it and/or modify it under +// the terms of the GNU General Public License, version 3.0, as published by the +// Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +// details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . +// +// As a special exception, AlliedModders LLC gives you permission to link the +// code of this program (as well as its derivative works) to "Half-Life 2," the +// "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software +// by the Valve Corporation. You must obey the GNU General Public License in +// all respects for all other code used. Additionally, AlliedModders LLC grants +// this exception to all derivative works. AlliedModders LLC defines further +// exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), +// or . + +#ifndef _include_sourcemod_logic_profile_tool_manager_h_ +#define _include_sourcemod_logic_profile_tool_manager_h_ + +#include +#include +#include +#include +#include "common_logic.h" + +using namespace SourcePawn; + +class ProfileToolManager + : public SMGlobalClass, + public IRootConsoleCommand +{ +public: + ProfileToolManager(); + + // SMGlobalClass + void OnSourceModAllInitialized() KE_OVERRIDE; + void OnSourceModShutdown() KE_OVERRIDE; + + // IRootConsoleCommand + void OnRootConsoleCommand2(const char *cmdname, const ICommandArgs *args) KE_OVERRIDE; + + void RegisterTool(IProfilingTool *tool) { + tools_.append(tool); + } + + bool IsActive() const { + return !!active_; + } + + // If we get problems with plugins not being able to balance the profile + // scopes, we should add a safety net that automatically clears any pending + // scopes. + void EnterScope(const char *group, const char *name) { + if (active_) + active_->EnterScope(group, name); + } + void LeaveScope() { + if (active_) + active_->LeaveScope(); + } + + IProfilingTool *FindToolByName(const char *name); + +private: + ke::Vector tools_; + IProfilingTool *active_; + bool enabled_; +}; + +extern ProfileToolManager g_ProfileToolManager; + +#endif // _include_sourcemod_logic_profile_tool_manager_h_ diff --git a/core/logic/Profiler.cpp b/core/logic/Profiler.cpp deleted file mode 100644 index e377010d..00000000 --- a/core/logic/Profiler.cpp +++ /dev/null @@ -1,516 +0,0 @@ -/** - * vim: set ts=4 sw=4 tw=99 noet : - * ============================================================================= - * SourceMod - * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. - * ============================================================================= - * - * This program is free software; you can redistribute it and/or modify it under - * the terms of the GNU General Public License, version 3.0, as published by the - * Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License along with - * this program. If not, see . - * - * As a special exception, AlliedModders LLC gives you permission to link the - * code of this program (as well as its derivative works) to "Half-Life 2," the - * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software - * by the Valve Corporation. You must obey the GNU General Public License in - * all respects for all other code used. Additionally, AlliedModders LLC grants - * this exception to all derivative works. AlliedModders LLC defines further - * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), - * or . - * - * Version: $Id$ - */ - -#include "Profiler.h" -#include -#if defined PLATFORM_POSIX -#include -#include -#endif -#include -#include "stringutil.h" - -ProfileEngine g_Profiler; -IProfiler *sm_profiler = &g_Profiler; - -#if defined PLATFORM_WINDOWS -double WINDOWS_PERFORMANCE_FREQUENCY; -#endif - -class EmptyProfiler : public IProfiler -{ -public: - void OnNativeBegin(IPluginContext *pContext, sp_native_t *native) - { - } - void OnNativeEnd() - { - } - void OnFunctionBegin(IPluginContext *pContext, const char *name) - { - } - void OnFunctionEnd() - { - } - int OnCallbackBegin(IPluginContext *pContext, sp_public_t *pubfunc) - { - return 0; - } - void OnCallbackEnd(int serial) - { - } -} s_EmptyProfiler; - -inline void InitProfPoint(prof_point_t &pt) -{ -#if defined PLATFORM_WINDOWS - QueryPerformanceCounter(&pt.value); -#elif defined PLATFORM_POSIX - gettimeofday(&pt.value, NULL); -#endif - pt.is_set = true; -} - -ProfileEngine::ProfileEngine() -{ - m_serial = 0; - -#if defined PLATFORM_WINDOWS - LARGE_INTEGER pf; - - if (QueryPerformanceFrequency(&pf)) - { - WINDOWS_PERFORMANCE_FREQUENCY = 1.0 / (double)(pf.QuadPart); - } - else - { - WINDOWS_PERFORMANCE_FREQUENCY = -1.0; - } -#endif - - if (IsEnabled()) - { - InitProfPoint(m_ProfStart); - } - else - { - sm_profiler = &s_EmptyProfiler; - } -} - -bool ProfileEngine::IsEnabled() -{ -#if defined PLATFORM_WINDOWS - return (WINDOWS_PERFORMANCE_FREQUENCY > 0.0); -#elif defined PLATFORM_POSIX - return true; -#endif -} - -inline double DiffProfPoints(const prof_point_t &start, const prof_point_t &end) -{ - double seconds; - -#if defined PLATFORM_WINDOWS - LONGLONG diff; - - diff = end.value.QuadPart - start.value.QuadPart; - seconds = diff * WINDOWS_PERFORMANCE_FREQUENCY; -#elif defined PLATFORM_POSIX - seconds = (double)(end.value.tv_sec - start.value.tv_sec); - - if (start.value.tv_usec > end.value.tv_usec) - { - seconds -= 1.0; - seconds += (double)(1000000 - (start.value.tv_usec - end.value.tv_usec)) / 1000000.0; - } - else - { - seconds += (double)(end.value.tv_usec - start.value.tv_usec) / 1000000.0; - } -#endif - - return seconds; -} - -inline double CalcAtomTime(const prof_atom_t &atom) -{ - if (!atom.end.is_set) - { - return atom.base_time; - } - - return atom.base_time + DiffProfPoints(atom.start, atom.end); -} - -void ProfileEngine::OnNativeBegin(IPluginContext *pContext, sp_native_t *native) -{ - PushProfileStack(pContext, SP_PROF_NATIVES, native->name); -} - -void ProfileEngine::OnNativeEnd() -{ - assert(!m_AtomStack.empty()); - assert(m_AtomStack.front().atom_type == SP_PROF_NATIVES); - - PopProfileStack(&m_Natives); -} - -void ProfileEngine::OnFunctionBegin(IPluginContext *pContext, const char *name) -{ - PushProfileStack(pContext, SP_PROF_FUNCTIONS, name); -} - -void ProfileEngine::OnFunctionEnd() -{ - assert(!m_AtomStack.empty()); - assert(m_AtomStack.front().atom_type == SP_PROF_FUNCTIONS); - - PopProfileStack(&m_Functions); -} - -int ProfileEngine::OnCallbackBegin(IPluginContext *pContext, sp_public_t *pubfunc) -{ - PushProfileStack(pContext, SP_PROF_CALLBACKS, pubfunc->name); - - return m_serial; -} - -void ProfileEngine::OnCallbackEnd(int serial) -{ - assert(!m_AtomStack.empty()); - - /** - * Account for the situation where the JIT discards the - * stack because there was an RTE of sorts. - */ - if (m_AtomStack.front().atom_type != SP_PROF_CALLBACKS - && m_AtomStack.front().atom_serial != serial) - { - prof_atom_t atom; - double total_time; - - /* There was an error, and we need to discard things. */ - total_time = 0.0; - while (!m_AtomStack.empty() - && m_AtomStack.front().atom_type != SP_PROF_CALLBACKS - && m_AtomStack.front().atom_serial != serial) - { - total_time += CalcAtomTime(m_AtomStack.front()); - m_AtomStack.pop(); - } - - /** - * Now we can end and discard ourselves, without saving the data. - * Since this data is all erroneous anyway, we don't care if it's - * not totally accurate. - */ - - assert(!m_AtomStack.empty()); - atom = m_AtomStack.front(); - m_AtomStack.pop(); - - /* Note: We don't need to resume ourselves because end is set by Pause(). */ - total_time += CalcAtomTime(atom); - - ResumeParent(total_time); - return; - } - - PopProfileStack(&m_Callbacks); -} - -void ProfileEngine::PushProfileStack(IPluginContext *ctx, int type, const char *name) -{ - prof_atom_t atom; - - PauseParent(); - - atom.atom_type = type; - atom.base_time = 0.0; - atom.ctx = ctx->GetContext(); - atom.name = name; - atom.end.is_set = false; - - if (type == SP_PROF_CALLBACKS) - { - atom.atom_serial = ++m_serial; - } - else - { - atom.atom_serial = 0; - } - - m_AtomStack.push(atom); - - /* Note: We do this after because the stack could grow and skew results */ - InitProfPoint(m_AtomStack.front().start); -} - -void ProfileEngine::PopProfileStack(ProfileReport *reporter) -{ - double total_time; - - prof_atom_t &atom = m_AtomStack.front(); - - /* We're okay to cache our used time. */ - InitProfPoint(atom.end); - total_time = CalcAtomTime(atom); - - /* Now it's time to save this! This may do a lot of computations which - * is why we've cached the time beforehand. - */ - reporter->SaveAtom(atom); - m_AtomStack.pop(); - - /* Finally, tell our parent how much time we used. */ - ResumeParent(total_time); -} - -void ProfileEngine::PauseParent() -{ - if (m_AtomStack.empty()) - { - return; - } - - InitProfPoint(m_AtomStack.front().end); -} - -void ProfileEngine::ResumeParent(double addTime) -{ - if (m_AtomStack.empty()) - { - return; - } - - prof_atom_t &atom = m_AtomStack.front(); - - /* Move its "paused time" to its base (known) time, - * then reset the start/end. Note that since CalcAtomTime() - * reads the base time, we SHOULD NOT use += to add. - */ - atom.base_time = CalcAtomTime(atom); - atom.base_time += addTime; - InitProfPoint(atom.start); - atom.end.is_set = false; -} - -void ProfileEngine::Clear() -{ - m_Natives.Clear(); - m_Callbacks.Clear(); - m_Functions.Clear(); - InitProfPoint(m_ProfStart); -} - -void ProfileEngine::OnSourceModAllInitialized() -{ - rootmenu->AddRootConsoleCommand2("profiler", "Profiler commands", this); -} - -void ProfileEngine::OnSourceModShutdown() -{ - rootmenu->RemoveRootConsoleCommand("profiler", this); -} - -void ProfileEngine::OnRootConsoleCommand2(const char *cmdname, const ICommandArgs *command) -{ - if (command->ArgC() >= 3) - { - if (strcmp(command->Arg(2), "flush") == 0) - { - FILE *fp; - char path[256]; - - g_pSM->BuildPath(Path_SM, path, sizeof(path), "logs/profile_%d.xml", (int)time(NULL)); - - if ((fp = fopen(path, "wt")) == NULL) - { - rootmenu->ConsolePrint("Failed, could not open file for writing: %s", path); - return; - } - - GenerateReport(fp); - - fclose(fp); - - Clear(); - - rootmenu->ConsolePrint("Profiler report generated as: %s\n", path); - - return; - } - else if (strcmp(command->Arg(2), "report") == 0) - { - FILE *fp; - char path[256]; - - g_pSM->BuildPath(Path_SM, path, sizeof(path), "logs/profile_%d.xml", (int)time(NULL)); - - if ((fp = fopen(path, "wt")) == NULL) - { - rootmenu->ConsolePrint("Failed, could not open file for writing: %s", path); - return; - } - - GenerateReport(fp); - - fclose(fp); - - rootmenu->ConsolePrint("Profiler report generated as: %s\n", path); - return; - } - else if (strcmp(command->Arg(2), "clear") == 0) - { - Clear(); - - rootmenu->ConsolePrint("Profiler statistics cleared.\n"); - return; - } - } - - rootmenu->ConsolePrint("Profiler commands:"); - rootmenu->DrawGenericOption("flush", "Flushes statistics to disk and starts over"); - rootmenu->DrawGenericOption("report", "Flushes statistics to disk"); - rootmenu->DrawGenericOption("clear", "Clears statistics"); -} - -bool ProfileEngine::GenerateReport(FILE *fp) -{ - time_t t; - double total_time; - prof_point_t end_time; - - InitProfPoint(end_time); - total_time = DiffProfPoints(m_ProfStart, end_time); - - t = time(NULL); - - fprintf(fp, "\n\n"); - fprintf(fp, "\n", (int)t, total_time); - WriteReport(fp, &m_Natives, "natives"); - WriteReport(fp, &m_Callbacks, "callbacks"); - WriteReport(fp, &m_Functions, "functions"); - fprintf(fp, "\n"); - - return true; -} - -void ProfileEngine::WriteReport(FILE *fp, ProfileReport *report, const char *name) -{ - size_t i, num; - prof_atom_report_t *ar; - char new_name[512]; - - fprintf(fp, " \n", name); - - num = report->GetNumReports(); - for (i = 0; i < num; i++) - { - ar = report->GetReport(i); - - strncopy(new_name, ar->atom_name, sizeof(new_name)); - UTIL_ReplaceAll(new_name, sizeof(new_name), "<", "<", true); - UTIL_ReplaceAll(new_name, sizeof(new_name), ">", ">", true); - - fprintf(fp, " \n", - new_name, - ar->num_calls, - ar->min_time, - ar->max_time, - ar->total_time); - } - - fprintf(fp, " \n"); -} - -ProfileReport::~ProfileReport() -{ - for (size_t i = 0; i < m_Reports.size(); i++) - { - delete m_Reports[i]; - } -} - -void ProfileReport::Clear() -{ - m_ReportLookup.clear(); - for (size_t i = 0; i < m_Reports.size(); i++) - { - delete m_Reports[i]; - } - m_Reports.clear(); -} - -size_t ProfileReport::GetNumReports() -{ - return m_Reports.size(); -} - -prof_atom_report_t *ProfileReport::GetReport(size_t i) -{ - return m_Reports[i]; -} - -void ProfileReport::SaveAtom(const prof_atom_t &atom) -{ - double atom_time; - char full_name[256]; - prof_atom_report_t *report; - - if (atom.atom_type == SP_PROF_NATIVES) - { - smcore.strncopy(full_name, atom.name, sizeof(full_name)); - } - else - { - IPlugin *pl; - const char *file; - - file = "unknown"; - if ((pl = pluginsys->FindPluginByContext(atom.ctx)) != NULL) - { - file = pl->GetFilename(); - } - - smcore.Format(full_name, sizeof(full_name), "%s!%s", file, atom.name); - } - - atom_time = CalcAtomTime(atom); - - if (!m_ReportLookup.retrieve(full_name, &report)) - { - report = new prof_atom_report_t; - - smcore.strncopy(report->atom_name, full_name, sizeof(report->atom_name)); - report->max_time = atom_time; - report->min_time = atom_time; - report->num_calls = 1; - report->total_time = atom_time; - - m_ReportLookup.insert(full_name, report); - m_Reports.push_back(report); - } - else - { - if (atom_time > report->max_time) - { - report->max_time = atom_time; - } - if (atom_time < report->min_time) - { - report->min_time = atom_time; - } - report->num_calls++; - report->total_time += atom_time; - } -} diff --git a/core/logic/Profiler.h b/core/logic/Profiler.h deleted file mode 100644 index a3317f69..00000000 --- a/core/logic/Profiler.h +++ /dev/null @@ -1,136 +0,0 @@ -/** - * vim: set ts=4 sw=4 tw=99 noet : - * ============================================================================= - * SourceMod - * Copyright (C) 2004-2008 AlliedModders LLC. All rights reserved. - * ============================================================================= - * - * This program is free software; you can redistribute it and/or modify it under - * the terms of the GNU General Public License, version 3.0, as published by the - * Free Software Foundation. - * - * This program is distributed in the hope that it will be useful, but WITHOUT - * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS - * FOR A PARTICULAR PURPOSE. See the GNU General Public License for more - * details. - * - * You should have received a copy of the GNU General Public License along with - * this program. If not, see . - * - * As a special exception, AlliedModders LLC gives you permission to link the - * code of this program (as well as its derivative works) to "Half-Life 2," the - * "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software - * by the Valve Corporation. You must obey the GNU General Public License in - * all respects for all other code used. Additionally, AlliedModders LLC grants - * this exception to all derivative works. AlliedModders LLC defines further - * exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), - * or . - * - * Version: $Id$ - */ - -#ifndef _INCLUDE_SOURCEMOD_PLUGIN_PROFILER_H_ -#define _INCLUDE_SOURCEMOD_PLUGIN_PROFILER_H_ - -#include -#include -#include -#include -#include -#include -#include "common_logic.h" -#include - -using namespace SourcePawn; -using namespace SourceHook; - -struct prof_point_t -{ -#if defined PLATFORM_WINDOWS - LARGE_INTEGER value; -#elif defined PLATFORM_POSIX - struct timeval value; -#endif - bool is_set; -}; - -struct prof_atom_t -{ - int atom_type; /* Type of object we're profiling */ - int atom_serial; /* Serial number, if appropriate */ - sp_context_t *ctx; /* Plugin context. */ - const char *name; /* Name of the function */ - prof_point_t start; /* Start time */ - prof_point_t end; /* End time */ - double base_time; /* Known time from children or pausing. */ -}; - -struct prof_atom_report_t -{ - char atom_name[256]; /* Full name to shove to logs */ - double total_time; /* Total time spent executing, in s */ - unsigned int num_calls; /* Number of invocations */ - double min_time; /* Min time spent in one call, in s */ - double max_time; /* Max time spent in one call, in s */ - - static inline bool matches(const char *name, const prof_atom_report_t *report) - { - return strcmp(report->atom_name, name) == 0; - } -}; - -class ProfileReport -{ -public: - ~ProfileReport(); -public: - void SaveAtom(const prof_atom_t &atom); - size_t GetNumReports(); - prof_atom_report_t *GetReport(size_t i); - void Clear(); -private: - NameHashSet m_ReportLookup; - CVector m_Reports; -}; - -class ProfileEngine : - public SMGlobalClass, - public IRootConsoleCommand, - public IProfiler -{ -public: - ProfileEngine(); -public: - bool IsEnabled(); - bool GenerateReport(FILE *fp); - void Clear(); -public: //SMGlobalClass - void OnSourceModAllInitialized(); - void OnSourceModShutdown(); -public: //IRootConsoleCommand - void OnRootConsoleCommand2(const char *cmdname, const ICommandArgs *command); -public: //IProfiler - void OnNativeBegin(IPluginContext *pContext, sp_native_t *native); - void OnNativeEnd() ; - void OnFunctionBegin(IPluginContext *pContext, const char *name); - void OnFunctionEnd(); - int OnCallbackBegin(IPluginContext *pContext, sp_public_t *pubfunc); - void OnCallbackEnd(int serial); -private: - void PushProfileStack(IPluginContext *ctx, int type, const char *name); - void PopProfileStack(ProfileReport *reporter); - void PauseParent(); - void ResumeParent(double addTime); - void WriteReport(FILE *fp, ProfileReport *report, const char *name); -private: - CStack m_AtomStack; - ProfileReport m_Callbacks; - ProfileReport m_Functions; - ProfileReport m_Natives; - int m_serial; - prof_point_t m_ProfStart; -}; - -extern IProfiler *sm_profiler; - -#endif //_INCLUDE_SOURCEMOD_PLUGIN_PROFILER_H_ diff --git a/core/logic/common_logic.cpp b/core/logic/common_logic.cpp index 32816078..7c988b5f 100644 --- a/core/logic/common_logic.cpp +++ b/core/logic/common_logic.cpp @@ -35,7 +35,6 @@ #include #include "common_logic.h" #include "TextParsers.h" -#include "Profiler.h" #include "sm_crc32.h" #include "MemoryUtils.h" #include "stringutil.h" @@ -50,6 +49,7 @@ #include "ExtensionSys.h" #include "ForwardSys.h" #include "AdminCache.h" +#include "ProfileTools.h" sm_core_t smcore; IHandleSys *handlesys = &g_HandleSys; @@ -109,11 +109,15 @@ static void DumpAdminCache(FILE *f) g_Admins.DumpCache(f); } +static void RegisterProfiler(IProfilingTool *tool) +{ + g_ProfileToolManager.RegisterTool(tool); +} + static sm_logic_t logic = { NULL, g_pThreader, - sm_profiler, &g_Translator, stristr, CoreTranslate, @@ -128,6 +132,7 @@ static sm_logic_t logic = AddNatives, DumpHandles, DumpAdminCache, + RegisterProfiler, &g_PluginSys, &g_ShareSys, &g_Extensions, diff --git a/core/logic/intercom.h b/core/logic/intercom.h index 9800a3a7..5d86853d 100644 --- a/core/logic/intercom.h +++ b/core/logic/intercom.h @@ -317,7 +317,6 @@ struct sm_logic_t { SMGlobalClass *head; IThreader *threader; - IProfiler *profiler; ITranslator *translator; const char *(*stristr)(const char *, const char *); bool (*CoreTranslate)(char *, size_t, const char *, unsigned int, size_t *, ...); @@ -332,6 +331,7 @@ struct sm_logic_t void (*AddNatives)(sp_nativeinfo_t *natives); void (*DumpHandles)(void (*dumpfn)(const char *fmt, ...)); void (*DumpAdminCache)(FILE *); + void (*RegisterProfiler)(IProfilingTool *tool); IScriptManager *scripts; IShareSys *sharesys; IExtensionSys *extsys; diff --git a/core/logic/smn_profiler.cpp b/core/logic/smn_profiler.cpp index 7d3df0f2..31cf646e 100644 --- a/core/logic/smn_profiler.cpp +++ b/core/logic/smn_profiler.cpp @@ -36,6 +36,7 @@ #include #include #endif +#include "ProfileTools.h" struct Profiler { @@ -185,12 +186,42 @@ static cell_t GetProfilerTime(IPluginContext *pContext, const cell_t *params) return sp_ftoc(fTime); } +static cell_t EnterProfilingEvent(IPluginContext *pContext, const cell_t *params) +{ + char *group; + pContext->LocalToString(params[1], &group); + + char *name; + pContext->LocalToString(params[2], &name); + + const char *groupname = NULL; + if (strcmp(group, "all") != 0) + groupname = group; + + g_ProfileToolManager.EnterScope(groupname, name); + return 1; +} + +static cell_t LeaveProfilingEvent(IPluginContext *pContext, const cell_t *params) +{ + g_ProfileToolManager.LeaveScope(); + return 1; +} + +static cell_t IsProfilingActive(IPluginContext *pContext, const cell_t *params) +{ + return g_ProfileToolManager.IsActive() ? 1 : 0; +} + REGISTER_NATIVES(profilerNatives) { {"CreateProfiler", CreateProfiler}, {"GetProfilerTime", GetProfilerTime}, {"StartProfiling", StartProfiling}, {"StopProfiling", StopProfiling}, + {"EnterProfilingEvent", EnterProfilingEvent}, + {"LeaveProfilingEvent", LeaveProfilingEvent}, + {"IsProfilingActive", IsProfilingActive}, {NULL, NULL}, }; diff --git a/core/logic_bridge.cpp b/core/logic_bridge.cpp index abb50685..057f660c 100644 --- a/core/logic_bridge.cpp +++ b/core/logic_bridge.cpp @@ -628,7 +628,6 @@ void InitLogicBridge() glob->m_pGlobalClassNext = logicore.head; g_pThreader = logicore.threader; - g_pSourcePawn2->SetProfiler(logicore.profiler); translator = logicore.translator; scripts = logicore.scripts; sharesys = logicore.sharesys; diff --git a/core/vprof_tool.cpp b/core/vprof_tool.cpp new file mode 100644 index 00000000..95519422 --- /dev/null +++ b/core/vprof_tool.cpp @@ -0,0 +1,117 @@ +// vim: set ts=4 sw=4 tw=99 noet : +// ============================================================================= +// SourceMod +// Copyright (C) 2004-2014 AlliedModders LLC. All rights reserved. +// ============================================================================= +// +// This program is free software; you can redistribute it and/or modify it under +// the terms of the GNU General Public License, version 3.0, as published by the +// Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +// details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . +// +// As a special exception, AlliedModders LLC gives you permission to link the +// code of this program (as well as its derivative works) to "Half-Life 2," the +// "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software +// by the Valve Corporation. You must obey the GNU General Public License in +// all respects for all other code used. Additionally, AlliedModders LLC grants +// this exception to all derivative works. AlliedModders LLC defines further +// exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), +// or . + +#include "vprof_tool.h" +#include "logic_bridge.h" +#include "sourcemod.h" +#include "sourcemm_api.h" + +#define VPROF_ENABLED +#include + +VProfTool sVProfTool; + +VProfTool::VProfTool() + : active_(false) +{ +} + +void +VProfTool::OnSourceModAllInitialized() +{ + logicore.RegisterProfiler(this); +} + +const char * +VProfTool::Name() +{ + return "vprof"; +} + +const char * +VProfTool::Description() +{ + return "Valve built-in profiler"; +} + +bool +VProfTool::Start() +{ + g_VProfCurrentProfile.Start(); + return IsActive(); +} + +void +VProfTool::Stop() +{ + g_VProfCurrentProfile.Stop(); +} + +bool +VProfTool::IsActive() +{ + return g_VProfCurrentProfile.IsEnabled(); +} + +intptr_t +VProfTool::RegisterCode( + uintptr_t addr, + size_t length, + const char *name, + const uintptr_t *line_map, + size_t line_count) +{ + return 0; +} + +void +VProfTool::DeregisterCode(intptr_t cookie) +{ +} + +void +VProfTool::EnterScope(const char *group, const char *name) +{ + if (IsActive()) { + if (!group) + group = VPROF_BUDGETGROUP_OTHER_UNACCOUNTED; + g_VProfCurrentProfile.EnterScope(name, 1, group, false, 0); + } +} + +void +VProfTool::LeaveScope() +{ + if (IsActive()) + g_VProfCurrentProfile.ExitScope(); +} + +void +VProfTool::RenderHelp(void (*render)(const char *fmt, ...)) +{ + render("Use vprof_generate_report in your console to analyze a profile session."); +} diff --git a/core/vprof_tool.h b/core/vprof_tool.h new file mode 100644 index 00000000..14bb0379 --- /dev/null +++ b/core/vprof_tool.h @@ -0,0 +1,66 @@ +// vim: set ts=4 sw=4 tw=99 noet : +// ============================================================================= +// SourceMod +// Copyright (C) 2004-2014 AlliedModders LLC. All rights reserved. +// ============================================================================= +// +// This program is free software; you can redistribute it and/or modify it under +// the terms of the GNU General Public License, version 3.0, as published by the +// Free Software Foundation. +// +// This program is distributed in the hope that it will be useful, but WITHOUT +// ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS +// FOR A PARTICULAR PURPOSE. See the GNU General Public License for more +// details. +// +// You should have received a copy of the GNU General Public License along with +// this program. If not, see . +// +// As a special exception, AlliedModders LLC gives you permission to link the +// code of this program (as well as its derivative works) to "Half-Life 2," the +// "Source Engine," the "SourcePawn JIT," and any Game MODs that run on software +// by the Valve Corporation. You must obey the GNU General Public License in +// all respects for all other code used. Additionally, AlliedModders LLC grants +// this exception to all derivative works. AlliedModders LLC defines further +// exceptions, found in LICENSE.txt (as of this writing, version JULY-31-2007), +// or . + +#ifndef _include_sourcemod_core_vprof_bridge_h_ +#define _include_sourcemod_core_vprof_bridge_h_ + +#include +#include "sm_globals.h" +#include + +class VProfTool + : public IProfilingTool, + public SMGlobalClass +{ +public: + VProfTool(); + + // IProfilingTool + const char *Name() KE_OVERRIDE; + const char *Description() KE_OVERRIDE; + bool Start() KE_OVERRIDE; + void Stop() KE_OVERRIDE; + bool IsActive() KE_OVERRIDE; + intptr_t RegisterCode( + uintptr_t addr, + size_t length, + const char *name, + const uintptr_t *line_map, + size_t line_count) KE_OVERRIDE; + void DeregisterCode(intptr_t cookie) KE_OVERRIDE; + void EnterScope(const char *group, const char *name) KE_OVERRIDE; + void LeaveScope() KE_OVERRIDE; + void RenderHelp(void (*render)(const char *fmt, ...)) KE_OVERRIDE; + + // SMGlobalClass + void OnSourceModAllInitialized() KE_OVERRIDE; + +private: + bool active_; +}; + +#endif // _include_sourcemod_core_vprof_bridge_h_ diff --git a/plugins/include/profiler.inc b/plugins/include/profiler.inc index 3c49b308..51c72aec 100644 --- a/plugins/include/profiler.inc +++ b/plugins/include/profiler.inc @@ -75,3 +75,26 @@ native StopProfiling(Handle:prof); * @error Invalid Handle. */ native Float:GetProfilerTime(Handle:prof); + +/** + * Mark the start of a profiling event. + * + * @param group Budget group. This can be "all" for a default, or a short + * description like "Timers" or "Events". + * @param name A name to attribute to this profiling event. + * @noreturn + */ +native EnterProfilingEvent(const String:group[], const String:name[]); + +/** + * Mark the end of the last profiling event. This must be called in the same + * stack frame as StartProfilingEvent(). Not doing so, or throwing errors, + * will make the resulting profile very wrong. + */ +native LeaveProfilingEvent(); + +/** + * Returns true if the global profiler is enabled; false otherwise. It is + * not necessary to call this before Enter/LeaveProfilingEvent. + */ +native bool:IsProfilingActive(); diff --git a/public/sourcepawn/sp_vm_api.h b/public/sourcepawn/sp_vm_api.h index 623b2103..d3253fa9 100644 --- a/public/sourcepawn/sp_vm_api.h +++ b/public/sourcepawn/sp_vm_api.h @@ -998,61 +998,99 @@ namespace SourcePawn }; /** - * @brief Represents a code profiler for plugins. + * @brief Removed. */ - class IProfiler + class IProfiler; + + /** + * @brief encapsulates a profiling tool that may be attached to SourcePawn. + */ + class IProfilingTool { public: /** - * @brief Invoked by the JIT to notify that a native is being started. + * @brief Return the name of the profiling tool. * - * @param pContext Plugin context. - * @param native Native information. + * @return Profiling tool name. */ - virtual void OnNativeBegin(IPluginContext *pContext, sp_native_t *native) =0; + virtual const char *Name() = 0; /** - * @brief Invoked by the JIT to notify that the last native on the stack - * is no longer being executed. + * @brief Description of the profiler. + * + * @return Description. */ - virtual void OnNativeEnd() =0; + virtual const char *Description() = 0; /** - * @brief Invoked by the JIT to notify that a function call is starting. + * @brief Called to render help text. * - * @param pContext Plugin context. - * @param name Function name, or NULL if not known. - * @param code_addr P-Code address. + * @param render Function to render one line of text. */ - virtual void OnFunctionBegin(IPluginContext *pContext, const char *name) =0; + virtual void RenderHelp(void (*render)(const char *fmt, ...)) = 0; + + /** + * @brief Initiate a start command. + * + * Initiate start commands through a profiling tool, returning whether + * or not the command is supported. If starting, SourceMod will generate + * events even if it cannot signal the external profiler. + */ + virtual bool Start() = 0; /** - * @brief Invoked by the JIT to notify that the last function call has - * concluded. In the case of an error inside a function, this will not - * be called. Instead, the VM will call OnCallbackEnd() and the profiler - * stack must be unwound. + * @brief Initiate a stop command. */ - virtual void OnFunctionEnd() =0; + virtual void Stop() = 0; + + /** + * @brief Returns whether or not a profiler is active. + * + * @return True if active, false otherwise. + */ + virtual bool IsActive() = 0; /** - * @brief Invoked by the VM to notify that a forward/callback is starting. + * @brief Registers JIT code with the profiler. * - * @param pContext Plugin context. - * @param pubfunc Public function information. - * @return Unique number to pass to OnFunctionEnd(). + * @param addr Address where the JIT code starts. + * @param length Length of the JIT code region. + * @param name Name to associate with the JIT code. + * @param line_map An array of pairs where the ith element is + * an offset from code_addr and the i+1th + * element is a line number. + * @param line_count Number of lines (line_map size should be lines*2). + * @return A cookie for deregistering, or 0 on failure. */ - virtual int OnCallbackBegin(IPluginContext *pContext, sp_public_t *pubfunc) =0; + virtual intptr_t RegisterCode( + uintptr_t addr, + size_t length, + const char *name, + const uintptr_t *line_map, + size_t line_count) = 0; /** - * @brief Invoked by the JIT to notify that a callback has ended. + * @brief Deregisters JIT code using a previous cookie. * - * As noted in OnFunctionEnd(), this my be called with a misaligned - * profiler stack. To correct this, the stack should be unwound - * (discarding data as appropriate) to a matching serial number. - * - * @param serial Unique number from OnCallbackBegin(). + * @param cookie A cookie returned by RegisterCode(). */ - virtual void OnCallbackEnd(int serial) =0; + virtual void DeregisterCode(intptr_t cookie) = 0; + + /** + * @brief Enters the scope of an event. + * + * LeaveScope() mus be called exactly once for each call to EnterScope(). + * + * @param group A named budget group, or NULL for the default. + * @param name Event name. + */ + virtual void EnterScope(const char *group, const char *name) = 0; + + /** + * @brief Leave a profiling scope. This must be called exactly once for + * each call to EnterScope(). + */ + virtual void LeaveScope() = 0; }; struct sp_plugin_s; @@ -1251,9 +1289,9 @@ namespace SourcePawn virtual IDebugListener *SetDebugListener(IDebugListener *listener) =0; /** - * @brief Sets the global profiler. + * @brief Deprecated. * - * @param profiler Profiler pointer. + * @param profiler Deprecated. */ virtual void SetProfiler(IProfiler *profiler) =0; @@ -1310,6 +1348,27 @@ namespace SourcePawn * @return True if the JIT is enabled, false otherwise. */ virtual bool IsJitEnabled() =0; + + /** + * @brief Enables profiling. SetProfilingTool() must have been called. + * + * Note that this does not activate the profiling tool. It only enables + * notifications to the profiling tool. SourcePawn will send events to + * the profiling tool even if the tool itself is reported as inactive. + */ + virtual void EnableProfiling() = 0; + + /** + * @brief Disables profiling. + */ + virtual void DisableProfiling() = 0; + + /** + * @brief Sets the profiling tool. + * + * @param tool Profiling tool. + */ + virtual void SetProfilingTool(IProfilingTool *tool) =0; }; }; diff --git a/sourcepawn/jit/BaseRuntime.cpp b/sourcepawn/jit/BaseRuntime.cpp index e3725e14..c7d5ec02 100644 --- a/sourcepawn/jit/BaseRuntime.cpp +++ b/sourcepawn/jit/BaseRuntime.cpp @@ -292,7 +292,6 @@ int BaseRuntime::CreateFromMemory(sp_file_hdr_t *hdr, uint8_t *base) md5_data.finalize(); md5_data.raw_digest(m_DataHash); - m_plugin.profiler = g_engine2.GetProfiler(); m_pCtx = new BaseContext(this); co_ = g_Jit.StartCompilation(this); @@ -491,9 +490,7 @@ BaseRuntime::GetFunctionById(funcid_t func_id) return NULL; pFunc = m_PubFuncs[func_id]; if (!pFunc) { - m_PubFuncs[func_id] = new CFunction(this, - (func_id << 1) | 1, - func_id); + m_PubFuncs[func_id] = new CFunction(this, (func_id << 1) | 1, func_id); pFunc = m_PubFuncs[func_id]; } } @@ -501,6 +498,21 @@ BaseRuntime::GetFunctionById(funcid_t func_id) return pFunc; } +CFunction * +BaseRuntime::GetPublicFunction(size_t index) +{ + CFunction *pFunc = m_PubFuncs[index]; + if (!pFunc) { + sp_public_t *pub = NULL; + GetPublicByIndex(index, &pub); + if (pub) + m_PubFuncs[index] = new CFunction(this, (index << 1) | 1, index); + pFunc = m_PubFuncs[index]; + } + + return pFunc; +} + IPluginFunction * BaseRuntime::GetFunctionByName(const char *public_name) { @@ -509,16 +521,7 @@ BaseRuntime::GetFunctionByName(const char *public_name) if (FindPublicByName(public_name, &index) != SP_ERROR_NONE) return NULL; - CFunction *pFunc = m_PubFuncs[index]; - if (!pFunc) { - sp_public_t *pub = NULL; - GetPublicByIndex(index, &pub); - if (pub) - m_PubFuncs[index] = new CFunction(this, (index << 1) | 1, index); - pFunc = m_PubFuncs[index]; - } - - return pFunc; + return GetPublicFunction(index); } bool BaseRuntime::IsDebugging() @@ -594,7 +597,6 @@ BaseRuntime::CreateBlank(uint32_t heastk) m_plugin.mem_size = heastk; m_plugin.memory = new uint8_t[heastk]; - m_plugin.profiler = g_engine2.GetProfiler(); m_pCtx = new BaseContext(this); co_ = g_Jit.StartCompilation(this); diff --git a/sourcepawn/jit/BaseRuntime.h b/sourcepawn/jit/BaseRuntime.h index c17cad47..6ee331be 100644 --- a/sourcepawn/jit/BaseRuntime.h +++ b/sourcepawn/jit/BaseRuntime.h @@ -71,6 +71,7 @@ class BaseRuntime void AddJittedFunction(JitFunction *fn); void SetName(const char *name); unsigned GetNativeReplacement(size_t index); + CFunction *GetPublicFunction(size_t index); BaseContext *GetBaseContext(); const sp_plugin_t *plugin() const { diff --git a/sourcepawn/jit/engine2.cpp b/sourcepawn/jit/engine2.cpp index 09023cbd..9ce845cb 100644 --- a/sourcepawn/jit/engine2.cpp +++ b/sourcepawn/jit/engine2.cpp @@ -14,7 +14,7 @@ using namespace SourcePawn; SourcePawnEngine2::SourcePawnEngine2() { - m_Profiler = NULL; + profiler_ = NULL; jit_enabled_ = true; } @@ -147,7 +147,7 @@ void SourcePawnEngine2::DestroyFakeNative(SPVM_NATIVE_FUNC func) const char *SourcePawnEngine2::GetEngineName() { - return "SourcePawn 1.2, jit-x86"; + return "SourcePawn 1.3, jit-x86"; } const char *SourcePawnEngine2::GetVersionString() @@ -155,16 +155,6 @@ const char *SourcePawnEngine2::GetVersionString() return SOURCEMOD_VERSION; } -IProfiler *SourcePawnEngine2::GetProfiler() -{ - return m_Profiler; -} - -void SourcePawnEngine2::SetProfiler(IProfiler *profiler) -{ - m_Profiler = profiler; -} - IDebugListener *SourcePawnEngine2::SetDebugListener(IDebugListener *listener) { return g_engine1.SetDebugListener(listener); diff --git a/sourcepawn/jit/engine2.h b/sourcepawn/jit/engine2.h index e9cb980e..249c24f6 100644 --- a/sourcepawn/jit/engine2.h +++ b/sourcepawn/jit/engine2.h @@ -21,7 +21,6 @@ namespace SourcePawn SPVM_NATIVE_FUNC CreateFakeNative(SPVM_FAKENATIVE_FUNC callback, void *pData); void DestroyFakeNative(SPVM_NATIVE_FUNC func); IDebugListener *SetDebugListener(IDebugListener *listener); - void SetProfiler(IProfiler *profiler); ICompilation *StartCompilation(); const char *GetErrorString(int err); bool Initialize(); @@ -37,14 +36,51 @@ namespace SourcePawn bool IsJitEnabled() { return jit_enabled_; } + + void SetProfiler(IProfiler *profiler) { + // Deprecated. + } + + void EnableProfiling() { + profiling_enabled_ = !!profiler_; + } + void DisableProfiling() { + profiling_enabled_ = false; + } + bool IsProfilingEnabled() { + return profiling_enabled_; + } + void SetProfilingTool(IProfilingTool *tool) { + profiler_ = tool; + } + public: - IProfiler *GetProfiler(); + IProfilingTool *GetProfiler() { + return profiler_; + } private: - IProfiler *m_Profiler; + IProfilingTool *profiler_; bool jit_enabled_; + bool profiling_enabled_; }; } extern SourcePawn::SourcePawnEngine2 g_engine2; +class EnterProfileScope +{ +public: + EnterProfileScope(const char *group, const char *name) + { + if (g_engine2.IsProfilingEnabled()) + g_engine2.GetProfiler()->EnterScope(group, name); + } + + ~EnterProfileScope() + { + if (g_engine2.IsProfilingEnabled()) + g_engine2.GetProfiler()->LeaveScope(); + } +}; + #endif //_INCLUDE_SOURCEPAWN_ENGINE_2_H_ diff --git a/sourcepawn/jit/jit_shared.h b/sourcepawn/jit/jit_shared.h index 883f3bd4..4642e5dd 100644 --- a/sourcepawn/jit/jit_shared.h +++ b/sourcepawn/jit/jit_shared.h @@ -59,7 +59,6 @@ namespace SourcePawn uint32_t num_pubvars; /**< Number of public variables */ sp_native_t *natives; /**< Natives table */ uint32_t num_natives; /**< Number of natives */ - IProfiler *profiler; /**< Pointer to IProfiler */ uint32_t prof_flags; /**< Profiling flags */ uint32_t run_flags; /**< Runtime flags */ uint32_t pcode_version; /**< P-Code version number */ diff --git a/sourcepawn/jit/sp_vm_basecontext.cpp b/sourcepawn/jit/sp_vm_basecontext.cpp index 666c6cfb..b2e841ad 100644 --- a/sourcepawn/jit/sp_vm_basecontext.cpp +++ b/sourcepawn/jit/sp_vm_basecontext.cpp @@ -549,65 +549,47 @@ int BaseContext::Execute2(IPluginFunction *function, const cell_t *params, unsig int ir; int serial; cell_t *sp; - funcid_t fnid; JitFunction *fn; - sp_public_t *pubfunc; cell_t _ignore_result; - unsigned int public_id; - fnid = function->GetFunctionID(); + EnterProfileScope profileScope("SourcePawn", "EnterJIT"); if (!g_WatchdogTimer.HandleInterrupt()) return SP_ERROR_TIMEOUT; - if (fnid & 1) - { - public_id = fnid >> 1; - - if (m_pRuntime->GetPublicByIndex(public_id, &pubfunc) != SP_ERROR_NONE) - { - return SP_ERROR_NOT_FOUND; - } - } - else - { + funcid_t fnid = function->GetFunctionID(); + if (!(fnid & 1)) return SP_ERROR_INVALID_ADDRESS; - } + + unsigned public_id = fnid >> 1; + CFunction *cfun = m_pRuntime->GetPublicFunction(public_id); + if (!cfun) + return SP_ERROR_NOT_FOUND; if (m_pRuntime->IsPaused()) - { return SP_ERROR_NOT_RUNNABLE; - } if ((cell_t)(m_ctx.hp + 16*sizeof(cell_t)) > (cell_t)(m_ctx.sp - (sizeof(cell_t) * (num_params + 1)))) - { return SP_ERROR_STACKLOW; - } if (result == NULL) - { result = &_ignore_result; - } /* We got this far. It's time to start profiling. */ - - if ((m_pRuntime->plugin()->prof_flags & SP_PROF_CALLBACKS) == SP_PROF_CALLBACKS) - { - serial = m_pRuntime->plugin()->profiler->OnCallbackBegin(this, pubfunc); - } + EnterProfileScope scriptScope("SourcePawn", cfun->FullName()); /* See if we have to compile the callee. */ if (g_engine2.IsJitEnabled() && (fn = m_pRuntime->m_PubJitFuncs[public_id]) == NULL) { /* We might not have to - check pcode offset. */ - fn = m_pRuntime->GetJittedFunctionByOffset(pubfunc->code_offs); + fn = m_pRuntime->GetJittedFunctionByOffset(cfun->Public()->code_offs); if (fn) { m_pRuntime->m_PubJitFuncs[public_id] = fn; } else { - if ((fn = g_Jit.CompileFunction(m_pRuntime, pubfunc->code_offs, &ir)) == NULL) + if ((fn = g_Jit.CompileFunction(m_pRuntime, cfun->Public()->code_offs, &ir)) == NULL) { return ir; } @@ -651,7 +633,7 @@ int BaseContext::Execute2(IPluginFunction *function, const cell_t *params, unsig if (g_engine2.IsJitEnabled()) ir = g_Jit.InvokeFunction(m_pRuntime, fn, result); else - ir = Interpret(m_pRuntime, pubfunc->code_offs, result); + ir = Interpret(m_pRuntime, cfun->Public()->code_offs, result); /* Restore some states, stop the frame tracer */ @@ -695,11 +677,6 @@ int BaseContext::Execute2(IPluginFunction *function, const cell_t *params, unsig m_ctx.hp = save_hp; m_ctx.rp = save_rp; - if ((m_pRuntime->plugin()->prof_flags & SP_PROF_CALLBACKS) == SP_PROF_CALLBACKS) - { - m_pRuntime->plugin()->profiler->OnCallbackEnd(serial); - } - m_ctx.cip = save_cip; m_ctx.n_idx = save_n_idx; m_ctx.n_err = SP_ERROR_NONE; diff --git a/sourcepawn/jit/sp_vm_function.cpp b/sourcepawn/jit/sp_vm_function.cpp index 15c117b0..3daa77d8 100644 --- a/sourcepawn/jit/sp_vm_function.cpp +++ b/sourcepawn/jit/sp_vm_function.cpp @@ -36,12 +36,9 @@ * FUNCTION CALLING * ********************/ -void CFunction::Set(BaseRuntime *runtime, funcid_t fnid, uint32_t pub_id) +CFunction::~CFunction() { - m_pRuntime = runtime; - m_curparam = 0; - m_errorstate = SP_ERROR_NONE; - m_FnId = fnid; + delete [] full_name_; } bool CFunction::IsRunnable() @@ -68,6 +65,16 @@ CFunction::CFunction(BaseRuntime *runtime, funcid_t id, uint32_t pub_id) : m_curparam(0), m_errorstate(SP_ERROR_NONE), m_FnId(id) { m_pRuntime = runtime; + + runtime->GetPublicByIndex(pub_id, &public_); + + size_t rt_len = strlen(runtime->plugin()->name); + size_t len = rt_len + strlen("::") + strlen(public_->name); + + full_name_ = new char[len + 1]; + strcpy(full_name_, runtime->plugin()->name); + strcpy(&full_name_[rt_len], "::"); + strcpy(&full_name_[rt_len + 2], public_->name); } int CFunction::PushCell(cell_t cell) diff --git a/sourcepawn/jit/sp_vm_function.h b/sourcepawn/jit/sp_vm_function.h index 48131b8b..6c9c0e13 100644 --- a/sourcepawn/jit/sp_vm_function.h +++ b/sourcepawn/jit/sp_vm_function.h @@ -61,6 +61,7 @@ public: CFunction(BaseRuntime *pRuntime, funcid_t fnid, uint32_t pub_id); + ~CFunction(); public: virtual int PushCell(cell_t cell); virtual int PushCellByRef(cell_t *cell, int flags); @@ -82,7 +83,12 @@ public: cell_t *result); IPluginRuntime *GetParentRuntime(); public: - void Set(BaseRuntime *runtime, funcid_t fnid, uint32_t pub_id); + const char *FullName() const { + return full_name_; + } + sp_public_t *Public() const { + return public_; + } private: int _PushString(const char *string, int sz_flags, int cp_flags, size_t len); int SetError(int err); @@ -93,6 +99,8 @@ private: unsigned int m_curparam; int m_errorstate; funcid_t m_FnId; + char *full_name_; + sp_public_t *public_; }; #endif //_INCLUDE_SOURCEMOD_BASEFUNCTION_H_ diff --git a/sourcepawn/jit/x86/jit_x86.cpp b/sourcepawn/jit/x86/jit_x86.cpp index df68bd57..fafc30cc 100644 --- a/sourcepawn/jit/x86/jit_x86.cpp +++ b/sourcepawn/jit/x86/jit_x86.cpp @@ -1950,7 +1950,6 @@ JITX86::SetupContextVars(BaseRuntime *runtime, BaseContext *pCtx, sp_context_t * ctx->tracker->pCur = ctx->tracker->pBase; ctx->tracker->size = 1024 / sizeof(cell_t); ctx->basecx = pCtx; - ctx->vm[JITVARS_PROFILER] = g_engine2.GetProfiler(); ctx->plugin = const_cast(runtime->plugin()); } From fac52a2897959e0442050298ad0539d50eea3182 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Tue, 24 Jun 2014 01:18:16 -0700 Subject: [PATCH 2/4] Fill out API more. --- core/logic/ProfileTools.cpp | 2 +- core/vprof_tool.cpp | 9 ++++++++- core/vprof_tool.h | 3 ++- public/sourcepawn/sp_vm_api.h | 13 +++++++++++-- 4 files changed, 22 insertions(+), 5 deletions(-) diff --git a/core/logic/ProfileTools.cpp b/core/logic/ProfileTools.cpp index 5cf9e5af..e528d8d5 100644 --- a/core/logic/ProfileTools.cpp +++ b/core/logic/ProfileTools.cpp @@ -95,7 +95,7 @@ ProfileToolManager::OnRootConsoleCommand2(const char *cmdname, const ICommandArg } g_pSourcePawn2->DisableProfiling(); g_pSourcePawn2->SetProfilingTool(nullptr); - active_->Stop(); + active_->Stop(render_help); active_->RenderHelp(render_help); return; } diff --git a/core/vprof_tool.cpp b/core/vprof_tool.cpp index 95519422..2d935309 100644 --- a/core/vprof_tool.cpp +++ b/core/vprof_tool.cpp @@ -66,9 +66,10 @@ VProfTool::Start() } void -VProfTool::Stop() +VProfTool::Stop(void (*render)(const char *fmt, ...)) { g_VProfCurrentProfile.Stop(); + RenderHelp(render); } bool @@ -77,6 +78,12 @@ VProfTool::IsActive() return g_VProfCurrentProfile.IsEnabled(); } +bool +VProfTool::IsAttached() +{ + return true; +} + intptr_t VProfTool::RegisterCode( uintptr_t addr, diff --git a/core/vprof_tool.h b/core/vprof_tool.h index 14bb0379..e1d0c997 100644 --- a/core/vprof_tool.h +++ b/core/vprof_tool.h @@ -43,8 +43,9 @@ public: const char *Name() KE_OVERRIDE; const char *Description() KE_OVERRIDE; bool Start() KE_OVERRIDE; - void Stop() KE_OVERRIDE; + void Stop(void (*render)(const char *fmt, ...)) KE_OVERRIDE; bool IsActive() KE_OVERRIDE; + bool IsAttached() KE_OVERRIDE; intptr_t RegisterCode( uintptr_t addr, size_t length, diff --git a/public/sourcepawn/sp_vm_api.h b/public/sourcepawn/sp_vm_api.h index d3253fa9..fdb7338f 100644 --- a/public/sourcepawn/sp_vm_api.h +++ b/public/sourcepawn/sp_vm_api.h @@ -1040,16 +1040,25 @@ namespace SourcePawn /** * @brief Initiate a stop command. + * + * @param render Function to render any help messages. */ - virtual void Stop() = 0; + virtual void Stop(void (*render)(const char *fmt, ...)) = 0; /** - * @brief Returns whether or not a profiler is active. + * @brief Returns whether or not the profiler is currently profiling. * * @return True if active, false otherwise. */ virtual bool IsActive() = 0; + /** + * @brief Returns whether the profiler is attached. + * + * @return True if attached, false otherwise. + */ + virtual bool IsAttached() = 0; + /** * @brief Registers JIT code with the profiler. * From f9defa7b375fd43a324ec805064ac7c4ff1a3b5a Mon Sep 17 00:00:00 2001 From: David Anderson Date: Tue, 24 Jun 2014 20:44:30 -0700 Subject: [PATCH 3/4] Remove JIT registration API; this needs more time. --- core/vprof_tool.cpp | 16 ---------------- core/vprof_tool.h | 7 ------- public/sourcepawn/sp_vm_api.h | 28 +--------------------------- 3 files changed, 1 insertion(+), 50 deletions(-) diff --git a/core/vprof_tool.cpp b/core/vprof_tool.cpp index 2d935309..75648e07 100644 --- a/core/vprof_tool.cpp +++ b/core/vprof_tool.cpp @@ -84,22 +84,6 @@ VProfTool::IsAttached() return true; } -intptr_t -VProfTool::RegisterCode( - uintptr_t addr, - size_t length, - const char *name, - const uintptr_t *line_map, - size_t line_count) -{ - return 0; -} - -void -VProfTool::DeregisterCode(intptr_t cookie) -{ -} - void VProfTool::EnterScope(const char *group, const char *name) { diff --git a/core/vprof_tool.h b/core/vprof_tool.h index e1d0c997..3289b490 100644 --- a/core/vprof_tool.h +++ b/core/vprof_tool.h @@ -46,13 +46,6 @@ public: void Stop(void (*render)(const char *fmt, ...)) KE_OVERRIDE; bool IsActive() KE_OVERRIDE; bool IsAttached() KE_OVERRIDE; - intptr_t RegisterCode( - uintptr_t addr, - size_t length, - const char *name, - const uintptr_t *line_map, - size_t line_count) KE_OVERRIDE; - void DeregisterCode(intptr_t cookie) KE_OVERRIDE; void EnterScope(const char *group, const char *name) KE_OVERRIDE; void LeaveScope() KE_OVERRIDE; void RenderHelp(void (*render)(const char *fmt, ...)) KE_OVERRIDE; diff --git a/public/sourcepawn/sp_vm_api.h b/public/sourcepawn/sp_vm_api.h index fdb7338f..ca4d50f9 100644 --- a/public/sourcepawn/sp_vm_api.h +++ b/public/sourcepawn/sp_vm_api.h @@ -1003,7 +1003,7 @@ namespace SourcePawn class IProfiler; /** - * @brief encapsulates a profiling tool that may be attached to SourcePawn. + * @brief Encapsulates a profiling tool that may be attached to SourcePawn. */ class IProfilingTool { @@ -1059,32 +1059,6 @@ namespace SourcePawn */ virtual bool IsAttached() = 0; - /** - * @brief Registers JIT code with the profiler. - * - * @param addr Address where the JIT code starts. - * @param length Length of the JIT code region. - * @param name Name to associate with the JIT code. - * @param line_map An array of pairs where the ith element is - * an offset from code_addr and the i+1th - * element is a line number. - * @param line_count Number of lines (line_map size should be lines*2). - * @return A cookie for deregistering, or 0 on failure. - */ - virtual intptr_t RegisterCode( - uintptr_t addr, - size_t length, - const char *name, - const uintptr_t *line_map, - size_t line_count) = 0; - - /** - * @brief Deregisters JIT code using a previous cookie. - * - * @param cookie A cookie returned by RegisterCode(). - */ - virtual void DeregisterCode(intptr_t cookie) = 0; - /** * @brief Enters the scope of an event. * From b26552d74ca9a79a590da84d82594888e2657f93 Mon Sep 17 00:00:00 2001 From: David Anderson Date: Tue, 24 Jun 2014 20:55:53 -0700 Subject: [PATCH 4/4] Improve usability of start/stop. --- core/logic/ProfileTools.cpp | 59 +++++++++++++++++++++++++++---------- core/logic/ProfileTools.h | 4 +++ 2 files changed, 48 insertions(+), 15 deletions(-) diff --git a/core/logic/ProfileTools.cpp b/core/logic/ProfileTools.cpp index e528d8d5..7538e034 100644 --- a/core/logic/ProfileTools.cpp +++ b/core/logic/ProfileTools.cpp @@ -31,7 +31,8 @@ ProfileToolManager g_ProfileToolManager; ProfileToolManager::ProfileToolManager() - : active_(nullptr) + : active_(nullptr), + default_(nullptr) { } @@ -70,6 +71,28 @@ render_help(const char *fmt, ...) rootmenu->ConsolePrint("%s", buffer); } +void +ProfileToolManager::StartFromConsole(IProfilingTool *tool) +{ + if (active_) { + rootmenu->ConsolePrint("A profile is already active using %s.", active_->Name()); + return; + } + + active_ = tool; + if (!active_->Start()) { + rootmenu->ConsolePrint("Failed to attach to or start %s.", active_->Name()); + active_ = nullptr; + return; + } + + g_pSourcePawn2->SetProfilingTool(active_); + g_pSourcePawn2->EnableProfiling(); + rootmenu->ConsolePrint("Started profiling with %s.", active_->Name()); + + default_ = active_; +} + void ProfileToolManager::OnRootConsoleCommand2(const char *cmdname, const ICommandArgs *args) { @@ -96,10 +119,26 @@ ProfileToolManager::OnRootConsoleCommand2(const char *cmdname, const ICommandArg g_pSourcePawn2->DisableProfiling(); g_pSourcePawn2->SetProfilingTool(nullptr); active_->Stop(render_help); - active_->RenderHelp(render_help); + active_ = nullptr; return; } + if (args->ArgC() < 4) { + if (strcmp(cmdname, "start") == 0) { + if (!default_) { + default_ = FindToolByName("vprof"); + if (!default_ && tools_.length() > 0) + default_ = tools_[0]; + if (!default_) { + rootmenu->ConsolePrint("Could not find any profiler to use."); + return; + } + } + StartFromConsole(default_); + return; + } + } + if (args->ArgC() < 4) { rootmenu->ConsolePrint("You must specify a profiling tool name."); return; @@ -107,22 +146,12 @@ ProfileToolManager::OnRootConsoleCommand2(const char *cmdname, const ICommandArg const char *toolname = args->Arg(3); if (strcmp(cmdname, "start") == 0) { - if (active_) { - rootmenu->ConsolePrint("A profile is already active using %s.", active_->Name()); - return; - } - if ((active_ = FindToolByName(toolname)) == nullptr) { + IProfilingTool *tool = FindToolByName(toolname); + if (!tool) { rootmenu->ConsolePrint("No tool with the name \"%s\" was found.", toolname); return; } - if (!active_->Start()) { - rootmenu->ConsolePrint("Failed to attach to or start %s.", active_->Name()); - active_ = nullptr; - return; - } - g_pSourcePawn2->SetProfilingTool(active_); - g_pSourcePawn2->EnableProfiling(); - rootmenu->ConsolePrint("Started profiling with %s.", active_->Name()); + StartFromConsole(tool); return; } if (strcmp(cmdname, "help") == 0) { diff --git a/core/logic/ProfileTools.h b/core/logic/ProfileTools.h index 2a760809..c7e31ccd 100644 --- a/core/logic/ProfileTools.h +++ b/core/logic/ProfileTools.h @@ -72,9 +72,13 @@ public: IProfilingTool *FindToolByName(const char *name); +private: + void StartFromConsole(IProfilingTool *tool); + private: ke::Vector tools_; IProfilingTool *active_; + IProfilingTool *default_; bool enabled_; };