#define PLUGIN_VERSION "1.1" #pragma semicolon 1 #pragma newdecls required #include <sourcemod> public Plugin myinfo = { name = "[ANY] [Debugger] Valve Profiler", description = "Measures per-plugin performance and provides a log with various counters", author = "Alex Dragokas", version = PLUGIN_VERSION, url = "https://github.com/dragokas/" }; /* Commands: - sm_debug - Start / stop vprof debug tracing Logfile: - addons/sourcemod/logs/profiler__<DATE>_<TIME>.log For details of implementation see also: https://github.com/alliedmodders/sourcemod/issues/1162 */ const float LOG_MAX_WAITTIME = 60.0; const float LOG_CHECK_INTERVAL = 5.0; char g_PathPrefix[PLATFORM_MAX_PATH], g_PathOrig[PLATFORM_MAX_PATH], g_PathProfilerLog[PLATFORM_MAX_PATH], g_PathCosole[] = "console.log"; ConVar g_CVarLogFile; Handle g_hTimer; bool g_bL4D2; int g_ptrFile; public APLRes AskPluginLoad2(Handle myself, bool late, char[] error, int err_max) { g_bL4D2 = (GetEngineVersion() == Engine_Left4Dead2); return APLRes_Success; } public void OnPluginStart() { CreateConVar("sm_prof_version", PLUGIN_VERSION, "Plugin Version", FCVAR_NOTIFY | FCVAR_DONTRECORD); g_CVarLogFile = FindConVar("con_logfile"); RegAdminCmd("sm_debug", Cmd_Debug, ADMFLAG_ROOT, "Start / stop the valve profiler"); BuildPath(Path_SM, g_PathPrefix, sizeof(g_PathPrefix), "logs/profiler_"); } public void OnConfigsExecuted() { g_CVarLogFile.GetString(g_PathOrig, sizeof(g_PathOrig)); } public Action Cmd_Debug(int client, int args) { static bool start; char sTime[32]; if( !start ) { delete g_hTimer; FormatTime(sTime, sizeof(sTime), "%F_%H-%M-%S", GetTime()); FormatEx(g_PathProfilerLog, sizeof(g_PathProfilerLog), "%s_%s.log", g_PathPrefix, sTime); if( g_bL4D2 ) { g_ptrFile = FileSize(g_PathCosole); } else { SetCvarSilent(g_CVarLogFile, g_PathProfilerLog); } ReplyToCommand(client, "\x04[START]\x05 Profiler is started..."); ServerCommand("vprof_on"); ServerExecute(); RequestFrame(OnFrameDelay); } else { ServerCommand("sm prof stop vprof"); ServerCommand("sm prof dump vprof"); ServerCommand("vprof_off"); ReplyToCommand(client, "\x04[STOP]\x05 Saving profiler log to: %s", g_PathProfilerLog); // Profiler needs some time for analysis if( g_bL4D2 ) { // L4D2 has bugged con_logfile: https://github.com/ValveSoftware/Source-1-Games/issues/3601 g_hTimer = CreateTimer(LOG_CHECK_INTERVAL, Timer_MirrorLog, 1); } else { g_hTimer = CreateTimer(LOG_MAX_WAITTIME, Timer_RestoreCvar); } } start = !start; return Plugin_Handled; } public void OnFrameDelay() { ServerCommand("sm prof start vprof"); } void SetCvarSilent(ConVar cvar, char[] value) { int flags = cvar.Flags; cvar.Flags &= ~ FCVAR_NOTIFY; cvar.SetString(value); cvar.Flags = flags; } public Action Timer_RestoreCvar(Handle timer) { SetCvarSilent(g_CVarLogFile, g_PathOrig); g_hTimer = null; return Plugin_Handled; } public Action Timer_MirrorLog(Handle timer, int init) { static float sec; if( init ) sec = 0.0; sec += LOG_CHECK_INTERVAL; if( sec > LOG_MAX_WAITTIME ) { g_hTimer = null; return Plugin_Handled; } if( FileSize(g_PathCosole) != g_ptrFile ) { File hr = OpenFile(g_PathCosole, "rb"); if( !hr ) { LogError("Cannot open file: %s", g_PathCosole); g_hTimer = null; return Plugin_Handled; } if( g_ptrFile != -1 ) { hr.Seek(g_ptrFile, SEEK_SET); } File hw = OpenFile(g_PathProfilerLog, "ab"); if( hw ) { static int bytesRead, buff[1024]; while( !hr.EndOfFile() ) { bytesRead = hr.Read(buff, sizeof(buff), 1); hw.Write(buff, bytesRead, 1); } delete hw; } g_ptrFile = hr.Position; delete hr; } g_hTimer = CreateTimer(LOG_CHECK_INTERVAL, Timer_MirrorLog, 0); return Plugin_Handled; }