Skip to content

Add serverless mode support to Profiler #2285

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

Merged
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
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,5 @@ SPDX-License-Identifier: Apache-2.0
</match>
</tracerFactory>

<!-- Cus4tom instrumentation approach. Hardcoded for POC, would be added to the profiler via an environment variable in the real world-->
<!-- TODO: Figure out how to configure this from an environment variable at runtime -->
<tracerFactory name="NewRelic.Providers.Wrapper.AwsLambda.HandlerMethod">
<match assemblyName="LambdaFunctionTestApp" className="LambdaFunctionTestApp.Function">
<exactMethodMatcher methodName="FunctionHandler" />
</match>
</tracerFactory>

</instrumentation>
</extension>
2 changes: 1 addition & 1 deletion src/Agent/NewRelic/Home/Home.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
</Target>

<ItemGroup>
<PackageReference Include="NewRelic.Agent.Internal.Profiler" Version="10.20.2.33"/>
<PackageReference Include="NewRelic.Agent.Internal.Profiler" Version="10.20.2.73"/>
</ItemGroup>

</Project>
2 changes: 1 addition & 1 deletion src/Agent/NewRelic/Profiler/Common/AssemblyVersion.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ namespace NewRelic { namespace Profiler
{
return nullptr;
}
auto identifiers = Configuration::Strings::Split(version, '.');
auto identifiers = Configuration::Strings::Split(version, _X("."));
int major = 0;
int minor = 0;
int build = 0;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,8 @@
#include "../RapidXML/rapidxml.hpp"
#include "../Common/AssemblyVersion.h"
#include "IgnoreInstrumentation.h"
#include "../Configuration/Strings.h"
#include "../Logging/DefaultFileLogLocation.h"

namespace NewRelic { namespace Profiler { namespace Configuration
{
Expand All @@ -24,9 +26,11 @@ namespace NewRelic { namespace Profiler { namespace Configuration
class InstrumentationConfiguration
{
public:
InstrumentationConfiguration(InstrumentationXmlSetPtr instrumentationXmls, IgnoreInstrumentationListPtr ignoreList) :
InstrumentationConfiguration(InstrumentationXmlSetPtr instrumentationXmls, IgnoreInstrumentationListPtr ignoreList, std::shared_ptr<NewRelic::Profiler::Logger::IFileDestinationSystemCalls> systemCalls = nullptr) :
_instrumentationPointsSet(new InstrumentationPointSet())
, _ignoreList(ignoreList)
, _systemCalls(systemCalls)
, _foundServerlessInstrumentationPoint(false)
{
// pull instrumentation points from every xml string
for (auto instrumentationXml : *instrumentationXmls)
Expand Down Expand Up @@ -57,6 +61,8 @@ namespace NewRelic { namespace Profiler { namespace Configuration
InstrumentationConfiguration(InstrumentationPointSetPtr instrumentationPoints, IgnoreInstrumentationListPtr ignoreList) :
_instrumentationPointsSet(new InstrumentationPointSet())
, _ignoreList(ignoreList)
, _systemCalls(nullptr)
, _foundServerlessInstrumentationPoint(false)
{
for (auto instrumentationPoint : *instrumentationPoints)
{
Expand Down Expand Up @@ -113,8 +119,47 @@ namespace NewRelic { namespace Profiler { namespace Configuration
return nullptr;
}

private:
void CheckForEnvironmentInstrumentationPoint(void)
{
if (_foundServerlessInstrumentationPoint || (_systemCalls == nullptr))
{
return;
}
auto lambdaInstPoint = _systemCalls->TryGetEnvironmentVariable(_X("AWS_LAMBDA_FUNCTION_NAME"));
if (lambdaInstPoint != nullptr)
{
AddInstrumentationPointToCollectionFromEnvironment(*lambdaInstPoint);
_foundServerlessInstrumentationPoint = true;
}
}

void AddInstrumentationPointToCollectionFromEnvironment(xstring_t text)
{
auto segments = Strings::Split(text, _X("::"));
if (segments.size() != 3)
{
LogWarn(text, L" is not a valid method descriptor. It must be in the format 'assembly::class::method'");
return;
}
LogInfo(L"Serverless mode detected. Assembly: ", segments[0], L" Class: ", segments[1], L" Method: ", segments[2]);

InstrumentationPointPtr instrumentationPoint(new InstrumentationPoint());
// Note that this must exactly match the wrapper name in the managed Agent
instrumentationPoint->TracerFactoryName = _X("NewRelic.Providers.Wrapper.AwsLambda.HandlerMethod");
instrumentationPoint->MetricName = _X("");
instrumentationPoint->MetricType = _X("");
instrumentationPoint->AssemblyName = segments[0];
instrumentationPoint->MinVersion = nullptr;
instrumentationPoint->MaxVersion = nullptr;
instrumentationPoint->ClassName = segments[1];
instrumentationPoint->MethodName = segments[2];
instrumentationPoint->Parameters = nullptr;

(*_instrumentationPointsMap)[instrumentationPoint->GetMatchKey()].insert(instrumentationPoint);
_instrumentationPointsSet->insert(instrumentationPoint);
}

private:
static bool InstrumentationXmlIsDeprecated(xstring_t instrumentationXmlFilePath)
{
bool returnValue = false;
Expand All @@ -140,6 +185,7 @@ namespace NewRelic { namespace Profiler { namespace Configuration
const xstring_t& methodName,
const xstring_t& parameters) const
{

auto matchKey = InstrumentationPoint::GetMatchKey(assemblyName, className, methodName, parameters);
auto matchInstrumentation = TryGetInstrumentationPoints(matchKey);

Expand Down Expand Up @@ -457,6 +503,8 @@ namespace NewRelic { namespace Profiler { namespace Configuration
InstrumentationPointSetPtr _instrumentationPointsSet;
uint16_t _invalidFileCount = 0;
IgnoreInstrumentationListPtr _ignoreList;
std::shared_ptr<NewRelic::Profiler::Logger::IFileDestinationSystemCalls> _systemCalls;
bool _foundServerlessInstrumentationPoint;
};
typedef std::shared_ptr<InstrumentationConfiguration> InstrumentationConfigurationPtr;
}}}
4 changes: 2 additions & 2 deletions src/Agent/NewRelic/Profiler/Configuration/Strings.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ namespace NewRelic { namespace Profiler { namespace Configuration
class Strings
{
public:
static std::vector<xstring_t> Split( const xstring_t& text, wchar_t delimiter )
static std::vector<xstring_t> Split( const xstring_t& text, const xstring_t& delimiter )
{
std::vector<xstring_t> result;

Expand All @@ -26,7 +26,7 @@ namespace NewRelic { namespace Profiler { namespace Configuration

result.push_back( token );

start = end + 1;
start = end + delimiter.length();
end = text.find( delimiter, start );
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ namespace NewRelic { namespace Profiler { namespace Configuration { namespace Te
Assert::IsFalse(IgnoreInstrumentation::Matches(ignoreList, _X("myassembly"), _X("M")));

auto xmlSet = GetInstrumentationXmlSet();
InstrumentationConfiguration instrumentation(xmlSet, ignoreList);
InstrumentationConfiguration instrumentation(xmlSet, ignoreList, nullptr);

auto instrumentationPoint = instrumentation.TryGetInstrumentationPoint(std::make_shared<MethodRewriter::Test::MockFunction>());
// Should fail to find the instrumentation because it's in the ignore list
Expand Down Expand Up @@ -74,7 +74,7 @@ namespace NewRelic { namespace Profiler { namespace Configuration { namespace Te
Assert::IsFalse(IgnoreInstrumentation::Matches(ignoreList, _X(""), _X("")));

auto xmlSet = GetInstrumentationXmlSet();
InstrumentationConfiguration instrumentation(xmlSet, ignoreList);
InstrumentationConfiguration instrumentation(xmlSet, ignoreList, nullptr);

auto instrumentationPoint = instrumentation.TryGetInstrumentationPoint(std::make_shared<MethodRewriter::Test::MockFunction>());
// Should fail to find the instrumentation because it's in the ignore list
Expand Down Expand Up @@ -103,7 +103,7 @@ namespace NewRelic { namespace Profiler { namespace Configuration { namespace Te
Assert::IsFalse(IgnoreInstrumentation::Matches(ignoreList, _X(""), _X("MyNamespace.MyClass")));

auto xmlSet = GetInstrumentationXmlSet();
InstrumentationConfiguration instrumentation(xmlSet, ignoreList);
InstrumentationConfiguration instrumentation(xmlSet, ignoreList, nullptr);

auto instrumentationPoint = instrumentation.TryGetInstrumentationPoint(std::make_shared<MethodRewriter::Test::MockFunction>());
// Should find the instrumentation because the ignore list is invalid
Expand Down Expand Up @@ -138,7 +138,7 @@ namespace NewRelic { namespace Profiler { namespace Configuration { namespace Te
Assert::IsFalse(IgnoreInstrumentation::Matches(ignoreList, _X("different"), _X("MyNamespace.MyClass")));

auto xmlSet = GetInstrumentationXmlSet();
InstrumentationConfiguration instrumentation(xmlSet, ignoreList);
InstrumentationConfiguration instrumentation(xmlSet, ignoreList, nullptr);

auto instrumentationPoint = instrumentation.TryGetInstrumentationPoint(std::make_shared<MethodRewriter::Test::MockFunction>());
// Should fail to find the instrumentation because it's in the ignore list
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -949,5 +949,43 @@ namespace NewRelic { namespace Profiler { namespace Configuration { namespace Te
auto instrumentationPoint = instrumentation.TryGetInstrumentationPoint(std::make_shared<MethodRewriter::Test::MockFunction>());
Assert::IsTrue(instrumentationPoint == nullptr);
}

TEST_METHOD(set_lambda_instrumentation_point_success)
{
InstrumentationXmlSetPtr xmlSet(new InstrumentationXmlSet());
xmlSet->emplace(L"filename", L"<?xml version=\"1.0\" encoding=\"utf-8\"?>");

InstrumentationConfiguration instrumentation(xmlSet, nullptr);
instrumentation.AddInstrumentationPointToCollectionFromEnvironment(_X("MyAssembly::MyNamespace.MyClass::MyMethod"));

auto instrumentationPoint = instrumentation.TryGetInstrumentationPoint(std::make_shared<MethodRewriter::Test::MockFunction>());
Assert::IsFalse(instrumentationPoint == nullptr);
}

TEST_METHOD(set_lambda_instrumentation_point_failure)
{
InstrumentationXmlSetPtr xmlSet(new InstrumentationXmlSet());
xmlSet->emplace(L"filename", L"<?xml version=\"1.0\" encoding=\"utf-8\"?>");

InstrumentationConfiguration instrumentation(xmlSet, nullptr);
instrumentation.AddInstrumentationPointToCollectionFromEnvironment(_X("MyAssembly::MyNamespace.MyClass::"));
instrumentation.AddInstrumentationPointToCollectionFromEnvironment(_X("MyAssembly::MyNamespace.MyClass::WrongMethod"));
instrumentation.AddInstrumentationPointToCollectionFromEnvironment(_X("MyAssembly::MyMethod"));
instrumentation.AddInstrumentationPointToCollectionFromEnvironment(_X("MyNamespace.MyClass:MyMethod"));
instrumentation.AddInstrumentationPointToCollectionFromEnvironment(_X("MyMethod"));
instrumentation.AddInstrumentationPointToCollectionFromEnvironment(_X(":::MyMethod"));
instrumentation.AddInstrumentationPointToCollectionFromEnvironment(_X("::::::MyMethod"));
instrumentation.AddInstrumentationPointToCollectionFromEnvironment(_X(":::MyMethod::"));
instrumentation.AddInstrumentationPointToCollectionFromEnvironment(_X("MyAssembly:MyNamespace.MyClass:MyMethod"));
instrumentation.AddInstrumentationPointToCollectionFromEnvironment(_X("MyAssembly/MyNamespace.MyClass/MyMethod"));
instrumentation.AddInstrumentationPointToCollectionFromEnvironment(_X("MyAssembly_MyNamespace.MyClass_MyMethod"));
instrumentation.AddInstrumentationPointToCollectionFromEnvironment(_X("MyAssembly MyNamespace.MyClass MyMethod"));
instrumentation.AddInstrumentationPointToCollectionFromEnvironment(_X(" MyAssembly::MyNamespace.MyClass:MyMethod"));
instrumentation.AddInstrumentationPointToCollectionFromEnvironment(_X("MyAssembly::MyNamespace.MyClass::MyMethod"));
instrumentation.AddInstrumentationPointToCollectionFromEnvironment(_X("MyAssembly::MyNamespace .MyClass:: MyMethod"));

auto instrumentationPoint = instrumentation.TryGetInstrumentationPoint(std::make_shared<MethodRewriter::Test::MockFunction>());
Assert::IsFalse(instrumentationPoint == nullptr);
}
};
}}}}
1 change: 1 addition & 0 deletions src/Agent/NewRelic/Profiler/MethodRewriter/Instrumentors.h
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ namespace NewRelic { namespace Profiler { namespace MethodRewriter
{
bool Instrument(IFunctionPtr function, InstrumentationSettingsPtr instrumentationSettings) override
{
instrumentationSettings->GetInstrumentationConfiguration()->CheckForEnvironmentInstrumentationPoint();
auto instrumentationPoint = instrumentationSettings->GetInstrumentationConfiguration()->TryGetInstrumentationPoint(function);
if (instrumentationPoint == nullptr)
{
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -643,7 +643,7 @@ namespace NewRelic { namespace Profiler {
newIgnoreInstrumentationList = oldIgnoreList;
}

auto instrumentationConfiguration = std::make_shared<Configuration::InstrumentationConfiguration>(instrumentationXmls, newIgnoreInstrumentationList);
auto instrumentationConfiguration = std::make_shared<Configuration::InstrumentationConfiguration>(instrumentationXmls, newIgnoreInstrumentationList, _systemCalls);
if (instrumentationConfiguration->GetInvalidFileCount() > 0) {
LogError(L"Unable to parse one or more instrumentation files. Instrumentation will not be refreshed.");
return S_FALSE;
Expand Down Expand Up @@ -1163,7 +1163,7 @@ namespace NewRelic { namespace Profiler {
std::shared_ptr<Configuration::InstrumentationConfiguration> InitializeInstrumentationConfig(NewRelic::Profiler::Configuration::IgnoreInstrumentationListPtr ignoreList)
{
auto instrumentationXmls = GetInstrumentationXmlsFromDisk(_systemCalls);
auto instrumentationConfiguration = std::make_shared<Configuration::InstrumentationConfiguration>(instrumentationXmls, ignoreList);
auto instrumentationConfiguration = std::make_shared<Configuration::InstrumentationConfiguration>(instrumentationXmls, ignoreList, _systemCalls);
if (instrumentationConfiguration->GetInvalidFileCount() > 0) {
LogWarn(L"Unable to parse one or more instrumentation files. Live instrumentation reloading will not work until the unparsable file(s) are corrected or removed.");
}
Expand Down