Disable profiler bootstrap process for .NET Core (#314)
* Disable profiler bootstrap process for .NET Core * Update src/OpenTelemetry.ClrProfiler.Native/cor_profiler.cpp Fixes spelling error. Co-authored-by: Paulo Janotti <pjanotti@splunk.com> * Update tests to use startup hook bootstrapping. * Add test to validate startup hook is necessary * Log if startup hook is not configured correctly. * Add native tests for the startup hook validation. * Update const and pass by ref for variables and parameters for startup hook functions * Updating test and sample apps to use the startup hook. * Updating scripts to set startup hook environment variable. * Shutdown profiler if startup hook not configured correctly. * Use const ref for all startup hook params and use OTEL_ naming convention. Co-authored-by: Paulo Janotti <pjanotti@splunk.com>
This commit is contained in:
parent
04e64e54b2
commit
372d9ac871
|
@ -1,5 +1,6 @@
|
||||||
export CORECLR_ENABLE_PROFILING=1
|
export CORECLR_ENABLE_PROFILING=1
|
||||||
export CORECLR_PROFILER='{918728DD-259F-4A6A-AC2B-B85E1B658318}'
|
export CORECLR_PROFILER='{918728DD-259F-4A6A-AC2B-B85E1B658318}'
|
||||||
export CORECLR_PROFILER_PATH=/opt/opentelemetry-dotnet-autoinstrumentation/OpenTelemetry.ClrProfiler.Native.so
|
export CORECLR_PROFILER_PATH=/opt/opentelemetry-dotnet-autoinstrumentation/OpenTelemetry.ClrProfiler.Native.so
|
||||||
|
export DOTNET_STARTUP_HOOKS=/opt/opentelemetry-dotnet-autoinstrumentation/OpenTelemetry.Instrumentation.StartupHook.dll
|
||||||
export OTEL_INTEGRATIONS=/opt/opentelemetry-dotnet-autoinstrumentation/integrations.json
|
export OTEL_INTEGRATIONS=/opt/opentelemetry-dotnet-autoinstrumentation/integrations.json
|
||||||
export OTEL_DOTNET_TRACER_HOME=/opt/opentelemetry-dotnet-autoinstrumentation
|
export OTEL_DOTNET_TRACER_HOME=/opt/opentelemetry-dotnet-autoinstrumentation
|
|
@ -53,6 +53,7 @@ then
|
||||||
export CORECLR_PROFILER_PATH_64="${CURDIR}/bin/tracer-home/win-x64/OpenTelemetry.ClrProfiler.Native.${SUFIX}"
|
export CORECLR_PROFILER_PATH_64="${CURDIR}/bin/tracer-home/win-x64/OpenTelemetry.ClrProfiler.Native.${SUFIX}"
|
||||||
export CORECLR_PROFILER_PATH_32="${CURDIR}/bin/tracer-home/win-x86/OpenTelemetry.ClrProfiler.Native.${SUFIX}"
|
export CORECLR_PROFILER_PATH_32="${CURDIR}/bin/tracer-home/win-x86/OpenTelemetry.ClrProfiler.Native.${SUFIX}"
|
||||||
fi
|
fi
|
||||||
|
export DOTNET_STARTUP_HOOKS="${CURDIR}/bin/tracer-home/netcoreapp3.1/OpenTelemetry.Instrumentation.StartupHook.dll"
|
||||||
|
|
||||||
# Configure OpenTelemetry Tracer
|
# Configure OpenTelemetry Tracer
|
||||||
export OTEL_DOTNET_TRACER_HOME="${CURDIR}/bin/tracer-home"
|
export OTEL_DOTNET_TRACER_HOME="${CURDIR}/bin/tracer-home"
|
||||||
|
|
|
@ -22,7 +22,7 @@ namespace Samples.AspNetCoreMvc.Controllers
|
||||||
ViewBag.ClrProfilerAssemblyLocation = instrumentationType?.Assembly.Location;
|
ViewBag.ClrProfilerAssemblyLocation = instrumentationType?.Assembly.Location;
|
||||||
ViewBag.StackTrace = StackTraceHelper.GetUsefulStack();
|
ViewBag.StackTrace = StackTraceHelper.GetUsefulStack();
|
||||||
|
|
||||||
var prefixes = new[] { "COR_", "CORECLR_", "OTEL_" };
|
var prefixes = new[] { "COR_", "CORECLR_", "DOTNET_", "OTEL_" };
|
||||||
|
|
||||||
var envVars = from envVar in Environment.GetEnvironmentVariables().Cast<DictionaryEntry>()
|
var envVars = from envVar in Environment.GetEnvironmentVariables().Cast<DictionaryEntry>()
|
||||||
from prefix in prefixes
|
from prefix in prefixes
|
||||||
|
|
|
@ -16,10 +16,11 @@
|
||||||
|
|
||||||
"CORECLR_ENABLE_PROFILING": "1",
|
"CORECLR_ENABLE_PROFILING": "1",
|
||||||
"CORECLR_PROFILER": "{918728DD-259F-4A6A-AC2B-B85E1B658318}",
|
"CORECLR_PROFILER": "{918728DD-259F-4A6A-AC2B-B85E1B658318}",
|
||||||
"CORECLR_PROFILER_PATH": "$(ProjectDir)$(OutputPath)profiler-lib\\OpenTelemetry.ClrProfiler.Native.dll",
|
"CORECLR_PROFILER_PATH": "$(SolutionDir)bin\\tracer-home\\win-x64\\OpenTelemetry.ClrProfiler.Native.dll",
|
||||||
|
"DOTNET_STARTUP_HOOKS": "$(SolutionDir)bin\\tracer-home\\netcoreapp3.1\\OpenTelemetry.Instrumentation.StartupHook.dll",
|
||||||
|
|
||||||
"OTEL_DOTNET_TRACER_HOME": "$(ProjectDir)$(OutputPath)profiler-lib",
|
"OTEL_DOTNET_TRACER_HOME": "$(SolutionDir)bin\\tracer-home",
|
||||||
"OTEL_INTEGRATIONS": "$(ProjectDir)$(OutputPath)profiler-lib\\integrations.json",
|
"OTEL_INTEGRATIONS": "$(SolutionDir)bin\\tracer-home\\integrations.json",
|
||||||
"OTEL_SERVICE_NAME": "Samples.AspNetCoreMvc31",
|
"OTEL_SERVICE_NAME": "Samples.AspNetCoreMvc31",
|
||||||
"OTEL_EXPORTER": "jaeger",
|
"OTEL_EXPORTER": "jaeger",
|
||||||
"OTEL_EXPORTER_JAEGER_AGENT_HOST": "localhost",
|
"OTEL_EXPORTER_JAEGER_AGENT_HOST": "localhost",
|
||||||
|
@ -36,10 +37,11 @@
|
||||||
|
|
||||||
"CORECLR_ENABLE_PROFILING": "1",
|
"CORECLR_ENABLE_PROFILING": "1",
|
||||||
"CORECLR_PROFILER": "{918728DD-259F-4A6A-AC2B-B85E1B658318}",
|
"CORECLR_PROFILER": "{918728DD-259F-4A6A-AC2B-B85E1B658318}",
|
||||||
"CORECLR_PROFILER_PATH": "$(ProjectDir)$(OutputPath)profiler-lib\\OpenTelemetry.ClrProfiler.Native.dll",
|
"CORECLR_PROFILER_PATH": "$(SolutionDir)bin\\tracer-home$(OutputPath)\\OpenTelemetry.ClrProfiler.Native.dll",
|
||||||
|
"DOTNET_STARTUP_HOOKS": "$(SolutionDir)bin\\tracer-home\\netcoreapp3.1\\OpenTelemetry.Instrumentation.StartupHook.dll",
|
||||||
|
|
||||||
"OTEL_DOTNET_TRACER_HOME": "$(ProjectDir)$(OutputPath)profiler-lib",
|
"OTEL_DOTNET_TRACER_HOME": "$(SolutionDir)bin\\tracer-home",
|
||||||
"OTEL_INTEGRATIONS": "$(ProjectDir)$(OutputPath)profiler-lib\\integrations.json",
|
"OTEL_INTEGRATIONS": "$(SolutionDir)bin\\tracer-home\\integrations.json",
|
||||||
|
|
||||||
"OTEL_SERVICE_NAME": "Samples.AspNetCoreMvc31",
|
"OTEL_SERVICE_NAME": "Samples.AspNetCoreMvc31",
|
||||||
"OTEL_EXPORTER": "jaeger",
|
"OTEL_EXPORTER": "jaeger",
|
||||||
|
|
|
@ -202,6 +202,7 @@
|
||||||
<ClInclude Include="module_metadata.h" />
|
<ClInclude Include="module_metadata.h" />
|
||||||
<ClInclude Include="pal.h" />
|
<ClInclude Include="pal.h" />
|
||||||
<ClInclude Include="rejit_handler.h" />
|
<ClInclude Include="rejit_handler.h" />
|
||||||
|
<ClInclude Include="startup_hook.h" />
|
||||||
<ClInclude Include="stats.h" />
|
<ClInclude Include="stats.h" />
|
||||||
<ClInclude Include="string.h" />
|
<ClInclude Include="string.h" />
|
||||||
<ClInclude Include="util.h" />
|
<ClInclude Include="util.h" />
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
#include "module_metadata.h"
|
#include "module_metadata.h"
|
||||||
#include "pal.h"
|
#include "pal.h"
|
||||||
#include "resource.h"
|
#include "resource.h"
|
||||||
|
#include "startup_hook.h"
|
||||||
#include "stats.h"
|
#include "stats.h"
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
#include "version.h"
|
#include "version.h"
|
||||||
|
@ -119,6 +120,10 @@ HRESULT STDMETHODCALLTYPE CorProfiler::Initialize(IUnknown* cor_profiler_info_un
|
||||||
if (SUCCEEDED(hr))
|
if (SUCCEEDED(hr))
|
||||||
{
|
{
|
||||||
Logger::Debug("Interface ICorProfilerInfo10 found.");
|
Logger::Debug("Interface ICorProfilerInfo10 found.");
|
||||||
|
// .NET Core applications should use the dotnet startup hook to bootstrap OpenTelemetry so that the
|
||||||
|
// necessary dependencies will be available. Bootstrapping with the profiling APIs occurs too early
|
||||||
|
// and the necessary dependencies are not available yet.
|
||||||
|
use_dotnet_startuphook_bootstrapper = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -135,6 +140,17 @@ HRESULT STDMETHODCALLTYPE CorProfiler::Initialize(IUnknown* cor_profiler_info_un
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (use_dotnet_startuphook_bootstrapper)
|
||||||
|
{
|
||||||
|
const auto home_path = GetEnvironmentValue(environment::profiler_home_path);
|
||||||
|
const auto startup_hooks = GetEnvironmentValue(environment::dotnet_startup_hooks);
|
||||||
|
if (!IsStartupHookEnabled(startup_hooks, home_path))
|
||||||
|
{
|
||||||
|
Logger::Error("The required startup hook was not configured correctly. No telemetry will be captured.");
|
||||||
|
return E_FAIL;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (IsAzureAppServices())
|
if (IsAzureAppServices())
|
||||||
{
|
{
|
||||||
Logger::Info("Profiler is operating within Azure App Services context.");
|
Logger::Info("Profiler is operating within Azure App Services context.");
|
||||||
|
@ -742,7 +758,7 @@ HRESULT STDMETHODCALLTYPE CorProfiler::JITCompilationStarted(FunctionID function
|
||||||
{
|
{
|
||||||
auto _ = trace::Stats::Instance()->JITCompilationStartedMeasure();
|
auto _ = trace::Stats::Instance()->JITCompilationStartedMeasure();
|
||||||
|
|
||||||
if (!is_attached_ || !is_safe_to_block)
|
if (!is_attached_ || !is_safe_to_block || use_dotnet_startuphook_bootstrapper)
|
||||||
{
|
{
|
||||||
return S_OK;
|
return S_OK;
|
||||||
}
|
}
|
||||||
|
|
|
@ -29,6 +29,7 @@ private:
|
||||||
|
|
||||||
// Startup helper variables
|
// Startup helper variables
|
||||||
bool first_jit_compilation_completed = false;
|
bool first_jit_compilation_completed = false;
|
||||||
|
bool use_dotnet_startuphook_bootstrapper = false;
|
||||||
|
|
||||||
bool instrument_domain_neutral_assemblies = false;
|
bool instrument_domain_neutral_assemblies = false;
|
||||||
bool corlib_module_loaded = false;
|
bool corlib_module_loaded = false;
|
||||||
|
|
|
@ -26,7 +26,8 @@ const WSTRING env_vars_to_display[]{environment::tracing_enabled,
|
||||||
environment::azure_app_services,
|
environment::azure_app_services,
|
||||||
environment::azure_app_services_app_pool_id,
|
environment::azure_app_services_app_pool_id,
|
||||||
environment::azure_app_services_cli_telemetry_profile_value,
|
environment::azure_app_services_cli_telemetry_profile_value,
|
||||||
environment::internal_trace_profiler_path};
|
environment::internal_trace_profiler_path,
|
||||||
|
environment::dotnet_startup_hooks};
|
||||||
|
|
||||||
const WSTRING skip_assembly_prefixes[]{
|
const WSTRING skip_assembly_prefixes[]{
|
||||||
WStr("Microsoft.AI"),
|
WStr("Microsoft.AI"),
|
||||||
|
|
|
@ -89,6 +89,12 @@ const WSTRING internal_trace_profiler_path =
|
||||||
// Sets whether to enable NGEN images.
|
// Sets whether to enable NGEN images.
|
||||||
const WSTRING clr_enable_ngen = WStr("OTEL_CLR_ENABLE_NGEN");
|
const WSTRING clr_enable_ngen = WStr("OTEL_CLR_ENABLE_NGEN");
|
||||||
|
|
||||||
|
// The list of startup hooks defined for .NET Core 3.1+ applications.
|
||||||
|
// This is a .NET runtime environment variable.
|
||||||
|
// See https://github.com/dotnet/runtime/blob/main/docs/design/features/host-startup-hook.md
|
||||||
|
// for more information about this environment variable.
|
||||||
|
const WSTRING dotnet_startup_hooks = WStr("DOTNET_STARTUP_HOOKS");
|
||||||
|
|
||||||
} // namespace environment
|
} // namespace environment
|
||||||
} // namespace trace
|
} // namespace trace
|
||||||
|
|
||||||
|
|
|
@ -22,6 +22,12 @@
|
||||||
#include "string.h" // NOLINT
|
#include "string.h" // NOLINT
|
||||||
#include "util.h"
|
#include "util.h"
|
||||||
|
|
||||||
|
#ifdef _WIN32
|
||||||
|
#define DIR_SEPARATOR WStr('\\')
|
||||||
|
#else
|
||||||
|
#define DIR_SEPARATOR WStr('/')
|
||||||
|
#endif
|
||||||
|
|
||||||
namespace trace
|
namespace trace
|
||||||
{
|
{
|
||||||
|
|
||||||
|
@ -34,13 +40,7 @@ inline WSTRING GetDatadogLogFilePath(const std::string& file_name_suffix)
|
||||||
|
|
||||||
if (directory.length() > 0)
|
if (directory.length() > 0)
|
||||||
{
|
{
|
||||||
return directory +
|
return directory + DIR_SEPARATOR + ToWSTRING(file_name);
|
||||||
#ifdef _WIN32
|
|
||||||
WStr('\\') +
|
|
||||||
#else
|
|
||||||
WStr('/') +
|
|
||||||
#endif
|
|
||||||
ToWSTRING(file_name);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
WSTRING path = GetEnvironmentValue(TLoggerPolicy::logging_environment::log_path);
|
WSTRING path = GetEnvironmentValue(TLoggerPolicy::logging_environment::log_path);
|
||||||
|
|
|
@ -0,0 +1,30 @@
|
||||||
|
#ifndef OTEL_CLR_PROFILER_STARTUP_HOOK_H_
|
||||||
|
#define OTEL_CLR_PROFILER_STARTUP_HOOK_H_
|
||||||
|
|
||||||
|
#include "string.h" // NOLINT
|
||||||
|
#include "pal.h"
|
||||||
|
|
||||||
|
namespace trace
|
||||||
|
{
|
||||||
|
|
||||||
|
inline WSTRING GetExpectedStartupHookPath(const WSTRING& home_path) {
|
||||||
|
WSTRING separator = EmptyWStr;
|
||||||
|
if (home_path.back() != DIR_SEPARATOR) {
|
||||||
|
separator = DIR_SEPARATOR;
|
||||||
|
}
|
||||||
|
|
||||||
|
return home_path + separator + WStr("netcoreapp3.1") + DIR_SEPARATOR +
|
||||||
|
WStr("OpenTelemetry.Instrumentation.StartupHook.dll");
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool IsStartupHookEnabled(const WSTRING& startup_hooks, const WSTRING& home_path)
|
||||||
|
{
|
||||||
|
const WSTRING expected_startup_hook = GetExpectedStartupHookPath(home_path);
|
||||||
|
auto n = startup_hooks.find(expected_startup_hook);
|
||||||
|
|
||||||
|
return n != WSTRING::npos;
|
||||||
|
}
|
||||||
|
|
||||||
|
} // namespace trace
|
||||||
|
|
||||||
|
#endif // OTEL_CLR_PROFILER_STARTUP_HOOK_H_
|
|
@ -80,6 +80,7 @@
|
||||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
|
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Debug|x64'">Create</PrecompiledHeader>
|
||||||
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
|
<PrecompiledHeader Condition="'$(Configuration)|$(Platform)'=='Release|x64'">Create</PrecompiledHeader>
|
||||||
</ClCompile>
|
</ClCompile>
|
||||||
|
<ClCompile Include="startup_hook_test.cpp" />
|
||||||
<ClCompile Include="version_struct_test.cpp" />
|
<ClCompile Include="version_struct_test.cpp" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
<ItemGroup>
|
<ItemGroup>
|
||||||
|
|
|
@ -0,0 +1,82 @@
|
||||||
|
#include "pch.h"
|
||||||
|
|
||||||
|
#include "../../src/OpenTelemetry.ClrProfiler.Native/startup_hook.h"
|
||||||
|
|
||||||
|
using namespace trace;
|
||||||
|
|
||||||
|
TEST(StartupHookTest, GetExpectedStartupHookPathDoesNotAddSeparator) {
|
||||||
|
const auto home_path = WStr("C:\\tracer_home\\");
|
||||||
|
const auto expected_path = WStr("C:\\tracer_home\\netcoreapp3.1\\OpenTelemetry.Instrumentation.StartupHook.dll");
|
||||||
|
|
||||||
|
const auto startup_hook_path = GetExpectedStartupHookPath(home_path);
|
||||||
|
|
||||||
|
ASSERT_EQ(expected_path, startup_hook_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(StartupHookTest, GetExpectedStartupHookPathAddsSeparator) {
|
||||||
|
const auto home_path = WStr("C:\\tracer_home");
|
||||||
|
const auto expected_path = WStr("C:\\tracer_home\\netcoreapp3.1\\OpenTelemetry.Instrumentation.StartupHook.dll");
|
||||||
|
|
||||||
|
const auto startup_hook_path = GetExpectedStartupHookPath(home_path);
|
||||||
|
|
||||||
|
ASSERT_EQ(expected_path, startup_hook_path);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(StartupHookTest, StartupHookIsEnabled) {
|
||||||
|
const auto startup_hooks = WStr("C:\\tracer_home\\netcoreapp3.1\\OpenTelemetry.Instrumentation.StartupHook.dll");
|
||||||
|
const auto home_path = WStr("C:\\tracer_home");
|
||||||
|
|
||||||
|
const auto is_enabled = IsStartupHookEnabled(startup_hooks, home_path);
|
||||||
|
|
||||||
|
ASSERT_TRUE(is_enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(StartupHookTest, StartupHookIsNotEnabledWhenStartupHooksIsEmpty) {
|
||||||
|
const auto startup_hooks = WStr("");
|
||||||
|
const auto home_path = WStr("C:\\tracer_home");
|
||||||
|
|
||||||
|
const auto is_enabled = IsStartupHookEnabled(startup_hooks, home_path);
|
||||||
|
|
||||||
|
ASSERT_FALSE(is_enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(StartupHookTest, StartupHookIsNotEnabledWhenNoStartupHooksDefined) {
|
||||||
|
const WSTRING startup_hooks;
|
||||||
|
const auto home_path = WStr("C:\\tracer_home");
|
||||||
|
|
||||||
|
const auto is_enabled = IsStartupHookEnabled(startup_hooks, home_path);
|
||||||
|
|
||||||
|
ASSERT_FALSE(is_enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(StartupHookTest, StartupHookIsNotEnabledWhenStartupHookDoesNotContainOpenTelemetryHook) {
|
||||||
|
const auto startup_hooks = WStr("C:\\other_folder\\StartupHook.dll");
|
||||||
|
const auto home_path = WStr("C:\\tracer_home");
|
||||||
|
|
||||||
|
const auto is_enabled = IsStartupHookEnabled(startup_hooks, home_path);
|
||||||
|
|
||||||
|
ASSERT_FALSE(is_enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(StartupHookTest, StartupHookIsNotEnabledWhenNotInTheCorrectLocation) {
|
||||||
|
const auto startup_hooks = WStr("C:\\other_folder\\netcoreapp3.1\\OpenTelemetry.Instrumentation.StartupHook.dll");
|
||||||
|
const auto home_path = WStr("C:\\tracer_home");
|
||||||
|
|
||||||
|
const auto is_enabled = IsStartupHookEnabled(startup_hooks, home_path);
|
||||||
|
|
||||||
|
ASSERT_FALSE(is_enabled);
|
||||||
|
}
|
||||||
|
|
||||||
|
TEST(StartupHookTest, StartupHookIsEnabledWhenMultipleStartupHooksDefined) {
|
||||||
|
std::stringstream ss;
|
||||||
|
ss << "C:\\folder1\\StartupHook.dll" << DIR_SEPARATOR
|
||||||
|
<< "C:\\tracer_home\\netcoreapp3.1\\OpenTelemetry.Instrumentation.StartupHook.dll"
|
||||||
|
<< DIR_SEPARATOR << "C:\\folder2\\StartupHook.dll";
|
||||||
|
|
||||||
|
const auto startup_hooks = ToWSTRING(ss.str());
|
||||||
|
const auto home_path = WStr("C:\\tracer_home");
|
||||||
|
|
||||||
|
const auto is_enabled = IsStartupHookEnabled(startup_hooks, home_path);
|
||||||
|
|
||||||
|
ASSERT_TRUE(is_enabled);
|
||||||
|
}
|
|
@ -142,34 +142,31 @@ namespace IntegrationTests.Helpers
|
||||||
int agentPort,
|
int agentPort,
|
||||||
int aspNetCorePort,
|
int aspNetCorePort,
|
||||||
StringDictionary environmentVariables,
|
StringDictionary environmentVariables,
|
||||||
string processToProfile = null,
|
bool enableStartupHook,
|
||||||
bool isStartupHook = false)
|
string processToProfile = null)
|
||||||
{
|
{
|
||||||
string profilerEnabled = _requiresProfiling ? "1" : "0";
|
string profilerEnabled = _requiresProfiling ? "1" : "0";
|
||||||
|
string profilerPath = GetProfilerPath();
|
||||||
|
|
||||||
if (isStartupHook)
|
if (IsCoreClr())
|
||||||
{
|
{
|
||||||
string hookPath = GetStartupHookOutputPath();
|
// enableStartupHook should be true by default, and the parameter should only be set
|
||||||
|
// to false when testing the case that instrumentation should not be available.
|
||||||
|
if (enableStartupHook)
|
||||||
|
{
|
||||||
|
string hookPath = GetStartupHookOutputPath();
|
||||||
|
environmentVariables["DOTNET_STARTUP_HOOKS"] = hookPath;
|
||||||
|
}
|
||||||
|
|
||||||
environmentVariables["DOTNET_STARTUP_HOOKS"] = hookPath;
|
environmentVariables["CORECLR_ENABLE_PROFILING"] = profilerEnabled;
|
||||||
environmentVariables["OTEL_DOTNET_TRACER_INSTRUMENTATIONS"] = "HttpClient";
|
environmentVariables["CORECLR_PROFILER"] = EnvironmentTools.ProfilerClsId;
|
||||||
|
environmentVariables["CORECLR_PROFILER_PATH"] = profilerPath;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
string profilerPath = GetProfilerPath();
|
environmentVariables["COR_ENABLE_PROFILING"] = profilerEnabled;
|
||||||
|
environmentVariables["COR_PROFILER"] = EnvironmentTools.ProfilerClsId;
|
||||||
if (IsCoreClr())
|
environmentVariables["COR_PROFILER_PATH"] = profilerPath;
|
||||||
{
|
|
||||||
environmentVariables["CORECLR_ENABLE_PROFILING"] = profilerEnabled;
|
|
||||||
environmentVariables["CORECLR_PROFILER"] = EnvironmentTools.ProfilerClsId;
|
|
||||||
environmentVariables["CORECLR_PROFILER_PATH"] = profilerPath;
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
environmentVariables["COR_ENABLE_PROFILING"] = profilerEnabled;
|
|
||||||
environmentVariables["COR_PROFILER"] = EnvironmentTools.ProfilerClsId;
|
|
||||||
environmentVariables["COR_PROFILER_PATH"] = profilerPath;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (DebugModeEnabled)
|
if (DebugModeEnabled)
|
||||||
|
|
|
@ -3,16 +3,17 @@ using System.Diagnostics;
|
||||||
|
|
||||||
namespace IntegrationTests.Helpers
|
namespace IntegrationTests.Helpers
|
||||||
{
|
{
|
||||||
public class ProfilerHelper
|
public class InstrumentedProcessHelper
|
||||||
{
|
{
|
||||||
public static Process StartProcessWithProfiler(
|
public static Process StartInstrumentedProcess(
|
||||||
string executable,
|
string executable,
|
||||||
EnvironmentHelper environmentHelper,
|
EnvironmentHelper environmentHelper,
|
||||||
string arguments = null,
|
string arguments = null,
|
||||||
bool redirectStandardInput = false,
|
bool redirectStandardInput = false,
|
||||||
int traceAgentPort = 9696,
|
int traceAgentPort = 9696,
|
||||||
int aspNetCorePort = 5000,
|
int aspNetCorePort = 5000,
|
||||||
string processToProfile = null)
|
string processToProfile = null,
|
||||||
|
bool enableStartupHook = true)
|
||||||
{
|
{
|
||||||
if (environmentHelper == null)
|
if (environmentHelper == null)
|
||||||
{
|
{
|
||||||
|
@ -24,7 +25,7 @@ namespace IntegrationTests.Helpers
|
||||||
|
|
||||||
var startInfo = new ProcessStartInfo(executable, $"{arguments ?? string.Empty}");
|
var startInfo = new ProcessStartInfo(executable, $"{arguments ?? string.Empty}");
|
||||||
|
|
||||||
environmentHelper.SetEnvironmentVariables(traceAgentPort, aspNetCorePort, startInfo.EnvironmentVariables, processToProfile);
|
environmentHelper.SetEnvironmentVariables(traceAgentPort, aspNetCorePort, startInfo.EnvironmentVariables, enableStartupHook, processToProfile);
|
||||||
|
|
||||||
startInfo.UseShellExecute = false;
|
startInfo.UseShellExecute = false;
|
||||||
startInfo.CreateNoWindow = true;
|
startInfo.CreateNoWindow = true;
|
|
@ -1,38 +0,0 @@
|
||||||
using System;
|
|
||||||
using System.Diagnostics;
|
|
||||||
|
|
||||||
namespace IntegrationTests.Helpers
|
|
||||||
{
|
|
||||||
public class StartupHookHelper
|
|
||||||
{
|
|
||||||
public static Process StartProcessWithStartupHook(
|
|
||||||
string executable,
|
|
||||||
EnvironmentHelper environmentHelper,
|
|
||||||
string arguments = null,
|
|
||||||
bool redirectStandardInput = false,
|
|
||||||
int traceAgentPort = 9696,
|
|
||||||
int aspNetCorePort = 5000,
|
|
||||||
string processToProfile = null)
|
|
||||||
{
|
|
||||||
if (environmentHelper == null)
|
|
||||||
{
|
|
||||||
throw new ArgumentNullException(nameof(environmentHelper));
|
|
||||||
}
|
|
||||||
|
|
||||||
// clear all relevant environment variables to start with a clean slate
|
|
||||||
EnvironmentHelper.ClearProfilerEnvironmentVariables();
|
|
||||||
|
|
||||||
var startInfo = new ProcessStartInfo(executable, $"{arguments ?? string.Empty}");
|
|
||||||
|
|
||||||
environmentHelper.SetEnvironmentVariables(traceAgentPort, aspNetCorePort, startInfo.EnvironmentVariables, processToProfile, true);
|
|
||||||
|
|
||||||
startInfo.UseShellExecute = false;
|
|
||||||
startInfo.CreateNoWindow = true;
|
|
||||||
startInfo.RedirectStandardOutput = true;
|
|
||||||
startInfo.RedirectStandardError = true;
|
|
||||||
startInfo.RedirectStandardInput = redirectStandardInput;
|
|
||||||
|
|
||||||
return Process.Start(startInfo);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -89,7 +89,7 @@ namespace IntegrationTests.Helpers
|
||||||
return new Container(container);
|
return new Container(container);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Process StartSample(int traceAgentPort, string arguments, string packageVersion, int aspNetCorePort, string framework = "", bool startupHook = false)
|
public Process StartSample(int traceAgentPort, string arguments, string packageVersion, int aspNetCorePort, string framework = "", bool enableStartupHook = true)
|
||||||
{
|
{
|
||||||
// get path to sample app that the profiler will attach to
|
// get path to sample app that the profiler will attach to
|
||||||
string sampleAppPath = EnvironmentHelper.GetSampleApplicationPath(packageVersion, framework);
|
string sampleAppPath = EnvironmentHelper.GetSampleApplicationPath(packageVersion, framework);
|
||||||
|
@ -102,31 +102,19 @@ namespace IntegrationTests.Helpers
|
||||||
var executable = EnvironmentHelper.IsCoreClr() ? EnvironmentHelper.GetSampleExecutionSource() : sampleAppPath;
|
var executable = EnvironmentHelper.IsCoreClr() ? EnvironmentHelper.GetSampleExecutionSource() : sampleAppPath;
|
||||||
var args = EnvironmentHelper.IsCoreClr() ? $"{sampleAppPath} {arguments ?? string.Empty}" : arguments;
|
var args = EnvironmentHelper.IsCoreClr() ? $"{sampleAppPath} {arguments ?? string.Empty}" : arguments;
|
||||||
|
|
||||||
if (startupHook)
|
return InstrumentedProcessHelper.StartInstrumentedProcess(
|
||||||
{
|
executable,
|
||||||
return StartupHookHelper.StartProcessWithStartupHook(
|
EnvironmentHelper,
|
||||||
executable,
|
args,
|
||||||
EnvironmentHelper,
|
traceAgentPort: traceAgentPort,
|
||||||
args,
|
aspNetCorePort: aspNetCorePort,
|
||||||
traceAgentPort: traceAgentPort,
|
processToProfile: executable,
|
||||||
aspNetCorePort: aspNetCorePort,
|
enableStartupHook: enableStartupHook);
|
||||||
processToProfile: executable);
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
return ProfilerHelper.StartProcessWithProfiler(
|
|
||||||
executable,
|
|
||||||
EnvironmentHelper,
|
|
||||||
args,
|
|
||||||
traceAgentPort: traceAgentPort,
|
|
||||||
aspNetCorePort: aspNetCorePort,
|
|
||||||
processToProfile: executable);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public ProcessResult RunSampleAndWaitForExit(int traceAgentPort, string arguments = null, string packageVersion = "", string framework = "", int aspNetCorePort = 5000, bool startupHook = false)
|
public ProcessResult RunSampleAndWaitForExit(int traceAgentPort, string arguments = null, string packageVersion = "", string framework = "", int aspNetCorePort = 5000, bool enableStartupHook = true)
|
||||||
{
|
{
|
||||||
var process = StartSample(traceAgentPort, arguments, packageVersion, aspNetCorePort: aspNetCorePort, framework: framework, startupHook: startupHook);
|
var process = StartSample(traceAgentPort, arguments, packageVersion, aspNetCorePort: aspNetCorePort, framework: framework, enableStartupHook: enableStartupHook);
|
||||||
var name = process.ProcessName;
|
var name = process.ProcessName;
|
||||||
|
|
||||||
using var helper = new ProcessHelper(process);
|
using var helper = new ProcessHelper(process);
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
using System;
|
using System;
|
||||||
using System.Collections.Generic;
|
using System.Collections.Generic;
|
||||||
|
using System.Collections.Immutable;
|
||||||
using System.Linq;
|
using System.Linq;
|
||||||
using IntegrationTests.Helpers;
|
using IntegrationTests.Helpers;
|
||||||
using IntegrationTests.Helpers.Mocks;
|
using IntegrationTests.Helpers.Mocks;
|
||||||
|
@ -18,6 +19,8 @@ namespace IntegrationTests.StartupHook
|
||||||
public StartupHookTests(ITestOutputHelper output)
|
public StartupHookTests(ITestOutputHelper output)
|
||||||
: base("StartupHook", output)
|
: base("StartupHook", output)
|
||||||
{
|
{
|
||||||
|
SetEnvironmentVariable("OTEL_SERVICE_NAME", ServiceName);
|
||||||
|
SetEnvironmentVariable("OTEL_DOTNET_TRACER_INSTRUMENTATIONS", "HttpClient");
|
||||||
_expectations.Add(new WebServerSpanExpectation(ServiceName, null, "SayHello", "SayHello", null));
|
_expectations.Add(new WebServerSpanExpectation(ServiceName, null, "SayHello", "SayHello", null));
|
||||||
_expectations.Add(new WebServerSpanExpectation(ServiceName, null, "HTTP GET", "HTTP GET", null, "GET"));
|
_expectations.Add(new WebServerSpanExpectation(ServiceName, null, "HTTP GET", "HTTP GET", null, "GET"));
|
||||||
}
|
}
|
||||||
|
@ -26,9 +29,22 @@ namespace IntegrationTests.StartupHook
|
||||||
[Trait("Category", "EndToEnd")]
|
[Trait("Category", "EndToEnd")]
|
||||||
public void SubmitsTraces()
|
public void SubmitsTraces()
|
||||||
{
|
{
|
||||||
SetEnvironmentVariable("OTEL_SERVICE_NAME", ServiceName);
|
const int expectedSpanCount = 2;
|
||||||
|
|
||||||
RunTestAndValidateResults(2);
|
var spans = RunTestApplication(enableStartupHook: true);
|
||||||
|
|
||||||
|
AssertExpectedSpansReceived(expectedSpanCount, spans);
|
||||||
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
[Trait("Category", "EndToEnd")]
|
||||||
|
public void InstrumentationNotAvailableWhenStartupHookIsNotEnabled()
|
||||||
|
{
|
||||||
|
const int expectedSpanCount = 0;
|
||||||
|
|
||||||
|
var spans = RunTestApplication(enableStartupHook: false);
|
||||||
|
|
||||||
|
AssertExpectedSpansReceived(expectedSpanCount, spans);
|
||||||
}
|
}
|
||||||
|
|
||||||
[Theory]
|
[Theory]
|
||||||
|
@ -45,30 +61,34 @@ namespace IntegrationTests.StartupHook
|
||||||
expectedSpanCount = 0;
|
expectedSpanCount = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
SetEnvironmentVariable("OTEL_SERVICE_NAME", ServiceName);
|
|
||||||
SetEnvironmentVariable("OTEL_PROFILER_EXCLUDE_PROCESSES", $"dotnet,dotnet.exe,{applicationNameToExclude}");
|
SetEnvironmentVariable("OTEL_PROFILER_EXCLUDE_PROCESSES", $"dotnet,dotnet.exe,{applicationNameToExclude}");
|
||||||
|
|
||||||
RunTestAndValidateResults(expectedSpanCount);
|
var spans = RunTestApplication(enableStartupHook: true);
|
||||||
|
|
||||||
|
AssertExpectedSpansReceived(expectedSpanCount, spans);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void RunTestAndValidateResults(int expectedSpanCount)
|
private IImmutableList<IMockSpan> RunTestApplication(bool enableStartupHook)
|
||||||
{
|
{
|
||||||
int agentPort = TcpPortProvider.GetOpenPort();
|
int agentPort = TcpPortProvider.GetOpenPort();
|
||||||
|
|
||||||
using (var agent = new MockZipkinCollector(Output, agentPort))
|
using (var agent = new MockZipkinCollector(Output, agentPort))
|
||||||
using (var processResult = RunSampleAndWaitForExit(agent.Port, startupHook: true))
|
using (var processResult = RunSampleAndWaitForExit(agent.Port, enableStartupHook: enableStartupHook))
|
||||||
{
|
{
|
||||||
Assert.True(processResult.ExitCode >= 0, $"Process exited with code {processResult.ExitCode} and exception: {processResult.StandardError}");
|
Assert.True(processResult.ExitCode >= 0, $"Process exited with code {processResult.ExitCode} and exception: {processResult.StandardError}");
|
||||||
var span = agent.WaitForSpans(2, 500);
|
return agent.WaitForSpans(2, 500);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
Assert.True(span.Count() == expectedSpanCount, $"Expecting {expectedSpanCount} spans, received {span.Count()}");
|
private void AssertExpectedSpansReceived(int expectedSpanCount, IImmutableList<IMockSpan> spans)
|
||||||
if (expectedSpanCount > 0)
|
{
|
||||||
{
|
Assert.True(spans.Count() == expectedSpanCount, $"Expecting {expectedSpanCount} spans, received {spans.Count()}");
|
||||||
Assert.Single(span.Select(s => s.Service).Distinct());
|
if (expectedSpanCount > 0)
|
||||||
|
{
|
||||||
|
Assert.Single(spans.Select(s => s.Service).Distinct());
|
||||||
|
|
||||||
var spanList = span.ToList();
|
var spanList = spans.ToList();
|
||||||
AssertExpectationsMet(_expectations, spanList);
|
AssertExpectationsMet(_expectations, spanList);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -28,7 +28,7 @@ namespace Samples.GraphQL
|
||||||
var logger = host.Services.GetRequiredService<ILogger<Program>>();
|
var logger = host.Services.GetRequiredService<ILogger<Program>>();
|
||||||
logger.LogInformation($"Instrumentation.ProfilerAttached = {IsProfilerAttached()}");
|
logger.LogInformation($"Instrumentation.ProfilerAttached = {IsProfilerAttached()}");
|
||||||
|
|
||||||
var prefixes = new[] { "COR_", "CORECLR_", "OTEL_" };
|
var prefixes = new[] { "COR_", "CORECLR_", "DOTNET_", "OTEL_" };
|
||||||
var envVars = from envVar in Environment.GetEnvironmentVariables().Cast<DictionaryEntry>()
|
var envVars = from envVar in Environment.GetEnvironmentVariables().Cast<DictionaryEntry>()
|
||||||
from prefix in prefixes
|
from prefix in prefixes
|
||||||
let key = (envVar.Key as string)?.ToUpperInvariant()
|
let key = (envVar.Key as string)?.ToUpperInvariant()
|
||||||
|
|
|
@ -17,6 +17,7 @@
|
||||||
"CORECLR_ENABLE_PROFILING": "1",
|
"CORECLR_ENABLE_PROFILING": "1",
|
||||||
"CORECLR_PROFILER": "{918728DD-259F-4A6A-AC2B-B85E1B658318}",
|
"CORECLR_PROFILER": "{918728DD-259F-4A6A-AC2B-B85E1B658318}",
|
||||||
"CORECLR_PROFILER_PATH": "$(SolutionDir)bin\\tracer-home\\win-x64\\OpenTelemetry.ClrProfiler.Native.dll",
|
"CORECLR_PROFILER_PATH": "$(SolutionDir)bin\\tracer-home\\win-x64\\OpenTelemetry.ClrProfiler.Native.dll",
|
||||||
|
"DOTNET_STARTUP_HOOKS": "$(SolutionDir)bin\\tracer-home\\netcoreapp3.1\\OpenTelemetry.Instrumentation.StartupHook.dll",
|
||||||
|
|
||||||
"OTEL_DOTNET_TRACER_HOME": "$(SolutionDir)bin\\tracer-home\\",
|
"OTEL_DOTNET_TRACER_HOME": "$(SolutionDir)bin\\tracer-home\\",
|
||||||
"OTEL_INTEGRATIONS": "$(SolutionDir)bin\\tracer-home\\integrations.json",
|
"OTEL_INTEGRATIONS": "$(SolutionDir)bin\\tracer-home\\integrations.json",
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
"CORECLR_ENABLE_PROFILING": "1",
|
"CORECLR_ENABLE_PROFILING": "1",
|
||||||
"CORECLR_PROFILER": "{918728DD-259F-4A6A-AC2B-B85E1B658318}",
|
"CORECLR_PROFILER": "{918728DD-259F-4A6A-AC2B-B85E1B658318}",
|
||||||
"CORECLR_PROFILER_PATH": "$(SolutionDir)bin\\tracer-home\\win-x64\\OpenTelemetry.ClrProfiler.Native.dll",
|
"CORECLR_PROFILER_PATH": "$(SolutionDir)bin\\tracer-home\\win-x64\\OpenTelemetry.ClrProfiler.Native.dll",
|
||||||
|
"DOTNET_STARTUP_HOOKS": "$(SolutionDir)bin\\tracer-home\\netcoreapp3.1\\OpenTelemetry.Instrumentation.StartupHook.dll",
|
||||||
|
|
||||||
"OTEL_DOTNET_TRACER_HOME": "$(SolutionDir)bin\\tracer-home\\",
|
"OTEL_DOTNET_TRACER_HOME": "$(SolutionDir)bin\\tracer-home\\",
|
||||||
"OTEL_INTEGRATIONS": "$(SolutionDir)bin\\tracer-home\\integrations.json",
|
"OTEL_INTEGRATIONS": "$(SolutionDir)bin\\tracer-home\\integrations.json",
|
||||||
|
|
|
@ -12,7 +12,7 @@ namespace Samples.AspNet.Controllers
|
||||||
{
|
{
|
||||||
public ActionResult Index()
|
public ActionResult Index()
|
||||||
{
|
{
|
||||||
var prefixes = new[] { "COR_", "CORECLR_", "OTEL_" };
|
var prefixes = new[] { "COR_", "CORECLR_", "DOTNET_", "OTEL_" };
|
||||||
|
|
||||||
var envVars = from envVar in Environment.GetEnvironmentVariables().Cast<DictionaryEntry>()
|
var envVars = from envVar in Environment.GetEnvironmentVariables().Cast<DictionaryEntry>()
|
||||||
from prefix in prefixes
|
from prefix in prefixes
|
||||||
|
|
Loading…
Reference in New Issue