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:
parent
f4e23a14dd
commit
0817bd0b8b
@ -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
483
core/Profiler.cpp
Normal 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), "<", "<");
|
||||
UTIL_ReplaceAll(new_name, sizeof(new_name), ">", ">");
|
||||
|
||||
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
131
core/Profiler.h
Normal 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_
|
@ -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"
|
||||
>
|
||||
|
@ -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)
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -39,6 +39,8 @@
|
||||
* :TODO: Make functions allocate as a lump instead of individual allocations!
|
||||
*/
|
||||
|
||||
extern IProfiler *sm_profiler;
|
||||
|
||||
namespace SourcePawn
|
||||
{
|
||||
class BaseContext :
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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_
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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
|
||||
|
85
tools/profiler/csharp/Comparators.cs
Normal file
85
tools/profiler/csharp/Comparators.cs
Normal 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
244
tools/profiler/csharp/Form1.Designer.cs
generated
Normal 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;
|
||||
}
|
||||
}
|
||||
|
98
tools/profiler/csharp/Form1.cs
Normal file
98
tools/profiler/csharp/Form1.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
126
tools/profiler/csharp/Form1.resx
Normal file
126
tools/profiler/csharp/Form1.resx
Normal 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>
|
157
tools/profiler/csharp/ProfReport.cs
Normal file
157
tools/profiler/csharp/ProfReport.cs
Normal 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];
|
||||
}
|
||||
}
|
||||
}
|
20
tools/profiler/csharp/Program.cs
Normal file
20
tools/profiler/csharp/Program.cs
Normal 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());
|
||||
}
|
||||
}
|
||||
}
|
80
tools/profiler/csharp/profviewer.csproj
Normal file
80
tools/profiler/csharp/profviewer.csproj
Normal 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>
|
20
tools/profiler/csharp/profviewer.sln
Normal file
20
tools/profiler/csharp/profviewer.sln
Normal 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
|
73
tools/profiler/php/ProfFileParser.class.php
Normal file
73
tools/profiler/php/ProfFileParser.class.php
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
?>
|
Loading…
Reference in New Issue
Block a user