added amb256 - (nice number), profiler complete with gui to show files

--HG--
extra : convert_revision : svn%3A39bc706e-5318-0410-9160-8a85361fbb7c/trunk%401914
This commit is contained in:
David Anderson 2008-03-02 06:40:59 +00:00
parent f4e23a14dd
commit 0817bd0b8b
23 changed files with 1874 additions and 34 deletions

View File

@ -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"

483
core/Profiler.cpp Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*
* 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 <http://www.sourcemod.net/license.php>.
*
* 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, "<?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\n");
fprintf(fp, "<profile time=\"%d\" uptime=\"%f\">\n", (int)t, total_time);
WriteReport(fp, &m_Natives, "natives");
WriteReport(fp, &m_Callbacks, "callbacks");
WriteReport(fp, &m_Functions, "functions");
fprintf(fp, "</profile>\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, " <report name=\"%s\">\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), "<", "&lt;");
UTIL_ReplaceAll(new_name, sizeof(new_name), ">", "&gt;");
fprintf(fp, " <item name=\"%s\" numcalls=\"%d\" mintime=\"%f\" maxtime=\"%f\" totaltime=\"%f\"/>\n",
new_name,
ar->num_calls,
ar->min_time,
ar->max_time,
ar->total_time);
}
fprintf(fp, " </report>\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;
}
}

131
core/Profiler.h Normal file
View File

@ -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 <http://www.gnu.org/licenses/>.
*
* 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 <http://www.sourcemod.net/license.php>.
*
* Version: $Id$
*/
#ifndef _INCLUDE_SOURCEMOD_PLUGIN_PROFILER_H_
#define _INCLUDE_SOURCEMOD_PLUGIN_PROFILER_H_
#include <sp_vm_api.h>
#include <sm_platform.h>
#include <sm_trie_tpl.h>
#include <sh_vector.h>
#include <sh_stack.h>
#include <stdio.h>
#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<prof_atom_report_t *> m_ReportLookup;
CVector<prof_atom_report_t *> 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<prof_atom_t> 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_

View File

@ -1,7 +1,7 @@
<?xml version="1.0" encoding="Windows-1252"?>
<VisualStudioProject
ProjectType="Visual C++"
Version="8,00"
Version="8.00"
Name="sourcemod_mm"
ProjectGUID="{E39527CD-7CAB-4420-97CC-DA1B93B260BC}"
RootNamespace="sourcemod_mm"
@ -723,6 +723,10 @@
RelativePath="..\PlayerManager.cpp"
>
</File>
<File
RelativePath="..\Profiler.cpp"
>
</File>
<File
RelativePath="..\sm_autonatives.cpp"
>
@ -881,6 +885,10 @@
RelativePath="..\PlayerManager.h"
>
</File>
<File
RelativePath="..\Profiler.h"
>
</File>
<File
RelativePath="..\sm_autonatives.h"
>

View File

@ -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)

View File

@ -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;
}
}

View File

@ -39,6 +39,8 @@
* :TODO: Make functions allocate as a lump instead of individual allocations!
*/
extern IProfiler *sm_profiler;
namespace SourcePawn
{
class BaseContext :

View File

@ -32,12 +32,13 @@
#include <stdio.h>
#include <string.h>
#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;
}

View File

@ -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_

View File

@ -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

View File

@ -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

View File

@ -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;
}

View File

@ -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 <addr>
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;
}

View File

@ -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

View File

@ -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;
}
}
}

244
tools/profiler/csharp/Form1.Designer.cs generated Normal file
View File

@ -0,0 +1,244 @@
namespace profviewer
{
partial class Main
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
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;
}
}

View File

@ -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);
}
}
}
}

View File

@ -0,0 +1,126 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="menuStrip1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
<metadata name="dialog_open.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=2.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>126, 17</value>
</metadata>
</root>

View File

@ -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<ProfileItem> 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<ProfileItem>();
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];
}
}
}

View File

@ -0,0 +1,20 @@
using System;
using System.Collections.Generic;
using System.Windows.Forms;
namespace profviewer
{
static class Program
{
/// <summary>
/// The main entry point for the application.
/// </summary>
[STAThread]
static void Main()
{
Application.EnableVisualStyles();
Application.SetCompatibleTextRenderingDefault(false);
Application.Run(new Main());
}
}
}

View File

@ -0,0 +1,80 @@
<Project DefaultTargets="Build" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>8.0.50727</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{1EE11F57-B933-4D06-B0E6-EAFB60ACAC73}</ProjectGuid>
<OutputType>WinExe</OutputType>
<AppDesignerFolder>Properties</AppDesignerFolder>
<RootNamespace>profviewer</RootNamespace>
<AssemblyName>profviewer</AssemblyName>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug\</OutputPath>
<DefineConstants>DEBUG;TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>pdbonly</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release\</OutputPath>
<DefineConstants>TRACE</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Data" />
<Reference Include="System.Deployment" />
<Reference Include="System.Drawing" />
<Reference Include="System.Windows.Forms" />
<Reference Include="System.Xml" />
</ItemGroup>
<ItemGroup>
<Compile Include="Comparators.cs" />
<Compile Include="Form1.cs">
<SubType>Form</SubType>
</Compile>
<Compile Include="Form1.Designer.cs">
<DependentUpon>Form1.cs</DependentUpon>
</Compile>
<Compile Include="ProfReport.cs" />
<Compile Include="Program.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<EmbeddedResource Include="Form1.resx">
<SubType>Designer</SubType>
<DependentUpon>Form1.cs</DependentUpon>
</EmbeddedResource>
<EmbeddedResource Include="Properties\Resources.resx">
<Generator>ResXFileCodeGenerator</Generator>
<LastGenOutput>Resources.Designer.cs</LastGenOutput>
<SubType>Designer</SubType>
</EmbeddedResource>
<Compile Include="Properties\Resources.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Resources.resx</DependentUpon>
</Compile>
<None Include="Properties\Settings.settings">
<Generator>SettingsSingleFileGenerator</Generator>
<LastGenOutput>Settings.Designer.cs</LastGenOutput>
</None>
<Compile Include="Properties\Settings.Designer.cs">
<AutoGen>True</AutoGen>
<DependentUpon>Settings.settings</DependentUpon>
<DesignTimeSharedInput>True</DesignTimeSharedInput>
</Compile>
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets.
<Target Name="BeforeBuild">
</Target>
<Target Name="AfterBuild">
</Target>
-->
</Project>

View File

@ -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

View File

@ -0,0 +1,73 @@
<?php
class ProfReport
{
public $time;
public $uptime;
public $items = array();
}
class ProfReportParser
{
private $report;
private $curtype;
public $last_error;
public function Parse($file)
{
$this->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;
}
}
}
?>