Skip to content

Commit e466d90

Browse files
Add serverless mode support to Profiler (#2285)
* Add serverless mode support to Profiler * Attempting to auto-instrument lambda * chore: Update Profiler NuGet Package Reference to v10.20.2.73. (#2302) Co-authored-by: chynesNR <[email protected]> --------- Co-authored-by: github-actions[bot] <41898282+github-actions[bot]@users.noreply.github.com>
1 parent 105a751 commit e466d90

File tree

9 files changed

+99
-20
lines changed

9 files changed

+99
-20
lines changed

src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AwsLambda/Instrumentation.xml

-8
Original file line numberDiff line numberDiff line change
@@ -13,13 +13,5 @@ SPDX-License-Identifier: Apache-2.0
1313
</match>
1414
</tracerFactory>
1515

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

src/Agent/NewRelic/Home/Home.csproj

+1-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@
1313
</Target>
1414

1515
<ItemGroup>
16-
<PackageReference Include="NewRelic.Agent.Internal.Profiler" Version="10.20.2.33"/>
16+
<PackageReference Include="NewRelic.Agent.Internal.Profiler" Version="10.20.2.73"/>
1717
</ItemGroup>
1818

1919
</Project>

src/Agent/NewRelic/Profiler/Common/AssemblyVersion.h

+1-1
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ namespace NewRelic { namespace Profiler
7575
{
7676
return nullptr;
7777
}
78-
auto identifiers = Configuration::Strings::Split(version, '.');
78+
auto identifiers = Configuration::Strings::Split(version, _X("."));
7979
int major = 0;
8080
int minor = 0;
8181
int build = 0;

src/Agent/NewRelic/Profiler/Configuration/InstrumentationConfiguration.h

+50-2
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,8 @@
1414
#include "../RapidXML/rapidxml.hpp"
1515
#include "../Common/AssemblyVersion.h"
1616
#include "IgnoreInstrumentation.h"
17+
#include "../Configuration/Strings.h"
18+
#include "../Logging/DefaultFileLogLocation.h"
1719

1820
namespace NewRelic { namespace Profiler { namespace Configuration
1921
{
@@ -24,9 +26,11 @@ namespace NewRelic { namespace Profiler { namespace Configuration
2426
class InstrumentationConfiguration
2527
{
2628
public:
27-
InstrumentationConfiguration(InstrumentationXmlSetPtr instrumentationXmls, IgnoreInstrumentationListPtr ignoreList) :
29+
InstrumentationConfiguration(InstrumentationXmlSetPtr instrumentationXmls, IgnoreInstrumentationListPtr ignoreList, std::shared_ptr<NewRelic::Profiler::Logger::IFileDestinationSystemCalls> systemCalls = nullptr) :
2830
_instrumentationPointsSet(new InstrumentationPointSet())
2931
, _ignoreList(ignoreList)
32+
, _systemCalls(systemCalls)
33+
, _foundServerlessInstrumentationPoint(false)
3034
{
3135
// pull instrumentation points from every xml string
3236
for (auto instrumentationXml : *instrumentationXmls)
@@ -57,6 +61,8 @@ namespace NewRelic { namespace Profiler { namespace Configuration
5761
InstrumentationConfiguration(InstrumentationPointSetPtr instrumentationPoints, IgnoreInstrumentationListPtr ignoreList) :
5862
_instrumentationPointsSet(new InstrumentationPointSet())
5963
, _ignoreList(ignoreList)
64+
, _systemCalls(nullptr)
65+
, _foundServerlessInstrumentationPoint(false)
6066
{
6167
for (auto instrumentationPoint : *instrumentationPoints)
6268
{
@@ -113,8 +119,47 @@ namespace NewRelic { namespace Profiler { namespace Configuration
113119
return nullptr;
114120
}
115121

116-
private:
122+
void CheckForEnvironmentInstrumentationPoint(void)
123+
{
124+
if (_foundServerlessInstrumentationPoint || (_systemCalls == nullptr))
125+
{
126+
return;
127+
}
128+
auto lambdaInstPoint = _systemCalls->TryGetEnvironmentVariable(_X("AWS_LAMBDA_FUNCTION_NAME"));
129+
if (lambdaInstPoint != nullptr)
130+
{
131+
AddInstrumentationPointToCollectionFromEnvironment(*lambdaInstPoint);
132+
_foundServerlessInstrumentationPoint = true;
133+
}
134+
}
135+
136+
void AddInstrumentationPointToCollectionFromEnvironment(xstring_t text)
137+
{
138+
auto segments = Strings::Split(text, _X("::"));
139+
if (segments.size() != 3)
140+
{
141+
LogWarn(text, L" is not a valid method descriptor. It must be in the format 'assembly::class::method'");
142+
return;
143+
}
144+
LogInfo(L"Serverless mode detected. Assembly: ", segments[0], L" Class: ", segments[1], L" Method: ", segments[2]);
117145

146+
InstrumentationPointPtr instrumentationPoint(new InstrumentationPoint());
147+
// Note that this must exactly match the wrapper name in the managed Agent
148+
instrumentationPoint->TracerFactoryName = _X("NewRelic.Providers.Wrapper.AwsLambda.HandlerMethod");
149+
instrumentationPoint->MetricName = _X("");
150+
instrumentationPoint->MetricType = _X("");
151+
instrumentationPoint->AssemblyName = segments[0];
152+
instrumentationPoint->MinVersion = nullptr;
153+
instrumentationPoint->MaxVersion = nullptr;
154+
instrumentationPoint->ClassName = segments[1];
155+
instrumentationPoint->MethodName = segments[2];
156+
instrumentationPoint->Parameters = nullptr;
157+
158+
(*_instrumentationPointsMap)[instrumentationPoint->GetMatchKey()].insert(instrumentationPoint);
159+
_instrumentationPointsSet->insert(instrumentationPoint);
160+
}
161+
162+
private:
118163
static bool InstrumentationXmlIsDeprecated(xstring_t instrumentationXmlFilePath)
119164
{
120165
bool returnValue = false;
@@ -140,6 +185,7 @@ namespace NewRelic { namespace Profiler { namespace Configuration
140185
const xstring_t& methodName,
141186
const xstring_t& parameters) const
142187
{
188+
143189
auto matchKey = InstrumentationPoint::GetMatchKey(assemblyName, className, methodName, parameters);
144190
auto matchInstrumentation = TryGetInstrumentationPoints(matchKey);
145191

@@ -457,6 +503,8 @@ namespace NewRelic { namespace Profiler { namespace Configuration
457503
InstrumentationPointSetPtr _instrumentationPointsSet;
458504
uint16_t _invalidFileCount = 0;
459505
IgnoreInstrumentationListPtr _ignoreList;
506+
std::shared_ptr<NewRelic::Profiler::Logger::IFileDestinationSystemCalls> _systemCalls;
507+
bool _foundServerlessInstrumentationPoint;
460508
};
461509
typedef std::shared_ptr<InstrumentationConfiguration> InstrumentationConfigurationPtr;
462510
}}}

src/Agent/NewRelic/Profiler/Configuration/Strings.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ namespace NewRelic { namespace Profiler { namespace Configuration
1313
class Strings
1414
{
1515
public:
16-
static std::vector<xstring_t> Split( const xstring_t& text, wchar_t delimiter )
16+
static std::vector<xstring_t> Split( const xstring_t& text, const xstring_t& delimiter )
1717
{
1818
std::vector<xstring_t> result;
1919

@@ -26,7 +26,7 @@ namespace NewRelic { namespace Profiler { namespace Configuration
2626

2727
result.push_back( token );
2828

29-
start = end + 1;
29+
start = end + delimiter.length();
3030
end = text.find( delimiter, start );
3131
}
3232

src/Agent/NewRelic/Profiler/ConfigurationTest/IgnoreInstrumentationTest.cpp

+4-4
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ namespace NewRelic { namespace Profiler { namespace Configuration { namespace Te
4343
Assert::IsFalse(IgnoreInstrumentation::Matches(ignoreList, _X("myassembly"), _X("M")));
4444

4545
auto xmlSet = GetInstrumentationXmlSet();
46-
InstrumentationConfiguration instrumentation(xmlSet, ignoreList);
46+
InstrumentationConfiguration instrumentation(xmlSet, ignoreList, nullptr);
4747

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

7676
auto xmlSet = GetInstrumentationXmlSet();
77-
InstrumentationConfiguration instrumentation(xmlSet, ignoreList);
77+
InstrumentationConfiguration instrumentation(xmlSet, ignoreList, nullptr);
7878

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

105105
auto xmlSet = GetInstrumentationXmlSet();
106-
InstrumentationConfiguration instrumentation(xmlSet, ignoreList);
106+
InstrumentationConfiguration instrumentation(xmlSet, ignoreList, nullptr);
107107

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

140140
auto xmlSet = GetInstrumentationXmlSet();
141-
InstrumentationConfiguration instrumentation(xmlSet, ignoreList);
141+
InstrumentationConfiguration instrumentation(xmlSet, ignoreList, nullptr);
142142

143143
auto instrumentationPoint = instrumentation.TryGetInstrumentationPoint(std::make_shared<MethodRewriter::Test::MockFunction>());
144144
// Should fail to find the instrumentation because it's in the ignore list

src/Agent/NewRelic/Profiler/ConfigurationTest/InstrumentationConfigurationTest.cpp

+38
Original file line numberDiff line numberDiff line change
@@ -949,5 +949,43 @@ namespace NewRelic { namespace Profiler { namespace Configuration { namespace Te
949949
auto instrumentationPoint = instrumentation.TryGetInstrumentationPoint(std::make_shared<MethodRewriter::Test::MockFunction>());
950950
Assert::IsTrue(instrumentationPoint == nullptr);
951951
}
952+
953+
TEST_METHOD(set_lambda_instrumentation_point_success)
954+
{
955+
InstrumentationXmlSetPtr xmlSet(new InstrumentationXmlSet());
956+
xmlSet->emplace(L"filename", L"<?xml version=\"1.0\" encoding=\"utf-8\"?>");
957+
958+
InstrumentationConfiguration instrumentation(xmlSet, nullptr);
959+
instrumentation.AddInstrumentationPointToCollectionFromEnvironment(_X("MyAssembly::MyNamespace.MyClass::MyMethod"));
960+
961+
auto instrumentationPoint = instrumentation.TryGetInstrumentationPoint(std::make_shared<MethodRewriter::Test::MockFunction>());
962+
Assert::IsFalse(instrumentationPoint == nullptr);
963+
}
964+
965+
TEST_METHOD(set_lambda_instrumentation_point_failure)
966+
{
967+
InstrumentationXmlSetPtr xmlSet(new InstrumentationXmlSet());
968+
xmlSet->emplace(L"filename", L"<?xml version=\"1.0\" encoding=\"utf-8\"?>");
969+
970+
InstrumentationConfiguration instrumentation(xmlSet, nullptr);
971+
instrumentation.AddInstrumentationPointToCollectionFromEnvironment(_X("MyAssembly::MyNamespace.MyClass::"));
972+
instrumentation.AddInstrumentationPointToCollectionFromEnvironment(_X("MyAssembly::MyNamespace.MyClass::WrongMethod"));
973+
instrumentation.AddInstrumentationPointToCollectionFromEnvironment(_X("MyAssembly::MyMethod"));
974+
instrumentation.AddInstrumentationPointToCollectionFromEnvironment(_X("MyNamespace.MyClass:MyMethod"));
975+
instrumentation.AddInstrumentationPointToCollectionFromEnvironment(_X("MyMethod"));
976+
instrumentation.AddInstrumentationPointToCollectionFromEnvironment(_X(":::MyMethod"));
977+
instrumentation.AddInstrumentationPointToCollectionFromEnvironment(_X("::::::MyMethod"));
978+
instrumentation.AddInstrumentationPointToCollectionFromEnvironment(_X(":::MyMethod::"));
979+
instrumentation.AddInstrumentationPointToCollectionFromEnvironment(_X("MyAssembly:MyNamespace.MyClass:MyMethod"));
980+
instrumentation.AddInstrumentationPointToCollectionFromEnvironment(_X("MyAssembly/MyNamespace.MyClass/MyMethod"));
981+
instrumentation.AddInstrumentationPointToCollectionFromEnvironment(_X("MyAssembly_MyNamespace.MyClass_MyMethod"));
982+
instrumentation.AddInstrumentationPointToCollectionFromEnvironment(_X("MyAssembly MyNamespace.MyClass MyMethod"));
983+
instrumentation.AddInstrumentationPointToCollectionFromEnvironment(_X(" MyAssembly::MyNamespace.MyClass:MyMethod"));
984+
instrumentation.AddInstrumentationPointToCollectionFromEnvironment(_X("MyAssembly::MyNamespace.MyClass::MyMethod"));
985+
instrumentation.AddInstrumentationPointToCollectionFromEnvironment(_X("MyAssembly::MyNamespace .MyClass:: MyMethod"));
986+
987+
auto instrumentationPoint = instrumentation.TryGetInstrumentationPoint(std::make_shared<MethodRewriter::Test::MockFunction>());
988+
Assert::IsFalse(instrumentationPoint == nullptr);
989+
}
952990
};
953991
}}}}

src/Agent/NewRelic/Profiler/MethodRewriter/Instrumentors.h

+1
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@ namespace NewRelic { namespace Profiler { namespace MethodRewriter
2929
{
3030
bool Instrument(IFunctionPtr function, InstrumentationSettingsPtr instrumentationSettings) override
3131
{
32+
instrumentationSettings->GetInstrumentationConfiguration()->CheckForEnvironmentInstrumentationPoint();
3233
auto instrumentationPoint = instrumentationSettings->GetInstrumentationConfiguration()->TryGetInstrumentationPoint(function);
3334
if (instrumentationPoint == nullptr)
3435
{

src/Agent/NewRelic/Profiler/Profiler/CorProfilerCallbackImpl.h

+2-2
Original file line numberDiff line numberDiff line change
@@ -643,7 +643,7 @@ namespace NewRelic { namespace Profiler {
643643
newIgnoreInstrumentationList = oldIgnoreList;
644644
}
645645

646-
auto instrumentationConfiguration = std::make_shared<Configuration::InstrumentationConfiguration>(instrumentationXmls, newIgnoreInstrumentationList);
646+
auto instrumentationConfiguration = std::make_shared<Configuration::InstrumentationConfiguration>(instrumentationXmls, newIgnoreInstrumentationList, _systemCalls);
647647
if (instrumentationConfiguration->GetInvalidFileCount() > 0) {
648648
LogError(L"Unable to parse one or more instrumentation files. Instrumentation will not be refreshed.");
649649
return S_FALSE;
@@ -1163,7 +1163,7 @@ namespace NewRelic { namespace Profiler {
11631163
std::shared_ptr<Configuration::InstrumentationConfiguration> InitializeInstrumentationConfig(NewRelic::Profiler::Configuration::IgnoreInstrumentationListPtr ignoreList)
11641164
{
11651165
auto instrumentationXmls = GetInstrumentationXmlsFromDisk(_systemCalls);
1166-
auto instrumentationConfiguration = std::make_shared<Configuration::InstrumentationConfiguration>(instrumentationXmls, ignoreList);
1166+
auto instrumentationConfiguration = std::make_shared<Configuration::InstrumentationConfiguration>(instrumentationXmls, ignoreList, _systemCalls);
11671167
if (instrumentationConfiguration->GetInvalidFileCount() > 0) {
11681168
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.");
11691169
}

0 commit comments

Comments
 (0)