diff --git a/configs/plugin_settings.cfg b/configs/plugin_settings.cfg
index d8364de0..cf46ae71 100644
--- a/configs/plugin_settings.cfg
+++ b/configs/plugin_settings.cfg
@@ -14,6 +14,13 @@
*
* You can also have an "Options" section declaring options to pass onto the JIT:
* "debug" - Whether or not to load the plugin in debug mode
+ * "profile" - Bit flags for profiling level. Add flags together to reach a value.
+ * WARNING: Profiler is _ALPHA_ software! Use it at your own risk for
+ * development cycles only (not production setups).
+ * See the wiki article "SourceMod Profiler" for more information.
+ * 1 - Profile natives
+ * 2 - Profile callbacks
+ * 4 - Profile internal plugin function calls
*/
"Plugins"
diff --git a/core/Profiler.cpp b/core/Profiler.cpp
new file mode 100644
index 00000000..7f119144
--- /dev/null
+++ b/core/Profiler.cpp
@@ -0,0 +1,483 @@
+/**
+ * vim: set ts=4 :
+ * =============================================================================
+ * SourceMod
+ * Copyright (C) 2004-2007 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 "PluginSys.h"
+#include "sm_stringutil.h"
+#include "Logger.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 > after.value.tv_usec)
+ {
+ seconds - 1.0;
+ seconds += (double)(1000000 - (before.value.tv_usec - after.tv_usec)) / 1000000.0;
+ }
+ else
+ {
+ seconds += (double)(after.value.tv_usec - before.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()
+{
+ g_RootMenu.AddRootConsoleCommand("profiler", "Profiler commands", this);
+}
+
+void ProfileEngine::OnSourceModShutdown()
+{
+ g_RootMenu.RemoveRootConsoleCommand("profiler", this);
+}
+
+void ProfileEngine::OnRootConsoleCommand(const char *cmdname, const CCommand &command)
+{
+ if (command.ArgC() >= 3)
+ {
+ if (strcmp(command.Arg(2), "flush") == 0)
+ {
+ FILE *fp;
+ char path[256];
+
+ g_SourceMod.BuildPath(Path_SM, path, sizeof(path), "logs/profile_%d.xml", (int)time(NULL));
+
+ if ((fp = fopen(path, "wt")) == NULL)
+ {
+ g_RootMenu.ConsolePrint("Failed, could not open file for writing: %s", path);
+ return;
+ }
+
+ GenerateReport(fp);
+
+ fclose(fp);
+
+ g_RootMenu.ConsolePrint("Profiler report generated as: %s\n", path);
+
+ return;
+ }
+ }
+
+ g_RootMenu.ConsolePrint("Profiler commands:");
+ g_RootMenu.DrawGenericOption("flush", "Flushes statistics to disk and starts over");
+}
+
+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), "<", "<");
+ UTIL_ReplaceAll(new_name, sizeof(new_name), ">", ">");
+
+ 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 **pReport, *report;
+
+ if (atom.atom_type == SP_PROF_NATIVES)
+ {
+ strncopy(full_name, atom.name, sizeof(full_name));
+ }
+ else
+ {
+ CPlugin *pl;
+ const char *file;
+
+ file = "unknown";
+ if ((pl = g_PluginSys.GetPluginByCtx(atom.ctx)) != NULL)
+ {
+ file = pl->GetFilename();
+ }
+
+ UTIL_Format(full_name, sizeof(full_name), "%s!%s", file, atom.name);
+ }
+
+ atom_time = CalcAtomTime(atom);
+
+ if ((pReport = m_ReportLookup.retrieve(full_name)) == NULL)
+ {
+ report = new prof_atom_report_t;
+
+ 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
+ {
+ report = *pReport;
+
+ 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/Profiler.h b/core/Profiler.h
new file mode 100644
index 00000000..3781a247
--- /dev/null
+++ b/core/Profiler.h
@@ -0,0 +1,131 @@
+/**
+ * vim: set ts=4 :
+ * =============================================================================
+ * SourceMod
+ * Copyright (C) 2004-2007 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 "sm_globals.h"
+#include "sm_srvcmds.h"
+
+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 */
+};
+
+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:
+ KTrie 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 OnRootConsoleCommand(const char *cmdname, const CCommand &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 ProfileEngine g_Profiler;
+
+#endif //_INCLUDE_SOURCEMOD_PLUGIN_PROFILER_H_
diff --git a/core/msvc8/sourcemod_mm.vcproj b/core/msvc8/sourcemod_mm.vcproj
index a4b30dd0..c3efbe2c 100644
--- a/core/msvc8/sourcemod_mm.vcproj
+++ b/core/msvc8/sourcemod_mm.vcproj
@@ -1,7 +1,7 @@
+
+
@@ -881,6 +885,10 @@
RelativePath="..\PlayerManager.h"
>
+
+
diff --git a/core/systems/PluginSys.cpp b/core/systems/PluginSys.cpp
index 0fac4d59..91977bdd 100644
--- a/core/systems/PluginSys.cpp
+++ b/core/systems/PluginSys.cpp
@@ -982,7 +982,6 @@ void CPluginManager::LoadPluginsFromDir(const char *basedir, const char *localpa
g_LibSys.CloseDirectory(dir);
}
-//well i have discovered that gabe newell is very fat, so i wrote this comment now
LoadRes CPluginManager::_LoadPlugin(CPlugin **_plugin, const char *path, bool debug, PluginType type, char error[], size_t maxlength)
{
if (m_LoadingLocked)
diff --git a/core/vm/sp_vm_basecontext.cpp b/core/vm/sp_vm_basecontext.cpp
index f9ad80f8..d851a714 100644
--- a/core/vm/sp_vm_basecontext.cpp
+++ b/core/vm/sp_vm_basecontext.cpp
@@ -61,6 +61,12 @@ BaseContext::BaseContext(sp_context_t *_ctx)
ctx = _ctx;
ctx->context = this;
ctx->dbreak = GlobalDebugBreak;
+
+ if (ctx->prof_flags != 0)
+ {
+ ctx->profiler = sm_profiler;
+ }
+
m_InExec = false;
m_CustomMsg = false;
m_funcsnum = ctx->vmbase->FunctionCount(ctx);
@@ -150,7 +156,7 @@ void BaseContext::RefreshFunctionCache()
{
continue;
}
- m_pub_funcs[i]->Set(pub->code_offs, this, pub->funcid);
+ m_pub_funcs[i]->Set(pub->code_offs, this, pub->funcid, i);
}
}
@@ -186,9 +192,16 @@ void BaseContext::SetContext(sp_context_t *_ctx)
{
return;
}
+
ctx = _ctx;
ctx->context = this;
ctx->dbreak = GlobalDebugBreak;
+
+ if (ctx->prof_flags != 0)
+ {
+ ctx->profiler = sm_profiler;
+ }
+
RefreshFunctionCache();
}
@@ -992,10 +1005,18 @@ IPluginFunction *BaseContext::GetFunctionById(funcid_t func_id)
pFunc = m_pub_funcs[func_id];
if (!pFunc)
{
- m_pub_funcs[func_id] = new CFunction(ctx->publics[func_id].code_offs, this, ctx->publics[func_id].funcid);
+ m_pub_funcs[func_id] = new CFunction(ctx->publics[func_id].code_offs,
+ this,
+ ctx->publics[func_id].funcid,
+ func_id);
pFunc = m_pub_funcs[func_id];
- } else if (pFunc->IsInvalidated()) {
- pFunc->Set(ctx->publics[func_id].code_offs, this, ctx->publics[func_id].funcid);
+ }
+ else if (pFunc->IsInvalidated())
+ {
+ pFunc->Set(ctx->publics[func_id].code_offs,
+ this,
+ ctx->publics[func_id].funcid,
+ func_id);
}
} else {
/* :TODO: currently not used */
@@ -1034,16 +1055,20 @@ IPluginFunction *BaseContext::GetFunctionByName(const char *public_name)
GetPublicByIndex(index, &pub);
if (pub)
{
- m_pub_funcs[index] = new CFunction(pub->code_offs, this, pub->funcid);
+ m_pub_funcs[index] = new CFunction(pub->code_offs, this, pub->funcid, index);
}
pFunc = m_pub_funcs[index];
- } else if (pFunc->IsInvalidated()) {
+ }
+ else if (pFunc->IsInvalidated())
+ {
sp_public_t *pub = NULL;
GetPublicByIndex(index, &pub);
if (pub)
{
- pFunc->Set(pub->code_offs, this, pub->funcid);
- } else {
+ pFunc->Set(pub->code_offs, this, pub->funcid, index);
+ }
+ else
+ {
pFunc = NULL;
}
}
diff --git a/core/vm/sp_vm_basecontext.h b/core/vm/sp_vm_basecontext.h
index ce399fd6..9df690b2 100644
--- a/core/vm/sp_vm_basecontext.h
+++ b/core/vm/sp_vm_basecontext.h
@@ -39,6 +39,8 @@
* :TODO: Make functions allocate as a lump instead of individual allocations!
*/
+extern IProfiler *sm_profiler;
+
namespace SourcePawn
{
class BaseContext :
diff --git a/core/vm/sp_vm_function.cpp b/core/vm/sp_vm_function.cpp
index bb0b1b08..30615034 100644
--- a/core/vm/sp_vm_function.cpp
+++ b/core/vm/sp_vm_function.cpp
@@ -32,12 +32,13 @@
#include
#include
#include "sp_vm_function.h"
+#include "sm_stringutil.h"
/********************
* FUNCTION CALLING *
********************/
-void CFunction::Set(uint32_t code_addr, IPluginContext *plugin, funcid_t id)
+void CFunction::Set(uint32_t code_addr, IPluginContext *plugin, funcid_t id, uint32_t pub_id)
{
m_codeaddr = code_addr;
m_pContext = plugin;
@@ -46,6 +47,8 @@ void CFunction::Set(uint32_t code_addr, IPluginContext *plugin, funcid_t id)
m_Invalid = false;
m_pCtx = plugin ? plugin->GetContext() : NULL;
m_FnId = id;
+
+ m_pContext->GetPublicByIndex(pub_id, &m_pPublic);
}
bool CFunction::IsRunnable()
@@ -55,17 +58,33 @@ bool CFunction::IsRunnable()
int CFunction::CallFunction(const cell_t *params, unsigned int num_params, cell_t *result)
{
+ int ir, serial;
+
if (!IsRunnable())
{
return SP_ERROR_NOT_RUNNABLE;
}
+ if ((m_pCtx->prof_flags & SP_PROF_CALLBACKS) == SP_PROF_CALLBACKS
+ && m_pPublic != NULL)
+ {
+ serial = m_pCtx->profiler->OnCallbackBegin(m_pContext, m_pPublic);
+ }
+
while (num_params--)
{
m_pContext->PushCell(params[num_params]);
}
- return m_pContext->Execute(m_codeaddr, result);
+ ir = m_pContext->Execute(m_codeaddr, result);
+
+ if ((m_pCtx->prof_flags & SP_PROF_CALLBACKS) == SP_PROF_CALLBACKS
+ && m_pPublic != NULL)
+ {
+ m_pCtx->profiler->OnCallbackEnd(serial);
+ }
+
+ return ir;
}
IPluginContext *CFunction::GetParentContext()
@@ -73,7 +92,7 @@ IPluginContext *CFunction::GetParentContext()
return m_pContext;
}
-CFunction::CFunction(uint32_t code_addr, IPluginContext *plugin, funcid_t id) :
+CFunction::CFunction(uint32_t code_addr, IPluginContext *plugin, funcid_t id, uint32_t pub_id) :
m_codeaddr(code_addr), m_pContext(plugin), m_curparam(0),
m_errorstate(SP_ERROR_NONE), m_FnId(id)
{
@@ -82,6 +101,7 @@ CFunction::CFunction(uint32_t code_addr, IPluginContext *plugin, funcid_t id) :
{
m_pCtx = plugin->GetContext();
}
+ m_pContext->GetPublicByIndex(pub_id, &m_pPublic);
}
int CFunction::PushCell(cell_t cell)
@@ -319,3 +339,4 @@ funcid_t CFunction::GetFunctionID()
{
return m_FnId;
}
+
diff --git a/core/vm/sp_vm_function.h b/core/vm/sp_vm_function.h
index f1bf210b..e19417b0 100644
--- a/core/vm/sp_vm_function.h
+++ b/core/vm/sp_vm_function.h
@@ -55,7 +55,10 @@ class CFunction : public IPluginFunction
{
friend class SourcePawnEngine;
public:
- CFunction(uint32_t code_addr, IPluginContext *pContext, funcid_t fnid);
+ CFunction(uint32_t code_addr,
+ IPluginContext *pContext,
+ funcid_t fnid,
+ uint32_t pub_id);
public:
virtual int PushCell(cell_t cell);
virtual int PushCellByRef(cell_t *cell, int flags);
@@ -79,7 +82,7 @@ public:
bool IsRunnable();
funcid_t GetFunctionID();
public:
- void Set(uint32_t code_addr, IPluginContext *plugin, funcid_t fnid);
+ void Set(uint32_t code_addr, IPluginContext *plugin, funcid_t fnid, uint32_t pub_id);
private:
int _PushString(const char *string, int sz_flags, int cp_flags, size_t len);
inline int SetError(int err)
@@ -98,6 +101,7 @@ private:
CFunction *m_pNext;
bool m_Invalid;
funcid_t m_FnId;
+ sp_public_t *m_pPublic;
};
#endif //_INCLUDE_SOURCEMOD_BASEFUNCTION_H_
diff --git a/public/sourcepawn/sp_vm_api.h b/public/sourcepawn/sp_vm_api.h
index 7920188b..b0f4b917 100644
--- a/public/sourcepawn/sp_vm_api.h
+++ b/public/sourcepawn/sp_vm_api.h
@@ -41,10 +41,10 @@
#include "sp_vm_types.h"
/** SourcePawn Engine API Version */
-#define SOURCEPAWN_ENGINE_API_VERSION 2
+#define SOURCEPAWN_ENGINE_API_VERSION 3
/** SourcePawn VM API Version */
-#define SOURCEPAWN_VM_API_VERSION 5
+#define SOURCEPAWN_VM_API_VERSION 6
#if !defined SOURCEMOD_BUILD
#define SOURCEMOD_BUILD
@@ -688,6 +688,63 @@ namespace SourcePawn
virtual void OnContextExecuteError(IPluginContext *ctx, IContextTrace *error) =0;
};
+ /**
+ * @brief Represents a code profiler for plugins.
+ */
+ class IProfiler
+ {
+ public:
+ /**
+ * @brief Invoked by the JIT to notify that a native is being started.
+ *
+ * @param pContext Plugin context.
+ * @param native Native information.
+ */
+ virtual void OnNativeBegin(IPluginContext *pContext, sp_native_t *native) =0;
+
+ /**
+ * @brief Invoked by the JIT to notify that the last native on the stack
+ * is no longer being executed.
+ */
+ virtual void OnNativeEnd() =0;
+
+ /**
+ * @brief Invoked by the JIT to notify that a function call is starting.
+ *
+ * @param pContext Plugin context.
+ * @param name Function name, or NULL if not known.
+ * @param code_addr P-Code address.
+ */
+ virtual void OnFunctionBegin(IPluginContext *pContext, const char *name) =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.
+ */
+ virtual void OnFunctionEnd() =0;
+
+ /**
+ * @brief Invoked by the VM to notify that a forward/callback is starting.
+ *
+ * @param pContext Plugin context.
+ * @param pubfunc Public function information.
+ * @return Unique number to pass to OnFunctionEnd().
+ */
+ virtual int OnCallbackBegin(IPluginContext *pContext, sp_public_t *pubfunc) =0;
+
+ /**
+ * @brief Invoked by the JIT to notify that a callback has ended.
+ *
+ * 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().
+ */
+ virtual void OnCallbackEnd(int serial) =0;
+ };
/**
* @brief Contains helper functions used by VMs and the host app
diff --git a/public/sourcepawn/sp_vm_types.h b/public/sourcepawn/sp_vm_types.h
index a64efeaa..4b348a86 100644
--- a/public/sourcepawn/sp_vm_types.h
+++ b/public/sourcepawn/sp_vm_types.h
@@ -47,6 +47,13 @@ typedef uint32_t funcid_t; /**< Function index code */
#define SP_MAX_EXEC_PARAMS 32 /**< Maximum number of parameters in a function */
+#define SP_JITCONF_DEBUG "debug" /**< Configuration option for debugging. */
+#define SP_JITCONF_PROFILE "profile" /**< Configuration option for profiling. */
+
+#define SP_PROF_NATIVES (1<<0) /**< Profile natives. */
+#define SP_PROF_CALLBACKS (1<<1) /**< Profile callbacks. */
+#define SP_PROF_FUNCTIONS (1<<2) /**< Profile functions. */
+
/**
* @brief Error codes for SourcePawn routines.
*/
@@ -139,6 +146,7 @@ namespace SourcePawn
{
class IPluginContext;
class IVirtualMachine;
+ class IProfiler;
};
struct sp_context_s;
@@ -285,6 +293,8 @@ typedef struct sp_context_s
sp_debug_file_t *files; /**< Files */
sp_debug_line_t *lines; /**< Lines */
sp_debug_symbol_t *symbols; /**< Symbols */
+ SourcePawn::IProfiler *profiler; /**< Pointer to IProfiler */
+ uint32_t prof_flags; /**< Profiling flags */
} sp_context_t;
#endif //_INCLUDE_SOURCEPAWN_VM_TYPES_H
diff --git a/sourcepawn/jit/x86/dll_exports.cpp b/sourcepawn/jit/x86/dll_exports.cpp
index f5f4c552..c69666c1 100644
--- a/sourcepawn/jit/x86/dll_exports.cpp
+++ b/sourcepawn/jit/x86/dll_exports.cpp
@@ -25,7 +25,7 @@ EXPORTFUNC int GiveEnginePointer2(SourcePawn::ISourcePawnEngine *engine_p, unsig
{
engine = engine_p;
- if (api_version > SOURCEPAWN_ENGINE_API_VERSION)
+ if (api_version > SOURCEPAWN_ENGINE_API_VERSION || api_version < 2)
{
return SP_ERROR_PARAM;
}
diff --git a/sourcepawn/jit/x86/jit_x86.cpp b/sourcepawn/jit/x86/jit_x86.cpp
index 05e48271..6f3d5138 100644
--- a/sourcepawn/jit/x86/jit_x86.cpp
+++ b/sourcepawn/jit/x86/jit_x86.cpp
@@ -1278,12 +1278,95 @@ inline void WriteOp_Retn(JitWriter *jit)
IA32_Return(jit);
}
+void ProfCallGate_Begin(sp_context_t *ctx, const char *name)
+{
+ ctx->profiler->OnFunctionBegin(ctx->context, name);
+}
+
+void ProfCallGate_End(sp_context_t *ctx)
+{
+ ctx->profiler->OnFunctionEnd();
+}
+
+const char *find_func_name(sp_plugin_t *plugin, uint32_t offs)
+{
+ uint32_t max, iter;
+ sp_fdbg_symbol_t *sym;
+ sp_fdbg_arraydim_t *arr;
+ uint8_t *cursor = (uint8_t *)(plugin->debug.symbols);
+
+ max = plugin->debug.syms_num;
+ for (iter = 0; iter < max; iter++)
+ {
+ sym = (sp_fdbg_symbol_t *)cursor;
+
+ if (sym->ident == SP_SYM_FUNCTION
+ && sym->codestart <= offs
+ && sym->codeend > offs)
+ {
+ return plugin->debug.stringbase + sym->name;
+ }
+
+ if (sym->dimcount > 0)
+ {
+ cursor += sizeof(sp_fdbg_symbol_t);
+ arr = (sp_fdbg_arraydim_t *)cursor;
+ cursor += sizeof(sp_fdbg_arraydim_t) * sym->dimcount;
+ continue;
+ }
+
+ cursor += sizeof(sp_fdbg_symbol_t);
+ }
+
+ return NULL;
+}
+
inline void WriteOp_Call(JitWriter *jit)
{
- cell_t offs = jit->read_cell();
+ cell_t offs;
+ jitoffs_t jmp;
+ CompData *data;
+
+ data = (CompData *)jit->data;
+ offs = jit->read_cell();
- jitoffs_t jmp = IA32_Call_Imm32(jit, 0);
- IA32_Write_Jump32(jit, jmp, RelocLookup(jit, offs, false));
+ if ((data->profile & SP_PROF_FUNCTIONS) == SP_PROF_FUNCTIONS)
+ {
+ const char *name;
+
+ /* Find the function name */
+ if ((name = find_func_name(data->plugin, offs)) == NULL)
+ {
+ name = "unknown";
+ }
+
+ //push name
+ //push [esi+context]
+ //call ProfCallGate_Begin
+ //add esp, 8
+ IA32_Push_Imm32(jit, (jit_int32_t)(intptr_t)name);
+ IA32_Push_Rm_Disp8(jit, AMX_REG_INFO, AMX_INFO_CONTEXT);
+ jmp = IA32_Call_Imm32(jit, 0);
+ IA32_Write_Jump32_Abs(jit, jmp, (void *)ProfCallGate_Begin);
+ IA32_Add_Rm_Imm8(jit, REG_ESP, 8, MOD_REG);
+
+ //call
+ jmp = IA32_Call_Imm32(jit, 0);
+ IA32_Write_Jump32(jit, jmp, RelocLookup(jit, offs, false));
+
+ //push [esi+context]
+ //call ProfCallGate_End
+ //add esp, 4
+ IA32_Push_Rm_Disp8(jit, AMX_REG_INFO, AMX_INFO_CONTEXT);
+ jmp = IA32_Call_Imm32(jit, 0);
+ IA32_Write_Jump32_Abs(jit, jmp, (void *)ProfCallGate_End);
+ IA32_Add_Rm_Imm8(jit, REG_ESP, 4, MOD_REG);
+ }
+ else
+ {
+ jmp = IA32_Call_Imm32(jit, 0);
+ IA32_Write_Jump32(jit, jmp, RelocLookup(jit, offs, false));
+ }
}
inline void WriteOp_Bounds(JitWriter *jit)
@@ -1653,9 +1736,25 @@ inline void WriteOp_Sysreq_N(JitWriter *jit)
jitoffs_t call = IA32_Call_Imm32(jit, 0);
if (!data->debug)
{
- IA32_Write_Jump32_Abs(jit, call, (void *)NativeCallback);
- } else {
- IA32_Write_Jump32_Abs(jit, call, (void *)NativeCallback_Debug);
+ if ((data->profile & SP_PROF_NATIVES) == SP_PROF_NATIVES)
+ {
+ IA32_Write_Jump32_Abs(jit, call, (void *)NativeCallback_Profile);
+ }
+ else
+ {
+ IA32_Write_Jump32_Abs(jit, call, (void *)NativeCallback);
+ }
+ }
+ else
+ {
+ if ((data->profile & SP_PROF_NATIVES) == SP_PROF_NATIVES)
+ {
+ IA32_Write_Jump32_Abs(jit, call, (void *)NativeCallback_Debug_Profile);
+ }
+ else
+ {
+ IA32_Write_Jump32_Abs(jit, call, (void *)NativeCallback_Debug);
+ }
}
/* check for errors */
@@ -2122,7 +2221,9 @@ inline void WriteOp_FloatCompare(JitWriter *jit)
cell_t NativeCallback(sp_context_t *ctx, ucell_t native_idx, cell_t *params)
{
- sp_native_t *native = &ctx->natives[native_idx];
+ sp_native_t *native;
+
+ native = &ctx->natives[native_idx];
ctx->n_idx = native_idx;
@@ -2136,12 +2237,27 @@ cell_t NativeCallback(sp_context_t *ctx, ucell_t native_idx, cell_t *params)
return native->pfn(ctx->context, params);
}
-static cell_t InvalidNative(IPluginContext *pCtx, const cell_t *params)
+cell_t NativeCallback_Profile(sp_context_t *ctx, ucell_t native_idx, cell_t *params)
{
- sp_context_t *ctx = pCtx->GetContext();
- ctx->n_err = SP_ERROR_INVALID_NATIVE;
+ cell_t val;
+ sp_native_t *native;
- return 0;
+ native = &ctx->natives[native_idx];
+
+ ctx->n_idx = native_idx;
+
+ /* Technically both aren't needed, I guess */
+ if (native->status == SP_NATIVE_UNBOUND)
+ {
+ ctx->n_err = SP_ERROR_INVALID_NATIVE;
+ return 0;
+ }
+
+ ctx->profiler->OnNativeBegin(ctx->context, native);
+ val = native->pfn(ctx->context, params);
+ ctx->profiler->OnNativeEnd();
+
+ return val;
}
cell_t NativeCallback_Debug(sp_context_t *ctx, ucell_t native_idx, cell_t *params)
@@ -2170,7 +2286,7 @@ cell_t NativeCallback_Debug(sp_context_t *ctx, ucell_t native_idx, cell_t *param
}
cell_t result = NativeCallback(ctx, native_idx, params);
-
+
if (ctx->n_err != SP_ERROR_NONE)
{
return result;
@@ -2180,7 +2296,9 @@ cell_t NativeCallback_Debug(sp_context_t *ctx, ucell_t native_idx, cell_t *param
{
ctx->n_err = SP_ERROR_STACKLEAK;
return result;
- } else if (save_hp != ctx->hp) {
+ }
+ else if (save_hp != ctx->hp)
+ {
ctx->n_err = SP_ERROR_HEAPLEAK;
return result;
}
@@ -2188,6 +2306,60 @@ cell_t NativeCallback_Debug(sp_context_t *ctx, ucell_t native_idx, cell_t *param
return result;
}
+cell_t NativeCallback_Debug_Profile(sp_context_t *ctx, ucell_t native_idx, cell_t *params)
+{
+ cell_t save_sp = ctx->sp;
+ cell_t save_hp = ctx->hp;
+
+ ctx->n_idx = native_idx;
+
+ if (ctx->hp < ctx->heap_base)
+ {
+ ctx->n_err = SP_ERROR_HEAPMIN;
+ return 0;
+ }
+
+ if (ctx->hp + STACK_MARGIN > ctx->sp)
+ {
+ ctx->n_err = SP_ERROR_STACKLOW;
+ return 0;
+ }
+
+ if ((uint32_t)ctx->sp >= ctx->mem_size)
+ {
+ ctx->n_err = SP_ERROR_STACKMIN;
+ return 0;
+ }
+
+ cell_t result = NativeCallback_Profile(ctx, native_idx, params);
+
+ if (ctx->n_err != SP_ERROR_NONE)
+ {
+ return result;
+ }
+
+ if (save_sp != ctx->sp)
+ {
+ ctx->n_err = SP_ERROR_STACKLEAK;
+ return result;
+ }
+ else if (save_hp != ctx->hp)
+ {
+ ctx->n_err = SP_ERROR_HEAPLEAK;
+ return result;
+ }
+
+ return result;
+}
+
+static cell_t InvalidNative(IPluginContext *pCtx, const cell_t *params)
+{
+ sp_context_t *ctx = pCtx->GetContext();
+ ctx->n_err = SP_ERROR_INVALID_NATIVE;
+
+ return 0;
+}
+
jitoffs_t RelocLookup(JitWriter *jit, cell_t pcode_offs, bool relative)
{
if (jit->outptr)
@@ -2204,7 +2376,9 @@ jitoffs_t RelocLookup(JitWriter *jit, cell_t pcode_offs, bool relative)
assert(pcode_offs >= 0 && (uint32_t)pcode_offs <= data->codesize);
/* Do the lookup in the native dictionary. */
return *(jitoffs_t *)(data->rebase + pcode_offs);
- } else {
+ }
+ else
+ {
return 0;
}
}
@@ -2423,6 +2597,7 @@ jit_rewind:
ctx->heap_base = plugin->data_size;
ctx->hp = ctx->heap_base;
ctx->sp = ctx->mem_size - sizeof(cell_t);
+ ctx->prof_flags = data->profile;
const char *strbase = plugin->info.stringbase;
uint32_t max, iter;
@@ -2726,7 +2901,7 @@ bool JITX86::SetCompilationOption(ICompilation *co, const char *key, const char
{
CompData *data = (CompData *)co;
- if (strcmp(key, "debug") == 0)
+ if (strcmp(key, SP_JITCONF_DEBUG) == 0)
{
if ((atoi(val) == 1) || !strcmp(val, "yes"))
{
@@ -2741,6 +2916,18 @@ bool JITX86::SetCompilationOption(ICompilation *co, const char *key, const char
}
return true;
}
+ else if (strcmp(key, SP_JITCONF_PROFILE) == 0)
+ {
+ data->profile = atoi(val);
+
+ /** Callbacks must be profiled to profile functions! */
+ if ((data->profile & SP_PROF_FUNCTIONS) == SP_PROF_FUNCTIONS)
+ {
+ data->profile |= SP_PROF_CALLBACKS;
+ }
+
+ return true;
+ }
return false;
}
diff --git a/sourcepawn/jit/x86/jit_x86.h b/sourcepawn/jit/x86/jit_x86.h
index 1bdc1311..a1d9dc0b 100644
--- a/sourcepawn/jit/x86/jit_x86.h
+++ b/sourcepawn/jit/x86/jit_x86.h
@@ -67,13 +67,14 @@ class CompData : public ICompilation
{
public:
CompData() : plugin(NULL),
- debug(false), inline_level(0), rebase(NULL),
+ debug(false), profile(0), inline_level(0), rebase(NULL),
error_set(SP_ERROR_NONE), func_idx(0)
{
};
public:
sp_plugin_t *plugin; /* plugin handle */
bool debug; /* whether to compile debug mode */
+ int profile; /* profiling flags */
int inline_level; /* inline optimization level */
jitcode_t rebase; /* relocation map */
int error_set; /* error code to halt process */
@@ -121,6 +122,8 @@ public:
cell_t NativeCallback(sp_context_t *ctx, ucell_t native_idx, cell_t *params);
cell_t NativeCallback_Debug(sp_context_t *ctx, ucell_t native_idx, cell_t *params);
+cell_t NativeCallback_Debug_Profile(sp_context_t *ctx, ucell_t native_idx, cell_t *params);
+cell_t NativeCallback_Profile(sp_context_t *ctx, ucell_t native_idx, cell_t *params);
jitoffs_t RelocLookup(JitWriter *jit, cell_t pcode_offs, bool relative=false);
#define AMX_REG_PRI REG_EAX
diff --git a/tools/profiler/csharp/Comparators.cs b/tools/profiler/csharp/Comparators.cs
new file mode 100644
index 00000000..301aa70b
--- /dev/null
+++ b/tools/profiler/csharp/Comparators.cs
@@ -0,0 +1,85 @@
+using System;
+using System.Collections;
+using System.Text;
+using System.Windows.Forms;
+
+namespace profviewer
+{
+ class LIStringComparator : IComparer
+ {
+ private int m_col;
+
+ public LIStringComparator(int col)
+ {
+ m_col = col;
+ }
+
+ public int Compare(object x, object y)
+ {
+ ListViewItem a = (ListViewItem)x;
+ ListViewItem b = (ListViewItem)y;
+
+ return String.Compare(a.SubItems[m_col].Text, b.SubItems[m_col].Text);
+ }
+ }
+
+ class LIIntComparator : IComparer
+ {
+ private int m_col;
+
+ public LIIntComparator(int col)
+ {
+ m_col = col;
+ }
+
+ public int Compare(object x, object y)
+ {
+ ListViewItem a = (ListViewItem)x;
+ ListViewItem b = (ListViewItem)y;
+
+ int num1 = Int32.Parse(a.SubItems[m_col].Text);
+ int num2 = Int32.Parse(b.SubItems[m_col].Text);
+
+ if (num1 > num2)
+ {
+ return -1;
+ }
+ else if (num1 < num2)
+ {
+ return 1;
+ }
+
+ return 0;
+ }
+ }
+
+ class LIDoubleComparator : IComparer
+ {
+ private int m_col;
+
+ public LIDoubleComparator(int col)
+ {
+ m_col = col;
+ }
+
+ public int Compare(object x, object y)
+ {
+ ListViewItem a = (ListViewItem)x;
+ ListViewItem b = (ListViewItem)y;
+
+ double num1 = Double.Parse(a.SubItems[m_col].Text);
+ double num2 = Double.Parse(b.SubItems[m_col].Text);
+
+ if (num1 > num2)
+ {
+ return -1;
+ }
+ else if (num1 < num2)
+ {
+ return 1;
+ }
+
+ return 0;
+ }
+ }
+}
diff --git a/tools/profiler/csharp/Form1.Designer.cs b/tools/profiler/csharp/Form1.Designer.cs
new file mode 100644
index 00000000..8b2cdd04
--- /dev/null
+++ b/tools/profiler/csharp/Form1.Designer.cs
@@ -0,0 +1,244 @@
+namespace profviewer
+{
+ partial class Main
+ {
+ ///
+ /// Required designer variable.
+ ///
+ private System.ComponentModel.IContainer components = null;
+
+ ///
+ /// Clean up any resources being used.
+ ///
+ /// true if managed resources should be disposed; otherwise, false.
+ protected override void Dispose(bool disposing)
+ {
+ if (disposing && (components != null))
+ {
+ components.Dispose();
+ }
+ base.Dispose(disposing);
+ }
+
+ #region Windows Form Designer generated code
+
+ ///
+ /// Required method for Designer support - do not modify
+ /// the contents of this method with the code editor.
+ ///
+ private void InitializeComponent()
+ {
+ this.report_list = new System.Windows.Forms.ListView();
+ this.pr_type = new System.Windows.Forms.ColumnHeader();
+ this.pr_name = new System.Windows.Forms.ColumnHeader();
+ this.pr_calls = new System.Windows.Forms.ColumnHeader();
+ this.pr_avg_time = new System.Windows.Forms.ColumnHeader();
+ this.pr_min_time = new System.Windows.Forms.ColumnHeader();
+ this.pr_max_time = new System.Windows.Forms.ColumnHeader();
+ this.pr_total_time = new System.Windows.Forms.ColumnHeader();
+ this.menuStrip1 = new System.Windows.Forms.MenuStrip();
+ this.fileToolStripMenuItem = new System.Windows.Forms.ToolStripMenuItem();
+ this.menu_file_open = new System.Windows.Forms.ToolStripMenuItem();
+ this.toolStripMenuItem1 = new System.Windows.Forms.ToolStripSeparator();
+ this.menu_file_exit = new System.Windows.Forms.ToolStripMenuItem();
+ this.label1 = new System.Windows.Forms.Label();
+ this.report_info_starttime = new System.Windows.Forms.Label();
+ this.label2 = new System.Windows.Forms.Label();
+ this.report_info_duration = new System.Windows.Forms.Label();
+ this.dialog_open = new System.Windows.Forms.OpenFileDialog();
+ this.panel1 = new System.Windows.Forms.Panel();
+ this.menuStrip1.SuspendLayout();
+ this.panel1.SuspendLayout();
+ this.SuspendLayout();
+ //
+ // report_list
+ //
+ this.report_list.AllowColumnReorder = true;
+ this.report_list.AutoArrange = false;
+ this.report_list.Columns.AddRange(new System.Windows.Forms.ColumnHeader[] {
+ this.pr_type,
+ this.pr_name,
+ this.pr_calls,
+ this.pr_avg_time,
+ this.pr_min_time,
+ this.pr_max_time,
+ this.pr_total_time});
+ this.report_list.Dock = System.Windows.Forms.DockStyle.Fill;
+ this.report_list.Location = new System.Drawing.Point(0, 24);
+ this.report_list.MultiSelect = false;
+ this.report_list.Name = "report_list";
+ this.report_list.Size = new System.Drawing.Size(759, 300);
+ this.report_list.TabIndex = 0;
+ this.report_list.UseCompatibleStateImageBehavior = false;
+ this.report_list.View = System.Windows.Forms.View.Details;
+ this.report_list.ColumnClick += new System.Windows.Forms.ColumnClickEventHandler(this.report_list_ColumnClick);
+ //
+ // pr_type
+ //
+ this.pr_type.Text = "Type";
+ this.pr_type.Width = 71;
+ //
+ // pr_name
+ //
+ this.pr_name.Text = "Name";
+ this.pr_name.Width = 270;
+ //
+ // pr_calls
+ //
+ this.pr_calls.Text = "Calls";
+ this.pr_calls.Width = 61;
+ //
+ // pr_avg_time
+ //
+ this.pr_avg_time.Text = "Avg Time";
+ this.pr_avg_time.Width = 74;
+ //
+ // pr_min_time
+ //
+ this.pr_min_time.Text = "Min Time";
+ this.pr_min_time.Width = 78;
+ //
+ // pr_max_time
+ //
+ this.pr_max_time.Text = "Max Time";
+ this.pr_max_time.Width = 77;
+ //
+ // pr_total_time
+ //
+ this.pr_total_time.Text = "Total Time";
+ this.pr_total_time.Width = 84;
+ //
+ // menuStrip1
+ //
+ this.menuStrip1.Items.AddRange(new System.Windows.Forms.ToolStripItem[] {
+ this.fileToolStripMenuItem});
+ this.menuStrip1.Location = new System.Drawing.Point(0, 0);
+ this.menuStrip1.Name = "menuStrip1";
+ this.menuStrip1.Size = new System.Drawing.Size(759, 24);
+ this.menuStrip1.TabIndex = 1;
+ this.menuStrip1.Text = "menuStrip1";
+ //
+ // fileToolStripMenuItem
+ //
+ this.fileToolStripMenuItem.DropDownItems.AddRange(new System.Windows.Forms.ToolStripItem[] {
+ this.menu_file_open,
+ this.toolStripMenuItem1,
+ this.menu_file_exit});
+ this.fileToolStripMenuItem.Name = "fileToolStripMenuItem";
+ this.fileToolStripMenuItem.Size = new System.Drawing.Size(35, 20);
+ this.fileToolStripMenuItem.Text = "&File";
+ //
+ // menu_file_open
+ //
+ this.menu_file_open.Name = "menu_file_open";
+ this.menu_file_open.Size = new System.Drawing.Size(100, 22);
+ this.menu_file_open.Text = "&Open";
+ this.menu_file_open.Click += new System.EventHandler(this.menu_file_open_Click);
+ //
+ // toolStripMenuItem1
+ //
+ this.toolStripMenuItem1.Name = "toolStripMenuItem1";
+ this.toolStripMenuItem1.Size = new System.Drawing.Size(97, 6);
+ //
+ // menu_file_exit
+ //
+ this.menu_file_exit.Name = "menu_file_exit";
+ this.menu_file_exit.Size = new System.Drawing.Size(100, 22);
+ this.menu_file_exit.Text = "E&xit";
+ this.menu_file_exit.Click += new System.EventHandler(this.menu_file_exit_Click);
+ //
+ // label1
+ //
+ this.label1.AutoSize = true;
+ this.label1.Location = new System.Drawing.Point(3, 13);
+ this.label1.Name = "label1";
+ this.label1.Size = new System.Drawing.Size(76, 13);
+ this.label1.TabIndex = 2;
+ this.label1.Text = "Profile Started:";
+ //
+ // report_info_starttime
+ //
+ this.report_info_starttime.AutoSize = true;
+ this.report_info_starttime.Location = new System.Drawing.Point(79, 13);
+ this.report_info_starttime.Name = "report_info_starttime";
+ this.report_info_starttime.Size = new System.Drawing.Size(0, 13);
+ this.report_info_starttime.TabIndex = 3;
+ //
+ // label2
+ //
+ this.label2.AutoSize = true;
+ this.label2.Location = new System.Drawing.Point(264, 13);
+ this.label2.Name = "label2";
+ this.label2.Size = new System.Drawing.Size(82, 13);
+ this.label2.TabIndex = 4;
+ this.label2.Text = "Profile Duration:";
+ //
+ // report_info_duration
+ //
+ this.report_info_duration.AutoSize = true;
+ this.report_info_duration.Location = new System.Drawing.Point(346, 13);
+ this.report_info_duration.Name = "report_info_duration";
+ this.report_info_duration.Size = new System.Drawing.Size(0, 13);
+ this.report_info_duration.TabIndex = 5;
+ //
+ // dialog_open
+ //
+ this.dialog_open.FileName = "openFileDialog1";
+ this.dialog_open.Filter = "Profiler files|*.xml|All files|*.*";
+ //
+ // panel1
+ //
+ this.panel1.Controls.Add(this.label1);
+ this.panel1.Controls.Add(this.report_info_duration);
+ this.panel1.Controls.Add(this.report_info_starttime);
+ this.panel1.Controls.Add(this.label2);
+ this.panel1.Dock = System.Windows.Forms.DockStyle.Bottom;
+ this.panel1.Location = new System.Drawing.Point(0, 324);
+ this.panel1.Name = "panel1";
+ this.panel1.Size = new System.Drawing.Size(759, 33);
+ this.panel1.TabIndex = 6;
+ //
+ // Main
+ //
+ this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F);
+ this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font;
+ this.ClientSize = new System.Drawing.Size(759, 357);
+ this.Controls.Add(this.report_list);
+ this.Controls.Add(this.menuStrip1);
+ this.Controls.Add(this.panel1);
+ this.MainMenuStrip = this.menuStrip1;
+ this.Name = "Main";
+ this.Text = "SourceMod Profiler Report Viewer";
+ this.menuStrip1.ResumeLayout(false);
+ this.menuStrip1.PerformLayout();
+ this.panel1.ResumeLayout(false);
+ this.panel1.PerformLayout();
+ this.ResumeLayout(false);
+ this.PerformLayout();
+
+ }
+
+ #endregion
+
+ private System.Windows.Forms.ListView report_list;
+ private System.Windows.Forms.ColumnHeader pr_type;
+ private System.Windows.Forms.ColumnHeader pr_name;
+ private System.Windows.Forms.ColumnHeader pr_calls;
+ private System.Windows.Forms.ColumnHeader pr_avg_time;
+ private System.Windows.Forms.ColumnHeader pr_min_time;
+ private System.Windows.Forms.ColumnHeader pr_max_time;
+ private System.Windows.Forms.ColumnHeader pr_total_time;
+ private System.Windows.Forms.MenuStrip menuStrip1;
+ private System.Windows.Forms.ToolStripMenuItem fileToolStripMenuItem;
+ private System.Windows.Forms.ToolStripMenuItem menu_file_open;
+ private System.Windows.Forms.ToolStripSeparator toolStripMenuItem1;
+ private System.Windows.Forms.ToolStripMenuItem menu_file_exit;
+ private System.Windows.Forms.Label label1;
+ private System.Windows.Forms.Label report_info_starttime;
+ private System.Windows.Forms.Label label2;
+ private System.Windows.Forms.Label report_info_duration;
+ private System.Windows.Forms.OpenFileDialog dialog_open;
+ private System.Windows.Forms.Panel panel1;
+ }
+}
+
diff --git a/tools/profiler/csharp/Form1.cs b/tools/profiler/csharp/Form1.cs
new file mode 100644
index 00000000..d6af181e
--- /dev/null
+++ b/tools/profiler/csharp/Form1.cs
@@ -0,0 +1,98 @@
+using System;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Data;
+using System.Drawing;
+using System.Text;
+using System.Windows.Forms;
+
+namespace profviewer
+{
+ public partial class Main : Form
+ {
+ private ProfileReport m_Report;
+
+ public Main()
+ {
+ InitializeComponent();
+ }
+
+ private void menu_file_open_Click(object sender, EventArgs e)
+ {
+ DialogResult res;
+
+ res = dialog_open.ShowDialog(this);
+
+ if (res != DialogResult.OK)
+ {
+ return;
+ }
+
+ m_Report = null;
+
+ try
+ {
+ m_Report = new ProfileReport(dialog_open.FileName);
+ }
+ catch (System.Exception ex)
+ {
+ MessageBox.Show("Error opening or parsing file: " + ex.Message);
+ }
+
+ UpdateListView();
+ }
+
+ private void UpdateListView()
+ {
+ ProfileItem atom;
+ ListViewItem item;
+
+ if (m_Report == null)
+ {
+ report_list.Items.Clear();
+ report_info_duration.Text = "";
+ report_info_starttime.Text = "";
+ return;
+ }
+
+ report_info_duration.Text = m_Report.Duration.ToString() + " seconds";
+ report_info_starttime.Text = m_Report.StartTime.ToString();
+
+ for (int i = 0; i < m_Report.Count; i++)
+ {
+ atom = m_Report.GetItem(i);
+ item = new ListViewItem(ProfileReport.TypeStrings[(int)atom.type]);
+
+ item.SubItems.Add(atom.name);
+ item.SubItems.Add(atom.num_calls.ToString());
+ item.SubItems.Add(atom.AverageTime.ToString("F6"));
+ item.SubItems.Add(atom.min_time.ToString("F6"));
+ item.SubItems.Add(atom.max_time.ToString("F6"));
+ item.SubItems.Add(atom.total_time.ToString("F6"));
+
+ report_list.Items.Add(item);
+ }
+ }
+
+ private void menu_file_exit_Click(object sender, EventArgs e)
+ {
+ Application.Exit();
+ }
+
+ private void report_list_ColumnClick(object sender, ColumnClickEventArgs e)
+ {
+ if (e.Column == 1)
+ {
+ report_list.ListViewItemSorter = new LIStringComparator(1);
+ }
+ else if (e.Column == 2)
+ {
+ report_list.ListViewItemSorter = new LIIntComparator(2);
+ }
+ else if (e.Column > 2)
+ {
+ report_list.ListViewItemSorter = new LIDoubleComparator(e.Column);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/tools/profiler/csharp/Form1.resx b/tools/profiler/csharp/Form1.resx
new file mode 100644
index 00000000..e0c7795c
--- /dev/null
+++ b/tools/profiler/csharp/Form1.resx
@@ -0,0 +1,126 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ 17, 17
+
+
+ 126, 17
+
+
\ No newline at end of file
diff --git a/tools/profiler/csharp/ProfReport.cs b/tools/profiler/csharp/ProfReport.cs
new file mode 100644
index 00000000..88073834
--- /dev/null
+++ b/tools/profiler/csharp/ProfReport.cs
@@ -0,0 +1,157 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+using System.Xml;
+
+namespace profviewer
+{
+ enum ProfileType : int
+ {
+ ProfType_Unknown = 0,
+ ProfType_Native,
+ ProfType_Callback,
+ ProfType_Function
+ }
+
+ class ProfileItem
+ {
+ public string name;
+ public double total_time;
+ public uint num_calls;
+ public double min_time;
+ public double max_time;
+ public ProfileType type;
+
+ public double AverageTime
+ {
+ get
+ {
+ return total_time / num_calls;
+ }
+ }
+ }
+
+ class ProfileReport
+ {
+ public static string[] TypeStrings;
+ private DateTime m_start_time;
+ private double m_duration;
+ private List m_Items;
+
+ public int Count
+ {
+ get
+ {
+ return m_Items.Count;
+ }
+ }
+
+ static ProfileReport()
+ {
+ TypeStrings = new string[4];
+
+ TypeStrings[0] = "unknown";
+ TypeStrings[1] = "native";
+ TypeStrings[2] = "callback";
+ TypeStrings[3] = "function";
+ }
+
+ public ProfileReport(string file)
+ {
+ bool in_profile;
+ ProfileType type;
+ string cur_report;
+ XmlTextReader xml;
+
+ xml = new XmlTextReader(file);
+ xml.WhitespaceHandling = WhitespaceHandling.None;
+
+ m_Items = new List();
+
+ type = ProfileType.ProfType_Unknown;
+ in_profile = false;
+ cur_report = null;
+ while (xml.Read())
+ {
+ if (xml.NodeType == XmlNodeType.Element)
+ {
+ if (xml.Name.CompareTo("profile") == 0)
+ {
+ in_profile = true;
+ m_duration = Double.Parse(xml.GetAttribute("uptime"));
+ m_start_time = new DateTime(1970, 1, 1, 0, 0, 0);
+ m_start_time.AddSeconds(Int32.Parse(xml.GetAttribute("time")));
+ }
+ else if (in_profile)
+ {
+ if (xml.Name.CompareTo("report") == 0)
+ {
+ cur_report = xml.GetAttribute("name");
+ if (cur_report.CompareTo("natives") == 0)
+ {
+ type = ProfileType.ProfType_Native;
+ }
+ else if (cur_report.CompareTo("callbacks") == 0)
+ {
+ type = ProfileType.ProfType_Callback;
+ }
+ else if (cur_report.CompareTo("functions") == 0)
+ {
+ type = ProfileType.ProfType_Function;
+ }
+ else
+ {
+ type = ProfileType.ProfType_Unknown;
+ }
+ }
+ else if (xml.Name.CompareTo("item") == 0 && cur_report != null)
+ {
+ ProfileItem item;
+
+ item = new ProfileItem();
+ item.name = xml.GetAttribute("name");
+ item.max_time = Double.Parse(xml.GetAttribute("maxtime"));
+ item.min_time = Double.Parse(xml.GetAttribute("mintime"));
+ item.num_calls = UInt32.Parse(xml.GetAttribute("numcalls"));
+ item.total_time = Double.Parse(xml.GetAttribute("totaltime"));
+ item.type = type;
+ m_Items.Add(item);
+ }
+ }
+ }
+ else if (xml.NodeType == XmlNodeType.EndElement)
+ {
+ if (xml.Name.CompareTo("profile") == 0)
+ {
+ break;
+ }
+ else if (xml.Name.CompareTo("report") == 0)
+ {
+ cur_report = null;
+ }
+ }
+ }
+ }
+
+ public double Duration
+ {
+ get
+ {
+ return m_duration;
+ }
+ }
+
+ public DateTime StartTime
+ {
+ get
+ {
+ return m_start_time;
+ }
+ }
+
+ public ProfileItem GetItem(int i)
+ {
+ return m_Items[i];
+ }
+ }
+}
diff --git a/tools/profiler/csharp/Program.cs b/tools/profiler/csharp/Program.cs
new file mode 100644
index 00000000..a14aa1ae
--- /dev/null
+++ b/tools/profiler/csharp/Program.cs
@@ -0,0 +1,20 @@
+using System;
+using System.Collections.Generic;
+using System.Windows.Forms;
+
+namespace profviewer
+{
+ static class Program
+ {
+ ///
+ /// The main entry point for the application.
+ ///
+ [STAThread]
+ static void Main()
+ {
+ Application.EnableVisualStyles();
+ Application.SetCompatibleTextRenderingDefault(false);
+ Application.Run(new Main());
+ }
+ }
+}
\ No newline at end of file
diff --git a/tools/profiler/csharp/profviewer.csproj b/tools/profiler/csharp/profviewer.csproj
new file mode 100644
index 00000000..04f791cf
--- /dev/null
+++ b/tools/profiler/csharp/profviewer.csproj
@@ -0,0 +1,80 @@
+
+
+ Debug
+ AnyCPU
+ 8.0.50727
+ 2.0
+ {1EE11F57-B933-4D06-B0E6-EAFB60ACAC73}
+ WinExe
+ Properties
+ profviewer
+ profviewer
+
+
+ true
+ full
+ false
+ bin\Debug\
+ DEBUG;TRACE
+ prompt
+ 4
+
+
+ pdbonly
+ true
+ bin\Release\
+ TRACE
+ prompt
+ 4
+
+
+
+
+
+
+
+
+
+
+
+
+ Form
+
+
+ Form1.cs
+
+
+
+
+
+ Designer
+ Form1.cs
+
+
+ ResXFileCodeGenerator
+ Resources.Designer.cs
+ Designer
+
+
+ True
+ Resources.resx
+
+
+ SettingsSingleFileGenerator
+ Settings.Designer.cs
+
+
+ True
+ Settings.settings
+ True
+
+
+
+
+
\ No newline at end of file
diff --git a/tools/profiler/csharp/profviewer.sln b/tools/profiler/csharp/profviewer.sln
new file mode 100644
index 00000000..80e570af
--- /dev/null
+++ b/tools/profiler/csharp/profviewer.sln
@@ -0,0 +1,20 @@
+
+Microsoft Visual Studio Solution File, Format Version 9.00
+# Visual Studio 2005
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "profviewer", "profviewer.csproj", "{1EE11F57-B933-4D06-B0E6-EAFB60ACAC73}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {1EE11F57-B933-4D06-B0E6-EAFB60ACAC73}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {1EE11F57-B933-4D06-B0E6-EAFB60ACAC73}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {1EE11F57-B933-4D06-B0E6-EAFB60ACAC73}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {1EE11F57-B933-4D06-B0E6-EAFB60ACAC73}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+EndGlobal
diff --git a/tools/profiler/php/ProfFileParser.class.php b/tools/profiler/php/ProfFileParser.class.php
new file mode 100644
index 00000000..88b5d472
--- /dev/null
+++ b/tools/profiler/php/ProfFileParser.class.php
@@ -0,0 +1,73 @@
+report = FALSE;
+ $this->curtype = FALSE;
+
+ if (($contents = file_get_contents($file)) === FALSE)
+ {
+ $this->last_error = 'File not found';
+ return FALSE;
+ }
+
+ $xml = xml_parser_create();
+ xml_set_object($xml, $this);
+ xml_set_element_handler($xml, 'tag_open', 'tag_close');
+ xml_parser_set_option($xml, XML_OPTION_CASE_FOLDING, false);
+
+ if (!xml_parse($xml, $contents))
+ {
+ $this->last_error = 'Line: ' . xml_get_current_line_number($xml) . ' -- ' . xml_error_string(xml_get_error_code($xml));
+ return FALSE;
+ }
+
+ return $this->report;
+ }
+
+ public function tag_open($parser, $tag, $attrs)
+ {
+ if ($tag == 'profile')
+ {
+ $this->report = new ProfReport();
+ $this->report->time = $attrs['time'];
+ $this->report->uptime = $attrs['uptime'];
+ }
+ else if ($tag == 'report')
+ {
+ $this->curtype = $attrs['name'];
+ }
+ else if ($tag == 'item')
+ {
+ if ($this->report === FALSE || $this->curtype === FALSE)
+ {
+ return;
+ }
+ $attrs['type'] = $this->curtype;
+ $this->report->items[] = $attrs;
+ }
+ }
+
+ public function tag_close($parser, $tag)
+ {
+ if ($tag == 'report')
+ {
+ $this->curtype = FALSE;
+ }
+ }
+}
+
+?>