blob: e51af0228c921c8872a3e47c977359db9cc8be13 [file]
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
#include "RuntimeBasePch.h"
#ifdef ENABLE_JS_ETW
#include "Core/EtwTraceCore.h"
#include "Base/EtwTrace.h"
using namespace Js;
//
// This C style callback is invoked by ETW when a trace session is started/stopped
// by an ETW controller for the Jscript and MSHTML providers.
//
void EtwCallbackApi::OnSessionChange(ULONG controlCode, PVOID callbackContext)
{
PMCGEN_TRACE_CONTEXT context = (PMCGEN_TRACE_CONTEXT)callbackContext;
// A manifest based provider can be enabled to multiple event tracing sessions
// As long as there is at least 1 enabled session, isEnabled will be TRUE
// We only care about Jscript events.
if(context->RegistrationHandle == Microsoft_JScriptHandle)
{
switch(controlCode)
{
case EVENT_CONTROL_CODE_ENABLE_PROVIDER:
case EVENT_CONTROL_CODE_CAPTURE_STATE:
if(McGenLevelKeywordEnabled(context,
TRACE_LEVEL_INFORMATION,
JSCRIPT_RUNDOWNSTART_KEYWORD))
{
EtwTrace::PerformRundown(/*start*/ true);
}
if(McGenLevelKeywordEnabled(context,
TRACE_LEVEL_INFORMATION,
JSCRIPT_RUNDOWNEND_KEYWORD))
{
EtwTrace::PerformRundown(/*start*/ false);
}
break;
case EVENT_CONTROL_CODE_DISABLE_PROVIDER:
break; // Do Nothing
}
}
}
//
// Registers the ETW provider - this is usually done on Jscript DLL load
// After registration, we will receive callbacks when ETW tracing is enabled/disabled.
//
void EtwTrace::Register()
{
EtwTraceCore::Register();
#ifdef TEST_ETW_EVENTS
TestEtwEventSink::Load();
#endif
}
//
// Unregister to ensure we do not get callbacks.
//
void EtwTrace::UnRegister()
{
EtwTraceCore::UnRegister();
#ifdef TEST_ETW_EVENTS
TestEtwEventSink::Unload();
#endif
}
//
// Enumerate through all the script contexts in the process and log events
// for each function loaded. Depending on the argument, start or end events are logged.
// In particular, a rundown is needed for the 'Attach' scenario of profiling.
//
void EtwTrace::PerformRundown(bool start)
{
// Lock threadContext list during etw rundown
AutoCriticalSection autoThreadContextCs(ThreadContext::GetCriticalSection());
ThreadContext * threadContext = ThreadContext::GetThreadContextList();
if(start)
{
JS_ETW(EventWriteDCStartInit());
}
else
{
JS_ETW(EventWriteDCEndInit());
}
while(threadContext != nullptr)
{
// Take etw rundown lock on this thread context
AutoCriticalSection autoEtwRundownCs(threadContext->GetFunctionBodyLock());
ScriptContext* scriptContext = threadContext->GetScriptContextList();
while(scriptContext != NULL)
{
if(scriptContext->IsClosed())
{
scriptContext = scriptContext->next;
continue;
}
if(start)
{
JS_ETW(EventWriteScriptContextDCStart(scriptContext));
if(scriptContext->GetSourceContextInfoMap() != nullptr)
{
scriptContext->GetSourceContextInfoMap()->Map( [=] (DWORD_PTR sourceContext, SourceContextInfo * sourceContextInfo)
{
if (sourceContext != Constants::NoHostSourceContext)
{
JS_ETW(LogSourceEvent(EventWriteSourceDCStart,
sourceContext,
scriptContext,
/* sourceFlags*/ 0,
sourceContextInfo->url));
}
});
}
}
else
{
JS_ETW(EventWriteScriptContextDCEnd(scriptContext));
if(scriptContext->GetSourceContextInfoMap() != nullptr)
{
scriptContext->GetSourceContextInfoMap()->Map( [=] (DWORD_PTR sourceContext, SourceContextInfo * sourceContextInfo)
{
if (sourceContext != Constants::NoHostSourceContext)
{
JS_ETW(LogSourceEvent(EventWriteSourceDCEnd,
sourceContext,
scriptContext,
/* sourceFlags*/ 0,
sourceContextInfo->url));
}
});
}
}
scriptContext->MapFunction([&start] (FunctionBody* body)
{
#if DYNAMIC_INTERPRETER_THUNK
if(body->HasInterpreterThunkGenerated())
{
if(start)
{
LogMethodInterpretedThunkEvent(EventWriteMethodDCStart, body);
}
else
{
LogMethodInterpretedThunkEvent(EventWriteMethodDCEnd, body);
}
}
#endif
#if ENABLE_NATIVE_CODEGEN
body->MapEntryPoints([&](int index, FunctionEntryPointInfo * entryPoint)
{
if(entryPoint->IsCodeGenDone())
{
if (start)
{
LogMethodNativeEvent(EventWriteMethodDCStart, body, entryPoint);
}
else
{
LogMethodNativeEvent(EventWriteMethodDCEnd, body, entryPoint);
}
}
});
// the functionBody may have not have bytecode generated yet before registering to utf8SourceInfo
// accessing MapLoopHeaders in background thread can causes the functionBody counters locked for updating
// and cause assertion when bytecode generation is done and updating bytecodeCount counter on functionBody
// so check if the functionBody has done loopbody codegen in advance to not call into Map function in case
// loopbody codegen is not done yet
if (body->GetHasDoneLoopBodyCodeGen())
{
body->MapLoopHeadersWithLock([&](uint loopNumber, LoopHeader* header)
{
header->MapEntryPoints([&](int index, LoopEntryPointInfo * entryPoint)
{
if (entryPoint->IsCodeGenDone())
{
if (start)
{
LogLoopBodyEventBG(EventWriteMethodDCStart, body, header, entryPoint, ((uint16)body->GetLoopNumberWithLock(header)));
}
else
{
LogLoopBodyEventBG(EventWriteMethodDCEnd, body, header, entryPoint, ((uint16)body->GetLoopNumberWithLock(header)));
}
}
});
});
}
#endif
});
scriptContext = scriptContext->next;
}
#ifdef NTBUILD
if (EventEnabledJSCRIPT_HOSTING_CEO_START())
{
threadContext->EtwLogPropertyIdList();
}
#endif
threadContext = threadContext->Next();
}
if(start)
{
JS_ETW(EventWriteDCStartComplete());
}
else
{
JS_ETW(EventWriteDCEndComplete());
}
}
//
// Returns an ID for the source file of the function.
//
DWORD_PTR EtwTrace::GetSourceId(FunctionBody* body)
{
DWORD_PTR sourceId = body->GetHostSourceContext();
// For dynamic scripts - use fixed source ID of -1.
// TODO: Find a way to generate unique ID for dynamic scripts.
if(sourceId == Js::Constants::NoHostSourceContext)
{
sourceId = (DWORD_PTR)-1;
}
return sourceId;
}
//
// Returns an ID to identify the function.
//
uint EtwTrace::GetFunctionId(FunctionProxy* body)
{
return body->GetFunctionNumber();
}
void EtwTrace::LogSourceUnloadEvents(ScriptContext* scriptContext)
{
if(scriptContext->GetSourceContextInfoMap() != nullptr)
{
scriptContext->GetSourceContextInfoMap()->Map( [&] (DWORD_PTR sourceContext, SourceContextInfo * sourceContextInfo)
{
if(sourceContext != Constants::NoHostSourceContext)
{
JS_ETW(LogSourceEvent(EventWriteSourceUnload,
sourceContext,
scriptContext,
/* sourceFlags*/ 0,
sourceContextInfo->url));
}
});
}
JS_ETW(EventWriteScriptContextUnload(scriptContext));
}
void EtwTrace::LogMethodInterpreterThunkLoadEvent(FunctionBody* body)
{
#if DYNAMIC_INTERPRETER_THUNK
LogMethodInterpretedThunkEvent(EventWriteMethodLoad, body);
#else
Assert(false); // Caller should not be enabled if Dynamic Interpreter Thunks are disabled
#endif
}
void EtwTrace::LogMethodNativeLoadEvent(FunctionBody* body, FunctionEntryPointInfo* entryPoint)
{
#if ENABLE_NATIVE_CODEGEN
LogMethodNativeEvent(EventWriteMethodLoad, body, entryPoint);
#else
Assert(false); // Caller should not be enabled if JIT is disabled
#endif
}
void EtwTrace::LogLoopBodyLoadEvent(FunctionBody* body, LoopHeader* loopHeader, LoopEntryPointInfo* entryPoint, uint16 loopNumber)
{
#if ENABLE_NATIVE_CODEGEN
LogLoopBodyEventBG(EventWriteMethodLoad, body, loopHeader, entryPoint, loopNumber);
#else
Assert(false); // Caller should not be enabled if JIT is disabled
#endif
}
void EtwTrace::LogMethodInterpreterThunkUnloadEvent(FunctionBody* body)
{
#if DYNAMIC_INTERPRETER_THUNK
LogMethodInterpretedThunkEvent(EventWriteMethodUnload, body);
#else
Assert(false); // Caller should not be enabled if dynamic interpreter thunks are disabled
#endif
}
void EtwTrace::LogMethodNativeUnloadEvent(FunctionBody* body, FunctionEntryPointInfo* entryPoint)
{
#if ENABLE_NATIVE_CODEGEN
LogMethodNativeEvent(EventWriteMethodUnload, body, entryPoint);
#else
Assert(false); // Caller should not be enabled if JIT is disabled
#endif
}
void EtwTrace::LogLoopBodyUnloadEvent(FunctionBody* body, LoopHeader* loopHeader, LoopEntryPointInfo* entryPoint)
{
#if ENABLE_NATIVE_CODEGEN
LogLoopBodyEvent(EventWriteMethodUnload, body, loopHeader, entryPoint);
#else
Assert(false); // Caller should not be enabled if JIT is disabled
#endif
}
//
// Logs the runtime script context load event
//
void EtwTrace::LogScriptContextLoadEvent(ScriptContext* scriptContext)
{
JS_ETW(EventWriteScriptContextLoad(
scriptContext));
}
//
// Logs the runtime source module load event.
//
void EtwTrace::LogSourceModuleLoadEvent(ScriptContext* scriptContext, DWORD_PTR sourceContext, _In_z_ const char16* url)
{
AssertMsg(sourceContext != Constants::NoHostSourceContext, "We should not be logged this if there is no source code available");
JS_ETW(LogSourceEvent(EventWriteSourceLoad,
sourceContext,
scriptContext,
/* sourceFlags*/ 0,
url));
}
//
// This emulates the logic used by the F12 profiler to give names to functions
//
const char16* EtwTrace::GetFunctionName(FunctionBody* body)
{
return body->GetExternalDisplayName();
}
size_t EtwTrace::GetLoopBodyName(_In_ FunctionBody* body, _In_ LoopHeader* loopHeader, _Out_writes_opt_z_(size) char16* nameBuffer, _In_ size_t size)
{
return body->GetLoopBodyName(body->GetLoopNumber(loopHeader), nameBuffer, size);
}
_Success_(return == 0)
size_t EtwTrace::GetSimpleJitFunctionName(
Js::FunctionBody *const body,
_Out_writes_opt_z_(nameCharCapacity) char16 *const name,
const size_t nameCharCapacity)
{
Assert(body);
Assert(name);
Assert(nameCharCapacity != 0);
const char16 *const suffix = _u("Simple");
const size_t suffixCharLength = _countof(_u("Simple")) - 1;
const char16 *const functionName = GetFunctionName(body);
const size_t functionNameCharLength = wcslen(functionName);
const size_t requiredCharCapacity = functionNameCharLength + suffixCharLength + 1;
if(requiredCharCapacity > nameCharCapacity || name == NULL)
{
return requiredCharCapacity;
}
wcscpy_s(name, nameCharCapacity, functionName);
wcscpy_s(&name[functionNameCharLength], nameCharCapacity - functionNameCharLength, suffix);
return 0;
}
#endif