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

feat: Enable DynamoDB entity relationships #2923

Merged
merged 52 commits into from
Jan 16, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
52 commits
Select commit Hold shift + click to select a range
4434a8c
First pass at instrumentation
chynesNR Sep 26, 2024
5475b49
First pass at integration tests
chynesNR Sep 30, 2024
3d213e6
Update ARN parsing logic
chynesNR Oct 8, 2024
7c33292
Updating ARN parsing logic
chynesNR Oct 8, 2024
c15328c
Fixes to integration tests
chynesNR Oct 9, 2024
8728cff
Forgot to add tests to workflow
chynesNR Oct 9, 2024
cdb0bf3
Don't generate a segment for the HTTP request
chynesNR Oct 14, 2024
10a5e4b
Fix some typos
chynesNR Oct 14, 2024
71304dc
Merge branch 'main' into feature/invoke-lambda-instrumentation
chynesNR Oct 14, 2024
db2202c
Cache constructed ARNs
chynesNR Oct 15, 2024
a14adbb
PR feedback
chynesNR Oct 15, 2024
28a5ebe
Fix unit test
chynesNR Oct 16, 2024
c673718
Merge branch 'main' into feature/invoke-lambda-instrumentation
chynesNR Oct 25, 2024
4340181
First pass at incorporating account ID
chynesNR Oct 28, 2024
2d1e5c0
First pass at ArnBuilder
chynesNR Nov 11, 2024
eb4a73e
Another pass at generic ARN construction
chynesNR Nov 15, 2024
35dad1c
Increasing code coverage
chynesNR Nov 15, 2024
d811849
Add supportability metric
chynesNR Nov 16, 2024
d47b5a4
Some cleanup and better logging
chynesNR Nov 25, 2024
00d2251
Merge branch 'main' into feature/invoke-lambda-instrumentation
chynesNR Nov 25, 2024
f229fc1
More logging improvements
chynesNR Nov 25, 2024
8af3e95
Fix bad merge
chynesNR Nov 25, 2024
272ace4
Fixing more merge errors
chynesNR Nov 26, 2024
0b91f6a
Trying to address CodeQL and CodeCov issues
chynesNR Nov 26, 2024
17f810f
Only pass on expected error
chynesNR Nov 26, 2024
614270c
Merge branch 'main' into feature/invoke-lambda-instrumentation
tippmar-nr Dec 2, 2024
641eafc
Revert change (expected error is not consistent)
chynesNR Dec 3, 2024
d107619
Merge branch 'feature/invoke-lambda-instrumentation' of https://githu…
chynesNR Dec 3, 2024
c36d25c
Merge remote-tracking branch 'origin/main' into feature/invoke-lambda…
tippmar-nr Dec 5, 2024
55cc8a4
Feature work/aws account id parsing (#2920)
tippmar-nr Dec 6, 2024
3377d4c
WIP: Dynamodb entity relationships
tippmar-nr Dec 6, 2024
411a3e0
Cache AWS Account ID by ClientConfig instance
tippmar-nr Dec 9, 2024
1da437b
Add aws sdk multi-service test (WIP)
tippmar-nr Dec 9, 2024
b8a7f2b
tweaks
tippmar-nr Dec 9, 2024
8d41f54
Moving cloud SDK attributes to Segments (#2924)
chynesNR Dec 10, 2024
f394d86
Merget from lambda invoke branch
tippmar-nr Dec 10, 2024
29e4db0
More wip
tippmar-nr Dec 10, 2024
8baf706
LRUCache, LRUHashSet, WeakReferenceKey
tippmar-nr Dec 10, 2024
da13c62
Some cleanup and code coverage
chynesNR Dec 10, 2024
662c1cd
More cleanup and code coverage
chynesNR Dec 11, 2024
4537d5d
Fixed unit test
chynesNR Dec 11, 2024
0a5cb25
Cache and weakrefkey fixes
tippmar-nr Dec 11, 2024
cad4613
Merge remote-tracking branch 'origin/feature/invoke-lambda-instrument…
tippmar-nr Dec 11, 2024
ea65673
Unit tests
tippmar-nr Dec 11, 2024
9de12e3
Unit test updates for code coverage
tippmar-nr Dec 11, 2024
a1e721b
Integration test cleanup
tippmar-nr Dec 11, 2024
d98fc3e
multi-service test update
tippmar-nr Dec 11, 2024
d23fe5d
Merge from main, resolve conflicts
tippmar-nr Jan 13, 2025
9fb17ae
Merge remote-tracking branch 'origin/main' into feature/dynamodb-enti…
tippmar-nr Jan 15, 2025
1c4e161
Merge latest, resolve conflicts, minor code cleanup
tippmar-nr Jan 15, 2025
7b39edf
integration test cleanup
tippmar-nr Jan 15, 2025
7970a23
Merge branch 'main' into feature/dynamodb-entity-relationship
tippmar-nr Jan 15, 2025
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
@@ -0,0 +1,121 @@
// Copyright 2020 New Relic, Inc. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

using System;
using System.Collections.Generic;
using System.Threading;

namespace NewRelic.Agent.Extensions.Caching
{
/// <summary>
/// A thread-safe LRU cache implementation.
/// </summary>
/// <typeparam name="TKey"></typeparam>
/// <typeparam name="TValue"></typeparam>
public class LRUCache<TKey, TValue>
{
private readonly int _capacity;
private readonly Dictionary<TKey, LinkedListNode<CacheItem>> _cacheMap;
private readonly LinkedList<CacheItem> _lruList;
private readonly ReaderWriterLockSlim _lock = new();

public LRUCache(int capacity)
{
if (capacity <= 0)
{
throw new ArgumentException("Capacity must be greater than zero.", nameof(capacity));
}

_capacity = capacity;
_cacheMap = new Dictionary<TKey, LinkedListNode<CacheItem>>(capacity);
_lruList = new LinkedList<CacheItem>();
}

public TValue Get(TKey key)
{
_lock.EnterUpgradeableReadLock();
try
{
if (_cacheMap.TryGetValue(key, out var node))
{
// Move the accessed node to the front of the list
_lock.EnterWriteLock();
try
{
_lruList.Remove(node);
_lruList.AddFirst(node);
}
finally
{
_lock.ExitWriteLock();
}
return node.Value.Value;
}
throw new KeyNotFoundException("The given key was not present in the cache.");
}
finally
{
_lock.ExitUpgradeableReadLock();
}
}

public void Put(TKey key, TValue value)
{
_lock.EnterWriteLock();
try
{
if (_cacheMap.TryGetValue(key, out var node))
{
// Update the value and move the node to the front of the list
node.Value.Value = value;
_lruList.Remove(node);
_lruList.AddFirst(node);
}
else
{
if (_cacheMap.Count >= _capacity)
{
// Remove the least recently used item
var lruNode = _lruList.Last;
_cacheMap.Remove(lruNode.Value.Key);
_lruList.RemoveLast();
}

// Add the new item to the cache
var cacheItem = new CacheItem(key, value);
var newNode = new LinkedListNode<CacheItem>(cacheItem);
_lruList.AddFirst(newNode);
_cacheMap[key] = newNode;
}
}
finally
{
_lock.ExitWriteLock();
}
}
public bool ContainsKey(TKey key)
{
_lock.EnterReadLock();
try
{
return _cacheMap.ContainsKey(key);
}
finally
{
_lock.ExitReadLock();
}
}

private class CacheItem
{
public TKey Key { get; }
public TValue Value { get; set; }

public CacheItem(TKey key, TValue value)
{
Key = key;
Value = value;
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
// Copyright 2020 New Relic, Inc. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

using System;
using System.Collections.Generic;
using System.Threading;

namespace NewRelic.Agent.Extensions.Caching
{
/// <summary>
/// A thread-safe LRU HashSet implementation.
/// </summary>
/// <typeparam name="T"></typeparam>
public class LRUHashSet<T>
{
private readonly int _capacity;
private readonly HashSet<T> _hashSet;
private readonly LinkedList<T> _lruList;
private readonly ReaderWriterLockSlim _lock = new();

public LRUHashSet(int capacity)
{
if (capacity <= 0)
{
throw new ArgumentException("Capacity must be greater than zero.", nameof(capacity));
}

_capacity = capacity;
_hashSet = new HashSet<T>();
_lruList = new LinkedList<T>();
}

public bool Add(T item)
{
_lock.EnterWriteLock();
try
{
if (_hashSet.Contains(item))
{
// Move the accessed item to the front of the list
_lruList.Remove(item);
_lruList.AddFirst(item);
return false;
}
else
{
if (_hashSet.Count >= _capacity)
{
// Remove the least recently used item
var lruItem = _lruList.Last.Value;
_hashSet.Remove(lruItem);
_lruList.RemoveLast();
}

// Add the new item to the set and list
_hashSet.Add(item);
_lruList.AddFirst(item);
return true;
}
}
finally
{
_lock.ExitWriteLock();
}
}

public bool Contains(T item)
{
_lock.EnterReadLock();
try
{
return _hashSet.Contains(item);
}
finally
{
_lock.ExitReadLock();
}
}

public bool Remove(T item)
{
_lock.EnterWriteLock();
try
{
if (_hashSet.Remove(item))
{
_lruList.Remove(item);
return true;
}
return false;
}
finally
{
_lock.ExitWriteLock();
}
}

public int Count
{
get
{
_lock.EnterReadLock();
try
{
return _hashSet.Count;
}
finally
{
_lock.ExitReadLock();
}
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright 2020 New Relic, Inc. All rights reserved.
// SPDX-License-Identifier: Apache-2.0

using System;

namespace NewRelic.Agent.Extensions.Caching
{
/// <summary>
/// Creates an object that can be used as a dictionary key, which holds a WeakReference&lt;T&gt;
/// </summary>
/// <typeparam name="T"></typeparam>
public class WeakReferenceKey<T> where T : class
{
private WeakReference<T> WeakReference { get; }

public WeakReferenceKey(T cacheKey)
{
WeakReference = new WeakReference<T>(cacheKey);
}

public override bool Equals(object obj)
{
if (obj is WeakReferenceKey<T> otherKey)
{
if (WeakReference.TryGetTarget(out var thisTarget) &&
otherKey.WeakReference.TryGetTarget(out var otherTarget))
{
return ReferenceEquals(thisTarget, otherTarget);
}
}

return false;
}

public override int GetHashCode()
{
if (WeakReference.TryGetTarget(out var target))
{
return target.GetHashCode();
}

return 0;
}

/// <summary>
/// Gets the value from the WeakReference or null if the target has been garbage collected.
/// </summary>
public T Value => WeakReference.TryGetTarget(out var target) ? target : null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,49 +4,62 @@
using System;
using NewRelic.Agent.Api;
using NewRelic.Agent.Extensions.AwsSdk;
using NewRelic.Agent.Extensions.Caching;
using NewRelic.Agent.Extensions.Providers.Wrapper;

namespace NewRelic.Providers.Wrapper.AwsSdk
namespace NewRelic.Providers.Wrapper.AwsSdk;

public class AmazonServiceClientWrapper : IWrapper
{
public class AmazonServiceClientWrapper : IWrapper
private const int LRUCapacity = 100;
// cache the account id per instance of AmazonServiceClient.Config
public static LRUCache<WeakReferenceKey<object>, string> AwsAccountIdByClientConfigCache = new(LRUCapacity);

// cache instances of AmazonServiceClient
private static readonly LRUHashSet<WeakReferenceKey<object>> AmazonServiceClientInstanceCache = new(LRUCapacity);

public bool IsTransactionRequired => false;

public CanWrapResponse CanWrap(InstrumentedMethodInfo instrumentedMethodInfo)
{
/// <summary>
/// The AWS account id.
/// Parsed from the access key in the credentials of the client - or fall back to the configuration value if parsing fails.
/// Assumes only a single account id is used in the application.
/// </summary>
public static string AwsAccountId { get; private set; }
return new CanWrapResponse(instrumentedMethodInfo.RequestedWrapperName == nameof(AmazonServiceClientWrapper));
}

public AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMethodCall instrumentedMethodCall, IAgent agent, ITransaction transaction)
{
object client = instrumentedMethodCall.MethodCall.InvocationTarget;

public bool IsTransactionRequired => false;
var weakReferenceKey = new WeakReferenceKey<object>(client);
if (AmazonServiceClientInstanceCache.Contains(weakReferenceKey)) // don't do anything if we've already seen this client instance
return Delegates.NoOp;

public CanWrapResponse CanWrap(InstrumentedMethodInfo instrumentedMethodInfo)
AmazonServiceClientInstanceCache.Add(weakReferenceKey);

string awsAccountId;
try
{
return new CanWrapResponse(instrumentedMethodInfo.RequestedWrapperName == nameof(AmazonServiceClientWrapper));
// get the AWSCredentials parameter
dynamic awsCredentials = instrumentedMethodCall.MethodCall.MethodArguments[0];

dynamic immutableCredentials = awsCredentials.GetCredentials();
string accessKey = immutableCredentials.AccessKey;

// convert the access key to an account id
awsAccountId = AwsAccountIdDecoder.GetAccountId(accessKey);
}
catch (Exception e)
{
agent.Logger.Info($"Unable to parse AWS Account ID from AccessKey. Using AccountId from configuration instead. Exception: {e.Message}");
awsAccountId = agent.Configuration.AwsAccountId;
}

public AfterWrappedMethodDelegate BeforeWrappedMethod(InstrumentedMethodCall instrumentedMethodCall, IAgent agent, ITransaction transaction)
return Delegates.GetDelegateFor(onComplete: () =>
{
if (AwsAccountId != null)
return Delegates.NoOp;

try
{
// get the AWSCredentials parameter
dynamic awsCredentials = instrumentedMethodCall.MethodCall.MethodArguments[0];

dynamic immutableCredentials = awsCredentials.GetCredentials();
string accessKey = immutableCredentials.AccessKey;

// convert the access key to an account id
AwsAccountId = AwsAccountIdDecoder.GetAccountId(accessKey);
}
catch (Exception e)
{
agent.Logger.Info($"Unable to parse AWS Account ID from AccessKey. Using AccountId from configuration instead. Exception: {e.Message}");
AwsAccountId = agent.Configuration.AwsAccountId;
}
// get the _config field from the client
object clientConfig = ((dynamic)client).Config;

return Delegates.NoOp;
}
// cache the account id using clientConfig as the key
AwsAccountIdByClientConfigCache.Put(new WeakReferenceKey<object>(clientConfig), awsAccountId);
});
}
}
Loading
Loading