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; + } + } +} + +?>