Skip to content

Commit 09f3e42

Browse files
authored
Merge pull request #602 from hjgraca/aot-metrics-support
chore: Metrics - Add support for AOT
2 parents cb11aff + d666ab2 commit 09f3e42

23 files changed

+579
-785
lines changed

docs/core/metrics.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ These metrics can be visualized through [Amazon CloudWatch Console](https://aws.
1313
* Validating your metrics against common metric definitions mistakes (for example, metric unit, values, max dimensions, max metrics)
1414
* Metrics are created asynchronously by the CloudWatch service. You do not need any custom stacks, and there is no impact to Lambda function latency
1515
* Context manager to create a one off metric with a different dimension
16+
* Ahead-of-Time compilation to native code support [AOT](https://docs.aws.amazon.com/lambda/latest/dg/dotnet-native-aot.html) from version 1.7.0
1617

1718
<br />
1819

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 & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -11,8 +11,4 @@
1111
<ProjectReference Include="..\AWS.Lambda.Powertools.Common\AWS.Lambda.Powertools.Common.csproj" PrivateAssets="All" />
1212
</ItemGroup>
1313

14-
<ItemGroup>
15-
<Folder Include="Serializer\" />
16-
</ItemGroup>
17-
1814
</Project>
Lines changed: 147 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,147 @@
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+
/// <summary>
43+
/// Gets the metrics instance.
44+
/// </summary>
45+
/// <value>The metrics instance.</value>
46+
private static IMetrics _metricsInstance;
47+
48+
static MetricsAspect()
49+
{
50+
_isColdStart = true;
51+
}
52+
53+
/// <summary>
54+
/// Runs before the execution of the method marked with the Metrics Attribute
55+
/// </summary>
56+
/// <param name="instance"></param>
57+
/// <param name="name"></param>
58+
/// <param name="args"></param>
59+
/// <param name="hostType"></param>
60+
/// <param name="method"></param>
61+
/// <param name="returnType"></param>
62+
/// <param name="triggers"></param>
63+
[Advice(Kind.Before)]
64+
public void Before(
65+
[Argument(Source.Instance)] object instance,
66+
[Argument(Source.Name)] string name,
67+
[Argument(Source.Arguments)] object[] args,
68+
[Argument(Source.Type)] Type hostType,
69+
[Argument(Source.Metadata)] MethodBase method,
70+
[Argument(Source.ReturnType)] Type returnType,
71+
[Argument(Source.Triggers)] Attribute[] triggers)
72+
{
73+
// Before running Function
74+
75+
var trigger = triggers.OfType<MetricsAttribute>().First();
76+
77+
_metricsInstance ??= new Metrics(
78+
PowertoolsConfigurations.Instance,
79+
trigger.Namespace,
80+
trigger.Service,
81+
trigger.RaiseOnEmptyMetrics,
82+
trigger.CaptureColdStart
83+
);
84+
85+
var eventArgs = new AspectEventArgs
86+
{
87+
Instance = instance,
88+
Type = hostType,
89+
Method = method,
90+
Name = name,
91+
Args = args,
92+
ReturnType = returnType,
93+
Triggers = triggers
94+
};
95+
96+
if (trigger.CaptureColdStart && _isColdStart)
97+
{
98+
_isColdStart = false;
99+
100+
var nameSpace = _metricsInstance.GetNamespace();
101+
var service = _metricsInstance.GetService();
102+
Dictionary<string, string> dimensions = null;
103+
104+
_clearLambdaContext = PowertoolsLambdaContext.Extract(eventArgs);
105+
106+
if (PowertoolsLambdaContext.Instance is not null)
107+
{
108+
dimensions = new Dictionary<string, string>
109+
{
110+
{ "FunctionName", PowertoolsLambdaContext.Instance.FunctionName }
111+
};
112+
}
113+
114+
_metricsInstance.PushSingleMetric(
115+
"ColdStart",
116+
1.0,
117+
MetricUnit.Count,
118+
nameSpace,
119+
service,
120+
dimensions
121+
);
122+
}
123+
}
124+
125+
/// <summary>
126+
/// OnExit runs after the execution of the method marked with the Metrics Attribute
127+
/// </summary>
128+
[Advice(Kind.After)]
129+
public void Exit()
130+
{
131+
_metricsInstance.Flush();
132+
if (_clearLambdaContext)
133+
PowertoolsLambdaContext.Clear();
134+
}
135+
136+
137+
/// <summary>
138+
/// Reset the aspect for testing purposes.
139+
/// </summary>
140+
internal static void ResetForTest()
141+
{
142+
_metricsInstance = null;
143+
_isColdStart = true;
144+
Metrics.ResetForTest();
145+
PowertoolsLambdaContext.Clear();
146+
}
147+
}

0 commit comments

Comments
 (0)