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:
Chris Ventura 2022-02-02 16:10:25 -08:00 committed by GitHub
parent 04e64e54b2
commit 372d9ac871
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 229 additions and 117 deletions

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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