diff --git a/docs/core/tracing.md b/docs/core/tracing.md index 883f8db86..a67460e79 100644 --- a/docs/core/tracing.md +++ b/docs/core/tracing.md @@ -419,5 +419,55 @@ Below is an example configuration needed for each test case. } ``` +## Advanced +### Lambda SnapStart priming +The Tracing utility integrates with AWS Lambda SnapStart to improve restore durations. To make sure the SnapStart priming logic of this utility runs correctly, you need an explicit reference to `TracingUtils` in your code to allow the library to register before SnapStart takes a memory snapshot. Learn more about what priming is in this [blog post](https://aws.amazon.com/blogs/compute/optimizing-cold-start-performance-of-aws-lambda-using-advanced-priming-strategies-with-snapstart/){target="_blank"}. + +Make sure to reference `TracingUtils` in your Lambda handler initialization code. This can be done by adding one of the following lines to your handler class: + +=== "Constructor" + + ```java hl_lines="7" + import software.amazon.lambda.powertools.tracing.Tracing; + import software.amazon.lambda.powertools.tracing.TracingUtils; + + public class MyFunctionHandler implements RequestHandler { + + public MyFunctionHandler() { + TracingUtils.prime(); // Ensure TracingUtils is loaded for SnapStart + } + + @Override + @Tracing + public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent input, Context context) { + // ... + return something; + } + } + ``` + +=== "Static Initializer" + + ```java hl_lines="7" + import software.amazon.lambda.powertools.tracing.Tracing; + import software.amazon.lambda.powertools.tracing.TracingUtils; + + public class MyFunctionHandler implements RequestHandler { + + static { + TracingUtils.prime(); // Ensure TracingUtils is loaded for SnapStart + } + + @Override + @Tracing + public APIGatewayProxyResponseEvent handleRequest(APIGatewayProxyRequestEvent input, Context context) { + // ... + return something; + } + } + ``` + +!!! note "Important: Direct TracingUtils reference required" + Using only the `@Tracing` annotation is not sufficient to trigger SnapStart priming. You must have a direct reference to `TracingUtils` in your code (as shown in the examples above) to ensure the CRaC hooks are properly registered. diff --git a/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/LambdaConstants.java b/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/LambdaConstants.java index d27ac1aa2..dda22c39b 100644 --- a/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/LambdaConstants.java +++ b/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/LambdaConstants.java @@ -23,4 +23,6 @@ public class LambdaConstants { public static final String ROOT_EQUALS = "Root="; public static final String POWERTOOLS_SERVICE_NAME = "POWERTOOLS_SERVICE_NAME"; public static final String SERVICE_UNDEFINED = "service_undefined"; + public static final String AWS_LAMBDA_INITIALIZATION_TYPE = "AWS_LAMBDA_INITIALIZATION_TYPE"; + public static final String PROVISIONED_CONCURRENCY = "provisioned-concurrency"; } diff --git a/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/LambdaHandlerProcessor.java b/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/LambdaHandlerProcessor.java index bfacd5204..96f74afee 100644 --- a/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/LambdaHandlerProcessor.java +++ b/powertools-common/src/main/java/software/amazon/lambda/powertools/common/internal/LambdaHandlerProcessor.java @@ -88,7 +88,19 @@ protected static void resetServiceName() { } public static boolean isColdStart() { - return IS_COLD_START == null; + // If this is not the first invocation, it's definitely not a cold start + if (IS_COLD_START != null) { + return false; + } + + // Check if this execution environment was pre-warmed via provisioned concurrency + String initType = getenv(LambdaConstants.AWS_LAMBDA_INITIALIZATION_TYPE); + if (LambdaConstants.PROVISIONED_CONCURRENCY.equals(initType)) { + return false; // Pre-warmed environment, not a cold start + } + + // Traditional cold start detection - first invocation without provisioned concurrency + return true; } public static void coldStartDone() { diff --git a/powertools-tracing/pom.xml b/powertools-tracing/pom.xml index 67de0be7d..3f4fb5a77 100644 --- a/powertools-tracing/pom.xml +++ b/powertools-tracing/pom.xml @@ -74,6 +74,10 @@ com.fasterxml.jackson.core jackson-annotations + + org.crac + crac + @@ -118,9 +122,32 @@ assertj-core test + + org.mockito + mockito-core + test + + + generate-classesloaded-file + + + + org.apache.maven.plugins + maven-surefire-plugin + + + -Xlog:class+load=info:classesloaded.txt + --add-opens java.base/java.util=ALL-UNNAMED + --add-opens java.base/java.lang=ALL-UNNAMED + + + + + + generate-graalvm-files diff --git a/powertools-tracing/src/main/java/software/amazon/lambda/powertools/tracing/TracingUtils.java b/powertools-tracing/src/main/java/software/amazon/lambda/powertools/tracing/TracingUtils.java index 954ed7da4..91e3c5331 100644 --- a/powertools-tracing/src/main/java/software/amazon/lambda/powertools/tracing/TracingUtils.java +++ b/powertools-tracing/src/main/java/software/amazon/lambda/powertools/tracing/TracingUtils.java @@ -21,17 +21,33 @@ import com.amazonaws.xray.entities.Subsegment; import com.fasterxml.jackson.databind.ObjectMapper; import java.util.function.Consumer; +import org.crac.Context; +import org.crac.Core; +import org.crac.Resource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import software.amazon.lambda.powertools.common.internal.ClassPreLoader; /** * A class of helper functions to add additional functionality and ease * of use. */ -public final class TracingUtils { +public final class TracingUtils implements Resource { private static final Logger LOG = LoggerFactory.getLogger(TracingUtils.class); private static ObjectMapper objectMapper; + // Dummy instance to register TracingUtils with CRaC + private static final TracingUtils INSTANCE = new TracingUtils(); + + // Static block to ensure CRaC registration happens at class loading time + static { + Core.getGlobalContext().register(INSTANCE); + } + + private TracingUtils() { + // Private constructor for singleton pattern + } + /** * Put an annotation to the current subsegment with a String value. * @@ -192,4 +208,38 @@ public static void defaultObjectMapper(ObjectMapper objectMapper) { public static ObjectMapper objectMapper() { return objectMapper; } + + /** + * Prime TracingUtils for AWS Lambda SnapStart. + * This method has no side-effects and can be safely called to trigger SnapStart priming. + */ + public static void prime() { + // This method intentionally does nothing but ensures TracingUtils is loaded + // The actual priming happens in the beforeCheckpoint() method via CRaC hooks + } + + @Override + public void beforeCheckpoint(Context context) throws Exception { + // Preload classes first to ensure this always runs + ClassPreLoader.preloadClasses(); + + // Initialize key components + if (objectMapper == null) { + objectMapper = new ObjectMapper(); + } + + // Initialize X-Ray components by accessing them + AWSXRay.getGlobalRecorder(); + + // Warm up tracing utilities by calling key methods + serviceName(); + + // Initialize ObjectMapper for JSON serialization + objectMapper.writeValueAsString("dummy"); + } + + @Override + public void afterRestore(Context context) throws Exception { + // No action needed after restore + } } diff --git a/powertools-tracing/src/main/resources/classesloaded.txt b/powertools-tracing/src/main/resources/classesloaded.txt new file mode 100644 index 000000000..c93b8343a --- /dev/null +++ b/powertools-tracing/src/main/resources/classesloaded.txt @@ -0,0 +1,66 @@ +java.lang.Object +java.io.Serializable +java.lang.Comparable +java.lang.CharSequence +java.lang.String +java.lang.Class +java.lang.Cloneable +java.lang.ClassLoader +java.lang.System +java.lang.Throwable +java.lang.Error +java.lang.Exception +java.lang.RuntimeException +com.amazonaws.xray.AWSXRay +com.amazonaws.xray.entities.Entity +com.amazonaws.xray.entities.Subsegment +com.amazonaws.xray.entities.Segment +com.amazonaws.xray.entities.TraceID +com.amazonaws.xray.entities.TraceHeader +com.amazonaws.xray.strategy.sampling.SamplingStrategy +com.amazonaws.xray.strategy.sampling.LocalizedSamplingStrategy +com.amazonaws.xray.strategy.sampling.NoSamplingStrategy +com.amazonaws.xray.strategy.sampling.AllSamplingStrategy +com.amazonaws.xray.strategy.sampling.CentralizedSamplingStrategy +com.amazonaws.xray.strategy.ContextMissingStrategy +com.amazonaws.xray.strategy.LogErrorContextMissingStrategy +com.amazonaws.xray.strategy.RuntimeErrorContextMissingStrategy +com.amazonaws.xray.strategy.IgnoreErrorContextMissingStrategy +com.amazonaws.xray.contexts.LambdaSegmentContext +com.amazonaws.xray.contexts.SegmentContext +com.amazonaws.xray.contexts.ThreadLocalSegmentContext +com.amazonaws.xray.emitters.Emitter +com.amazonaws.xray.emitters.UDPEmitter +com.amazonaws.xray.listeners.SegmentListener +com.amazonaws.xray.plugins.Plugin +com.amazonaws.xray.plugins.ECSPlugin +com.amazonaws.xray.plugins.EC2Plugin +com.amazonaws.xray.plugins.EKSPlugin +software.amazon.lambda.powertools.tracing.TracingUtils +software.amazon.lambda.powertools.tracing.Tracing +software.amazon.lambda.powertools.tracing.CaptureMode +software.amazon.lambda.powertools.tracing.internal.LambdaTracingAspect +software.amazon.lambda.powertools.common.internal.LambdaHandlerProcessor +software.amazon.lambda.powertools.common.internal.LambdaConstants +com.fasterxml.jackson.databind.ObjectMapper +com.fasterxml.jackson.databind.JsonNode +com.fasterxml.jackson.databind.node.ObjectNode +com.fasterxml.jackson.databind.node.ArrayNode +com.fasterxml.jackson.databind.node.TextNode +com.fasterxml.jackson.databind.node.NumericNode +com.fasterxml.jackson.databind.node.BooleanNode +com.fasterxml.jackson.databind.node.NullNode +com.fasterxml.jackson.core.JsonFactory +com.fasterxml.jackson.core.JsonGenerator +com.fasterxml.jackson.core.JsonParser +com.fasterxml.jackson.core.JsonToken +com.fasterxml.jackson.databind.DeserializationFeature +com.fasterxml.jackson.databind.SerializationFeature +com.fasterxml.jackson.databind.MapperFeature +com.fasterxml.jackson.databind.JsonSerializer +com.fasterxml.jackson.databind.JsonDeserializer +com.fasterxml.jackson.databind.SerializerProvider +com.fasterxml.jackson.databind.DeserializationContext +org.slf4j.Logger +org.slf4j.LoggerFactory +org.slf4j.MDC diff --git a/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/TracingUtilsCracTest.java b/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/TracingUtilsCracTest.java new file mode 100644 index 000000000..4e4c3e8dc --- /dev/null +++ b/powertools-tracing/src/test/java/software/amazon/lambda/powertools/tracing/TracingUtilsCracTest.java @@ -0,0 +1,40 @@ +/* + * Copyright 2023 Amazon.com, Inc. or its affiliates. + * Licensed under the Apache License, Version 2.0 (the + * "License"); you may not use this file except in compliance + * with the License. You may obtain a copy of the License at + * http://www.apache.org/licenses/LICENSE-2.0 + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + */ + +package software.amazon.lambda.powertools.tracing; + +import static org.assertj.core.api.Assertions.assertThatNoException; +import static org.mockito.Mockito.mock; + +import java.lang.reflect.Field; +import org.crac.Context; +import org.crac.Resource; +import org.junit.jupiter.api.Test; + +class TracingUtilsCracTest { + + Context context = mock(Context.class); + + @Test + void testPrimeMethodDoesNotThrowException() { + assertThatNoException().isThrownBy(() -> TracingUtils.prime()); + } + + @Test + void testTracingUtilsLoadsSuccessfully() { + // Simply calling TracingUtils.prime() should trigger CRaC registration + TracingUtils.prime(); + // If we get here without exception, the test passes + } +} diff --git a/spotbugs-exclude.xml b/spotbugs-exclude.xml index 35aed5e26..e9fad38d2 100644 --- a/spotbugs-exclude.xml +++ b/spotbugs-exclude.xml @@ -193,8 +193,16 @@ - - + + + + + + + + + +