Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

IIS Out of Process Instrumentation Fix #1392

Merged
merged 9 commits into from
Feb 23, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions src/Agent/CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).

## [Unreleased]
* Fixes [#1353](https://github.com/newrelic/newrelic-dotnet-agent/issues/1353) so that out-of-process .Net Core web applications are instrumented according to the <applicationPools> setting in newrelic.config. [1392](https://github.com/newrelic/newrelic-dotnet-agent/pull/1392)

## [10.7.0]

Expand Down
7 changes: 7 additions & 0 deletions src/Agent/NewRelic/Profiler/Common/Strings.h
Original file line number Diff line number Diff line change
Expand Up @@ -62,5 +62,12 @@ namespace NewRelic { namespace Profiler

return true;
}

static xstring_t ToUpper(xstring_t source)
{
std::transform(source.begin(), source.end(), source.begin(), ::towupper);
return source;
}

};
}}
22 changes: 13 additions & 9 deletions src/Agent/NewRelic/Profiler/Configuration/Configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -171,11 +171,11 @@ namespace NewRelic { namespace Profiler { namespace Configuration {
});
}

bool ShouldInstrument(xstring_t const& processPath, xstring_t const& appPoolId, xstring_t const& commandLine, bool isCoreClr)
bool ShouldInstrument(xstring_t const& processPath, xstring_t const& parentProcessPath, xstring_t const& appPoolId, xstring_t const& commandLine, bool isCoreClr)
{
if (isCoreClr)
{
return ShouldInstrumentNetCore(processPath, appPoolId, commandLine);
return ShouldInstrumentNetCore(processPath, parentProcessPath, appPoolId, commandLine);
}
else
{
Expand Down Expand Up @@ -423,9 +423,13 @@ namespace NewRelic { namespace Profiler { namespace Configuration {
return false;
}

bool IsW3wpProcess(const xstring_t& processName)
bool IsW3wpProcess(const xstring_t& processName, xstring_t const& parentProcessName)
{
return Strings::EndsWith(processName, _X("W3WP.EXE"));
auto isIis = Strings::EndsWith(processName, _X("W3WP.EXE")) || Strings::EndsWith(parentProcessName, _X("W3WP.EXE"));

LogInfo(_X("Process ") + processName + _X(" with parent process ") + parentProcessName + (isIis ? _X(" is") : _X(" is not")) + _X(" IIS."));

return isIis;
}

bool ShouldInstrumentApplicationPool(const xstring_t& appPoolId)
Expand Down Expand Up @@ -487,7 +491,7 @@ namespace NewRelic { namespace Profiler { namespace Configuration {
}

// Test to see if we should instrument this .NET Core application at all
bool ShouldInstrumentNetCore(xstring_t const& processPath, xstring_t const& appPoolId, xstring_t const& commandLine)
bool ShouldInstrumentNetCore(xstring_t const& processPath, xstring_t const& parentProcessPath, xstring_t const& appPoolId, xstring_t const& commandLine)
{
//If it contains MsBuild, it is a build command and should not be profiled.
bool isMsBuildInvocation = NewRelic::Profiler::Strings::ContainsCaseInsensitive(commandLine, _X("MSBuild.dll"));
Expand Down Expand Up @@ -533,7 +537,7 @@ namespace NewRelic { namespace Profiler { namespace Configuration {
return false;
}

if (IsW3wpProcess(processPath)) {
if (IsW3wpProcess(processPath, parentProcessPath)) {
return ShouldInstrumentApplicationPool(appPoolId);
}

Expand All @@ -543,10 +547,10 @@ namespace NewRelic { namespace Profiler { namespace Configuration {
// Test to see if we should instrument this .NET Framework application at all
bool ShouldInstrumentNetFramework(xstring_t const& processPath, xstring_t const& appPoolId)
{
return ShouldInstrumentProcess(processPath, appPoolId);
return ShouldInstrumentProcess(processPath, _X(""), appPoolId);
}

bool ShouldInstrumentProcess(const xstring_t& processName, const xstring_t& appPoolId)
bool ShouldInstrumentProcess(const xstring_t& processName, xstring_t const& parentProcessName, const xstring_t& appPoolId)
{
if (!_agentEnabled) {
LogInfo("New Relic has been disabled via newrelic.config file.");
Expand Down Expand Up @@ -574,7 +578,7 @@ namespace NewRelic { namespace Profiler { namespace Configuration {
return true;
}

if (IsW3wpProcess(processName)) {
if (IsW3wpProcess(processName, parentProcessName)) {
return ShouldInstrumentApplicationPool(appPoolId);
}

Expand Down
197 changes: 160 additions & 37 deletions src/Agent/NewRelic/Profiler/ConfigurationTest/ConfigurationTest.cpp

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
Expand Up @@ -17,50 +17,50 @@ namespace NewRelic { namespace Profiler { namespace Configuration { namespace Te

auto isCoreClr = true;

Assert::IsFalse(configuration.ShouldInstrument(processPath, appPoolId, _X("dotnet run"), isCoreClr));
Assert::IsFalse(configuration.ShouldInstrument(processPath, appPoolId, _X("DotNet Run"), isCoreClr));
Assert::IsFalse(configuration.ShouldInstrument(processPath, appPoolId, _X("dotnet.exe run"), isCoreClr));
Assert::IsFalse(configuration.ShouldInstrument(processPath, appPoolId, _X("\"dotnet.exe\" run"), isCoreClr));
Assert::IsFalse(configuration.ShouldInstrument(processPath, appPoolId, _X("\"c:\\program files\\dotnet.exe\" run"), isCoreClr));
Assert::IsFalse(configuration.ShouldInstrument(processPath, appPoolId, _X("\"c:\\program files\\dotnet.exe\" run"), isCoreClr));
Assert::IsFalse(configuration.ShouldInstrument(processPath, appPoolId, _X("dotnet publish"), isCoreClr));
Assert::IsFalse(configuration.ShouldInstrument(processPath, appPoolId, _X("dotnet restore"), isCoreClr));
Assert::IsFalse(configuration.ShouldInstrument(processPath, appPoolId, _X("dotnet run -p c:\\test\\test.csproj"), isCoreClr));
Assert::IsFalse(configuration.ShouldInstrument(processPath, appPoolId, _X("dotnet run -p \"c:\\program files\\test.csproj\""), isCoreClr));
Assert::IsFalse(configuration.ShouldInstrument(processPath, appPoolId, _X("dotnet run -p ~/test.csproj"), isCoreClr));
Assert::IsFalse(configuration.ShouldInstrument(processPath, appPoolId, _X("dotnet.exe exec \"c:\\program files\\MSBuild.dll\" -maxcpucount"), isCoreClr));
Assert::IsFalse(configuration.ShouldInstrument(processPath, appPoolId, _X("dotnet.exe exec c:\\test\\msbuild.dll"), isCoreClr));
Assert::IsFalse(configuration.ShouldInstrument(processPath, appPoolId, _X("dotnet new console"), isCoreClr));
Assert::IsFalse(configuration.ShouldInstrument(processPath, appPoolId, _X("dotnet new mvc"), isCoreClr));
Assert::IsFalse(configuration.ShouldInstrument(processPath, appPoolId, _X("dotnet\" run"), isCoreClr));
Assert::IsFalse(configuration.ShouldInstrument(processPath, appPoolId, _X("\"dotnet\" run"), isCoreClr));
Assert::IsFalse(configuration.ShouldInstrument(processPath, appPoolId, _X("app1.exe | dotnet run"), isCoreClr));
Assert::IsFalse(configuration.ShouldInstrument(processPath, appPoolId, _X("dotnet Kudu.Services.Web.dll"), isCoreClr));
Assert::IsFalse(configuration.ShouldInstrument(processPath, appPoolId, _X("/opt/Kudu/Kudu.Services.Web"), isCoreClr));
Assert::IsFalse(configuration.ShouldInstrument(processPath, L"", appPoolId, _X("dotnet run"), isCoreClr));
Assert::IsFalse(configuration.ShouldInstrument(processPath, L"", appPoolId, _X("DotNet Run"), isCoreClr));
Assert::IsFalse(configuration.ShouldInstrument(processPath, L"", appPoolId, _X("dotnet.exe run"), isCoreClr));
Assert::IsFalse(configuration.ShouldInstrument(processPath, L"", appPoolId, _X("\"dotnet.exe\" run"), isCoreClr));
Assert::IsFalse(configuration.ShouldInstrument(processPath, L"", appPoolId, _X("\"c:\\program files\\dotnet.exe\" run"), isCoreClr));
Assert::IsFalse(configuration.ShouldInstrument(processPath, L"", appPoolId, _X("\"c:\\program files\\dotnet.exe\" run"), isCoreClr));
Assert::IsFalse(configuration.ShouldInstrument(processPath, L"", appPoolId, _X("dotnet publish"), isCoreClr));
Assert::IsFalse(configuration.ShouldInstrument(processPath, L"", appPoolId, _X("dotnet restore"), isCoreClr));
Assert::IsFalse(configuration.ShouldInstrument(processPath, L"", appPoolId, _X("dotnet run -p c:\\test\\test.csproj"), isCoreClr));
Assert::IsFalse(configuration.ShouldInstrument(processPath, L"", appPoolId, _X("dotnet run -p \"c:\\program files\\test.csproj\""), isCoreClr));
Assert::IsFalse(configuration.ShouldInstrument(processPath, L"", appPoolId, _X("dotnet run -p ~/test.csproj"), isCoreClr));
Assert::IsFalse(configuration.ShouldInstrument(processPath, L"", appPoolId, _X("dotnet.exe exec \"c:\\program files\\MSBuild.dll\" -maxcpucount"), isCoreClr));
Assert::IsFalse(configuration.ShouldInstrument(processPath, L"", appPoolId, _X("dotnet.exe exec c:\\test\\msbuild.dll"), isCoreClr));
Assert::IsFalse(configuration.ShouldInstrument(processPath, L"", appPoolId, _X("dotnet new console"), isCoreClr));
Assert::IsFalse(configuration.ShouldInstrument(processPath, L"", appPoolId, _X("dotnet new mvc"), isCoreClr));
Assert::IsFalse(configuration.ShouldInstrument(processPath, L"", appPoolId, _X("dotnet\" run"), isCoreClr));
Assert::IsFalse(configuration.ShouldInstrument(processPath, L"", appPoolId, _X("\"dotnet\" run"), isCoreClr));
Assert::IsFalse(configuration.ShouldInstrument(processPath, L"", appPoolId, _X("app1.exe | dotnet run"), isCoreClr));
Assert::IsFalse(configuration.ShouldInstrument(processPath, L"", appPoolId, _X("dotnet Kudu.Services.Web.dll"), isCoreClr));
Assert::IsFalse(configuration.ShouldInstrument(processPath, L"", appPoolId, _X("/opt/Kudu/Kudu.Services.Web"), isCoreClr));

Assert::IsTrue(configuration.ShouldInstrument(processPath, appPoolId, _X("dotnetXexe restore"), isCoreClr));
Assert::IsTrue(configuration.ShouldInstrument(processPath, appPoolId, _X("\"c:\\program files\\dotnet.exe\"run"), isCoreClr));
Assert::IsTrue(configuration.ShouldInstrument(processPath, appPoolId, _X("dotnet exec test.dll"), isCoreClr));
Assert::IsTrue(configuration.ShouldInstrument(processPath, appPoolId, _X("dotnet exec publish.dll"), isCoreClr));
Assert::IsTrue(configuration.ShouldInstrument(processPath, appPoolId, _X("dotnet publish.dll"), isCoreClr));
Assert::IsTrue(configuration.ShouldInstrument(processPath, appPoolId, _X("dotnet run.dll"), isCoreClr));
Assert::IsTrue(configuration.ShouldInstrument(processPath, appPoolId, _X("dotnet restore.dll"), isCoreClr));
Assert::IsTrue(configuration.ShouldInstrument(processPath, appPoolId, _X("dotnet.exerun publish.dll"), isCoreClr));
Assert::IsTrue(configuration.ShouldInstrument(processPath, appPoolId, _X("IpublishedThis.exe"), isCoreClr));
Assert::IsTrue(configuration.ShouldInstrument(processPath, appPoolId, _X("dotnet new.dll"), isCoreClr));
Assert::IsTrue(configuration.ShouldInstrument(processPath, appPoolId, _X("dotnet exec new.dll"), isCoreClr));
Assert::IsTrue(configuration.ShouldInstrument(processPath, appPoolId, _X("dotnet myapp.dll run thisapp"), isCoreClr));
Assert::IsTrue(configuration.ShouldInstrument(processPath, appPoolId, _X("dotnet/run/IpublishedThis.exe"), isCoreClr));
Assert::IsTrue(configuration.ShouldInstrument(processPath, appPoolId, _X("run dotnet"), isCoreClr));
Assert::IsTrue(configuration.ShouldInstrument(processPath, appPoolId, _X("IpublishedThis.exe \"dotnet run\""), isCoreClr));
Assert::IsTrue(configuration.ShouldInstrument(processPath, appPoolId, _X("IpublishedThis.exe 'dotnet run'"), isCoreClr));
Assert::IsTrue(configuration.ShouldInstrument(processPath, appPoolId, _X("dotnet exec IpublishedThis.dll dotnet run"), isCoreClr));
Assert::IsTrue(configuration.ShouldInstrument(processPath, appPoolId, _X("dotnet exec IpublishedThis.dll \"dotnet run \""), isCoreClr));
Assert::IsTrue(configuration.ShouldInstrument(processPath, L"", appPoolId, _X("dotnetXexe restore"), isCoreClr));
Assert::IsTrue(configuration.ShouldInstrument(processPath, L"", appPoolId, _X("\"c:\\program files\\dotnet.exe\"run"), isCoreClr));
Assert::IsTrue(configuration.ShouldInstrument(processPath, L"", appPoolId, _X("dotnet exec test.dll"), isCoreClr));
Assert::IsTrue(configuration.ShouldInstrument(processPath, L"", appPoolId, _X("dotnet exec publish.dll"), isCoreClr));
Assert::IsTrue(configuration.ShouldInstrument(processPath, L"", appPoolId, _X("dotnet publish.dll"), isCoreClr));
Assert::IsTrue(configuration.ShouldInstrument(processPath, L"", appPoolId, _X("dotnet run.dll"), isCoreClr));
Assert::IsTrue(configuration.ShouldInstrument(processPath, L"", appPoolId, _X("dotnet restore.dll"), isCoreClr));
Assert::IsTrue(configuration.ShouldInstrument(processPath, L"", appPoolId, _X("dotnet.exerun publish.dll"), isCoreClr));
Assert::IsTrue(configuration.ShouldInstrument(processPath, L"", appPoolId, _X("IpublishedThis.exe"), isCoreClr));
Assert::IsTrue(configuration.ShouldInstrument(processPath, L"", appPoolId, _X("dotnet new.dll"), isCoreClr));
Assert::IsTrue(configuration.ShouldInstrument(processPath, L"", appPoolId, _X("dotnet exec new.dll"), isCoreClr));
Assert::IsTrue(configuration.ShouldInstrument(processPath, L"", appPoolId, _X("dotnet myapp.dll run thisapp"), isCoreClr));
Assert::IsTrue(configuration.ShouldInstrument(processPath, L"", appPoolId, _X("dotnet/run/IpublishedThis.exe"), isCoreClr));
Assert::IsTrue(configuration.ShouldInstrument(processPath, L"", appPoolId, _X("run dotnet"), isCoreClr));
Assert::IsTrue(configuration.ShouldInstrument(processPath, L"", appPoolId, _X("IpublishedThis.exe \"dotnet run\""), isCoreClr));
Assert::IsTrue(configuration.ShouldInstrument(processPath, L"", appPoolId, _X("IpublishedThis.exe 'dotnet run'"), isCoreClr));
Assert::IsTrue(configuration.ShouldInstrument(processPath, L"", appPoolId, _X("dotnet exec IpublishedThis.dll dotnet run"), isCoreClr));
Assert::IsTrue(configuration.ShouldInstrument(processPath, L"", appPoolId, _X("dotnet exec IpublishedThis.dll \"dotnet run \""), isCoreClr));

//These will incorrectly not be instrumented, but they are edge cases. We are documenting them here.
Assert::IsFalse(configuration.ShouldInstrument(processPath, appPoolId, _X("IpublishedThis.exe \"dotnet run \""), isCoreClr));
Assert::IsFalse(configuration.ShouldInstrument(processPath, appPoolId, _X("'IpublishedThis.exe' \"dotnet run \""), isCoreClr));
Assert::IsFalse(configuration.ShouldInstrument(processPath, appPoolId, _X("IpublishedThis.exe dotnet run"), isCoreClr));
Assert::IsFalse(configuration.ShouldInstrument(processPath, L"", appPoolId, _X("IpublishedThis.exe \"dotnet run \""), isCoreClr));
Assert::IsFalse(configuration.ShouldInstrument(processPath, L"", appPoolId, _X("'IpublishedThis.exe' \"dotnet run \""), isCoreClr));
Assert::IsFalse(configuration.ShouldInstrument(processPath, L"", appPoolId, _X("IpublishedThis.exe dotnet run"), isCoreClr));
}
};
}}}}
Original file line number Diff line number Diff line change
Expand Up @@ -15,4 +15,4 @@
<s:String x:Key="/Default/CodeStyle/Naming/CppNaming/Rules/=Parameters/@EntryIndexedValue">&lt;NamingElement Priority="5"&gt;&lt;Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"&gt;&lt;type Name="parameter" /&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="aaBb" /&gt;&lt;/NamingElement&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CppNaming/Rules/=Typedefs/@EntryIndexedValue">&lt;NamingElement Priority="17"&gt;&lt;Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"&gt;&lt;type Name="type alias" /&gt;&lt;type Name="typedef" /&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AA_BB" /&gt;&lt;/NamingElement&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CppNaming/Rules/=Union_0020members/@EntryIndexedValue">&lt;NamingElement Priority="12"&gt;&lt;Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"&gt;&lt;type Name="union member" /&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;&lt;/NamingElement&gt;</s:String>
<s:String x:Key="/Default/CodeStyle/Naming/CppNaming/Rules/=Unions/@EntryIndexedValue">&lt;NamingElement Priority="3"&gt;&lt;Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"&gt;&lt;type Name="union" /&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;&lt;/NamingElement&gt;</s:String></wpf:ResourceDictionary>
<s:String x:Key="/Default/CodeStyle/Naming/CppNaming/Rules/=Unions/@EntryIndexedValue">&lt;NamingElement Priority="3"&gt;&lt;Descriptor Static="Indeterminate" Constexpr="Indeterminate" Const="Indeterminate" Volatile="Indeterminate" Accessibility="NOT_APPLICABLE"&gt;&lt;type Name="union" /&gt;&lt;/Descriptor&gt;&lt;Policy Inspect="True" Prefix="" Suffix="" Style="AaBb" /&gt;&lt;/NamingElement&gt;</s:String></wpf:ResourceDictionary>
12 changes: 3 additions & 9 deletions src/Agent/NewRelic/Profiler/Profiler/CorProfilerCallbackImpl.h
Original file line number Diff line number Diff line change
Expand Up @@ -235,11 +235,12 @@ namespace NewRelic { namespace Profiler {

LogTrace("Checking to see if we should instrument this process.");
auto forceProfiling = _systemCalls->GetForceProfiling();
auto processPath = GetAndTransformProcessPath();
auto processPath = Strings::ToUpper(_systemCalls->GetProcessPath());
auto commandLine = _systemCalls->GetProgramCommandLine();
auto parentProcessPath = Strings::ToUpper(_systemCalls->GetParentProcessPath());
auto appPoolId = GetAppPoolId(_systemCalls);
LogInfo(L"Command line: ", commandLine);
if (!forceProfiling && !configuration->ShouldInstrument(processPath, appPoolId, commandLine, _isCoreClr)) {
if (!forceProfiling && !configuration->ShouldInstrument(processPath, parentProcessPath, appPoolId, commandLine, _isCoreClr)) {
LogInfo("This process should not be instrumented, unloading profiler.");
return CORPROF_E_PROFILER_CANCEL_ACTIVATION;
}
Expand Down Expand Up @@ -1265,13 +1266,6 @@ namespace NewRelic { namespace Profiler {
}
}

xstring_t GetAndTransformProcessPath()
{
auto processPath = _systemCalls->GetProcessPath();
std::transform(processPath.begin(), processPath.end(), processPath.begin(), ::towupper);
return processPath;
}

void DelayProfilerAttach()
{
auto profilerDelay = _systemCalls->GetProfilerDelay();
Expand Down
Loading