Skip to content

Commit

Permalink
Add profiler support for in-process azure functions
Browse files Browse the repository at this point in the history
  • Loading branch information
tippmar-nr committed Feb 13, 2025
1 parent 35c7685 commit 32165bb
Show file tree
Hide file tree
Showing 2 changed files with 82 additions and 8 deletions.
23 changes: 22 additions & 1 deletion src/Agent/NewRelic/Profiler/Configuration/Configuration.h
Original file line number Diff line number Diff line change
Expand Up @@ -644,18 +644,39 @@ namespace NewRelic { namespace Profiler { namespace Configuration {

bool IsAzureFunction() const
{
// Azure Functions sets the FUNCTIONS_WORKER_RUNTIME environment variable to "dotnet-isolated" when running in the .NET worker.
// Azure Functions sets the FUNCTIONS_WORKER_RUNTIME environment variable when running in Azure Functions
auto functionsWorkerRuntime = _systemCalls->TryGetEnvironmentVariable(_X("FUNCTIONS_WORKER_RUNTIME"));
return functionsWorkerRuntime != nullptr && functionsWorkerRuntime->length() > 0;
}

bool IsAzureFunctionInProcess() const
{
auto functionsWorkerRuntime = _systemCalls->TryGetEnvironmentVariable(_X("FUNCTIONS_WORKER_RUNTIME"));

// "dotnet" when running in in-process mode
// "dotnet-isolated" when running in isolated mode
return functionsWorkerRuntime != nullptr && Strings::AreEqualCaseInsensitive(*functionsWorkerRuntime, _X("dotnet"));
}

/// <summary>
/// Returns 0 if the process should not be instrumented, 1 if it should be instrumented, and -1 if it is indeterminate.
/// </summary>
int ShouldInstrumentAzureFunction(xstring_t const& processPath, xstring_t const& appPoolId, xstring_t const& commandLine)
{
LogInfo(_X("Azure function detected. Determining whether to instrument ") + commandLine);

if (IsAzureFunctionInProcess())
{
// appPoolName starts with "~" for Azure Functions background tasks (kudu / scm)
if (appPoolId.find(_X("~")) == 0) {
LogInfo(_X("This application pool (") + appPoolId + _X(") has been identified as an in-process Azure Functions built-in background application and will be ignored."));
return 0;
}

LogInfo(L"Function is running in-process. This process will be instrumented.");
return 1;
}

bool isAzureWebJobsScriptWebHost = appPoolId.length() > 0 && NewRelic::Profiler::Strings::ContainsCaseInsensitive(commandLine, appPoolId);
if (isAzureWebJobsScriptWebHost)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ namespace NewRelic { namespace Profiler { namespace Configuration { namespace Te
// tests to verify that "legacy" behavior (before azure function support) is retained.
// If NEW_RELIC_AZURE_FUNCTION_MODE_ENABLED environment variable is not set or is set to false,
// we should behave as if no azure function support has been added.
TEST_METHOD(azure_function_should_behave_as_legacy_if_azure_function_mode_disabled)
TEST_METHOD(isolated_azure_function_should_behave_as_legacy_if_azure_function_mode_disabled)
{
std::wstring configurationXml(L"\
<?xml version=\"1.0\"?>\
Expand All @@ -113,8 +113,25 @@ namespace NewRelic { namespace Profiler { namespace Configuration { namespace Te

Assert::IsTrue(configuration.ShouldInstrument(L"functionsnethost.exe", L"", L"", L"blah blah blah FooBarBaz blah blah blah", true));
}
TEST_METHOD(in_proc_azure_function_should_behave_as_legacy_if_azure_function_mode_disabled)
{
std::wstring configurationXml(L"\
<?xml version=\"1.0\"?>\
<configuration>\
<log level=\"deBug\"/>\
</configuration>\
");

auto systemCalls = std::make_shared<NewRelic::Profiler::Logger::Test::SystemCalls>();
systemCalls->environmentVariables[L"FUNCTIONS_WORKER_RUNTIME"] = L"dotnet";
systemCalls->environmentVariables[L"NEW_RELIC_AZURE_FUNCTION_MODE_ENABLED"] = L"0";

Configuration configuration(configurationXml, _missingConfig, L"", systemCalls);

Assert::IsTrue(configuration.ShouldInstrument(L"w3wp.exe", L"", L"someapppoolname", L"blah blah blah FooBarBaz blah blah blah", true));
}

TEST_METHOD(should_not_instrument_azure_function_app_pool_id_in_commandline)
TEST_METHOD(should_not_instrument_isolated_azure_function_app_pool_id_in_commandline)
{
std::wstring configurationXml(L"\
<?xml version=\"1.0\"?>\
Expand All @@ -132,7 +149,7 @@ namespace NewRelic { namespace Profiler { namespace Configuration { namespace Te
Assert::IsFalse(configuration.ShouldInstrument(L"w3wp.exe", L"", L"FooBarBaz", L"blah blah blah FooBarBaz blah blah blah", true));
}

TEST_METHOD(should_instrument_azure_function_fallback_to_app_pool_checking)
TEST_METHOD(should_instrument_isolated_azure_function_fallback_to_app_pool_checking)
{
std::wstring configurationXml(L"\
<?xml version=\"1.0\"?>\
Expand All @@ -150,7 +167,7 @@ namespace NewRelic { namespace Profiler { namespace Configuration { namespace Te
Assert::IsTrue(configuration.ShouldInstrument(L"w3wp.exe", L"", L"foo", L"", true));
}

TEST_METHOD(should_not_instrument_azure_function_func_exe_process_path)
TEST_METHOD(should_not_instrument_isolated_azure_function_func_exe_process_path)
{
std::wstring configurationXml(L"\
<?xml version=\"1.0\"?>\
Expand All @@ -168,7 +185,7 @@ namespace NewRelic { namespace Profiler { namespace Configuration { namespace Te
Assert::IsFalse(configuration.ShouldInstrument(L"func.exe", L"", L"", L"blah blah blah FooBarBaz blah blah blah", true));
}

TEST_METHOD(should_instrument_azure_function_functionsnethost_exe_process_path)
TEST_METHOD(should_instrument_isolated_azure_function_functionsnethost_exe_process_path)
{
std::wstring configurationXml(L"\
<?xml version=\"1.0\"?>\
Expand All @@ -186,7 +203,7 @@ namespace NewRelic { namespace Profiler { namespace Configuration { namespace Te
Assert::IsTrue(configuration.ShouldInstrument(L"functionsnethost.exe", L"", L"", L"blah blah blah FooBarBaz blah blah blah", true));
}

TEST_METHOD(should_instrument_azure_function_functions_worker_id_in_command_line)
TEST_METHOD(should_instrument_isolated_azure_function_functions_worker_id_in_command_line)
{
std::wstring configurationXml(L"\
<?xml version=\"1.0\"?>\
Expand All @@ -204,7 +221,7 @@ namespace NewRelic { namespace Profiler { namespace Configuration { namespace Te
Assert::IsTrue(configuration.ShouldInstrument(L"SomeFW481FunctionApp.exe", L"", L"", L"blah blah blah --functions-worker-id FooBarBaz blah blah blah", false));
}

TEST_METHOD(should_instrument_azure_function_worker_id_in_command_line)
TEST_METHOD(should_instrument_isolated_azure_function_worker_id_in_command_line)
{
std::wstring configurationXml(L"\
<?xml version=\"1.0\"?>\
Expand All @@ -222,6 +239,42 @@ namespace NewRelic { namespace Profiler { namespace Configuration { namespace Te
Assert::IsTrue(configuration.ShouldInstrument(L"SomeFW481FunctionApp.exe", L"", L"", L"blah blah blah --worker-id FooBarBaz blah blah blah", false));
}

TEST_METHOD(should_instrument_in_process_azure_function)
{
std::wstring configurationXml(L"\
<?xml version=\"1.0\"?>\
<configuration>\
<log level=\"deBug\"/>\
</configuration>\
");

auto systemCalls = std::make_shared<NewRelic::Profiler::Logger::Test::SystemCalls>();
systemCalls->environmentVariables[L"FUNCTIONS_WORKER_RUNTIME"] = L"dotnet";
systemCalls->environmentVariables[L"NEW_RELIC_AZURE_FUNCTION_MODE_ENABLED"] = L"true";

Configuration configuration(configurationXml, _missingConfig, L"", systemCalls);

Assert::IsTrue(configuration.ShouldInstrument(L"SomeFW481FunctionApp.exe", L"", L"someapppoolname", L"blah blah blah FooBarBaz blah blah blah", false));
}

TEST_METHOD(should_not_instrument_in_process_azure_function_kudu_app_pool)
{
std::wstring configurationXml(L"\
<?xml version=\"1.0\"?>\
<configuration>\
<log level=\"deBug\"/>\
</configuration>\
");

auto systemCalls = std::make_shared<NewRelic::Profiler::Logger::Test::SystemCalls>();
systemCalls->environmentVariables[L"FUNCTIONS_WORKER_RUNTIME"] = L"dotnet";
systemCalls->environmentVariables[L"NEW_RELIC_AZURE_FUNCTION_MODE_ENABLED"] = L"true";

Configuration configuration(configurationXml, _missingConfig, L"", systemCalls);

Assert::IsFalse(configuration.ShouldInstrument(L"SomeFW481FunctionApp.exe", L"", L"~somekuduapppool", L"blah blah blah FooBarBaz blah blah blah", false));
}

TEST_METHOD(instrument_process)
{
ProcessesPtr processes(new Processes());
Expand Down

0 comments on commit 32165bb

Please sign in to comment.