Skip to content

Commit

Permalink
feat: Add AWSSDK.DynamoDBv2 instrumentation (#2858)
Browse files Browse the repository at this point in the history
* Bring initial POC to feature branch (#2836)

* First pass at DynamoDB support

* db.system attribute was not being set correctly

---------

Co-authored-by: chynesNR <chynes@newrelic.com>

* Clean up POC (#2839)

* Remove datastore vendor name from ConnectionInfo in MemcachedHelpers

* Get operation name from request type by converting PascalCaseRequest to snake_case

* Make operation name cache thread safe (#2841)

* Make operation name cache thread safe

* Cleaner implementation based on PR feedback

* Update src/Agent/NewRelic/Agent/Extensions/Providers/Wrapper/AwsSdk/DynamoDbRequestHandler.cs

Co-authored-by: Chris Ventura <45495992+nrcventura@users.noreply.github.com>

---------

Co-authored-by: Chris Ventura <45495992+nrcventura@users.noreply.github.com>

* DynamoDB integration tests (#2854)

* Rename things in preparation to add another AWS SDK test type

* Add project dependencies

* More renaming

* Initial plumbing created

* More cleanup

* Forklift exerciser methods from standalone test app

* Working tests

* Cleanup

* Fix port assignment issue seen in CI

* Only wait up to two minutes for table to become active

* List ports in use in container tests host

For temporary troubleshooting

* Update tests/Agent/IntegrationTests/ContainerApplications/AwsSdkTestApp/AwsSdkExercisers/AwsSdkDynamoDBExerciser.cs

Co-authored-by: Marty T <120425148+tippmar-nr@users.noreply.github.com>

* Fix port conflict issue plus PR feedback

---------

Co-authored-by: Marty T <120425148+tippmar-nr@users.noreply.github.com>

* Add unit tests for ToSnakeCase() (#2859)

Unit tests for ToSnakeCase()

---------

Co-authored-by: chynesNR <chynes@newrelic.com>
Co-authored-by: Chris Ventura <45495992+nrcventura@users.noreply.github.com>
Co-authored-by: Marty T <120425148+tippmar-nr@users.noreply.github.com>
  • Loading branch information
4 people authored Oct 29, 2024
1 parent ae1d422 commit 2460527
Show file tree
Hide file tree
Showing 41 changed files with 752 additions and 112 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/run_linux_container_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -76,6 +76,6 @@ jobs:
INTEGRATION_TEST_SECRETS: ${{ secrets.TEST_SECRETS }}
run: |
echo $INTEGRATION_TEST_SECRETS | dotnet user-secrets set --project ${{ env.integration_tests_shared_project }}
- name: Build & Run Linux Container Integration Tests
run: dotnet test ./tests/Agent/IntegrationTests/ContainerIntegrationTests/ContainerIntegrationTests.csproj --framework net8.0
2 changes: 1 addition & 1 deletion src/Agent/NewRelic/Agent/Core/Api/TransactionBridgeApi.cs
Original file line number Diff line number Diff line change
Expand Up @@ -226,7 +226,7 @@ public ISegment StartDatastoreSegment(string vendor, string model, string operat
var method = new Method(typeof(object), "StartDatastoreSegment", string.Empty);
var methodCall = new MethodCall(method, null, null, false);
var parsedSqlStatement = new ParsedSqlStatement(DatastoreVendor.Other, model, operation);
var connectionInfo = new ConnectionInfo(vendor.ToLower(), host, portPathOrID, databaseName);
var connectionInfo = new ConnectionInfo(host, portPathOrID, databaseName);
return _transaction.StartDatastoreSegment(
methodCall: methodCall,
parsedSqlStatement: parsedSqlStatement,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,6 @@ public class DatastoreSegmentData : AbstractSegmentData, IDatastoreSegmentData
public DatastoreVendor DatastoreVendorName => _parsedSqlStatement.DatastoreVendor;
public string Model => _parsedSqlStatement.Model;
public string CommandText { get; set; }
public string Vendor => _connectionInfo.Vendor;
public string Host => _connectionInfo.Host;
public int? Port => _connectionInfo.Port;
public string PathOrId => _connectionInfo.PathOrId;
Expand Down Expand Up @@ -222,7 +221,7 @@ public override void SetSpanTypeSpecificAttributes(SpanAttributeValueCollection
AttribDefs.DbCollection.TrySetValue(attribVals, _parsedSqlStatement.Model);
}

AttribDefs.DbSystem.TrySetValue(attribVals, Vendor);
AttribDefs.DbSystem.TrySetValue(attribVals, DatastoreVendorName.ToKnownName());
AttribDefs.DbInstance.TrySetValue(attribVals, DatabaseName);
AttribDefs.DbOperation.TrySetValue(attribVals, Operation);
AttribDefs.PeerAddress.TrySetValue(attribVals, $"{Host}:{PortPathOrId}");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public ConnectionInfo GetConnectionInfo(string utilizationHostName)
var portPathOrId = ParsePortPathOrId();
var databaseName = ConnectionStringParserHelper.GetKeyValuePair(_connectionStringBuilder, _databaseNameKeys)?.Value;

return new ConnectionInfo(DatastoreVendor.IBMDB2.ToKnownName(), host, portPathOrId, databaseName);
return new ConnectionInfo(host, portPathOrId, databaseName);
}

private string ParseHost()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ public ConnectionInfo GetConnectionInfo(string utilizationHostName)
var portPathOrId = ParsePortPathOrId();
var databaseName = ConnectionStringParserHelper.GetKeyValuePair(_connectionStringBuilder, _databaseNameKeys)?.Value;
var instanceName = ParseInstanceName();
return new ConnectionInfo(DatastoreVendor.MySQL.ToKnownName(), host, portPathOrId, databaseName, instanceName);
return new ConnectionInfo(host, portPathOrId, databaseName, instanceName);
}

private string ParseHost()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public ConnectionInfo GetConnectionInfo(string utilizationHostName)
var port = ConnectionStringParserHelper.GetKeyValuePair(_connectionStringBuilder, _portKeys)?.Value;
if (port == null && host != null)
{
return new ConnectionInfo(DatastoreVendor.MySQL.ToKnownName(), host, "default", databaseName);
return new ConnectionInfo(host, "default", databaseName);
}
else
{
Expand All @@ -44,7 +44,7 @@ public ConnectionInfo GetConnectionInfo(string utilizationHostName)
{
portNum = -1;
}
return new ConnectionInfo(DatastoreVendor.MySQL.ToKnownName(), host, portNum, databaseName);
return new ConnectionInfo(host, portNum, databaseName);
}


Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,14 +29,14 @@ public ConnectionInfo GetConnectionInfo(string utilizationHostName)
var portStr = ParsePortString();
if (string.IsNullOrEmpty(portStr))
{
return new ConnectionInfo(DatastoreVendor.Oracle.ToKnownName(), host, "default", null);
return new ConnectionInfo(host, "default", null);
}
int port;
if (!int.TryParse(portStr, out port))
{
port = -1;
}
return new ConnectionInfo(DatastoreVendor.Oracle.ToKnownName(), host, port, null);
return new ConnectionInfo(host, port, null);
}

private string ParseHost()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ public ConnectionInfo GetConnectionInfo(string utilizationHostName)
portNum = -1;
}

return new ConnectionInfo(DatastoreVendor.Postgres.ToKnownName(), host, portNum, databaseName);
return new ConnectionInfo(host, portNum, databaseName);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ public ConnectionInfo GetConnectionInfo(string utilizationHostName)
{
portNum = -1;
}
return new ConnectionInfo(DatastoreVendor.Redis.ToKnownName(), ConnectionStringParserHelper.NormalizeHostname(hostPortPair[0], utilizationHostName), portNum, null);
return new ConnectionInfo(ConnectionStringParserHelper.NormalizeHostname(hostPortPair[0], utilizationHostName), portNum, null);
}

return new ConnectionInfo(null, null, null, null);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@ namespace NewRelic.Agent.Extensions.Parsing
{
public class ConnectionInfo
{
public ConnectionInfo(string vendor, string host, int port, string databaseName, string instanceName = null)
public ConnectionInfo(string host, int port, string databaseName, string instanceName = null)
{
Vendor = vendor;
Host = ValueOrUnknown(host);
if (port >= 0)
{
Expand All @@ -18,9 +17,8 @@ public ConnectionInfo(string vendor, string host, int port, string databaseName,
InstanceName = instanceName;
}

public ConnectionInfo(string vendor, string host, string pathOrId, string databaseName, string instanceName = null)
public ConnectionInfo(string host, string pathOrId, string databaseName, string instanceName = null)
{
Vendor = vendor;
Host = ValueOrUnknown(host);
Port = null;
PathOrId = ValueOrUnknown(pathOrId);
Expand All @@ -33,7 +31,6 @@ private static string ValueOrUnknown(string value)
return string.IsNullOrEmpty(value) ? "unknown" : value;
}

public string Vendor { get; private set; }
public string Host { get; private set; }
public string PortPathOrId { get => (Port != null) ? Port.ToString() : PathOrId; }
public int? Port { get; private set; } = null;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,5 +119,34 @@ public static string RemoveBracketsQuotesParenthesis(string value)

return value;
}

public static string ToSnakeCase(this string text)
{
if (text == null)
{
throw new ArgumentNullException(nameof(text));
}
if (text.Length < 2)
{
return text.ToLowerInvariant();
}
var sb = new StringBuilder();
sb.Append(char.ToLowerInvariant(text[0]));
for (int i = 1; i < text.Length; ++i)
{
char c = text[i];
if (char.IsUpper(c))
{
sb.Append('_');
sb.Append(char.ToLowerInvariant(c));
}
else
{
sb.Append(c);
}
}
return sb.ToString();
}

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ public enum DatastoreVendor
//SQLite,
CosmosDB,
Elasticsearch,
DynamoDB,
Other
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,10 @@ public AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMethodCall ins
{
return SQSRequestHandler.HandleSQSRequest(instrumentedMethodCall, agent, transaction, request, isAsync, executionContext);
}
else if (requestType.StartsWith("Amazon.DynamoDBv2"))
{
return DynamoDbRequestHandler.HandleDynamoDbRequest(instrumentedMethodCall, agent, transaction, request, isAsync, executionContext);
}

if (!_unsupportedRequestTypes.Contains(requestType)) // log once per unsupported request type
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
// Copyright 2020 New Relic, Inc. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

using System.Collections.Concurrent;
using System.Threading.Tasks;
using NewRelic.Agent.Api;
using NewRelic.Agent.Extensions.Parsing;
using NewRelic.Agent.Extensions.Providers.Wrapper;

namespace NewRelic.Providers.Wrapper.AwsSdk
{
internal static class DynamoDbRequestHandler
{

private static ConcurrentDictionary<string,string> _operationNameCache = new ConcurrentDictionary<string,string>();

public static AfterWrappedMethodDelegate HandleDynamoDbRequest(InstrumentedMethodCall instrumentedMethodCall, IAgent agent, ITransaction transaction, dynamic request, bool isAsync, dynamic executionContext)
{
var requestType = request.GetType().Name as string;

string model;
string operation;

// PutItemRequest => put_item,
// CreateTableRequest => create_table, etc.
operation = _operationNameCache.GetOrAdd(requestType, GetOperationNameFromRequestType);

// Even though there is no common interface they all implement, every Request type I checked
// has a TableName property
model = request.TableName;

var segment = transaction.StartDatastoreSegment(instrumentedMethodCall.MethodCall, new ParsedSqlStatement(DatastoreVendor.DynamoDB, model, operation), isLeaf: true);
return isAsync ?
Delegates.GetAsyncDelegateFor<Task>(agent, segment)
:
Delegates.GetDelegateFor(segment);
}

private static string GetOperationNameFromRequestType(string requestType)
{
return requestType.Replace("Request", string.Empty).ToSnakeCase();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2020 New Relic, Inc. All rights reserved.
// Copyright 2020 New Relic, Inc. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

using System;
Expand Down Expand Up @@ -78,7 +78,7 @@ public AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMethodCall ins
var segment = transaction.StartDatastoreSegment(
instrumentedMethodCall.MethodCall,
new ParsedSqlStatement(DatastoreVendor.CosmosDB, model, operation),
connectionInfo: endpoint != null ? new ConnectionInfo(DatastoreVendor.CosmosDB.ToKnownName(), endpoint.Host, endpoint.Port, databaseName) : new ConnectionInfo(string.Empty, string.Empty, string.Empty, databaseName),
connectionInfo: endpoint != null ? new ConnectionInfo(endpoint.Host, endpoint.Port, databaseName) : new ConnectionInfo(string.Empty, string.Empty, string.Empty, databaseName),
commandText : querySpec != null ? _queryGetter.Invoke(querySpec) : string.Empty,
isLeaf: true);

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
// Copyright 2020 New Relic, Inc. All rights reserved.
// Copyright 2020 New Relic, Inc. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

using System;
Expand Down Expand Up @@ -67,7 +67,7 @@ public AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMethodCall ins
var segment = transaction.StartDatastoreSegment(
instrumentedMethodCall.MethodCall,
new ParsedSqlStatement(DatastoreVendor.CosmosDB, model, operation),
connectionInfo: endpoint != null ? new ConnectionInfo(DatastoreVendor.CosmosDB.ToKnownName(), endpoint.Host, endpoint.Port, databaseName) : new ConnectionInfo(string.Empty, string.Empty, string.Empty, databaseName),
connectionInfo: endpoint != null ? new ConnectionInfo(endpoint.Host, endpoint.Port, databaseName) : new ConnectionInfo(string.Empty, string.Empty, string.Empty, databaseName),
isLeaf: true);

return Delegates.GetAsyncDelegateFor<Task>(agent, segment);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ public AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMethodCall ins
}

var transactionExperimental = transaction.GetExperimentalApi();
var datastoreSegmentData = transactionExperimental.CreateDatastoreSegmentData(new ParsedSqlStatement(DatastoreVendor.Elasticsearch, model, operation), new ConnectionInfo(DatastoreVendor.Elasticsearch.ToKnownName(), string.Empty, string.Empty, string.Empty), string.Empty, null);
var datastoreSegmentData = transactionExperimental.CreateDatastoreSegmentData(new ParsedSqlStatement(DatastoreVendor.Elasticsearch, model, operation), new ConnectionInfo(string.Empty, string.Empty, string.Empty), string.Empty, null);
var segment = transactionExperimental.StartSegment(instrumentedMethodCall.MethodCall);
segment.GetExperimentalApi().SetSegmentData(datastoreSegmentData).MakeLeaf();

Expand Down Expand Up @@ -271,7 +271,7 @@ private static void SetUriOnDatastoreSegment(ISegment segment, Uri uri)
{
var segmentExperimentalApi = segment.GetExperimentalApi();
var data = segmentExperimentalApi.SegmentData as IDatastoreSegmentData;
data.SetConnectionInfo(new ConnectionInfo(DatastoreVendor.Elasticsearch.ToKnownName(), uri.Host, uri.Port, string.Empty));
data.SetConnectionInfo(new ConnectionInfo(uri.Host, uri.Port, string.Empty));
segmentExperimentalApi.SetSegmentData(data);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
using System;
using NewRelic.Agent.Api;
using NewRelic.Agent.Extensions.Parsing;
using NewRelic.Agent.Extensions.Providers.Wrapper;
using NewRelic.Reflection;

namespace NewRelic.Providers.Wrapper.Memcached
Expand All @@ -27,7 +26,7 @@ public static ConnectionInfo GetConnectionInfo(string key, object target, IAgent
{
if (_hasGetServerFailed)
{
return new ConnectionInfo(DatastoreVendor.Memcached.ToKnownName(), null, -1, null);
return new ConnectionInfo(null, -1, null);
}

try
Expand Down Expand Up @@ -67,13 +66,13 @@ public static ConnectionInfo GetConnectionInfo(string key, object target, IAgent
_portGetter ??= VisibilityBypasser.Instance.GeneratePropertyAccessor<int>(endpointType, "Port");
int? port = _portGetter(endpoint);

return new ConnectionInfo(DatastoreVendor.Memcached.ToKnownName(), address, port.HasValue ? port.Value : -1, null);
return new ConnectionInfo(address, port.HasValue ? port.Value : -1, null);
}
catch (Exception exception)
{
agent.Logger.Warn(exception, "Unable to get Memcached server address/port, likely to due to type differences. Server address/port will not be available.");
_hasGetServerFailed = true;
return new ConnectionInfo(DatastoreVendor.Memcached.ToKnownName(), null, -1, null);
return new ConnectionInfo(null, -1, null);
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -128,7 +128,7 @@ public static ConnectionInfo GetConnectionInfoFromCursor(object asyncCursor, obj

var databaseName = GetDatabaseNameFromCollectionNamespace(collectionNamespace);

return new ConnectionInfo(DatastoreVendor.MongoDB.ToKnownName(), host, port, databaseName);
return new ConnectionInfo(host, port, databaseName);
}

public static ConnectionInfo GetConnectionInfoFromDatabase(object database, string utilizationHostName)
Expand All @@ -146,7 +146,7 @@ public static ConnectionInfo GetConnectionInfoFromDatabase(object database, stri
host = ConnectionStringParserHelper.NormalizeHostname(rawHost, utilizationHostName);
}

return new ConnectionInfo(DatastoreVendor.MongoDB.ToKnownName(), host, port, databaseName);
return new ConnectionInfo(host, port, databaseName);
}

private static IList GetServersFromDatabase(object database)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,7 @@ public AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMethodCall ins
portNum = -1;
}
var databaseName = TryGetPropertyName(PropertyDatabaseName, contextObject);
var connectionInfo = new ConnectionInfo(DatastoreVendor.Redis.ToKnownName(), host, portNum, databaseName);
var connectionInfo = new ConnectionInfo(host, portNum, databaseName);

var segment = transaction.StartDatastoreSegment(instrumentedMethodCall.MethodCall, ParsedSqlStatement.FromOperation(DatastoreVendor.Redis, operation), connectionInfo);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ public static ConnectionInfo GetConnectionInfoFromConnectionMultiplexer(MethodCa
return null;
}

return new ConnectionInfo(DatastoreVendor.Redis.ToKnownName(), host, port, null, null);
return new ConnectionInfo(host, port, null, null);
}

private static string GetCommandNameFromEnumValue(Enum commandValue)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -121,14 +121,14 @@ private ConnectionInfo GetConnectionInfo(EndPoint endpoint)
{
var port = dnsEndpoint.Port;
var host = ConnectionStringParserHelper.NormalizeHostname(dnsEndpoint.Host, _agent.Configuration.UtilizationHostName);
return new ConnectionInfo(DatastoreVendor.Redis.ToKnownName(), host, port, null, null);
return new ConnectionInfo(host, port, null, null);
}

if (endpoint is IPEndPoint ipEndpoint)
{
var port = ipEndpoint.Port;
var host = ConnectionStringParserHelper.NormalizeHostname(ipEndpoint.Address.ToString(), _agent.Configuration.UtilizationHostName);
return new ConnectionInfo(DatastoreVendor.Redis.ToKnownName(), host, port, null, null);
return new ConnectionInfo(host, port, null, null);
}

return null;
Expand Down

This file was deleted.

Loading

0 comments on commit 2460527

Please sign in to comment.