Skip to content

Commit 96239ea

Browse files
committed
Metrics AOT Support. REmove dependency from UniversalWrapperAspect.cs. Refactor EMFValidationTests.cs no more mocks
1 parent a59fe93 commit 96239ea

File tree

11 files changed

+418
-748
lines changed

11 files changed

+418
-748
lines changed

libraries/src/AWS.Lambda.Powertools.Common/Aspects/UniversalWrapperAspect.cs

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
11
/*
22
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3-
*
3+
*
44
* Licensed under the Apache License, Version 2.0 (the "License").
55
* You may not use this file except in compliance with the License.
66
* A copy of the License is located at
7-
*
7+
*
88
* http://aws.amazon.com/apache2.0
9-
*
9+
*
1010
* or in the "license" file accompanying this file. This file is distributed
1111
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
1212
* express or implied. See the License for the specific language governing
@@ -15,6 +15,7 @@
1515

1616
using System;
1717
using System.Collections.Generic;
18+
using System.Diagnostics.CodeAnalysis;
1819
using System.Linq;
1920
using System.Linq.Expressions;
2021
using System.Reflection;
@@ -32,19 +33,19 @@ public class UniversalWrapperAspect
3233
/// <summary>
3334
/// The delegate cache
3435
/// </summary>
35-
private static readonly Dictionary<MethodBase, Handler> _delegateCache = new();
36+
private static readonly Dictionary<MethodBase, Handler> DelegateCache = new();
3637

3738
/// <summary>
3839
/// The asynchronous generic handler
3940
/// </summary>
40-
private static readonly MethodInfo _asyncGenericHandler =
41+
private static readonly MethodInfo AsyncGenericHandler =
4142
typeof(UniversalWrapperAttribute).GetMethod(nameof(UniversalWrapperAttribute.WrapAsync),
4243
BindingFlags.NonPublic | BindingFlags.Instance);
4344

4445
/// <summary>
4546
/// The synchronize generic handler
4647
/// </summary>
47-
private static readonly MethodInfo _syncGenericHandler =
48+
private static readonly MethodInfo SyncGenericHandler =
4849
typeof(UniversalWrapperAttribute).GetMethod(nameof(UniversalWrapperAttribute.WrapSync),
4950
BindingFlags.NonPublic | BindingFlags.Instance);
5051

@@ -94,6 +95,7 @@ public object Handle(
9495
/// <param name="returnType">Type of the return.</param>
9596
/// <param name="wrappers">The wrappers.</param>
9697
/// <returns>Handler.</returns>
98+
[UnconditionalSuppressMessage("AOT", "IL3050:Calling members annotated with 'RequiresDynamicCodeAttribute' may break functionality when AOT compiling.", Justification = "<Pending>")]
9799
private static Handler CreateMethodHandler(Type returnType, IEnumerable<UniversalWrapperAttribute> wrappers)
98100
{
99101
var targetParam = Expression.Parameter(typeof(Func<object[], object>), "orig");
@@ -107,13 +109,13 @@ private static Handler CreateMethodHandler(Type returnType, IEnumerable<Universa
107109
? returnType.GenericTypeArguments[0]
108110
: Type.GetType("System.Threading.Tasks.VoidTaskResult");
109111
returnType = typeof(Task<>).MakeGenericType(taskType);
110-
wrapperMethod = _asyncGenericHandler.MakeGenericMethod(taskType);
112+
wrapperMethod = AsyncGenericHandler.MakeGenericMethod(taskType);
111113
}
112114
else
113115
{
114116
if (returnType == typeof(void))
115117
returnType = typeof(object);
116-
wrapperMethod = _syncGenericHandler.MakeGenericMethod(returnType);
118+
wrapperMethod = SyncGenericHandler.MakeGenericMethod(returnType);
117119
}
118120

119121
var converArgs = Expression.Parameter(typeof(object[]), "args");
@@ -128,9 +130,9 @@ private static Handler CreateMethodHandler(Type returnType, IEnumerable<Universa
128130
argsParam);
129131
}
130132

131-
var orig_args = Expression.Parameter(typeof(object[]), "orig_args");
132-
var handler = Expression.Lambda<Handler>(Expression.Convert(Expression.Invoke(next, orig_args), typeof(object)),
133-
targetParam, orig_args, eventArgsParam);
133+
var origArgs = Expression.Parameter(typeof(object[]), "orig_args");
134+
var handler = Expression.Lambda<Handler>(Expression.Convert(Expression.Invoke(next, origArgs), typeof(object)),
135+
targetParam, origArgs, eventArgsParam);
134136

135137
var handlerCompiled = handler.Compile();
136138

@@ -147,14 +149,14 @@ private static Handler CreateMethodHandler(Type returnType, IEnumerable<Universa
147149
private static Handler GetMethodHandler(MethodBase method, Type returnType,
148150
IEnumerable<UniversalWrapperAttribute> wrappers)
149151
{
150-
if (!_delegateCache.TryGetValue(method, out var handler))
151-
lock (method)
152-
{
153-
if (!_delegateCache.TryGetValue(method, out handler))
154-
_delegateCache[method] = handler = CreateMethodHandler(returnType, wrappers);
155-
}
156-
157-
return handler;
152+
lock (method)
153+
{
154+
if (!DelegateCache.TryGetValue(method, out var handler))
155+
if (!DelegateCache.TryGetValue(method, out handler))
156+
DelegateCache[method] = handler = CreateMethodHandler(returnType, wrappers);
157+
158+
return handler;
159+
}
158160
}
159161

160162
/// <summary>

libraries/src/AWS.Lambda.Powertools.Common/Core/ISystemWrapper.cs

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
* permissions and limitations under the License.
1414
*/
1515

16+
using System.IO;
17+
1618
namespace AWS.Lambda.Powertools.Common;
1719

1820
/// <summary>
@@ -57,4 +59,15 @@ public interface ISystemWrapper
5759
/// </summary>
5860
/// <param name="type"></param>
5961
void SetExecutionEnvironment<T>(T type);
62+
63+
/// <summary>
64+
/// Sets console output
65+
/// Useful for testing and checking the console output
66+
/// <code>
67+
/// var consoleOut = new StringWriter();
68+
/// SystemWrapper.Instance.SetOut(consoleOut);
69+
/// </code>
70+
/// </summary>
71+
/// <param name="writeTo">The TextWriter instance where to write to</param>
72+
void SetOut(TextWriter writeTo);
6073
}

libraries/src/AWS.Lambda.Powertools.Common/Core/SystemWrapper.cs

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,12 @@ public void SetExecutionEnvironment<T>(T type)
126126
SetEnvironmentVariable(envName, envValue.ToString());
127127
}
128128

129+
/// <inheritdoc />
130+
public void SetOut(TextWriter writeTo)
131+
{
132+
Console.SetOut(writeTo);
133+
}
134+
129135
/// <summary>
130136
/// Parsing the name to conform with the required naming convention for the UserAgent header (PTFeature/Name/Version)
131137
/// Fallback to Assembly Name on exception

libraries/src/AWS.Lambda.Powertools.Metrics/AWS.Lambda.Powertools.Metrics.csproj

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,12 +5,10 @@
55
<Description>Powertools for AWS Lambda (.NET) - Metrics package.</Description>
66
<AssemblyName>AWS.Lambda.Powertools.Metrics</AssemblyName>
77
<RootNamespace>AWS.Lambda.Powertools.Metrics</RootNamespace>
8-
<Version>1.9.3</Version>
98
</PropertyGroup>
109

1110
<ItemGroup>
1211
<ProjectReference Include="..\AWS.Lambda.Powertools.Common\AWS.Lambda.Powertools.Common.csproj" PrivateAssets="All" />
13-
<TrimmerRootAssembly Include="AWS.Lambda.Powertools.Common" />
1412
</ItemGroup>
1513

1614
</Project>
Lines changed: 136 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,136 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
using System;
17+
using System.Collections.Generic;
18+
using System.Linq;
19+
using System.Reflection;
20+
using AspectInjector.Broker;
21+
using AWS.Lambda.Powertools.Common;
22+
23+
namespace AWS.Lambda.Powertools.Metrics;
24+
25+
/// <summary>
26+
/// MetricsAspect class is responsible for capturing ColdStart metric and flushing metrics on function exit.
27+
/// Scope.Global - means aspect will operate as singleton.
28+
/// </summary>
29+
[Aspect(Scope.Global)]
30+
public class MetricsAspect
31+
{
32+
/// <summary>
33+
/// The is cold start
34+
/// </summary>
35+
private static bool _isColdStart;
36+
37+
/// <summary>
38+
/// Specify to clear Lambda Context on exit
39+
/// </summary>
40+
private bool _clearLambdaContext;
41+
42+
private IMetrics _metricsInstance;
43+
44+
static MetricsAspect()
45+
{
46+
_isColdStart = true;
47+
}
48+
49+
/// <summary>
50+
/// Runs before the execution of the method marked with the Metrics Attribute
51+
/// </summary>
52+
/// <param name="instance"></param>
53+
/// <param name="name"></param>
54+
/// <param name="args"></param>
55+
/// <param name="hostType"></param>
56+
/// <param name="method"></param>
57+
/// <param name="returnType"></param>
58+
/// <param name="triggers"></param>
59+
[Advice(Kind.Before)]
60+
public void Before(
61+
[Argument(Source.Instance)] object instance,
62+
[Argument(Source.Name)] string name,
63+
[Argument(Source.Arguments)] object[] args,
64+
[Argument(Source.Type)] Type hostType,
65+
[Argument(Source.Metadata)] MethodBase method,
66+
[Argument(Source.ReturnType)] Type returnType,
67+
[Argument(Source.Triggers)] Attribute[] triggers)
68+
{
69+
70+
// Before running Function
71+
72+
var trigger = triggers.OfType<MetricsAttribute>().First();
73+
74+
_metricsInstance = trigger.MetricsInstance;
75+
76+
var eventArgs = new AspectEventArgs
77+
{
78+
Instance = instance,
79+
Type = hostType,
80+
Method = method,
81+
Name = name,
82+
Args = args,
83+
ReturnType = returnType,
84+
Triggers = triggers
85+
};
86+
87+
if (trigger.CaptureColdStart && _isColdStart)
88+
{
89+
_isColdStart = false;
90+
91+
var nameSpace = _metricsInstance.GetNamespace();
92+
var service = _metricsInstance.GetService();
93+
Dictionary<string, string> dimensions = null;
94+
95+
_clearLambdaContext = PowertoolsLambdaContext.Extract(eventArgs);
96+
97+
if (PowertoolsLambdaContext.Instance is not null)
98+
{
99+
dimensions = new Dictionary<string, string>
100+
{
101+
{ "FunctionName", PowertoolsLambdaContext.Instance.FunctionName }
102+
};
103+
}
104+
105+
_metricsInstance.PushSingleMetric(
106+
"ColdStart",
107+
1.0,
108+
MetricUnit.Count,
109+
nameSpace,
110+
service,
111+
dimensions
112+
);
113+
}
114+
}
115+
116+
/// <summary>
117+
/// OnExit runs after the execution of the method marked with the Metrics Attribute
118+
/// </summary>
119+
[Advice(Kind.After)]
120+
public void Exit() {
121+
_metricsInstance.Flush();
122+
if (_clearLambdaContext)
123+
PowertoolsLambdaContext.Clear();
124+
}
125+
126+
127+
/// <summary>
128+
/// Reset the aspect for testing purposes.
129+
/// </summary>
130+
internal static void ResetForTest()
131+
{
132+
_isColdStart = true;
133+
Metrics.ResetForTest();
134+
PowertoolsLambdaContext.Clear();
135+
}
136+
}

0 commit comments

Comments
 (0)